diff --git a/web/src/components/AdvancedChart.tsx b/web/src/components/AdvancedChart.tsx index 1460ff56..42bb46a6 100644 --- a/web/src/components/AdvancedChart.tsx +++ b/web/src/components/AdvancedChart.tsx @@ -446,37 +446,70 @@ export function AdvancedChart({ if (orders.length > 0) { console.log('[AdvancedChart] Creating markers from', orders.length, 'orders') - // 过滤掉无效时间戳的订单(小于2024年的时间戳) - const minValidTimestamp = new Date('2024-01-01').getTime() / 1000 - const validOrders = orders.filter(order => { - if (order.time < minValidTimestamp) { - console.warn('[AdvancedChart] ⚠️ Skipping order with invalid timestamp:', order.time, '(', new Date(order.time * 1000).toISOString(), ')') - return false + // 提取 K 线时间数组(已排序) + const klineTimes = klineData.map((k: any) => k.time as number) + const klineMinTime = klineTimes[0] || 0 + const klineMaxTime = klineTimes[klineTimes.length - 1] || 0 + console.log('[AdvancedChart] Kline time range:', klineMinTime, '-', klineMaxTime, '(', klineTimes.length, 'candles)') + + // 二分查找:找到订单时间所属的 K 线蜡烛 + // 返回 time <= orderTime 的最大 K 线时间 + const findCandleTime = (orderTime: number): number | null => { + if (orderTime < klineMinTime || orderTime > klineMaxTime) { + return null // 超出范围 } - return true - }) - console.log('[AdvancedChart] Valid orders:', validOrders.length, 'out of', orders.length) + let left = 0 + let right = klineTimes.length - 1 + + while (left < right) { + const mid = Math.ceil((left + right + 1) / 2) + if (klineTimes[mid] <= orderTime) { + left = mid + } else { + right = mid - 1 + } + } + + return klineTimes[left] + } + + // 过滤并对齐订单到 K 线时间 + const markers: Array<{ + time: Time + position: 'belowBar' + color: string + shape: 'circle' + text: string + size: number + }> = [] + + orders.forEach(order => { + // 使用二分查找找到对应的 K 线蜡烛时间 + const candleTime = findCandleTime(order.time) + + if (candleTime === null) { + console.warn('[AdvancedChart] ⚠️ Skipping order outside kline range:', + order.time, '(', new Date(order.time * 1000).toISOString(), ')') + return + } - const markers = validOrders.map(order => { - // 直接使用 rawSide 字段判断买卖(更准确) - // rawSide = 'buy' → 绿色 B - // rawSide = 'sell' → 红色 S const isBuy = order.rawSide === 'buy' - - const marker = { - time: order.time as Time, + markers.push({ + time: candleTime as Time, position: 'belowBar' as const, - color: isBuy ? '#0ECB81' : '#F6465D', // BUY绿色, SELL红色 - shape: 'circle' as const, // 使用圆形作为背景 - text: isBuy ? 'B' : 'S', // 显示 B 或 S - size: 1, // 稍微大一点以显示文字 - } - - console.log('[AdvancedChart] ✅ Created marker:', marker.text, 'for', order.rawSide, 'at', new Date(order.time * 1000).toISOString()) - return marker + color: isBuy ? '#0ECB81' : '#F6465D', + shape: 'circle' as const, + text: isBuy ? 'B' : 'S', + size: 1, + }) }) + // 按时间排序(lightweight-charts 要求标记按时间顺序) + markers.sort((a, b) => (a.time as number) - (b.time as number)) + + console.log('[AdvancedChart] Valid markers:', markers.length, 'out of', orders.length) + console.log('[AdvancedChart] Setting', markers.length, 'markers on candlestick series') console.log('[AdvancedChart] Markers data:', JSON.stringify(markers, null, 2)) diff --git a/web/src/components/ChartWithOrders.tsx b/web/src/components/ChartWithOrders.tsx index 853a042d..73c97a17 100644 --- a/web/src/components/ChartWithOrders.tsx +++ b/web/src/components/ChartWithOrders.tsx @@ -311,6 +311,29 @@ export function ChartWithOrders({ console.log('[ChartWithOrders] Kline data received:', klineData.length, 'candles') candlestickSeriesRef.current.setData(klineData) + // 构建 K 线时间集合,用于快速查找 + const klineTimeSet = new Set(klineData.map(k => k.time as number)) + const klineMinTime = klineData.length > 0 ? klineData[0].time : 0 + const klineMaxTime = klineData.length > 0 ? klineData[klineData.length - 1].time : 0 + console.log('[ChartWithOrders] Kline time range:', klineMinTime, '-', klineMaxTime, 'candles:', klineData.length) + + // 计算时间周期的秒数 + const getIntervalSeconds = (interval: string): number => { + const match = interval.match(/(\d+)([smhd])/) + if (!match) return 60 // 默认1分钟 + const [, num, unit] = match + const n = parseInt(num) + switch (unit) { + case 's': return n + case 'm': return n * 60 + case 'h': return n * 3600 + case 'd': return n * 86400 + default: return 60 + } + } + const intervalSeconds = getIntervalSeconds(interval) + console.log('[ChartWithOrders] Interval:', interval, '=', intervalSeconds, 'seconds') + // 2. 获取订单数据并添加标记 if (traderID) { console.log('[ChartWithOrders] Fetching orders for trader:', traderID, 'symbol:', symbol) @@ -321,36 +344,42 @@ export function ChartWithOrders({ console.log('[ChartWithOrders] No orders to display') } - // 过滤掉无效时间戳的订单(小于2024年的时间戳) - const minValidTimestamp = new Date('2024-01-01').getTime() / 1000 - const validOrders = orders.filter(order => { - if (order.time < minValidTimestamp) { - console.warn('[ChartWithOrders] ⚠️ Skipping order with invalid timestamp:', order.time, '(', new Date(order.time * 1000).toISOString(), ')') - return false + // 转换订单为图表标记,并对齐到 K 线时间 + const markers: Array<{ + time: Time + position: 'belowBar' + color: string + shape: 'circle' + text: string + price: number + size: number + }> = [] + + orders.forEach((order) => { + // 将订单时间对齐到 K 线周期(向下取整) + const alignedTime = Math.floor(order.time / intervalSeconds) * intervalSeconds + + // 检查对齐后的时间是否在 K 线数据中存在 + if (!klineTimeSet.has(alignedTime)) { + console.warn('[ChartWithOrders] ⚠️ Skipping order - no matching kline:', + order.time, '→', alignedTime, '(', new Date(order.time * 1000).toISOString(), ')') + return } - return true - }) - console.log('[ChartWithOrders] Valid orders:', validOrders.length, 'out of', orders.length) - - // 转换订单为图表标记 - 简洁版:只用 B/S - const markers = validOrders.map((order) => { - // 使用 side 字段判断买卖(更准确) - // side = BUY → 绿色 B - // side = SELL → 红色 S const isBuy = order.side === 'BUY' - - return { - time: order.time as Time, + markers.push({ + time: alignedTime as Time, position: 'belowBar' as const, color: isBuy ? '#0ECB81' : '#F6465D', shape: 'circle' as const, text: isBuy ? 'B' : 'S', price: order.price, size: 1, - } + }) }) + console.log('[ChartWithOrders] Valid markers (with matching klines):', markers.length, 'out of', orders.length) + console.log('[ChartWithOrders] Setting', markers.length, 'markers on chart') try {