From 9d942e9deedc5a0ff3b36664256958a9690a3781 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Mon, 4 May 2026 22:50:24 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20HL=20size=20=E5=8A=A8=E6=80=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=EF=BC=88=E4=BB=8E=20Meta=20=E5=8F=96=20szDec?= =?UTF-8?q?imals=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 HyperLiquidTrade.GetSize(),从 HL Meta.Universe 获取 每个币的 SzDecimals 运行时格式化订单数量 - 替代硬编码的 exchange.GetHLSize() switch case - trader.go 中 placeOrder/placeOrderAt 两处调用切换为新方法 - 新增 TryEntry 价格<=0 检查,过滤异常数据 --- exchange/hyperliquid_trade.go | 49 ++++++++++++++++++++++++++++++++++- trader.go | 10 +++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/exchange/hyperliquid_trade.go b/exchange/hyperliquid_trade.go index 74c40b4..7766ce5 100644 --- a/exchange/hyperliquid_trade.go +++ b/exchange/hyperliquid_trade.go @@ -24,6 +24,10 @@ type HyperLiquidTrade struct { nonceMu sync.Mutex lastNonce int64 configured bool + + // szDecimals maps coin name -> decimal places for size formatting + // Populated from HL Meta on initExchange() + szDecimals map[string]int } func NewHyperLiquidTrade(privateKeyHex, mainAddress, apiAddress string) (*HyperLiquidTrade, error) { @@ -76,14 +80,57 @@ func (h *HyperLiquidTrade) initExchange() error { } h.exchange = hl.NewExchange(ctx, h.privateKey, hl.MainnetAPIURL, meta, "", h.mainAddress, spotMeta, nil) + + // Build szDecimals map from HL Meta for correct size formatting + h.szDecimals = make(map[string]int, len(meta.Universe)) + for _, asset := range meta.Universe { + h.szDecimals[asset.Name] = asset.SzDecimals + } return nil } +// GetSize returns a formatted size string for HL orders using the correct szDecimals. +func (h *HyperLiquidTrade) GetSize(coin string, amountUSD, price float64) string { + sz := amountUSD / price + decimals, ok := h.szDecimals[coin] + if !ok { + // Fallback: 4 decimal places + return fmt.Sprintf("%.4f", math.Floor(sz*10000)/10000) + } + switch decimals { + case 0: + sz = math.Floor(sz) + if sz < 1 { + sz = 1 + } + return fmt.Sprintf("%.0f", sz) + case 1: + sz = math.Floor(sz*10) / 10 + if sz < 0.1 { + sz = 0.1 + } + return fmt.Sprintf("%.1f", sz) + case 2: + sz = math.Floor(sz*100) / 100 + if sz < 0.01 { + sz = 0.01 + } + return fmt.Sprintf("%.2f", sz) + default: + mult := math.Pow10(decimals) + sz = math.Floor(sz*mult) / mult + if sz < 1/mult { + sz = 1 / mult + } + return fmt.Sprintf("%."+strconv.Itoa(decimals)+"f", sz) + } +} + +// PlaceMarketOrder places a market order and returns the raw JSON response. func (h *HyperLiquidTrade) IsConfigured() bool { return h.configured } -// PlaceMarketOrder places a market order and returns the raw JSON response. func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, error) { if !h.configured { return "", fmt.Errorf("HL not configured") diff --git a/trader.go b/trader.go index 86c8913..1608388 100644 --- a/trader.go +++ b/trader.go @@ -488,6 +488,12 @@ func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Noti return false } + // Data quality: reject if either price is zero or negative (stale/fake data) + if opp.BuyPrice <= 0 || opp.SellPrice <= 0 { + log.Printf("[Trader] %s: skip entry (price=%v/%v <= 0), likely stale/delisted coin", opp.Coin, opp.BuyPrice, opp.SellPrice) + return false + } + t.mu.Lock() if t.shuttingDown { t.mu.Unlock() @@ -1159,7 +1165,7 @@ func (t *Trader) placeOrder(leg *PositionLeg, side string, store *PriceStore) (s } return "", fee } else { - size := exchange.GetHLSize(leg.Coin, t.cfg.TradeAmountUSD, leg.EntryPrice) + size := t.hyperliquid.GetSize(leg.Coin, t.cfg.TradeAmountUSD, leg.EntryPrice) resp, err := t.hyperliquid.PlaceMarketOrder(leg.Coin, side, size) if err != nil { return fmt.Sprintf("HL %s error: %v", side, err), 0 @@ -1544,7 +1550,7 @@ func (t *Trader) placeOrderAt(leg *PositionLeg, side string, store *PriceStore, return fmt.Sprintf("BG %s error: %v", side, err) } } else { - size := exchange.GetHLSize(leg.Coin, t.cfg.TradeAmountUSD, price) + size := t.hyperliquid.GetSize(leg.Coin, t.cfg.TradeAmountUSD, price) _, err := t.hyperliquid.PlaceMarketOrder(leg.Coin, side, size) if err != nil { return fmt.Sprintf("HL %s error: %v", side, err)