/*
* =============================================================================
* 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
);
}