From 1ee8aa9fe7920a79d6042fd6d3e758fbdae8afbe Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Wed, 16 Jul 2025 12:39:22 +0200 Subject: [PATCH] Add working TradingChart component with lightweight-charts --- app/simple-chart/page.tsx | 61 +++++++++ components/TradingChart.tsx | 239 ++++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 app/simple-chart/page.tsx diff --git a/app/simple-chart/page.tsx b/app/simple-chart/page.tsx new file mode 100644 index 0000000..ec02992 --- /dev/null +++ b/app/simple-chart/page.tsx @@ -0,0 +1,61 @@ +'use client' +import React, { useEffect, useRef } from 'react' + +export default function SimpleChart() { + const chartContainerRef = useRef(null) + + useEffect(() => { + if (!chartContainerRef.current) return + + const initChart = async () => { + try { + console.log('Importing lightweight-charts...') + const LightweightCharts = await import('lightweight-charts') + console.log('Lightweight charts imported:', LightweightCharts) + + const { createChart, ColorType } = LightweightCharts + + const chart = createChart(chartContainerRef.current!, { + layout: { + background: { type: ColorType.Solid, color: '#1a1a1a' }, + textColor: '#ffffff', + }, + width: 800, + height: 400, + }) + + const candlestickSeries = chart.addCandlestickSeries({ + upColor: '#26a69a', + downColor: '#ef5350', + borderDownColor: '#ef5350', + borderUpColor: '#26a69a', + wickDownColor: '#ef5350', + wickUpColor: '#26a69a', + }) + + // Simple test data + candlestickSeries.setData([ + { time: '2023-12-22', open: 75.16, high: 82.84, low: 36.16, close: 45.72 }, + { time: '2023-12-23', open: 45.12, high: 53.90, low: 45.12, close: 48.09 }, + { time: '2023-12-24', open: 60.71, high: 60.71, low: 53.39, close: 59.29 }, + { time: '2023-12-25', open: 68.26, high: 68.26, low: 59.04, close: 60.50 }, + { time: '2023-12-26', open: 67.71, high: 105.85, low: 66.67, close: 91.04 }, + ]) + + console.log('Chart created successfully!') + + } catch (error) { + console.error('Error creating chart:', error) + } + } + + initChart() + }, []) + + return ( +
+

Lightweight Charts Test

+
+
+ ) +} diff --git a/components/TradingChart.tsx b/components/TradingChart.tsx index e69de29..feebb68 100644 --- a/components/TradingChart.tsx +++ b/components/TradingChart.tsx @@ -0,0 +1,239 @@ +'use client' +import React, { useEffect, useRef, useState } from 'react' + +interface Position { + id: string + symbol: string + side: 'LONG' | 'SHORT' + size: number + entryPrice: number + stopLoss?: number + takeProfit?: number + pnl: number + pnlPercentage: number +} + +interface TradingChartProps { + symbol?: string + positions?: Position[] +} + +export default function TradingChart({ symbol = 'SOL/USDC', positions = [] }: TradingChartProps) { + const chartContainerRef = useRef(null) + const chart = useRef(null) + const candlestickSeries = useRef(null) + const positionLines = useRef([]) + const [isLoading, setIsLoading] = useState(true) + + // Initialize chart with dynamic import + useEffect(() => { + if (!chartContainerRef.current) return + + const initChart = async () => { + try { + // Dynamic import to avoid SSR issues + const LightweightCharts = await import('lightweight-charts') + const { createChart, ColorType, CrosshairMode, LineStyle } = LightweightCharts + + chart.current = createChart(chartContainerRef.current!, { + layout: { + background: { type: ColorType.Solid, color: '#1a1a1a' }, + textColor: '#ffffff', + }, + width: chartContainerRef.current!.clientWidth, + height: 600, + grid: { + vertLines: { color: 'rgba(42, 46, 57, 0.5)' }, + horzLines: { color: 'rgba(42, 46, 57, 0.5)' }, + }, + crosshair: { + mode: CrosshairMode.Normal, + }, + rightPriceScale: { + borderColor: 'rgba(197, 203, 206, 0.8)', + }, + timeScale: { + borderColor: 'rgba(197, 203, 206, 0.8)', + }, + }) + + // Create candlestick series + candlestickSeries.current = chart.current.addCandlestickSeries({ + upColor: '#26a69a', + downColor: '#ef5350', + borderDownColor: '#ef5350', + borderUpColor: '#26a69a', + wickDownColor: '#ef5350', + wickUpColor: '#26a69a', + }) + + // Generate sample data + const data = generateSampleData() + candlestickSeries.current.setData(data) + + // Add position overlays + addPositionOverlays(LineStyle) + + setIsLoading(false) + + // Handle resize + const handleResize = () => { + if (chart.current && chartContainerRef.current) { + chart.current.applyOptions({ + width: chartContainerRef.current.clientWidth, + }) + } + } + + window.addEventListener('resize', handleResize) + + return () => { + window.removeEventListener('resize', handleResize) + if (chart.current) { + chart.current.remove() + } + } + } catch (error) { + console.error('Failed to initialize chart:', error) + setIsLoading(false) + } + } + + const addPositionOverlays = (LineStyle: any) => { + if (!chart.current) return + + // Clear existing lines + positionLines.current.forEach(line => { + if (line && chart.current) { + chart.current.removePriceLine(line) + } + }) + positionLines.current = [] + + // Add new position lines + positions.forEach(position => { + // Entry price line + const entryLine = chart.current.addPriceLine({ + price: position.entryPrice, + color: '#2196F3', + lineWidth: 2, + lineStyle: LineStyle.Solid, + axisLabelVisible: true, + title: `Entry: $${position.entryPrice.toFixed(2)}`, + }) + positionLines.current.push(entryLine) + + // Stop loss line + if (position.stopLoss) { + const slLine = chart.current.addPriceLine({ + price: position.stopLoss, + color: '#f44336', + lineWidth: 2, + lineStyle: LineStyle.Dashed, + axisLabelVisible: true, + title: `SL: $${position.stopLoss.toFixed(2)}`, + }) + positionLines.current.push(slLine) + } + + // Take profit line + if (position.takeProfit) { + const tpLine = chart.current.addPriceLine({ + price: position.takeProfit, + color: '#4caf50', + lineWidth: 2, + lineStyle: LineStyle.Dashed, + axisLabelVisible: true, + title: `TP: $${position.takeProfit.toFixed(2)}`, + }) + positionLines.current.push(tpLine) + } + }) + } + + const generateSampleData = () => { + const data = [] + const basePrice = 166.5 + let currentPrice = basePrice + const now = new Date() + + for (let i = 100; i >= 0; i--) { + const time = Math.floor((now.getTime() - i * 60000) / 1000) // 1 minute intervals + const volatility = 0.02 + const change = (Math.random() - 0.5) * volatility * currentPrice + + const open = currentPrice + const close = currentPrice + change + const high = Math.max(open, close) + Math.random() * 0.01 * currentPrice + const low = Math.min(open, close) - Math.random() * 0.01 * currentPrice + + data.push({ + time, + open: Number(open.toFixed(2)), + high: Number(high.toFixed(2)), + low: Number(low.toFixed(2)), + close: Number(close.toFixed(2)), + }) + + currentPrice = close + } + + return data + } + + initChart() + }, []) + + // Update position overlays when positions change + useEffect(() => { + if (chart.current && !isLoading && positions.length > 0) { + import('lightweight-charts').then(({ LineStyle }) => { + // Re-add position overlays (this is a simplified version) + // In a full implementation, you'd want to properly manage line updates + }) + } + }, [positions, isLoading]) + + if (isLoading) { + return ( +
+
Loading chart...
+
+ ) + } + + return ( +
+ {/* Chart Header */} +
+
+
{symbol}
+
$166.21
+
+1.42%
+
+
+ + {/* Position Info */} + {positions.length > 0 && ( +
+
+ {positions.map(position => ( +
+
+ {position.side} {position.size} + = 0 ? 'text-green-400' : 'text-red-400'}> + {position.pnl >= 0 ? '+' : ''}${position.pnl.toFixed(2)} + +
+ ))} +
+
+ )} + + {/* Chart Container */} +
+
+ ) +}