mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
Merge pull request #16 from hrzisme/feature/hyperliquid-support
feat: Add Hyperliquid exchange support with unified trader interface
This commit is contained in:
75
README.md
75
README.md
@@ -1,4 +1,4 @@
|
||||
# 🤖 NOFX - AI-Driven Binance Futures Auto Trading Competition System
|
||||
# 🤖 NOFX - AI-Driven Crypto Futures Auto Trading Competition System
|
||||
|
||||
[](https://golang.org/)
|
||||
[](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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
57
go.mod
57
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
|
||||
)
|
||||
|
||||
157
go.sum
157
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=
|
||||
|
||||
@@ -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实例
|
||||
|
||||
@@ -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()),
|
||||
|
||||
672
trader/hyperliquid_trader.go
Normal file
672
trader/hyperliquid_trader.go
Normal file
@@ -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
|
||||
}
|
||||
41
trader/interface.go
Normal file
41
trader/interface.go
Normal file
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user