mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-21 19:27:39 +08:00
fix: fetch all Hyperliquid fills — UserFillsByTime is capped at 100
Root cause of the dashboard over-reporting profit while the account lost money: the fill sync used UserFillsByTime, which hard-caps at 100 fills per response. At 20x/4-position frequency the account does >100 fills/24h, so ~20% of fills were silently dropped — and the dropped ones skewed toward losers, inflating recorded PnL and under-counting fees. Verified: over a 46h window the DB showed net +$39 while Hyperliquid official was -$18.6, and the equity drop ($176 -> $156) confirmed the loss. Switch GetTrades to UserFills (returns up to 2000 recent fills), filtering to startTime client-side, and widen the sync lookback 24h -> 7d so gaps backfill. Verified live: a sync now pulls 675 fills where it previously always received exactly 100. Note: this stops future drift; already-corrupted historical position rows are not retroactively rebuilt (dedup blocks re-processing). Account equity remains the authoritative scoreboard.
This commit is contained in:
@@ -20,13 +20,16 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
|
||||
return fmt.Errorf("store is nil")
|
||||
}
|
||||
|
||||
// Get recent trades (last 24 hours)
|
||||
startTime := time.Now().Add(-24 * time.Hour)
|
||||
// Look back 7 days. GetTrades now pulls up to 2000 recent fills (UserFills)
|
||||
// and filters to this window, so a wide lookback backfills any fills missed
|
||||
// during past outages/gaps without dropping recent ones. Dedup by trade ID
|
||||
// keeps re-processing idempotent.
|
||||
startTime := time.Now().Add(-7 * 24 * time.Hour)
|
||||
|
||||
logger.Infof("🔄 Syncing Hyperliquid trades from: %s", startTime.Format(time.RFC3339))
|
||||
|
||||
// Use GetTrades method to fetch trade records
|
||||
trades, err := t.GetTrades(startTime, 1000)
|
||||
trades, err := t.GetTrades(startTime, 2000)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get trades: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
hl "github.com/sonirico/go-hyperliquid"
|
||||
)
|
||||
|
||||
// GetBalance gets account balance
|
||||
@@ -548,15 +550,23 @@ func (t *HyperliquidTrader) GetClosedPnL(startTime time.Time, limit int) ([]type
|
||||
|
||||
// GetTrades retrieves trade history from Hyperliquid
|
||||
func (t *HyperliquidTrader) GetTrades(startTime time.Time, limit int) ([]types.TradeRecord, error) {
|
||||
// Use UserFillsByTime API
|
||||
// Use UserFills (returns up to 2000 recent fills) rather than
|
||||
// UserFillsByTime, which is hard-capped at 100 fills per response. At
|
||||
// this trading frequency the account exceeds 100 fills/24h, so
|
||||
// UserFillsByTime silently dropped ~20% of fills — skewing recorded PnL
|
||||
// and fees away from the exchange truth. 2000 recent fills covers many
|
||||
// days of history; we filter to startTime client-side.
|
||||
startTimeMs := startTime.UnixMilli()
|
||||
fills, err := t.exchange.Info().UserFillsByTime(t.ctx, t.walletAddr, startTimeMs, nil, nil)
|
||||
fills, err := t.exchange.Info().UserFills(t.ctx, hl.UserFillsParams{Address: t.walletAddr})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user fills: %w", err)
|
||||
}
|
||||
|
||||
var trades []types.TradeRecord
|
||||
for _, fill := range fills {
|
||||
if fill.Time < startTimeMs {
|
||||
continue
|
||||
}
|
||||
price, _ := strconv.ParseFloat(fill.Price, 64)
|
||||
qty, _ := strconv.ParseFloat(fill.Size, 64)
|
||||
fee, _ := strconv.ParseFloat(fill.Fee, 64)
|
||||
|
||||
Reference in New Issue
Block a user