'use client' import Link from 'next/link' import { useSearchParams } from 'next/navigation' import { ArrowLeft } from 'lucide-react' import { Suspense, useState, useEffect, useMemo } from 'react' import { CandlestickGraph } from '@/components/candlestick-chart' // Timeframe options const TIMEFRAMES = ['Custom', '1D', '5D', '10D', '1M', '3M', '6M', 'YTD', '1Y', '2Y', '3Y'] as const type Timeframe = typeof TIMEFRAMES[number] // Bar size options per timeframe — each bar size must be able to cover the // visible range within IB API max duration limits: // 1m→1D, 2m→2D, 5m→1W, 15m→2W, 30m/1h/2h/4h→1M, 1D/1W/1M→1Y+ const BAR_SIZE_MAP: Record, string[]> = { '1D': ['1m', '2m', '5m', '15m'], '5D': ['5m', '15m', '30m', '1h'], '10D': ['15m', '30m', '1h', '2h'], '1M': ['30m', '1h', '4h', '1D'], '3M': ['1D', '1W'], '6M': ['1D', '1W'], 'YTD': ['1D', '1W'], '1Y': ['1D', '1W'], '2Y': ['1D', '1W', '1M'], '3Y': ['1W', '1M'], } // Default bar size for each timeframe const DEFAULT_BAR_SIZE: Record, string> = { '1D': '5m', '5D': '30m', '10D': '1h', '1M': '1D', '3M': '1D', '6M': '1D', 'YTD': '1D', '1Y': '1D', '2Y': '1W', '3Y': '1W', } /** * Check if a date is a trading day (Mon-Fri, simplified - doesn't account for holidays) */ function isTradingDay(date: Date): boolean { const day = date.getDay() return day >= 1 && day <= 5 } /** * Get the last trading day (today if market open, otherwise previous trading day) */ function getLastTradingDay(): Date { const today = new Date() today.setHours(0, 0, 0, 0) while (!isTradingDay(today)) { today.setDate(today.getDate() - 1) } return today } /** * Go back N trading days from a given date */ function goBackTradingDays(fromDate: Date, tradingDays: number): Date { const result = new Date(fromDate) let daysToGoBack = tradingDays while (daysToGoBack > 0) { result.setDate(result.getDate() - 1) if (isTradingDay(result)) { daysToGoBack-- } } return result } /** * Calculate start date based on timeframe from end date * For day-based timeframes (1D, 5D, 10D), counts back trading days only */ function calculateStartDate(timeframe: Timeframe, endDate: Date): Date { const start = new Date(endDate) switch (timeframe) { case '1D': // Same day - no change return start case '5D': // Go back 4 trading days (5 days total including end date) return goBackTradingDays(start, 4) case '10D': // Go back 9 trading days (10 days total including end date) return goBackTradingDays(start, 9) case '1M': start.setMonth(start.getMonth() - 1) break case '3M': start.setMonth(start.getMonth() - 3) break case '6M': start.setMonth(start.getMonth() - 6) break case 'YTD': start.setMonth(0) start.setDate(1) break case '1Y': start.setFullYear(start.getFullYear() - 1) break case '2Y': start.setFullYear(start.getFullYear() - 2) break case '3Y': start.setFullYear(start.getFullYear() - 3) break default: break } return start } /** * Count trading days between two dates (simplified - Mon-Fri only) */ function countTradingDays(start: Date, end: Date): number { let count = 0 const current = new Date(start) while (current <= end) { if (isTradingDay(current)) { count++ } current.setDate(current.getDate() + 1) } return count } /** * Get bar size options for custom timeframe based on date range. * Thresholds match IB API max duration limits per bar size: * 5m→1W(5 trading), 15m→2W(10), 30m/1h/2h/4h→1M(~20), 1D+→1Y+ */ function getCustomBarSizeOptions(startDate: Date, endDate: Date): { options: string[], default: string } { const tradingDays = countTradingDays(startDate, endDate) if (tradingDays <= 1) { return { options: ['1m', '2m', '5m', '15m'], default: '5m' } } else if (tradingDays <= 5) { return { options: ['5m', '15m', '30m', '1h'], default: '15m' } } else if (tradingDays <= 10) { return { options: ['15m', '30m', '1h', '2h'], default: '30m' } } else if (tradingDays <= 20) { return { options: ['30m', '1h', '4h', '1D'], default: '1D' } } else { return { options: ['1D', '1W'], default: '1D' } } } /** * Format date to YYYY-MM-DD for input. * Uses local-time methods to match how parseInputDate creates dates, * avoiding the day-shift that toISOString() causes in non-UTC timezones. */ function formatDateForInput(date: Date): string { const y = date.getFullYear() const m = String(date.getMonth() + 1).padStart(2, '0') const d = String(date.getDate()).padStart(2, '0') return `${y}-${m}-${d}` } /** * Parse date string to Date object */ function parseInputDate(dateStr: string): Date { const [year, month, day] = dateStr.split('-').map(Number) return new Date(year, month - 1, day) } function ComplexChartContent() { const searchParams = useSearchParams() const symbol = searchParams.get('symbol') || 'AAPL' // Initialize with last trading day const lastTradingDay = useMemo(() => getLastTradingDay(), []) const [timeframe, setTimeframe] = useState('5D') const [barSize, setBarSize] = useState('30m') const [endDate, setEndDate] = useState(() => formatDateForInput(lastTradingDay)) const [startDate, setStartDate] = useState(() => { const start = calculateStartDate('5D', lastTradingDay) return formatDateForInput(start) }) const [savedChart, setSavedChart] = useState('') // Get available bar sizes based on current timeframe/dates const barSizeOptions = useMemo(() => { if (timeframe === 'Custom') { const start = parseInputDate(startDate) const end = parseInputDate(endDate) return getCustomBarSizeOptions(start, end) } return { options: BAR_SIZE_MAP[timeframe], default: DEFAULT_BAR_SIZE[timeframe] } }, [timeframe, startDate, endDate]) // Update dates when timeframe changes (except for Custom) useEffect(() => { if (timeframe !== 'Custom') { const end = parseInputDate(endDate) const start = calculateStartDate(timeframe, end) setStartDate(formatDateForInput(start)) // Update bar size to default for this timeframe setBarSize(DEFAULT_BAR_SIZE[timeframe]) } }, [timeframe, endDate]) // When custom dates change, update bar size if current is invalid useEffect(() => { if (timeframe === 'Custom') { if (!barSizeOptions.options.includes(barSize)) { setBarSize(barSizeOptions.default) } } }, [timeframe, barSizeOptions, barSize]) // Handle timeframe change const handleTimeframeChange = (newTimeframe: Timeframe) => { setTimeframe(newTimeframe) } // Handle end date change const handleEndDateChange = (newEndDate: string) => { setEndDate(newEndDate) if (timeframe !== 'Custom') { const end = parseInputDate(newEndDate) const start = calculateStartDate(timeframe, end) setStartDate(formatDateForInput(start)) } } // Handle start date change (switches to Custom if not already) const handleStartDateChange = (newStartDate: string) => { setStartDate(newStartDate) if (timeframe !== 'Custom') { setTimeframe('Custom') } } const handleGo = () => { // Placeholder for Go button action } const handleSaveChart = () => { // Placeholder for Save chart action } return (
{/* Header */}

Chart

{/* Toolbar */}
{/* Left side */}
handleStartDateChange(e.target.value)} className="border border-border bg-input text-foreground text-sm px-3 py-1.5 cursor-pointer" /> handleEndDateChange(e.target.value)} className="border border-border bg-input text-foreground text-sm px-3 py-1.5 cursor-pointer" />
{/* Right side */}
{/* Chart area */}
) } export default function ComplexChart() { return (
Loading...
}>
) }