fix: competition chart data sync and display issues

- Add real-time data point to equity history API for chart/leaderboard consistency
- Fix chart legend showing 0% by finding each trader's last available data point
- Use consistent timestamp for all real-time data points in batch requests
- Increase chart trader limit from 5 to 10
- Remove Mini Stats Bar trader limit
This commit is contained in:
tinkle-community
2025-12-11 12:32:04 +08:00
parent 438f55bc30
commit 78b5e73966
3 changed files with 61 additions and 15 deletions

View File

@@ -2505,11 +2505,15 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) {
// getEquityHistoryForTraders Get historical data for multiple traders
// Query directly from database, not dependent on trader in memory (so historical data can be retrieved after restart)
// Also appends current real-time data point to ensure chart matches leaderboard
func (s *Server) getEquityHistoryForTraders(traderIDs []string) map[string]interface{} {
result := make(map[string]interface{})
histories := make(map[string]interface{})
errors := make(map[string]string)
// Use a single consistent timestamp for all real-time data points
now := time.Now()
// Pre-fetch initial balances for all traders
initialBalances := make(map[string]float64)
for _, traderID := range traderIDs {
@@ -2535,21 +2539,16 @@ func (s *Server) getEquityHistoryForTraders(traderIDs []string) map[string]inter
continue
}
if len(snapshots) == 0 {
// No historical records, return empty array
histories[traderID] = []map[string]interface{}{}
continue
}
// Get initial balance for calculating PnL percentage
initialBalance := initialBalances[traderID]
if initialBalance <= 0 {
if initialBalance <= 0 && len(snapshots) > 0 {
// If no initial balance configured, use the first snapshot's equity as baseline
initialBalance = snapshots[0].TotalEquity
}
// Build return rate historical data with PnL percentage
history := make([]map[string]interface{}, 0, len(snapshots))
history := make([]map[string]interface{}, 0, len(snapshots)+1)
var lastSnapshotTime time.Time
for _, snap := range snapshots {
// Calculate PnL percentage: (current_equity - initial_balance) / initial_balance * 100
pnlPct := 0.0
@@ -2564,6 +2563,43 @@ func (s *Server) getEquityHistoryForTraders(traderIDs []string) map[string]inter
"total_pnl_pct": pnlPct,
"balance": snap.Balance,
})
if snap.Timestamp.After(lastSnapshotTime) {
lastSnapshotTime = snap.Timestamp
}
}
// Append current real-time data point to ensure chart matches leaderboard
// This ensures the latest point is always current, not from a potentially stale snapshot
if trader, err := s.traderManager.GetTrader(traderID); err == nil {
if accountInfo, err := trader.GetAccountInfo(); err == nil {
// Only append if it's been more than 30 seconds since last snapshot
if now.Sub(lastSnapshotTime) > 30*time.Second {
totalEquity := 0.0
if v, ok := accountInfo["total_equity"].(float64); ok {
totalEquity = v
}
totalPnL := 0.0
if v, ok := accountInfo["total_pnl"].(float64); ok {
totalPnL = v
}
walletBalance := 0.0
if v, ok := accountInfo["wallet_balance"].(float64); ok {
walletBalance = v
}
pnlPct := 0.0
if initialBalance > 0 {
pnlPct = (totalEquity - initialBalance) / initialBalance * 100
}
history = append(history, map[string]interface{}{
"timestamp": now,
"total_equity": totalEquity,
"total_pnl": totalPnL,
"total_pnl_pct": pnlPct,
"balance": walletBalance,
})
}
}
}
histories[traderID] = history

View File

@@ -257,11 +257,19 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
return null
}
// Calculate stats
const lastPoint = displayData[displayData.length - 1]
// Calculate stats - find each trader's last available data point
const traderStats = traders.map(trader => {
const currentPnl = lastPoint?.[`${trader.trader_id}_pnl_pct`] || 0
const currentEquity = lastPoint?.[`${trader.trader_id}_equity`] || 0
// Find the last data point that has data for this trader
let currentPnl = 0
let currentEquity = 0
for (let i = displayData.length - 1; i >= 0; i--) {
const pnl = displayData[i]?.[`${trader.trader_id}_pnl_pct`]
if (pnl !== undefined) {
currentPnl = pnl
currentEquity = displayData[i]?.[`${trader.trader_id}_equity`] || 0
break
}
}
return { ...trader, currentPnl, currentEquity }
}).sort((a, b) => b.currentPnl - a.currentPnl)
@@ -274,7 +282,7 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
<div className="space-y-4">
{/* Mini Stats Bar */}
<div className="flex items-center gap-3 flex-wrap">
{traderStats.slice(0, 5).map((trader, idx) => (
{traderStats.map((trader, idx) => (
<div key={trader.trader_id}
className="flex items-center gap-2 px-3 py-1.5 rounded-full transition-all hover:scale-105"
style={{
@@ -418,7 +426,9 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
<div style={{ display: 'flex', justifyContent: 'center', gap: '20px', flexWrap: 'wrap' }}>
{filteredPayload.map((entry: any, index: number) => {
const trader = traders.find((t) => t.trader_name === entry.value)
const pnl = trader ? lastPoint?.[`${trader.trader_id}_pnl_pct`] || 0 : 0
// Find this trader's last available PnL from traderStats
const traderStat = traderStats.find((t) => t.trader_id === trader?.trader_id)
const pnl = traderStat?.currentPnl || 0
return (
<div key={`legend-${index}`} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<div style={{

View File

@@ -210,7 +210,7 @@ export function CompetitionPage() {
{t('realTimePnL', language)}
</div>
</div>
<ComparisonChart traders={sortedTraders.slice(0, 5)} />
<ComparisonChart traders={sortedTraders.slice(0, 10)} />
</div>
{/* Right: Leaderboard */}