- Delete exchange/aevo.go (AevoWS, aevoTickerMsg, aevoTickerData, etc.) - scanner.go: remove ExAevo constant, fee rates, scan pair entries, shortName mapping - main.go: remove aevoSymbols collection loop and TrackedSymbol usage
161 lines
3.9 KiB
Go
161 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"log"
|
|
"sort"
|
|
"time"
|
|
|
|
"exchange-monitor/exchange"
|
|
)
|
|
|
|
// Exchange names
|
|
const (
|
|
ExBinance = "Binance"
|
|
ExHyperLiquid = "HyperLiquid"
|
|
ExBitget = "Bitget"
|
|
ExDydx = "dYdX"
|
|
)
|
|
|
|
// Fee rates (%) — taker fees per exchange
|
|
var feeRates = map[string]float64{
|
|
ExBinance: 0.040,
|
|
ExHyperLiquid: 0.035,
|
|
ExBitget: 0.040, // standard taker
|
|
ExDydx: 0.050, // dYdX v4 standard taker
|
|
}
|
|
|
|
// Maker fee rates (%) — for limit orders
|
|
var makerFees = map[string]float64{
|
|
ExBinance: 0.020, // standard maker (USDT pairs)
|
|
ExHyperLiquid: 0.015,
|
|
ExBitget: 0.020, // standard maker
|
|
ExDydx: 0.020,
|
|
}
|
|
|
|
// TickerCoins defines all coins we monitor.
|
|
var TrackedCoins = []TrackedCoin{
|
|
{Name: "DOGE", BN: "DOGEUSDT", BG: "DOGEUSDT", HL: "DOGE"},
|
|
{Name: "LINK", BN: "LINKUSDT", BG: "LINKUSDT", HL: "LINK"},
|
|
{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"},
|
|
}
|
|
|
|
// netProfit calculates net profit % after fees for a complete round trip (entry + exit).
|
|
// B#6: Delegates to exchange.CalcNetProfit to eliminate formula duplication.
|
|
func netProfit(buyPrice, sellPrice, buyFee, sellFee float64) float64 {
|
|
return exchange.CalcNetProfit(buyPrice, sellPrice, buyFee, sellFee, buyFee, sellFee)
|
|
}
|
|
|
|
// ScanArbWithFees checks all coins for arbitrage opportunities using a custom fee map.
|
|
// Pass feeRates for taker fees or makerFees for limit order fees.
|
|
func ScanArbWithFees(store *PriceStore, fees map[string]float64) []*ArbOpportunity {
|
|
snapshot := store.GetAll()
|
|
var results []*ArbOpportunity
|
|
|
|
for _, coin := range TrackedCoins {
|
|
coinStart := time.Now()
|
|
exMap := snapshot[coin.Name]
|
|
if exMap == nil {
|
|
continue
|
|
}
|
|
bnP := exMap[ExBinance]
|
|
hlP := exMap[ExHyperLiquid]
|
|
bgP := exMap[ExBitget]
|
|
dyP := exMap[ExDydx]
|
|
|
|
var pairs []struct {
|
|
profit float64
|
|
buyEx string
|
|
sellEx string
|
|
buyP float64
|
|
sellP float64
|
|
}
|
|
|
|
addPair := func(ex1, ex2 string, p1, p2 float64) {
|
|
if p1 <= 0 || p2 <= 0 {
|
|
return
|
|
}
|
|
pairs = append(pairs,
|
|
struct {
|
|
profit float64
|
|
buyEx string
|
|
sellEx string
|
|
buyP float64
|
|
sellP float64
|
|
}{netProfit(p1, p2, fees[ex1], fees[ex2]), ex1, ex2, p1, p2},
|
|
struct {
|
|
profit float64
|
|
buyEx string
|
|
sellEx string
|
|
buyP float64
|
|
sellP float64
|
|
}{netProfit(p2, p1, fees[ex2], fees[ex1]), ex2, ex1, p2, p1},
|
|
)
|
|
}
|
|
|
|
addPair(ExBinance, ExHyperLiquid, bnP, hlP)
|
|
addPair(ExBinance, ExBitget, bnP, bgP)
|
|
addPair(ExBinance, ExDydx, bnP, dyP)
|
|
addPair(ExHyperLiquid, ExBitget, hlP, bgP)
|
|
addPair(ExHyperLiquid, ExDydx, hlP, dyP)
|
|
addPair(ExBitget, ExDydx, bgP, dyP)
|
|
|
|
if len(pairs) == 0 {
|
|
continue
|
|
}
|
|
|
|
best := pairs[0]
|
|
for _, p := range pairs[1:] {
|
|
if p.profit > best.profit {
|
|
best = p
|
|
}
|
|
}
|
|
|
|
grossBasis := (best.sellP - best.buyP) / best.buyP * 100
|
|
|
|
results = append(results, &ArbOpportunity{
|
|
Coin: coin.Name,
|
|
Direction: shortName(best.buyEx) + "->" + shortName(best.sellEx),
|
|
BuyEx: best.buyEx,
|
|
SellEx: best.sellEx,
|
|
BuyPrice: best.buyP,
|
|
SellPrice: best.sellP,
|
|
NetProfit: best.profit,
|
|
GrossBasis: grossBasis,
|
|
})
|
|
|
|
coinElapsed := time.Since(coinStart)
|
|
if coinElapsed > time.Millisecond {
|
|
log.Printf("[Profile] scan %s took %dµs", coin.Name, coinElapsed.Microseconds())
|
|
}
|
|
}
|
|
|
|
sort.Slice(results, func(i, j int) bool {
|
|
return results[i].NetProfit > results[j].NetProfit
|
|
})
|
|
|
|
return results
|
|
}
|
|
|
|
// ScanArb checks all coins using taker fees.
|
|
func ScanArb(store *PriceStore) []*ArbOpportunity {
|
|
return ScanArbWithFees(store, feeRates)
|
|
}
|
|
|
|
|
|
func shortName(exchange string) string {
|
|
switch exchange {
|
|
case ExBinance:
|
|
return "BN"
|
|
case ExHyperLiquid:
|
|
return "HL"
|
|
case ExBitget:
|
|
return "BG"
|
|
case ExDydx:
|
|
return "dYdX"
|
|
}
|
|
return "??"
|
|
}
|