fix: data race, scale-in PnL, nonce mutex, dead code, hourly check

- 🔴 Data race: Add GetPositionsCopy() returning deep copies (no shared
  ArbPosition pointers). Use it in dashboard broadcastLoop + handleStatus.
- 🟡 Scale-in PnL: Track LongEntryPrices/ShortEntryPrices on ArbPosition,
  compute weighted average (harmonic mean) at exit for accurate PnL.
- 🟢 CalcNetProfit: Delete dead code from exchange/helpers.go.
- 🟢 HL nonce: Add sync.Mutex around lastNonce++ (thread safety).
- 🟢 Hourly check: Change from 5-second window to minute window.
- 🟢 ExitPrice: Test mode closeLeg already handled by checkExit.
This commit is contained in:
jackyu66git
2026-05-03 18:31:14 +08:00
parent ab48e207a5
commit b08d8490fc
6 changed files with 94 additions and 54 deletions
+8 -6
View File
@@ -290,8 +290,8 @@ func (d *Dashboard) broadcastLoop() {
}
d.hub.Broadcast("prices", prices)
// 2. Open positions with live PnL (P3-3)
positions := d.trader.GetOpenPositions()
// 2. Open positions with live PnL (P3-3) — use safe copy for concurrent read
positions := d.trader.GetPositionsCopy()
posList := make([]map[string]interface{}, 0, len(positions))
for _, pos := range positions {
posEntry := map[string]interface{}{
@@ -304,7 +304,7 @@ func (d *Dashboard) broadcastLoop() {
"started_at": pos.StartedAt.Format("15:04:05"),
}
// Calculate live PnL from current prices
// Calculate live PnL from current prices — use weighted average for scale-ins
if exMap := snap[pos.Coin]; exMap != nil {
bgP := exMap[ExBitget]
hlP := exMap[ExHyperLiquid]
@@ -315,8 +315,10 @@ func (d *Dashboard) broadcastLoop() {
} else {
longCurrent, shortCurrent = hlP, bgP
}
longPnl := (longCurrent - pos.LongLeg.EntryPrice) / pos.LongLeg.EntryPrice * 100
shortPnl := (pos.ShortLeg.EntryPrice - shortCurrent) / pos.ShortLeg.EntryPrice * 100
longAvg := weightedAvgPrice(pos.LongEntryPrices, pos.AmountUSD/float64(max(1, len(pos.LongEntryPrices))))
shortAvg := weightedAvgPrice(pos.ShortEntryPrices, pos.AmountUSD/float64(max(1, len(pos.ShortEntryPrices))))
longPnl := (longCurrent - longAvg) / longAvg * 100
shortPnl := (shortAvg - shortCurrent) / shortAvg * 100
totalFees := 2 * (makerFees[ExBitget] + makerFees[ExHyperLiquid])
netPnl := longPnl + shortPnl - totalFees
@@ -426,7 +428,7 @@ func (d *Dashboard) handleIndex(w http.ResponseWriter, r *http.Request) {
func (d *Dashboard) handleStatus(w http.ResponseWriter, r *http.Request) {
snap := d.store.GetAll()
positions := d.trader.GetOpenPositions()
positions := d.trader.GetPositionsCopy()
converged, diverged, flat, total := d.trader.GetClosedStats()
resp := map[string]interface{}{