mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-15 00:36:56 +08:00
fix: competition chart showing only one line
- Normalize timestamps to nearest minute for proper alignment - Forward-fill missing values to ensure continuous lines - Use actual data range for Y-axis instead of forcing zero
This commit is contained in:
@@ -76,56 +76,85 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
|||||||
{
|
{
|
||||||
timestamp: string
|
timestamp: string
|
||||||
time: string
|
time: string
|
||||||
traders: Map<string, { pnl_pct: number; equity: number }>
|
traders: Map<string, { pnl_pct: number; equity: number; originalTs?: string }>
|
||||||
}
|
}
|
||||||
>()
|
>()
|
||||||
|
|
||||||
|
// Helper function to normalize timestamp to nearest minute
|
||||||
|
const normalizeTimestamp = (ts: string): string => {
|
||||||
|
const date = new Date(ts)
|
||||||
|
date.setSeconds(0, 0) // Round to minute
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
traderHistories.forEach((history, index) => {
|
traderHistories.forEach((history, index) => {
|
||||||
const trader = traders[index]
|
const trader = traders[index]
|
||||||
if (!history.data) return
|
if (!history.data) return
|
||||||
|
|
||||||
history.data.forEach((point: any) => {
|
history.data.forEach((point: any) => {
|
||||||
const ts = point.timestamp
|
// Normalize timestamp to nearest minute so different traders' data aligns
|
||||||
|
const normalizedTs = normalizeTimestamp(point.timestamp)
|
||||||
|
|
||||||
if (!timestampMap.has(ts)) {
|
if (!timestampMap.has(normalizedTs)) {
|
||||||
const time = new Date(ts).toLocaleTimeString('zh-CN', {
|
const time = new Date(normalizedTs).toLocaleTimeString('zh-CN', {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
})
|
})
|
||||||
timestampMap.set(ts, {
|
timestampMap.set(normalizedTs, {
|
||||||
timestamp: ts,
|
timestamp: normalizedTs,
|
||||||
time,
|
time,
|
||||||
traders: new Map(),
|
traders: new Map(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
timestampMap.get(ts)!.traders.set(trader.trader_id, {
|
// Use latest value if multiple points fall in same minute
|
||||||
pnl_pct: point.total_pnl_pct || 0,
|
const existing = timestampMap.get(normalizedTs)!.traders.get(trader.trader_id)
|
||||||
equity: point.total_equity,
|
if (!existing || new Date(point.timestamp) > new Date(existing.originalTs || '')) {
|
||||||
})
|
timestampMap.get(normalizedTs)!.traders.set(trader.trader_id, {
|
||||||
|
pnl_pct: point.total_pnl_pct || 0,
|
||||||
|
equity: point.total_equity,
|
||||||
|
originalTs: point.timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const combined = Array.from(timestampMap.entries())
|
const sortedEntries = Array.from(timestampMap.entries())
|
||||||
.sort(([tsA], [tsB]) => new Date(tsA).getTime() - new Date(tsB).getTime())
|
.sort(([tsA], [tsB]) => new Date(tsA).getTime() - new Date(tsB).getTime())
|
||||||
.map(([ts, data], index) => {
|
|
||||||
const entry: any = {
|
|
||||||
index: index + 1,
|
|
||||||
time: data.time,
|
|
||||||
timestamp: ts,
|
|
||||||
}
|
|
||||||
|
|
||||||
traders.forEach((trader) => {
|
// Track last known values for each trader to fill gaps
|
||||||
const traderData = data.traders.get(trader.trader_id)
|
const lastKnown: Map<string, { pnl_pct: number; equity: number }> = new Map()
|
||||||
if (traderData) {
|
|
||||||
entry[`${trader.trader_id}_pnl_pct`] = traderData.pnl_pct
|
const combined = sortedEntries.map(([ts, data], index) => {
|
||||||
entry[`${trader.trader_id}_equity`] = traderData.equity
|
const entry: any = {
|
||||||
|
index: index + 1,
|
||||||
|
time: data.time,
|
||||||
|
timestamp: ts,
|
||||||
|
}
|
||||||
|
|
||||||
|
traders.forEach((trader) => {
|
||||||
|
const traderData = data.traders.get(trader.trader_id)
|
||||||
|
if (traderData) {
|
||||||
|
// Update last known value
|
||||||
|
lastKnown.set(trader.trader_id, {
|
||||||
|
pnl_pct: traderData.pnl_pct,
|
||||||
|
equity: traderData.equity,
|
||||||
|
})
|
||||||
|
entry[`${trader.trader_id}_pnl_pct`] = traderData.pnl_pct
|
||||||
|
entry[`${trader.trader_id}_equity`] = traderData.equity
|
||||||
|
} else {
|
||||||
|
// Use last known value to fill gap
|
||||||
|
const last = lastKnown.get(trader.trader_id)
|
||||||
|
if (last) {
|
||||||
|
entry[`${trader.trader_id}_pnl_pct`] = last.pnl_pct
|
||||||
|
entry[`${trader.trader_id}_equity`] = last.equity
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return entry
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
|
||||||
return combined
|
return combined
|
||||||
}, [allTraderHistories, traders])
|
}, [allTraderHistories, traders])
|
||||||
|
|
||||||
@@ -187,14 +216,15 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
|||||||
|
|
||||||
const minVal = Math.min(...allValues)
|
const minVal = Math.min(...allValues)
|
||||||
const maxVal = Math.max(...allValues)
|
const maxVal = Math.max(...allValues)
|
||||||
|
const range = maxVal - minVal
|
||||||
|
|
||||||
// Ensure zero is visible and add symmetric padding
|
// Use actual data range with 20% padding on each side
|
||||||
const absMax = Math.max(Math.abs(maxVal), Math.abs(minVal), 0.5)
|
// This ensures both lines are clearly visible
|
||||||
const padding = absMax * 0.3
|
const padding = Math.max(range * 0.2, 2) // At least 2% padding
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Math.floor((Math.min(minVal, 0) - padding) * 10) / 10,
|
Math.floor((minVal - padding) * 10) / 10,
|
||||||
Math.ceil((Math.max(maxVal, 0) + padding) * 10) / 10
|
Math.ceil((maxVal + padding) * 10) / 10
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user