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:
tinkle-community
2026-07-16 11:11:56 +09:00
parent eabd279d10
commit 0f3e71560c
2 changed files with 18 additions and 5 deletions

View File

@@ -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)