mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-10 22:36:58 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user