/* * ============================================================================= * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform * ----------------------------------------------------------------------------- * File: client/src/components/financial/advanced-price-chart.tsx * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * Website: https://www.spboucher.ai * Demo: https://www.vquant.ai * License: MIT (see LICENSE) * * Copyright © 2026 Simon-Pierre Boucher. All rights reserved. * ============================================================================= */ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { TrendingUp, TrendingDown, Calendar, BarChart3, LineChart as LineChartIcon } from "lucide-react"; import { useState } from "react"; import { ComposedChart, Line, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Area, ReferenceLine, Legend, Brush, } from "recharts"; interface HistoricalPrice { date: string; open: number; high: number; low: number; close: number; adjClose: number; volume: number; unadjustedVolume: number; change: number; changePercent: number; vwap: number; label: string; changeOverTime: number; } interface AdvancedPriceChartProps { data: { symbol: string; historical: HistoricalPrice[]; }; } export function AdvancedPriceChart({ data }: AdvancedPriceChartProps) { const [chartType, setChartType] = useState<'area' | 'line'>('area'); const [showVolume, setShowVolume] = useState(true); const [showMA, setShowMA] = useState(true); const [timeRange, setTimeRange] = useState<'1M' | '3M' | '6M' | 'YTD' | '1Y' | 'ALL'>('3M'); if (!data || !data.historical || data.historical.length === 0) { return ( Advanced Price Chart No price data available ); } const symbol = data.symbol; const allData = [...data.historical].reverse(); // Filter data based on time range const filterDataByRange = () => { const now = new Date(); let startDate = new Date(); switch (timeRange) { case '1M': startDate.setMonth(now.getMonth() - 1); break; case '3M': startDate.setMonth(now.getMonth() - 3); break; case '6M': startDate.setMonth(now.getMonth() - 6); break; case 'YTD': startDate = new Date(now.getFullYear(), 0, 1); break; case '1Y': startDate.setFullYear(now.getFullYear() - 1); break; case 'ALL': return allData; } return allData.filter(item => new Date(item.date) >= startDate); }; const filteredData = filterDataByRange(); // Calculate moving averages const calculateMA = (period: number) => { return filteredData.map((item, index) => { if (index < period - 1) return null; const sum = filteredData .slice(index - period + 1, index + 1) .reduce((acc, curr) => acc + curr.close, 0); return sum / period; }); }; const calculateEMA = (period: number) => { const multiplier = 2 / (period + 1); const ema = []; // Start with SMA let sum = 0; for (let i = 0; i < period; i++) { if (i >= filteredData.length) break; sum += filteredData[i].close; ema.push(i === period - 1 ? sum / period : null); } // Calculate EMA for (let i = period; i < filteredData.length; i++) { const prevEMA = ema[i - 1] || filteredData[i - 1].close; ema.push(filteredData[i].close * multiplier + prevEMA * (1 - multiplier)); } return ema; }; const ma20 = calculateMA(20); const ma50 = calculateMA(50); const ema12 = calculateEMA(12); const ema26 = calculateEMA(26); const enrichedData = filteredData.map((item, index) => ({ ...item, dateFormatted: new Date(item.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: '2-digit' }), ma20: ma20[index], ma50: ma50[index], ema12: ema12[index], ema26: ema26[index], isGreen: item.close >= item.open, })); const firstPrice = enrichedData[0]?.close || 0; const lastPrice = enrichedData[enrichedData.length - 1]?.close || 0; const priceChange = lastPrice - firstPrice; const priceChangePercent = (priceChange / firstPrice) * 100; const isPositive = priceChange >= 0; const minPrice = Math.min(...enrichedData.map(d => d.low)); const maxPrice = Math.max(...enrichedData.map(d => d.high)); const avgVolume = enrichedData.reduce((sum, d) => sum + d.volume, 0) / enrichedData.length; const high52w = Math.max(...allData.slice(-252).map(d => d.high)); const low52w = Math.min(...allData.slice(-252).map(d => d.low)); const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0].payload; return (
{data.label}
Open: ${data.open?.toFixed(2)}
High: ${data.high?.toFixed(2)}
Low: ${data.low?.toFixed(2)}
Close: ${data.close?.toFixed(2)}
Change: = 0 ? 'text-green-600' : 'text-red-600'}`}> {data.changePercent >= 0 ? '+' : ''}{data.changePercent?.toFixed(2)}%
Volume: {(data.volume / 1_000_000).toFixed(2)}M
{showMA && ( <> {data.ma20 && (
MA(20): ${data.ma20?.toFixed(2)}
)} {data.ma50 && (
MA(50): ${data.ma50?.toFixed(2)}
)} )}
); } return null; }; return (
{symbol} - Historical Price Chart
Advanced charting with technical indicators · {enrichedData.length} trading days
${lastPrice.toFixed(2)}
{isPositive ? : } {isPositive ? '+' : ''}{priceChange.toFixed(2)} ({isPositive ? '+' : ''}{priceChangePercent.toFixed(2)}%)
{/* Controls */}
{/* Time Range Selector */}
{(['1M', '3M', '6M', 'YTD', '1Y', 'ALL'] as const).map((range) => ( ))}
{/* Chart Type Selector */}
{/* Key Stats */}
Period High
${maxPrice.toFixed(2)}
Period Low
${minPrice.toFixed(2)}
52W High
${high52w.toFixed(2)}
52W Low
${low52w.toFixed(2)}
Avg Volume
{(avgVolume / 1_000_000).toFixed(1)}M
{/* Main Price Chart */}
`$${value.toFixed(0)}`} width={60} /> } /> {/* 52-week high/low reference lines */} {/* Chart Types */} {chartType === 'area' && ( )} {chartType === 'line' && ( )} {/* Moving Averages */} {showMA && ( <> )}
{/* Volume Chart */} {showVolume && (
Trading Volume
`${(value / 1_000_000).toFixed(0)}M`} width={50} /> [(value / 1_000_000).toFixed(2) + 'M', 'Volume']} /> {enrichedData.map((entry, index) => ( ))}
)} {/* Price Performance Summary */}
📊 Period Performance
Period Start
${firstPrice.toFixed(2)}
Period End
${lastPrice.toFixed(2)}
Total Return
{isPositive ? '+' : ''}{priceChangePercent.toFixed(2)}%
Volatility
{((maxPrice - minPrice) / firstPrice * 100).toFixed(1)}%
Total Vol Traded
{(enrichedData.reduce((sum, d) => sum + d.volume, 0) / 1_000_000_000).toFixed(2)}B
); }