package main import ( "sort" ) // Exchange names — only Bitget and HyperLiquid are trading exchanges const ( ExHyperLiquid = "HyperLiquid" ExBitget = "Bitget" ) // Taker fee rates (%) — for IOC market orders on trading exchanges var takerFees = map[string]float64{ ExHyperLiquid: 0.045, ExBitget: 0.060, } // TickerCoins defines all coins we monitor. var TrackedCoins = []TrackedCoin{ {Name: "DOGE", BN: "DOGEUSDT", BG: "DOGEUSDT", HL: "DOGE"}, // LINK removed — not listed on HL testnet {Name: "ONDO", BN: "ONDOUSDT", BG: "ONDOUSDT", HL: "ONDO"}, {Name: "OP", BN: "OPUSDT", BG: "OPUSDT", HL: "OP"}, {Name: "WIF", BN: "WIFUSDT", BG: "WIFUSDT", HL: "WIF"}, {Name: "ARB", BN: "ARBUSDT", BG: "ARBUSDT", HL: "ARB"}, // New pairs — common on BG↔HL {Name: "ALGO", BG: "ALGOUSDT", HL: "ALGO"}, {Name: "PYTH", BG: "PYTHUSDT", HL: "PYTH"}, {Name: "SAND", BG: "SANDUSDT", HL: "SAND"}, {Name: "ADA", BG: "ADAUSDT", HL: "ADA"}, {Name: "HBAR", BG: "HBARUSDT", HL: "HBAR"}, {Name: "IOTA", BG: "IOTAUSDT", HL: "IOTA"}, {Name: "FET", BG: "FETUSDT", HL: "FET"}, {Name: "WLD", BG: "WLDUSDT", HL: "WLD"}, {Name: "SUI", BG: "SUIUSDT", HL: "SUI"}, {Name: "APT", BG: "APTUSDT", HL: "APT"}, {Name: "NEAR", BG: "NEARUSDT", HL: "NEAR"}, {Name: "FIL", BG: "FILUSDT", HL: "FIL"}, {Name: "DYDX", BG: "DYDXUSDT", HL: "DYDX"}, {Name: "JUP", BG: "JUPUSDT", HL: "JUP"}, {Name: "TIA", BG: "TIAUSDT", HL: "TIA"}, {Name: "MOVE", BG: "MOVEUSDT", HL: "MOVE"}, } // netProfit calculates net profit % after fees for a complete round trip (entry + exit). // NOTE: Does NOT swap prices — callers (ScanArbWithFees) pass prices in explicit buy/sell order // and try both directions via addPair. Using exchange.CalcNetProfit would double-swap (B#6). func netProfit(buyPrice, sellPrice, buyFee, sellFee float64) float64 { if buyPrice <= 0 || sellPrice <= 0 { return 0 } // Entry: buy at buyPrice (pay buyFee), sell short at sellPrice (pay sellFee) cost := buyPrice * (1 + buyFee/100) revenue := sellPrice * (1 - sellFee/100) // Exit: sell long (pay sellFee), buy back short (pay buyFee) // Total fees = 2 * (buyFee + sellFee), first round already in formula above return (revenue/cost - 1)*100 - 2*(buyFee + sellFee) } // ScanBGHL scans coins for arbitrage ONLY between Bitget and HyperLiquid (P3-1). // Returns both directions (BG->HL and HL->BG) sorted by net profit descending. func ScanBGHL(snap map[string]map[string]float64) []*ArbOpportunity { var results []*ArbOpportunity for _, coin := range TrackedCoins { exMap := snap[coin.Name] if exMap == nil { continue } bgP := exMap[ExBitget] hlP := exMap[ExHyperLiquid] if bgP <= 0 || hlP <= 0 { continue } // BG->HL: buy cheap at Bitget, sell expensive at HyperLiquid profitBG := netProfit(bgP, hlP, takerFees[ExBitget], takerFees[ExHyperLiquid]) // HL->BG: buy cheap at HyperLiquid, sell expensive at Bitget profitHL := netProfit(hlP, bgP, takerFees[ExHyperLiquid], takerFees[ExBitget]) grossBG := (hlP - bgP) / bgP * 100 grossHL := (bgP - hlP) / hlP * 100 results = append(results, &ArbOpportunity{ Coin: coin.Name, Direction: "BG->HL", BuyEx: ExBitget, SellEx: ExHyperLiquid, BuyPrice: bgP, SellPrice: hlP, NetProfit: profitBG, GrossBasis: grossBG, }, &ArbOpportunity{ Coin: coin.Name, Direction: "HL->BG", BuyEx: ExHyperLiquid, SellEx: ExBitget, BuyPrice: hlP, SellPrice: bgP, NetProfit: profitHL, GrossBasis: grossHL, }) } sort.Slice(results, func(i, j int) bool { return results[i].NetProfit > results[j].NetProfit }) return results }