diff --git a/README.md b/README.md index 080e2293..c957998a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🤖 NOFX - AI-Driven Binance Futures Auto Trading Competition System +# 🤖 NOFX - AI-Driven Crypto Futures Auto Trading Competition System [![Go Version](https://img.shields.io/badge/Go-1.21+-00ADD8?style=flat&logo=go)](https://golang.org/) [![React](https://img.shields.io/badge/React-18+-61DAFB?style=flat&logo=react)](https://reactjs.org/) @@ -9,7 +9,7 @@ --- -An automated Binance futures trading system powered by **DeepSeek/Qwen AI**, supporting **multi-AI model live trading competition**, featuring comprehensive market analysis, AI decision-making, **self-learning mechanism**, and professional Web monitoring interface. +An automated crypto futures trading system powered by **DeepSeek/Qwen AI**, supporting **Binance and Hyperliquid exchanges**, **multi-AI model live trading competition**, featuring comprehensive market analysis, AI decision-making, **self-learning mechanism**, and professional Web monitoring interface. > ⚠️ **Risk Warning**: This system is experimental. AI auto-trading carries significant risks. Strongly recommended for learning/research purposes or testing with small amounts only! @@ -21,6 +21,35 @@ Join our Telegram developer community to discuss, share ideas, and get support: --- +## 🆕 What's New (Latest Update) + +### 🚀 Hyperliquid Exchange Support Added! + +NOFX now supports **Hyperliquid** - a high-performance decentralized perpetual futures exchange! + +**Key Features:** +- ✅ Full trading support (long/short, leverage, stop-loss/take-profit) +- ✅ Automatic precision handling (order size & price) +- ✅ Unified trader interface (seamless exchange switching) +- ✅ Support for both mainnet and testnet +- ✅ No API keys needed - just your Ethereum private key + +**Why Hyperliquid?** +- 🔥 Lower fees than centralized exchanges +- 🔒 Non-custodial - you control your funds +- ⚡ Fast execution with on-chain settlement +- 🌍 No KYC required + +**Quick Start:** +1. Get your MetaMask private key (remove `0x` prefix) +2. Set `"exchange": "hyperliquid"` in config.json +3. Add `"hyperliquid_private_key": "your_key"` +4. Start trading! + +See [Configuration Guide](#-alternative-using-hyperliquid-exchange) for details. + +--- + ## ✨ Core Features ### 🏆 Multi-AI Competition Mode @@ -363,6 +392,48 @@ cp config.json.example config.json --- +#### 🔷 Alternative: Using Hyperliquid Exchange + +**NOFX also supports Hyperliquid** - a decentralized perpetual futures exchange. To use Hyperliquid instead of Binance: + +**Step 1**: Get your Ethereum private key (for Hyperliquid authentication) + +1. Open **MetaMask** (or any Ethereum wallet) +2. Export your private key +3. **Remove the `0x` prefix** from the key +4. Fund your wallet on [Hyperliquid](https://hyperliquid.xyz) + +**Step 2**: Configure `config.json` for Hyperliquid + +```json +{ + "traders": [ + { + "id": "hyperliquid_trader", + "name": "My Hyperliquid Trader", + "ai_model": "deepseek", + "exchange": "hyperliquid", + "hyperliquid_private_key": "your_private_key_without_0x", + "hyperliquid_testnet": false, + "deepseek_key": "sk-xxxxxxxxxxxxx", + "initial_balance": 1000.0, + "scan_interval_minutes": 3 + } + ], + "use_default_coins": true, + "api_server_port": 8080 +} +``` + +**Key Differences from Binance Config:** +- Replace `binance_api_key` + `binance_secret_key` with `hyperliquid_private_key` +- Add `"exchange": "hyperliquid"` field +- Set `hyperliquid_testnet: false` for mainnet (or `true` for testnet) + +**⚠️ Security Warning**: Never share your private key! Use a dedicated wallet for trading, not your main wallet. + +--- + #### ⚔️ Expert Mode: Multi-Trader Competition For running multiple AI traders competing against each other: diff --git a/config.json.example b/config.json.example index eb817ea6..3655f5a3 100644 --- a/config.json.example +++ b/config.json.example @@ -1,15 +1,25 @@ { "traders": [ { - "id": "my_trader", - "name": "My AI Trader", + "id": "hyperliquid_deepseek", + "name": "Hyperliquid DeepSeek Trader", "ai_model": "deepseek", - "binance_api_key": "YOUR_BINANCE_API_KEY", - "binance_secret_key": "YOUR_BINANCE_SECRET_KEY", - "use_qwen": false, - "deepseek_key": "YOUR_DEEPSEEK_API_KEY", - "qwen_key": "", - "initial_balance": 1000.0, + "exchange": "hyperliquid", + "hyperliquid_private_key": "your_ethereum_private_key_without_0x_prefix", + "hyperliquid_testnet": false, + "deepseek_key": "your_deepseek_api_key", + "initial_balance": 1000, + "scan_interval_minutes": 3 + }, + { + "id": "binance_qwen", + "name": "Binance Qwen Trader", + "ai_model": "qwen", + "exchange": "binance", + "binance_api_key": "your_binance_api_key", + "binance_secret_key": "your_binance_secret_key", + "qwen_key": "your_qwen_api_key", + "initial_balance": 1000, "scan_interval_minutes": 3 } ], @@ -17,7 +27,7 @@ "coin_pool_api_url": "", "oi_top_api_url": "", "api_server_port": 8080, - "max_daily_loss": 5.0, - "max_drawdown": 10.0, - "stop_trading_minutes": 30 + "max_daily_loss": 10.0, + "max_drawdown": 20.0, + "stop_trading_minutes": 60 } diff --git a/config/config.go b/config/config.go index efaf7a75..67b5e559 100644 --- a/config/config.go +++ b/config/config.go @@ -12,10 +12,22 @@ type TraderConfig struct { ID string `json:"id"` Name string `json:"name"` AIModel string `json:"ai_model"` // "qwen" or "deepseek" - BinanceAPIKey string `json:"binance_api_key"` - BinanceSecretKey string `json:"binance_secret_key"` + + // 交易平台选择(二选一) + Exchange string `json:"exchange"` // "binance" or "hyperliquid" + + // 币安配置 + BinanceAPIKey string `json:"binance_api_key,omitempty"` + BinanceSecretKey string `json:"binance_secret_key,omitempty"` + + // Hyperliquid配置 + HyperliquidPrivateKey string `json:"hyperliquid_private_key,omitempty"` + HyperliquidTestnet bool `json:"hyperliquid_testnet,omitempty"` + + // AI配置 QwenKey string `json:"qwen_key,omitempty"` DeepSeekKey string `json:"deepseek_key,omitempty"` + InitialBalance float64 `json:"initial_balance"` ScanIntervalMinutes int `json:"scan_interval_minutes"` } @@ -79,9 +91,26 @@ func (c *Config) Validate() error { if trader.AIModel != "qwen" && trader.AIModel != "deepseek" { return fmt.Errorf("trader[%d]: ai_model必须是 'qwen' 或 'deepseek'", i) } - if trader.BinanceAPIKey == "" || trader.BinanceSecretKey == "" { - return fmt.Errorf("trader[%d]: 币安API密钥不能为空", i) + + // 验证交易平台配置 + if trader.Exchange == "" { + trader.Exchange = "binance" // 默认使用币安 } + if trader.Exchange != "binance" && trader.Exchange != "hyperliquid" { + return fmt.Errorf("trader[%d]: exchange必须是 'binance' 或 'hyperliquid'", i) + } + + // 根据平台验证对应的密钥 + if trader.Exchange == "binance" { + if trader.BinanceAPIKey == "" || trader.BinanceSecretKey == "" { + return fmt.Errorf("trader[%d]: 使用币安时必须配置binance_api_key和binance_secret_key", i) + } + } else if trader.Exchange == "hyperliquid" { + if trader.HyperliquidPrivateKey == "" { + return fmt.Errorf("trader[%d]: 使用Hyperliquid时必须配置hyperliquid_private_key", i) + } + } + if trader.AIModel == "qwen" && trader.QwenKey == "" { return fmt.Errorf("trader[%d]: 使用Qwen时必须配置qwen_key", i) } diff --git a/go.mod b/go.mod index 9fe0c15c..cddc9e3a 100644 --- a/go.mod +++ b/go.mod @@ -1,46 +1,77 @@ module nofx -go 1.24 - -require github.com/adshao/go-binance/v2 v2.8.7 +go 1.25.0 require ( + github.com/adshao/go-binance/v2 v2.8.7 + github.com/ethereum/go-ethereum v1.16.5 + github.com/gin-gonic/gin v1.11.0 + github.com/sonirico/go-hyperliquid v0.17.0 +) + +require ( + github.com/armon/go-radix v1.0.0 // indirect github.com/bitly/go-simplejson v0.5.0 // indirect + github.com/bits-and-blooms/bitset v1.24.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/consensys/gnark-crypto v0.19.0 // indirect + github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/elastic/go-sysinfo v1.15.4 // indirect + github.com/elastic/go-windows v1.0.2 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ethereum/go-verkle v0.2.2 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.1.0 // indirect - github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect + github.com/rs/zerolog v1.34.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/sonirico/vago v0.9.0 // indirect + github.com/sonirico/vago/lol v0.0.0-20250901170347-2d1d82c510bd // indirect + github.com/supranational/blst v0.3.16 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect + github.com/valyala/fastjson v1.6.4 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + go.elastic.co/apm/module/apmzerolog/v2 v2.7.1 // indirect + go.elastic.co/apm/v2 v2.7.1 // indirect + go.elastic.co/fastjson v1.5.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.34.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect google.golang.org/protobuf v1.36.9 // indirect + howett.net/plist v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index d348442d..c30f7b02 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,13 @@ +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/adshao/go-binance/v2 v2.8.7 h1:n7jkhwIHMdtd/9ZU2gTqFV15XVSbUCjyFlOUAtTd8uU= github.com/adshao/go-binance/v2 v2.8.7/go.mod h1:XkkuecSyJKPolaCGf/q4ovJYB3t0P+7RUYTbGr+LMGM= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM= +github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= @@ -10,96 +16,205 @@ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZw github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/consensys/gnark-crypto v0.19.0 h1:zXCqeY2txSaMl6G5wFpZzMWJU9HPNh8qxPnYJ1BL9vA= +github.com/consensys/gnark-crypto v0.19.0/go.mod h1:rT23F0XSZqE0mUA0+pRtnL56IbPxs6gp4CeRsBk4XS0= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= +github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q= +github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU= +github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= +github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= +github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= +github.com/ethereum/go-ethereum v1.16.5 h1:GZI995PZkzP7ySCxEFaOPzS8+bd8NldE//1qvQDQpe0= +github.com/ethereum/go-ethereum v1.16.5/go.mod h1:kId9vOtlYg3PZk9VwKbGlQmSACB5ESPTBGT+M9zjmok= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= +github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sonirico/go-hyperliquid v0.17.0 h1:eXYACWupwu41O1VtKw17dqe9oOLQ1A2nRElGhg5Ox+4= +github.com/sonirico/go-hyperliquid v0.17.0/go.mod h1:sH51Vsu+tPUwc95TL2MoQ8YXSewLWBEJirgzo7sZx6w= +github.com/sonirico/vago v0.9.0 h1:DF2OWW2Aaf1xPZmnFv79kBrHmjKX3mVvMbP08vERlKo= +github.com/sonirico/vago v0.9.0/go.mod h1:fZxV1RzMe2eaZokbbDvuyoOzG3YapzqRQoOiD9VyJH0= +github.com/sonirico/vago/lol v0.0.0-20250901170347-2d1d82c510bd h1:rbvNORW8/0AtH/8W/SUwUykbuh2SeQBrNgFLqYpGTWY= +github.com/sonirico/vago/lol v0.0.0-20250901170347-2d1d82c510bd/go.mod h1:pteYccB32seEf19i0TPk7DKdEZdWJ/n9K9DF8AFeXGU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE= +github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= +github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +go.elastic.co/apm/module/apmzerolog/v2 v2.7.1 h1:C9+KrlqS8F4SZFu+ct0Jmv2YLmzDhWsI8htK6exd3vg= +go.elastic.co/apm/module/apmzerolog/v2 v2.7.1/go.mod h1:wXViB7paxMUrERgZrmUb+0FCqgb13Dull1JOOd8Hcj0= +go.elastic.co/apm/v2 v2.7.1 h1:OFjARuESjBsxw7wHrEAnfSVNCHGBATXSI/kPvBARY/A= +go.elastic.co/apm/v2 v2.7.1/go.mod h1:tQhBAjwh93b2leuAdzGwta/sP7Yc7QoKTSjeIHHDuog= +go.elastic.co/fastjson v1.5.1 h1:zeh1xHrFH79aQ6Xsw7YxixvnOdAl3OSv0xch/jRDzko= +go.elastic.co/fastjson v1.5.1/go.mod h1:WtvH5wz8z9pDOPqNYSYKoLLv/9zCWZLeejHWuvdL/EM= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/dnaeon/go-vcr.v4 v4.0.5 h1:I0hpTIvD5rII+8LgYGrHMA2d4SQPoL6u7ZvJakWKsiA= +gopkg.in/dnaeon/go-vcr.v4 v4.0.5/go.mod h1:dRos81TkW9C1WJt6tTaE+uV2Lo8qJT3AG2b35+CB/nQ= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/manager/trader_manager.go b/manager/trader_manager.go index 6c44c93b..5747572a 100644 --- a/manager/trader_manager.go +++ b/manager/trader_manager.go @@ -33,20 +33,23 @@ func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string, // 构建AutoTraderConfig traderConfig := trader.AutoTraderConfig{ - ID: cfg.ID, - Name: cfg.Name, - AIModel: cfg.AIModel, - BinanceAPIKey: cfg.BinanceAPIKey, - BinanceSecretKey: cfg.BinanceSecretKey, - CoinPoolAPIURL: coinPoolURL, - UseQwen: cfg.AIModel == "qwen", - DeepSeekKey: cfg.DeepSeekKey, - QwenKey: cfg.QwenKey, - ScanInterval: cfg.GetScanInterval(), - InitialBalance: cfg.InitialBalance, - MaxDailyLoss: maxDailyLoss, - MaxDrawdown: maxDrawdown, - StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, + ID: cfg.ID, + Name: cfg.Name, + AIModel: cfg.AIModel, + Exchange: cfg.Exchange, + BinanceAPIKey: cfg.BinanceAPIKey, + BinanceSecretKey: cfg.BinanceSecretKey, + HyperliquidPrivateKey: cfg.HyperliquidPrivateKey, + HyperliquidTestnet: cfg.HyperliquidTestnet, + CoinPoolAPIURL: coinPoolURL, + UseQwen: cfg.AIModel == "qwen", + DeepSeekKey: cfg.DeepSeekKey, + QwenKey: cfg.QwenKey, + ScanInterval: cfg.GetScanInterval(), + InitialBalance: cfg.InitialBalance, + MaxDailyLoss: maxDailyLoss, + MaxDrawdown: maxDrawdown, + StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, } // 创建trader实例 diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 34f49475..64ad0e65 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -20,10 +20,18 @@ type AutoTraderConfig struct { Name string // Trader显示名称 AIModel string // AI模型: "qwen" 或 "deepseek" - // API配置 + // 交易平台选择 + Exchange string // "binance" 或 "hyperliquid" + + // 币安API配置 BinanceAPIKey string BinanceSecretKey string - CoinPoolAPIURL string + + // Hyperliquid配置 + HyperliquidPrivateKey string + HyperliquidTestnet bool + + CoinPoolAPIURL string // AI配置 UseQwen bool @@ -47,8 +55,9 @@ type AutoTrader struct { id string // Trader唯一标识 name string // Trader显示名称 aiModel string // AI模型名称 + exchange string // 交易平台名称 config AutoTraderConfig - trader *FuturesTrader + trader Trader // 使用Trader接口(支持多平台) decisionLogger *logger.DecisionLogger // 决策日志记录器 initialBalance float64 dailyPnL float64 @@ -91,8 +100,28 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) { pool.SetCoinPoolAPI(config.CoinPoolAPIURL) } - // 初始化币安合约交易器 - trader := NewFuturesTrader(config.BinanceAPIKey, config.BinanceSecretKey) + // 设置默认交易平台 + if config.Exchange == "" { + config.Exchange = "binance" + } + + // 根据配置创建对应的交易器 + var trader Trader + var err error + + switch config.Exchange { + case "binance": + log.Printf("🏦 [%s] 使用币安合约交易", config.Name) + trader = NewFuturesTrader(config.BinanceAPIKey, config.BinanceSecretKey) + case "hyperliquid": + log.Printf("🏦 [%s] 使用Hyperliquid交易", config.Name) + trader, err = NewHyperliquidTrader(config.HyperliquidPrivateKey, config.HyperliquidTestnet) + if err != nil { + return nil, fmt.Errorf("初始化Hyperliquid交易器失败: %w", err) + } + default: + return nil, fmt.Errorf("不支持的交易平台: %s", config.Exchange) + } // 验证初始金额配置 if config.InitialBalance <= 0 { @@ -107,6 +136,7 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) { id: config.ID, name: config.Name, aiModel: config.AIModel, + exchange: config.Exchange, config: config, trader: trader, decisionLogger: decisionLogger, @@ -687,6 +717,7 @@ func (at *AutoTrader) GetStatus() map[string]interface{} { "trader_id": at.id, "trader_name": at.name, "ai_model": at.aiModel, + "exchange": at.exchange, "is_running": at.isRunning, "start_time": at.startTime.Format(time.RFC3339), "runtime_minutes": int(time.Since(at.startTime).Minutes()), diff --git a/trader/hyperliquid_trader.go b/trader/hyperliquid_trader.go new file mode 100644 index 00000000..a9402209 --- /dev/null +++ b/trader/hyperliquid_trader.go @@ -0,0 +1,672 @@ +package trader + +import ( + "context" + "crypto/ecdsa" + "fmt" + "log" + "strconv" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/sonirico/go-hyperliquid" +) + +// HyperliquidTrader Hyperliquid交易器 +type HyperliquidTrader struct { + exchange *hyperliquid.Exchange + ctx context.Context + walletAddr string + meta *hyperliquid.Meta // 缓存meta信息(包含精度等) +} + +// NewHyperliquidTrader 创建Hyperliquid交易器 +func NewHyperliquidTrader(privateKeyHex string, testnet bool) (*HyperliquidTrader, error) { + // 解析私钥 + privateKey, err := crypto.HexToECDSA(privateKeyHex) + if err != nil { + return nil, fmt.Errorf("解析私钥失败: %w", err) + } + + // 选择API URL + apiURL := hyperliquid.MainnetAPIURL + if testnet { + apiURL = hyperliquid.TestnetAPIURL + } + + // 从私钥生成钱包地址 + pubKey := privateKey.Public() + publicKeyECDSA, ok := pubKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("无法转换公钥") + } + walletAddr := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() + + ctx := context.Background() + + // 创建Exchange客户端(Exchange包含Info功能) + exchange := hyperliquid.NewExchange( + ctx, + privateKey, + apiURL, + nil, // Meta will be fetched automatically + "", // vault address (empty for personal account) + walletAddr, // wallet address + nil, // SpotMeta will be fetched automatically + ) + + log.Printf("✓ Hyperliquid交易器初始化成功 (testnet=%v, wallet=%s)", testnet, walletAddr) + + // 获取meta信息(包含精度等配置) + meta, err := exchange.Info().Meta(ctx) + if err != nil { + return nil, fmt.Errorf("获取meta信息失败: %w", err) + } + + return &HyperliquidTrader{ + exchange: exchange, + ctx: ctx, + walletAddr: walletAddr, + meta: meta, + }, nil +} + +// GetBalance 获取账户余额 +func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) { + log.Printf("🔄 正在调用Hyperliquid API获取账户余额...") + + // 获取账户状态 + accountState, err := t.exchange.Info().UserState(t.ctx, t.walletAddr) + if err != nil { + log.Printf("❌ Hyperliquid API调用失败: %v", err) + return nil, fmt.Errorf("获取账户信息失败: %w", err) + } + + // 解析余额信息(MarginSummary字段都是string) + result := make(map[string]interface{}) + + accountValue, _ := strconv.ParseFloat(accountState.CrossMarginSummary.AccountValue, 64) + totalMarginUsed, _ := strconv.ParseFloat(accountState.CrossMarginSummary.TotalMarginUsed, 64) + + // ⚠️ 关键修复:从所有持仓中累加真正的未实现盈亏 + totalUnrealizedPnl := 0.0 + for _, assetPos := range accountState.AssetPositions { + unrealizedPnl, _ := strconv.ParseFloat(assetPos.Position.UnrealizedPnl, 64) + totalUnrealizedPnl += unrealizedPnl + } + + // ✅ 正确理解Hyperliquid字段: + // AccountValue = 账户净值(包含未实现盈亏)= 这是真正的总资产 + // 钱包余额(已实现)= AccountValue - 未实现盈亏 + walletBalance := accountValue - totalUnrealizedPnl + + result["totalWalletBalance"] = walletBalance // 钱包余额(已实现部分) + result["availableBalance"] = accountValue - totalMarginUsed // 可用余额 + result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏 + + log.Printf("✓ Hyperliquid API返回: 账户净值=%.2f, 钱包余额=%.2f, 可用=%.2f, 未实现盈亏=%.2f", + accountValue, + result["totalWalletBalance"], + result["availableBalance"], + result["totalUnrealizedProfit"]) + + return result, nil +} + +// GetPositions 获取所有持仓 +func (t *HyperliquidTrader) GetPositions() ([]map[string]interface{}, error) { + // 获取账户状态 + accountState, err := t.exchange.Info().UserState(t.ctx, t.walletAddr) + if err != nil { + return nil, fmt.Errorf("获取持仓失败: %w", err) + } + + var result []map[string]interface{} + + // 遍历所有持仓 + for _, assetPos := range accountState.AssetPositions { + position := assetPos.Position + + // 持仓数量(string类型) + posAmt, _ := strconv.ParseFloat(position.Szi, 64) + + if posAmt == 0 { + continue // 跳过无持仓的 + } + + posMap := make(map[string]interface{}) + + // 标准化symbol格式(Hyperliquid使用如"BTC",我们转换为"BTCUSDT") + symbol := position.Coin + "USDT" + posMap["symbol"] = symbol + + // 持仓数量和方向 + if posAmt > 0 { + posMap["side"] = "long" + posMap["positionAmt"] = posAmt + } else { + posMap["side"] = "short" + posMap["positionAmt"] = -posAmt // 转为正数 + } + + // 价格信息(EntryPx和LiquidationPx是指针类型) + var entryPrice, liquidationPx float64 + if position.EntryPx != nil { + entryPrice, _ = strconv.ParseFloat(*position.EntryPx, 64) + } + if position.LiquidationPx != nil { + liquidationPx, _ = strconv.ParseFloat(*position.LiquidationPx, 64) + } + + positionValue, _ := strconv.ParseFloat(position.PositionValue, 64) + unrealizedPnl, _ := strconv.ParseFloat(position.UnrealizedPnl, 64) + + // 计算mark price(positionValue / abs(posAmt)) + var markPrice float64 + if posAmt != 0 { + markPrice = positionValue / absFloat(posAmt) + } + + posMap["entryPrice"] = entryPrice + posMap["markPrice"] = markPrice + posMap["unRealizedProfit"] = unrealizedPnl + posMap["leverage"] = float64(position.Leverage.Value) + posMap["liquidationPrice"] = liquidationPx + + result = append(result, posMap) + } + + return result, nil +} + +// SetLeverage 设置杠杆 +func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error { + // Hyperliquid symbol格式(去掉USDT后缀) + coin := convertSymbolToHyperliquid(symbol) + + // 调用UpdateLeverage (leverage int, name string, isCross bool) + _, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, false) // false = 逐仓模式 + if err != nil { + return fmt.Errorf("设置杠杆失败: %w", err) + } + + log.Printf(" ✓ %s 杠杆已切换为 %dx", symbol, leverage) + return nil +} + +// OpenLong 开多仓 +func (t *HyperliquidTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) { + // 先取消该币种的所有委托单 + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消旧委托单失败: %v", err) + } + + // 设置杠杆 + if err := t.SetLeverage(symbol, leverage); err != nil { + return nil, err + } + + // Hyperliquid symbol格式 + coin := convertSymbolToHyperliquid(symbol) + + // 获取当前价格(用于市价单) + price, err := t.GetMarketPrice(symbol) + if err != nil { + return nil, err + } + + // ⚠️ 关键:根据币种精度要求,四舍五入数量 + roundedQuantity := t.roundToSzDecimals(coin, quantity) + log.Printf(" 📏 数量精度处理: %.8f -> %.8f (szDecimals=%d)", quantity, roundedQuantity, t.getSzDecimals(coin)) + + // ⚠️ 关键:价格也需要处理为5位有效数字 + aggressivePrice := t.roundPriceToSigfigs(price * 1.01) + log.Printf(" 💰 价格精度处理: %.8f -> %.8f (5位有效数字)", price*1.01, aggressivePrice) + + // 创建市价买入订单(使用IOC limit order with aggressive price) + order := hyperliquid.CreateOrderRequest{ + Coin: coin, + IsBuy: true, + Size: roundedQuantity, // 使用四舍五入后的数量 + Price: aggressivePrice, // 使用处理后的价格 + OrderType: hyperliquid.OrderType{ + Limit: &hyperliquid.LimitOrderType{ + Tif: hyperliquid.TifIoc, // Immediate or Cancel (类似市价单) + }, + }, + ReduceOnly: false, + } + + _, err = t.exchange.Order(t.ctx, order, nil) + if err != nil { + return nil, fmt.Errorf("开多仓失败: %w", err) + } + + log.Printf("✓ 开多仓成功: %s 数量: %.4f", symbol, roundedQuantity) + + result := make(map[string]interface{}) + result["orderId"] = 0 // Hyperliquid没有返回order ID + result["symbol"] = symbol + result["status"] = "FILLED" + + return result, nil +} + +// OpenShort 开空仓 +func (t *HyperliquidTrader) OpenShort(symbol string, quantity float64, leverage int) (map[string]interface{}, error) { + // 先取消该币种的所有委托单 + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消旧委托单失败: %v", err) + } + + // 设置杠杆 + if err := t.SetLeverage(symbol, leverage); err != nil { + return nil, err + } + + // Hyperliquid symbol格式 + coin := convertSymbolToHyperliquid(symbol) + + // 获取当前价格 + price, err := t.GetMarketPrice(symbol) + if err != nil { + return nil, err + } + + // ⚠️ 关键:根据币种精度要求,四舍五入数量 + roundedQuantity := t.roundToSzDecimals(coin, quantity) + log.Printf(" 📏 数量精度处理: %.8f -> %.8f (szDecimals=%d)", quantity, roundedQuantity, t.getSzDecimals(coin)) + + // ⚠️ 关键:价格也需要处理为5位有效数字 + aggressivePrice := t.roundPriceToSigfigs(price * 0.99) + log.Printf(" 💰 价格精度处理: %.8f -> %.8f (5位有效数字)", price*0.99, aggressivePrice) + + // 创建市价卖出订单 + order := hyperliquid.CreateOrderRequest{ + Coin: coin, + IsBuy: false, + Size: roundedQuantity, // 使用四舍五入后的数量 + Price: aggressivePrice, // 使用处理后的价格 + OrderType: hyperliquid.OrderType{ + Limit: &hyperliquid.LimitOrderType{ + Tif: hyperliquid.TifIoc, + }, + }, + ReduceOnly: false, + } + + _, err = t.exchange.Order(t.ctx, order, nil) + if err != nil { + return nil, fmt.Errorf("开空仓失败: %w", err) + } + + log.Printf("✓ 开空仓成功: %s 数量: %.4f", symbol, roundedQuantity) + + result := make(map[string]interface{}) + result["orderId"] = 0 + result["symbol"] = symbol + result["status"] = "FILLED" + + return result, nil +} + +// CloseLong 平多仓 +func (t *HyperliquidTrader) CloseLong(symbol string, quantity float64) (map[string]interface{}, error) { + // 如果数量为0,获取当前持仓数量 + if quantity == 0 { + positions, err := t.GetPositions() + if err != nil { + return nil, err + } + + for _, pos := range positions { + if pos["symbol"] == symbol && pos["side"] == "long" { + quantity = pos["positionAmt"].(float64) + break + } + } + + if quantity == 0 { + return nil, fmt.Errorf("没有找到 %s 的多仓", symbol) + } + } + + // Hyperliquid symbol格式 + coin := convertSymbolToHyperliquid(symbol) + + // 获取当前价格 + price, err := t.GetMarketPrice(symbol) + if err != nil { + return nil, err + } + + // ⚠️ 关键:根据币种精度要求,四舍五入数量 + roundedQuantity := t.roundToSzDecimals(coin, quantity) + log.Printf(" 📏 数量精度处理: %.8f -> %.8f (szDecimals=%d)", quantity, roundedQuantity, t.getSzDecimals(coin)) + + // ⚠️ 关键:价格也需要处理为5位有效数字 + aggressivePrice := t.roundPriceToSigfigs(price * 0.99) + log.Printf(" 💰 价格精度处理: %.8f -> %.8f (5位有效数字)", price*0.99, aggressivePrice) + + // 创建平仓订单(卖出 + ReduceOnly) + order := hyperliquid.CreateOrderRequest{ + Coin: coin, + IsBuy: false, + Size: roundedQuantity, // 使用四舍五入后的数量 + Price: aggressivePrice, // 使用处理后的价格 + OrderType: hyperliquid.OrderType{ + Limit: &hyperliquid.LimitOrderType{ + Tif: hyperliquid.TifIoc, + }, + }, + ReduceOnly: true, // 只平仓,不开新仓 + } + + _, err = t.exchange.Order(t.ctx, order, nil) + if err != nil { + return nil, fmt.Errorf("平多仓失败: %w", err) + } + + log.Printf("✓ 平多仓成功: %s 数量: %.4f", symbol, roundedQuantity) + + // 平仓后取消该币种的所有挂单 + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消挂单失败: %v", err) + } + + result := make(map[string]interface{}) + result["orderId"] = 0 + result["symbol"] = symbol + result["status"] = "FILLED" + + return result, nil +} + +// CloseShort 平空仓 +func (t *HyperliquidTrader) CloseShort(symbol string, quantity float64) (map[string]interface{}, error) { + // 如果数量为0,获取当前持仓数量 + if quantity == 0 { + positions, err := t.GetPositions() + if err != nil { + return nil, err + } + + for _, pos := range positions { + if pos["symbol"] == symbol && pos["side"] == "short" { + quantity = pos["positionAmt"].(float64) + break + } + } + + if quantity == 0 { + return nil, fmt.Errorf("没有找到 %s 的空仓", symbol) + } + } + + // Hyperliquid symbol格式 + coin := convertSymbolToHyperliquid(symbol) + + // 获取当前价格 + price, err := t.GetMarketPrice(symbol) + if err != nil { + return nil, err + } + + // ⚠️ 关键:根据币种精度要求,四舍五入数量 + roundedQuantity := t.roundToSzDecimals(coin, quantity) + log.Printf(" 📏 数量精度处理: %.8f -> %.8f (szDecimals=%d)", quantity, roundedQuantity, t.getSzDecimals(coin)) + + // ⚠️ 关键:价格也需要处理为5位有效数字 + aggressivePrice := t.roundPriceToSigfigs(price * 1.01) + log.Printf(" 💰 价格精度处理: %.8f -> %.8f (5位有效数字)", price*1.01, aggressivePrice) + + // 创建平仓订单(买入 + ReduceOnly) + order := hyperliquid.CreateOrderRequest{ + Coin: coin, + IsBuy: true, + Size: roundedQuantity, // 使用四舍五入后的数量 + Price: aggressivePrice, // 使用处理后的价格 + OrderType: hyperliquid.OrderType{ + Limit: &hyperliquid.LimitOrderType{ + Tif: hyperliquid.TifIoc, + }, + }, + ReduceOnly: true, + } + + _, err = t.exchange.Order(t.ctx, order, nil) + if err != nil { + return nil, fmt.Errorf("平空仓失败: %w", err) + } + + log.Printf("✓ 平空仓成功: %s 数量: %.4f", symbol, roundedQuantity) + + // 平仓后取消该币种的所有挂单 + if err := t.CancelAllOrders(symbol); err != nil { + log.Printf(" ⚠ 取消挂单失败: %v", err) + } + + result := make(map[string]interface{}) + result["orderId"] = 0 + result["symbol"] = symbol + result["status"] = "FILLED" + + return result, nil +} + +// CancelAllOrders 取消该币种的所有挂单 +func (t *HyperliquidTrader) CancelAllOrders(symbol string) error { + coin := convertSymbolToHyperliquid(symbol) + + // 获取所有挂单 + openOrders, err := t.exchange.Info().OpenOrders(t.ctx, t.walletAddr) + if err != nil { + return fmt.Errorf("获取挂单失败: %w", err) + } + + // 取消该币种的所有挂单 + for _, order := range openOrders { + if order.Coin == coin { + _, err := t.exchange.Cancel(t.ctx, coin, order.Oid) + if err != nil { + log.Printf(" ⚠ 取消订单失败 (oid=%d): %v", order.Oid, err) + } + } + } + + log.Printf(" ✓ 已取消 %s 的所有挂单", symbol) + return nil +} + +// GetMarketPrice 获取市场价格 +func (t *HyperliquidTrader) GetMarketPrice(symbol string) (float64, error) { + coin := convertSymbolToHyperliquid(symbol) + + // 获取所有市场价格 + allMids, err := t.exchange.Info().AllMids(t.ctx) + if err != nil { + return 0, fmt.Errorf("获取价格失败: %w", err) + } + + // 查找对应币种的价格(allMids是map[string]string) + if priceStr, ok := allMids[coin]; ok { + priceFloat, err := strconv.ParseFloat(priceStr, 64) + if err == nil { + return priceFloat, nil + } + return 0, fmt.Errorf("价格格式错误: %v", err) + } + + return 0, fmt.Errorf("未找到 %s 的价格", symbol) +} + +// SetStopLoss 设置止损单 +func (t *HyperliquidTrader) SetStopLoss(symbol string, positionSide string, quantity, stopPrice float64) error { + coin := convertSymbolToHyperliquid(symbol) + + isBuy := positionSide == "SHORT" // 空仓止损=买入,多仓止损=卖出 + + // ⚠️ 关键:根据币种精度要求,四舍五入数量 + roundedQuantity := t.roundToSzDecimals(coin, quantity) + + // ⚠️ 关键:价格也需要处理为5位有效数字 + roundedStopPrice := t.roundPriceToSigfigs(stopPrice) + + // 创建止损单(Trigger Order) + order := hyperliquid.CreateOrderRequest{ + Coin: coin, + IsBuy: isBuy, + Size: roundedQuantity, // 使用四舍五入后的数量 + Price: roundedStopPrice, // 使用处理后的价格 + OrderType: hyperliquid.OrderType{ + Trigger: &hyperliquid.TriggerOrderType{ + TriggerPx: roundedStopPrice, + IsMarket: true, + Tpsl: "sl", // stop loss + }, + }, + ReduceOnly: true, + } + + _, err := t.exchange.Order(t.ctx, order, nil) + if err != nil { + return fmt.Errorf("设置止损失败: %w", err) + } + + log.Printf(" 止损价设置: %.4f", roundedStopPrice) + return nil +} + +// SetTakeProfit 设置止盈单 +func (t *HyperliquidTrader) SetTakeProfit(symbol string, positionSide string, quantity, takeProfitPrice float64) error { + coin := convertSymbolToHyperliquid(symbol) + + isBuy := positionSide == "SHORT" // 空仓止盈=买入,多仓止盈=卖出 + + // ⚠️ 关键:根据币种精度要求,四舍五入数量 + roundedQuantity := t.roundToSzDecimals(coin, quantity) + + // ⚠️ 关键:价格也需要处理为5位有效数字 + roundedTakeProfitPrice := t.roundPriceToSigfigs(takeProfitPrice) + + // 创建止盈单(Trigger Order) + order := hyperliquid.CreateOrderRequest{ + Coin: coin, + IsBuy: isBuy, + Size: roundedQuantity, // 使用四舍五入后的数量 + Price: roundedTakeProfitPrice, // 使用处理后的价格 + OrderType: hyperliquid.OrderType{ + Trigger: &hyperliquid.TriggerOrderType{ + TriggerPx: roundedTakeProfitPrice, + IsMarket: true, + Tpsl: "tp", // take profit + }, + }, + ReduceOnly: true, + } + + _, err := t.exchange.Order(t.ctx, order, nil) + if err != nil { + return fmt.Errorf("设置止盈失败: %w", err) + } + + log.Printf(" 止盈价设置: %.4f", roundedTakeProfitPrice) + return nil +} + +// FormatQuantity 格式化数量到正确的精度 +func (t *HyperliquidTrader) FormatQuantity(symbol string, quantity float64) (string, error) { + coin := convertSymbolToHyperliquid(symbol) + szDecimals := t.getSzDecimals(coin) + + // 使用szDecimals格式化数量 + formatStr := fmt.Sprintf("%%.%df", szDecimals) + return fmt.Sprintf(formatStr, quantity), nil +} + +// getSzDecimals 获取币种的数量精度 +func (t *HyperliquidTrader) getSzDecimals(coin string) int { + if t.meta == nil { + log.Printf("⚠️ meta信息为空,使用默认精度4") + return 4 // 默认精度 + } + + // 在meta.Universe中查找对应的币种 + for _, asset := range t.meta.Universe { + if asset.Name == coin { + return asset.SzDecimals + } + } + + log.Printf("⚠️ 未找到 %s 的精度信息,使用默认精度4", coin) + return 4 // 默认精度 +} + +// roundToSzDecimals 将数量四舍五入到正确的精度 +func (t *HyperliquidTrader) roundToSzDecimals(coin string, quantity float64) float64 { + szDecimals := t.getSzDecimals(coin) + + // 计算倍数(10^szDecimals) + multiplier := 1.0 + for i := 0; i < szDecimals; i++ { + multiplier *= 10.0 + } + + // 四舍五入 + return float64(int(quantity*multiplier+0.5)) / multiplier +} + +// roundPriceToSigfigs 将价格四舍五入到5位有效数字 +// Hyperliquid要求价格使用5位有效数字(significant figures) +func (t *HyperliquidTrader) roundPriceToSigfigs(price float64) float64 { + if price == 0 { + return 0 + } + + const sigfigs = 5 // Hyperliquid标准:5位有效数字 + + // 计算价格的数量级 + var magnitude float64 + if price < 0 { + magnitude = -price + } else { + magnitude = price + } + + // 计算需要的倍数 + multiplier := 1.0 + for magnitude >= 10 { + magnitude /= 10 + multiplier /= 10 + } + for magnitude < 1 { + magnitude *= 10 + multiplier *= 10 + } + + // 应用有效数字精度 + for i := 0; i < sigfigs-1; i++ { + multiplier *= 10 + } + + // 四舍五入 + rounded := float64(int(price*multiplier+0.5)) / multiplier + return rounded +} + +// convertSymbolToHyperliquid 将标准symbol转换为Hyperliquid格式 +// 例如: "BTCUSDT" -> "BTC" +func convertSymbolToHyperliquid(symbol string) string { + // 去掉USDT后缀 + if len(symbol) > 4 && symbol[len(symbol)-4:] == "USDT" { + return symbol[:len(symbol)-4] + } + return symbol +} + +// absFloat 返回浮点数的绝对值 +func absFloat(x float64) float64 { + if x < 0 { + return -x + } + return x +} diff --git a/trader/interface.go b/trader/interface.go new file mode 100644 index 00000000..77ee6ea8 --- /dev/null +++ b/trader/interface.go @@ -0,0 +1,41 @@ +package trader + +// Trader 交易器统一接口 +// 支持多个交易平台(币安、Hyperliquid等) +type Trader interface { + // GetBalance 获取账户余额 + GetBalance() (map[string]interface{}, error) + + // GetPositions 获取所有持仓 + GetPositions() ([]map[string]interface{}, error) + + // OpenLong 开多仓 + OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) + + // OpenShort 开空仓 + OpenShort(symbol string, quantity float64, leverage int) (map[string]interface{}, error) + + // CloseLong 平多仓(quantity=0表示全部平仓) + CloseLong(symbol string, quantity float64) (map[string]interface{}, error) + + // CloseShort 平空仓(quantity=0表示全部平仓) + CloseShort(symbol string, quantity float64) (map[string]interface{}, error) + + // SetLeverage 设置杠杆 + SetLeverage(symbol string, leverage int) error + + // GetMarketPrice 获取市场价格 + GetMarketPrice(symbol string) (float64, error) + + // SetStopLoss 设置止损单 + SetStopLoss(symbol string, positionSide string, quantity, stopPrice float64) error + + // SetTakeProfit 设置止盈单 + SetTakeProfit(symbol string, positionSide string, quantity, takeProfitPrice float64) error + + // CancelAllOrders 取消该币种的所有挂单 + CancelAllOrders(symbol string) error + + // FormatQuantity 格式化数量到正确的精度 + FormatQuantity(symbol string, quantity float64) (string, error) +}