vjeai's picture
Deploy: all fixes — yfinance candles, ml_signal 2y history, no handoff schemas, sequential report phase
3be03dd
Raw
History Blame Contribute Delete
4.75 kB
import {
ResponsiveContainer,
ComposedChart,
Line,
XAxis,
YAxis,
Tooltip,
ReferenceLine,
CartesianGrid,
} from 'recharts'
function CustomTooltip({ active, payload, label }) {
if (!active || !payload?.length) return null
const price = payload.find(p => p.dataKey === 'price')
return (
<div style={{
background: 'rgba(15,15,30,0.95)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 8,
padding: '0.5rem 0.75rem',
fontSize: '0.78rem',
}}>
<div style={{ color: 'var(--text-muted)', marginBottom: 4 }}>{label}</div>
{price && (
<div style={{ color: 'var(--text)', fontWeight: 600 }}>
${Number(price.value).toFixed(2)}
</div>
)}
</div>
)
}
export default function PriceChart({ data }) {
const { price_series, ma20, ma50, bb_upper, bb_lower, current_price } = data
if (!price_series || Object.keys(price_series).length === 0) return null
const chartData = Object.entries(price_series).map(([date, price]) => ({
date: date.slice(5),
price: Number(price),
ma20: ma20 ? Number(ma20) : undefined,
ma50: ma50 ? Number(ma50) : undefined,
bbUpper: bb_upper ? Number(bb_upper) : undefined,
bbLower: bb_lower ? Number(bb_lower) : undefined,
}))
const prices = chartData.map(d => d.price)
const lastPrice = prices[prices.length - 1]
const isUp = lastPrice >= prices[0]
const lineColor = isUp ? 'var(--buy)' : 'var(--sell)'
const allValues = [
...prices,
bb_upper ? Number(bb_upper) : null,
bb_lower ? Number(bb_lower) : null,
].filter(Boolean)
const minY = Math.min(...allValues) * 0.995
const maxY = Math.max(...allValues) * 1.005
const change = lastPrice - prices[0]
const changePct = ((change / prices[0]) * 100).toFixed(2)
return (
<div className="card price-chart-card">
<div className="price-chart-header">
<h3>30-Day Price</h3>
<span className="price-chart-change" style={{ color: lineColor }}>
{change >= 0 ? '+' : ''}{changePct}%
</span>
</div>
<div style={{ width: '100%', height: 200 }}>
<ResponsiveContainer>
<ComposedChart data={chartData} margin={{ top: 4, right: 4, left: 0, bottom: 0 }}>
<CartesianGrid stroke="rgba(255,255,255,0.04)" vertical={false} />
<XAxis
dataKey="date"
tick={{ fill: 'var(--text-faint)', fontSize: 10 }}
tickLine={false}
axisLine={false}
interval="preserveStartEnd"
/>
<YAxis
domain={[minY, maxY]}
tick={{ fill: 'var(--text-faint)', fontSize: 10 }}
tickLine={false}
axisLine={false}
tickFormatter={v => `$${v.toFixed(0)}`}
width={50}
/>
<Tooltip content={<CustomTooltip />} />
{/* Bollinger band lines */}
{chartData[0]?.bbUpper && (
<Line dataKey="bbUpper" stroke="rgba(255,255,255,0.08)" strokeWidth={1}
dot={false} isAnimationActive={false} strokeDasharray="2 4" />
)}
{chartData[0]?.bbLower && (
<Line dataKey="bbLower" stroke="rgba(255,255,255,0.08)" strokeWidth={1}
dot={false} isAnimationActive={false} strokeDasharray="2 4" />
)}
{/* MA50 */}
{ma50 && (
<Line dataKey="ma50" stroke="rgba(255,204,68,0.55)" strokeWidth={1.5}
dot={false} isAnimationActive={false} strokeDasharray="4 2" />
)}
{/* MA20 */}
{ma20 && (
<Line dataKey="ma20" stroke="rgba(68,170,255,0.65)" strokeWidth={1.5}
dot={false} isAnimationActive={false} strokeDasharray="4 2" />
)}
{/* Price */}
<Line dataKey="price" stroke={lineColor} strokeWidth={2}
dot={false} activeDot={{ r: 4, fill: lineColor, stroke: 'none' }}
isAnimationActive={false} />
{/* Current price dashed line */}
<ReferenceLine y={lastPrice} stroke={lineColor}
strokeOpacity={0.25} strokeDasharray="2 5" />
</ComposedChart>
</ResponsiveContainer>
</div>
<div className="price-chart-legend">
<span style={{ color: lineColor }}>— Price</span>
{ma20 && <span style={{ color: 'rgba(68,170,255,0.65)' }}>-- MA20</span>}
{ma50 && <span style={{ color: 'rgba(255,204,68,0.55)' }}>-- MA50</span>}
{bb_upper && <span style={{ color: 'rgba(255,255,255,0.3)' }}>-- Bollinger</span>}
</div>
</div>
)
}