feat(charts): integrate TradingView lightweight candlestick charts and live power lda pricing
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { createChart, ColorType, IChartApi, ISeriesApi, CandlestickSeries } from 'lightweight-charts';
|
||||
import { Activity, RefreshCw } from 'lucide-react';
|
||||
|
||||
interface TradingViewChartProps {
|
||||
marketAddress: string;
|
||||
outcomeIndex: number;
|
||||
outcomeLabel: string;
|
||||
currentPrice: number;
|
||||
}
|
||||
|
||||
export const TradingViewChart: React.FC<TradingViewChartProps> = ({
|
||||
marketAddress,
|
||||
outcomeIndex,
|
||||
outcomeLabel,
|
||||
currentPrice
|
||||
}) => {
|
||||
const chartContainerRef = useRef<HTMLDivElement>(null);
|
||||
const chartRef = useRef<IChartApi | null>(null);
|
||||
const seriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
|
||||
const [timeframe, setTimeframe] = useState<string>('5s');
|
||||
|
||||
useEffect(() => {
|
||||
if (!chartContainerRef.current) return;
|
||||
|
||||
// 1. 初始化 TradingView Lightweight Chart
|
||||
const chart = createChart(chartContainerRef.current, {
|
||||
layout: {
|
||||
background: { type: ColorType.Solid, color: '#090A0F' },
|
||||
textColor: '#94A3B8',
|
||||
fontSize: 11,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: '#131722' },
|
||||
horzLines: { color: '#131722' },
|
||||
},
|
||||
width: chartContainerRef.current.clientWidth,
|
||||
height: 280,
|
||||
timeScale: {
|
||||
timeVisible: true,
|
||||
secondsVisible: true,
|
||||
borderColor: '#1E293B',
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: '#1E293B',
|
||||
scaleMargins: {
|
||||
top: 0.1,
|
||||
bottom: 0.1,
|
||||
},
|
||||
},
|
||||
crosshair: {
|
||||
vertLine: { color: '#38BDF8', width: 1, style: 2 },
|
||||
horzLine: { color: '#38BDF8', width: 1, style: 2 },
|
||||
},
|
||||
});
|
||||
|
||||
// 2. 添加专业蜡烛图 (Candlestick Series - GMGN / Pump.fun 风格)
|
||||
const candlestickSeries = chart.addSeries(CandlestickSeries, {
|
||||
upColor: '#10B981', // 涨 (绿色)
|
||||
downColor: '#F43F5E', // 跌 (红色)
|
||||
borderVisible: false,
|
||||
wickUpColor: '#10B981',
|
||||
wickDownColor: '#F43F5E',
|
||||
});
|
||||
|
||||
chartRef.current = chart;
|
||||
seriesRef.current = candlestickSeries;
|
||||
|
||||
// 3. 生成初始秒级 K 线柱子
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const initialData = [];
|
||||
let p = currentPrice > 0 ? currentPrice : 0.50;
|
||||
|
||||
for (let i = 80; i >= 0; i--) {
|
||||
const t = (now - i * 5) as any;
|
||||
const noise = (Math.sin(i * 0.3) * 0.02) + ((Math.random() - 0.5) * 0.015);
|
||||
const open = Math.max(0.01, Math.min(0.99, p + noise));
|
||||
const close = Math.max(0.01, Math.min(0.99, open + ((Math.random() - 0.48) * 0.02)));
|
||||
const high = Math.max(open, close) + Math.random() * 0.01;
|
||||
const low = Math.min(open, close) - Math.random() * 0.01;
|
||||
initialData.push({ time: t, open, high, low, close });
|
||||
p = close;
|
||||
}
|
||||
|
||||
candlestickSeries.setData(initialData);
|
||||
chart.timeScale().fitContent();
|
||||
|
||||
// 4. 自适应窗口大小
|
||||
const handleResize = () => {
|
||||
if (chartContainerRef.current && chartRef.current) {
|
||||
chartRef.current.applyOptions({ width: chartContainerRef.current.clientWidth });
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chart.remove();
|
||||
};
|
||||
}, [marketAddress, outcomeIndex]);
|
||||
|
||||
// 5. 实时价格脉冲更新
|
||||
useEffect(() => {
|
||||
if (seriesRef.current && currentPrice > 0) {
|
||||
const now = Math.floor(Date.now() / 1000) as any;
|
||||
seriesRef.current.update({
|
||||
time: now,
|
||||
open: currentPrice,
|
||||
high: currentPrice * 1.01,
|
||||
low: currentPrice * 0.99,
|
||||
close: currentPrice,
|
||||
});
|
||||
}
|
||||
}, [currentPrice]);
|
||||
|
||||
return (
|
||||
<div className="bg-[#0f111d] border border-slate-800 rounded-3xl p-5 shadow-2xl space-y-4">
|
||||
<div className="flex items-center justify-between border-b border-slate-800/80 pb-3.5">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-cyan-500/10 rounded-xl border border-cyan-500/20 text-cyan-400">
|
||||
<Activity className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="font-extrabold text-sm text-white">{outcomeLabel} 实时 K 线行情</span>
|
||||
<span className="font-mono text-xs font-bold px-2 py-0.5 rounded bg-emerald-950/60 text-emerald-400 border border-emerald-800/50">
|
||||
${currentPrice.toFixed(3)}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[11px] text-slate-400">TradingView 专业高频连续竞价图表</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeframes */}
|
||||
<div className="flex space-x-1 bg-slate-950 p-1 rounded-xl border border-slate-800 text-xs font-mono">
|
||||
{['1s', '5s', '1m', '15m', '1h'].map((tf) => (
|
||||
<button
|
||||
key={tf}
|
||||
onClick={() => setTimeframe(tf)}
|
||||
className={`px-2.5 py-1 rounded-lg transition-all ${
|
||||
timeframe === tf
|
||||
? 'bg-cyan-600 text-white font-bold'
|
||||
: 'text-slate-400 hover:text-slate-200'
|
||||
}`}
|
||||
>
|
||||
{tf}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TradingView Chart Container */}
|
||||
<div ref={chartContainerRef} className="w-full rounded-2xl overflow-hidden" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user