Add working TradingChart component with lightweight-charts
This commit is contained in:
61
app/simple-chart/page.tsx
Normal file
61
app/simple-chart/page.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
'use client'
|
||||||
|
import React, { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
export default function SimpleChart() {
|
||||||
|
const chartContainerRef = useRef<HTMLDivElement>(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 (
|
||||||
|
<div className="min-h-screen bg-gray-900 p-8">
|
||||||
|
<h1 className="text-white text-2xl mb-4">Lightweight Charts Test</h1>
|
||||||
|
<div ref={chartContainerRef} className="bg-gray-800 rounded" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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<HTMLDivElement>(null)
|
||||||
|
const chart = useRef<any>(null)
|
||||||
|
const candlestickSeries = useRef<any>(null)
|
||||||
|
const positionLines = useRef<any[]>([])
|
||||||
|
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 (
|
||||||
|
<div className="h-[600px] bg-gray-900 rounded-lg flex items-center justify-center">
|
||||||
|
<div className="text-white">Loading chart...</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
{/* Chart Header */}
|
||||||
|
<div className="absolute top-4 left-4 z-10 bg-gray-800/80 rounded-lg px-3 py-2">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="text-white font-bold text-lg">{symbol}</div>
|
||||||
|
<div className="text-green-400 font-semibold">$166.21</div>
|
||||||
|
<div className="text-green-400 text-sm">+1.42%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Position Info */}
|
||||||
|
{positions.length > 0 && (
|
||||||
|
<div className="absolute top-4 right-4 z-10 bg-gray-800/80 rounded-lg px-3 py-2">
|
||||||
|
<div className="text-white text-sm">
|
||||||
|
{positions.map(position => (
|
||||||
|
<div key={position.id} className="flex items-center space-x-2">
|
||||||
|
<div className={`w-2 h-2 rounded-full ${
|
||||||
|
position.side === 'LONG' ? 'bg-green-400' : 'bg-red-400'
|
||||||
|
}`}></div>
|
||||||
|
<span>{position.side} {position.size}</span>
|
||||||
|
<span className={position.pnl >= 0 ? 'text-green-400' : 'text-red-400'}>
|
||||||
|
{position.pnl >= 0 ? '+' : ''}${position.pnl.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Chart Container */}
|
||||||
|
<div ref={chartContainerRef} className="w-full h-[600px] bg-gray-900 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user