From 0f3e71560cdc26fd839335fc9bb455fd9d7b3254 Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Thu, 16 Jul 2026 11:11:56 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20fetch=20all=20Hyperliquid=20fills=20?= =?UTF-8?q?=E2=80=94=20UserFillsByTime=20is=20capped=20at=20100?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- trader/hyperliquid/order_sync.go | 9 ++++++--- trader/hyperliquid/trader_account.go | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/trader/hyperliquid/order_sync.go b/trader/hyperliquid/order_sync.go index eff95452..1dbb66b4 100644 --- a/trader/hyperliquid/order_sync.go +++ b/trader/hyperliquid/order_sync.go @@ -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) } diff --git a/trader/hyperliquid/trader_account.go b/trader/hyperliquid/trader_account.go index 6215516a..0e53231a 100644 --- a/trader/hyperliquid/trader_account.go +++ b/trader/hyperliquid/trader_account.go @@ -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)