mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 16:26:57 +08:00
Refactor: Improve comparison chart data synchronization
Major improvements: - Replace useState+useEffect with useMemo for better performance - Use timestamp-based grouping instead of cycle_number - Fix data alignment issues when backend resets cycles - Add detailed console logging for debugging - Optimize data merging logic with Map structures Chart updates: - Display sequence number instead of cycle number on X-axis - Improve tooltip to show actual time and sequence - Better handling of multi-trader data synchronization - More reliable data point matching across traders Performance: - Reduce unnecessary re-renders with useMemo - More efficient data processing with Maps - Better dependency tracking in hooks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
LineChart,
|
LineChart,
|
||||||
Line,
|
Line,
|
||||||
@@ -19,8 +19,6 @@ interface ComparisonChartProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ComparisonChart({ traders }: ComparisonChartProps) {
|
export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||||
const [combinedData, setCombinedData] = useState<any[]>([]);
|
|
||||||
|
|
||||||
// 获取所有trader的历史数据
|
// 获取所有trader的历史数据
|
||||||
const traderHistories = traders.map((trader) => {
|
const traderHistories = traders.map((trader) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
@@ -30,47 +28,87 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
// 使用useMemo自动处理数据合并,直接使用data对象作为依赖
|
||||||
|
const combinedData = useMemo(() => {
|
||||||
// 等待所有数据加载完成
|
// 等待所有数据加载完成
|
||||||
const allLoaded = traderHistories.every((h) => h.data);
|
const allLoaded = traderHistories.every((h) => h.data);
|
||||||
if (!allLoaded) return;
|
if (!allLoaded) return [];
|
||||||
|
|
||||||
// 合并所有trader的数据 - 使用cycle_number作为key确保数据对齐
|
console.log(`[${new Date().toISOString()}] Recalculating chart data...`);
|
||||||
const cycleMap = new Map<number, any>();
|
|
||||||
|
// 新方案:按时间戳分组,不再依赖 cycle_number(因为后端会重置)
|
||||||
|
// 收集所有时间戳
|
||||||
|
const timestampMap = new Map<string, {
|
||||||
|
timestamp: string;
|
||||||
|
time: string;
|
||||||
|
traders: Map<string, { pnl_pct: number; equity: number }>;
|
||||||
|
}>();
|
||||||
|
|
||||||
traderHistories.forEach((history, index) => {
|
traderHistories.forEach((history, index) => {
|
||||||
const trader = traders[index];
|
const trader = traders[index];
|
||||||
history.data?.forEach((point: any) => {
|
if (!history.data) return;
|
||||||
const cycleNumber = point.cycle_number || 0;
|
|
||||||
const time = new Date(point.timestamp).toLocaleTimeString('zh-CN', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!cycleMap.has(cycleNumber)) {
|
console.log(`Trader ${trader.trader_id}: ${history.data.length} data points`);
|
||||||
cycleMap.set(cycleNumber, {
|
|
||||||
cycle: cycleNumber,
|
history.data.forEach((point: any) => {
|
||||||
|
const ts = point.timestamp;
|
||||||
|
|
||||||
|
if (!timestampMap.has(ts)) {
|
||||||
|
const time = new Date(ts).toLocaleTimeString('zh-CN', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
timestampMap.set(ts, {
|
||||||
|
timestamp: ts,
|
||||||
time,
|
time,
|
||||||
timestamp: point.timestamp
|
traders: new Map()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = cycleMap.get(cycleNumber);
|
timestampMap.get(ts)!.traders.set(trader.trader_id, {
|
||||||
entry[`${trader.trader_id}_pnl_pct`] = point.total_pnl_pct;
|
pnl_pct: point.total_pnl_pct,
|
||||||
entry[`${trader.trader_id}_equity`] = point.total_equity;
|
equity: point.total_equity
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 转换为数组并按cycle排序
|
// 按时间戳排序,转换为数组
|
||||||
const combined = Array.from(cycleMap.values())
|
const combined = Array.from(timestampMap.entries())
|
||||||
.filter(item => {
|
.sort(([tsA], [tsB]) => new Date(tsA).getTime() - new Date(tsB).getTime())
|
||||||
// 只保留所有trader都有数据的点
|
.map(([ts, data], index) => {
|
||||||
return traders.every(t => item[`${t.trader_id}_pnl_pct`] !== undefined);
|
const entry: any = {
|
||||||
})
|
index: index + 1, // 使用序号代替cycle
|
||||||
.sort((a, b) => a.cycle - b.cycle);
|
time: data.time,
|
||||||
|
timestamp: ts
|
||||||
|
};
|
||||||
|
|
||||||
setCombinedData(combined);
|
traders.forEach((trader) => {
|
||||||
}, [traderHistories.map((h) => h.data).join(',')]);
|
const traderData = data.traders.get(trader.trader_id);
|
||||||
|
if (traderData) {
|
||||||
|
entry[`${trader.trader_id}_pnl_pct`] = traderData.pnl_pct;
|
||||||
|
entry[`${trader.trader_id}_equity`] = traderData.equity;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (combined.length > 0) {
|
||||||
|
const lastPoint = combined[combined.length - 1];
|
||||||
|
console.log(`Chart: ${combined.length} data points, last time: ${lastPoint.time}, timestamp: ${lastPoint.timestamp}`);
|
||||||
|
console.log('Last 3 points:', combined.slice(-3).map(p => ({
|
||||||
|
time: p.time,
|
||||||
|
timestamp: p.timestamp,
|
||||||
|
deepseek: p.deepseek_trader_pnl_pct,
|
||||||
|
qwen: p.qwen_trader_pnl_pct
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}, [
|
||||||
|
traderHistories[0]?.data,
|
||||||
|
traderHistories[1]?.data,
|
||||||
|
]);
|
||||||
|
|
||||||
const isLoading = traderHistories.some((h) => !h.data);
|
const isLoading = traderHistories.some((h) => !h.data);
|
||||||
|
|
||||||
@@ -142,7 +180,7 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="rounded p-3 shadow-xl" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
<div className="rounded p-3 shadow-xl" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
<div className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
<div className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
||||||
Cycle #{data.cycle} - {data.time}
|
{data.time} - #{data.index}
|
||||||
</div>
|
</div>
|
||||||
{traders.map((trader) => {
|
{traders.map((trader) => {
|
||||||
const pnlPct = data[`${trader.trader_id}_pnl_pct`];
|
const pnlPct = data[`${trader.trader_id}_pnl_pct`];
|
||||||
|
|||||||
Reference in New Issue
Block a user