fix: HL size 动态格式化(从 Meta 取 szDecimals)
- 新增 HyperLiquidTrade.GetSize(),从 HL Meta.Universe 获取 每个币的 SzDecimals 运行时格式化订单数量 - 替代硬编码的 exchange.GetHLSize() switch case - trader.go 中 placeOrder/placeOrderAt 两处调用切换为新方法 - 新增 TryEntry 价格<=0 检查,过滤异常数据
This commit is contained in:
@@ -24,6 +24,10 @@ type HyperLiquidTrade struct {
|
|||||||
nonceMu sync.Mutex
|
nonceMu sync.Mutex
|
||||||
lastNonce int64
|
lastNonce int64
|
||||||
configured bool
|
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) {
|
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)
|
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
|
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 {
|
func (h *HyperLiquidTrade) IsConfigured() bool {
|
||||||
return h.configured
|
return h.configured
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlaceMarketOrder places a market order and returns the raw JSON response.
|
|
||||||
func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, error) {
|
func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, error) {
|
||||||
if !h.configured {
|
if !h.configured {
|
||||||
return "", fmt.Errorf("HL not configured")
|
return "", fmt.Errorf("HL not configured")
|
||||||
|
|||||||
@@ -488,6 +488,12 @@ func (t *Trader) TryEntry(opp *ArbOpportunity, store *PriceStore, notifier *Noti
|
|||||||
return false
|
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()
|
t.mu.Lock()
|
||||||
if t.shuttingDown {
|
if t.shuttingDown {
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
@@ -1159,7 +1165,7 @@ func (t *Trader) placeOrder(leg *PositionLeg, side string, store *PriceStore) (s
|
|||||||
}
|
}
|
||||||
return "", fee
|
return "", fee
|
||||||
} else {
|
} 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)
|
resp, err := t.hyperliquid.PlaceMarketOrder(leg.Coin, side, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("HL %s error: %v", side, err), 0
|
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)
|
return fmt.Sprintf("BG %s error: %v", side, err)
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
_, err := t.hyperliquid.PlaceMarketOrder(leg.Coin, side, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf("HL %s error: %v", side, err)
|
return fmt.Sprintf("HL %s error: %v", side, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user