"use client"; /** * K 线图 —— TradingView Lightweight Charts 实现(v3 §21 推荐库)。 * * 与 ECharts 版(CandleChart.tsx)共用同一 props 接口(chartTypes.ts), * 供「两套实现并存对比」,组件层可整体替换。 * 标记语义(v3 §20.3):成交 fills = 实心箭头;未成交信号 = 空心圆点。 * 组件只展示,不重算。 */ import { useEffect, useRef } from "react"; import { createChart, ColorType, CrosshairMode, type IChartApi, type ISeriesApi, type SeriesMarker, type Time, } from "lightweight-charts"; import type { CandleChartProps } from "./chartTypes"; const C = { faint: "#8b98b2", grid: "rgba(150,165,195,0.09)", up: "#e5484d", down: "#2fb36b", ma20: "#3fb6ff", ma60: "#d4a72c", text: "#e9eef7", markerBuy: "#3ddc97", markerSell: "#ff5a5a", markerSig: "#b98bff", }; function toTime(t: string): Time { return t as Time; // 'YYYY-MM-DD'(v4 支持字符串 business day) } export function CandleChartLW({ data, volume = [], indicators = {}, fills = [], signals = [], height = 480, name = "", }: CandleChartProps) { const ref = useRef(null); useEffect(() => { if (!ref.current || data.length === 0) return; const chart: IChartApi = createChart(ref.current, { autoSize: true, height, layout: { background: { type: ColorType.Solid, color: "transparent" }, textColor: C.text, fontSize: 11, }, grid: { vertLines: { color: C.grid }, horzLines: { color: C.grid }, }, rightPriceScale: { borderColor: C.grid }, timeScale: { borderColor: C.grid, timeVisible: false }, crosshair: { mode: CrosshairMode.Normal }, }); const candle: ISeriesApi<"Candlestick"> = chart.addCandlestickSeries({ upColor: C.up, downColor: C.down, borderUpColor: C.up, borderDownColor: C.down, wickUpColor: C.up, wickDownColor: C.down, }); candle.setData( data.map((d) => ({ time: toTime(d.time), open: d.open, high: d.high, low: d.low, close: d.close, })) ); // 成交量(独立价格刻度,置于底部 20%) const vol: ISeriesApi<"Histogram"> = chart.addHistogramSeries({ priceFormat: { type: "volume" }, priceScaleId: "vol", lastValueVisible: false, priceLineVisible: false, }); chart.priceScale("vol").applyOptions({ scaleMargins: { top: 0.8, bottom: 0 } }); vol.setData( volume.map((v) => ({ time: toTime(v.time), value: v.value ?? 0, color: C.grid })) ); // MA 指标线 for (const [name, pts] of Object.entries(indicators)) { const line = chart.addLineSeries({ color: name === "ma60" ? C.ma60 : C.ma20, lineWidth: 1, priceLineVisible: false, lastValueVisible: false, crosshairMarkerVisible: false, }); line.setData( pts .filter((p) => p.value != null) .map((p) => ({ time: toTime(p.time), value: p.value as number })) ); } // 事件标记:成交=实心箭头;未成交信号=圆点 const markers: SeriesMarker