Merge pull request #59 from liugangdao/main

fix: hyperliquid余额不准确
This commit is contained in:
tinkle-community
2025-10-30 17:05:54 +08:00
committed by GitHub
6 changed files with 52 additions and 47 deletions

View File

@@ -9,19 +9,20 @@ import (
// TraderConfig 单个trader的配置 // TraderConfig 单个trader的配置
type TraderConfig struct { type TraderConfig struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
AIModel string `json:"ai_model"` // "qwen" or "deepseek" AIModel string `json:"ai_model"` // "qwen" or "deepseek"
// 交易平台选择(二选一) // 交易平台选择(二选一)
Exchange string `json:"exchange"` // "binance" or "hyperliquid" Exchange string `json:"exchange"` // "binance" or "hyperliquid"
// 币安配置 // 币安配置
BinanceAPIKey string `json:"binance_api_key,omitempty"` BinanceAPIKey string `json:"binance_api_key,omitempty"`
BinanceSecretKey string `json:"binance_secret_key,omitempty"` BinanceSecretKey string `json:"binance_secret_key,omitempty"`
// Hyperliquid配置 // Hyperliquid配置
HyperliquidPrivateKey string `json:"hyperliquid_private_key,omitempty"` HyperliquidPrivateKey string `json:"hyperliquid_private_key,omitempty"`
HyperliquidWalletAddr string `json:"hyperliquid_wallet_addr,omitempty"`
HyperliquidTestnet bool `json:"hyperliquid_testnet,omitempty"` HyperliquidTestnet bool `json:"hyperliquid_testnet,omitempty"`
// Aster配置 // Aster配置
@@ -30,13 +31,13 @@ type TraderConfig struct {
AsterPrivateKey string `json:"aster_private_key,omitempty"` // Aster API钱包私钥 AsterPrivateKey string `json:"aster_private_key,omitempty"` // Aster API钱包私钥
// AI配置 // AI配置
QwenKey string `json:"qwen_key,omitempty"` QwenKey string `json:"qwen_key,omitempty"`
DeepSeekKey string `json:"deepseek_key,omitempty"` DeepSeekKey string `json:"deepseek_key,omitempty"`
// 自定义AI API配置支持任何OpenAI格式的API // 自定义AI API配置支持任何OpenAI格式的API
CustomAPIURL string `json:"custom_api_url,omitempty"` CustomAPIURL string `json:"custom_api_url,omitempty"`
CustomAPIKey string `json:"custom_api_key,omitempty"` CustomAPIKey string `json:"custom_api_key,omitempty"`
CustomModelName string `json:"custom_model_name,omitempty"` CustomModelName string `json:"custom_model_name,omitempty"`
InitialBalance float64 `json:"initial_balance"` InitialBalance float64 `json:"initial_balance"`
ScanIntervalMinutes int `json:"scan_interval_minutes"` ScanIntervalMinutes int `json:"scan_interval_minutes"`
@@ -44,15 +45,15 @@ type TraderConfig struct {
// LeverageConfig 杠杆配置 // LeverageConfig 杠杆配置
type LeverageConfig struct { type LeverageConfig struct {
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC和ETH的杠杆倍数主账户建议5-50子账户≤5 BTCETHLeverage int `json:"btc_eth_leverage"` // BTC和ETH的杠杆倍数主账户建议5-50子账户≤5
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币的杠杆倍数主账户建议5-20子账户≤5 AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币的杠杆倍数主账户建议5-20子账户≤5
} }
// Config 总配置 // Config 总配置
type Config struct { type Config struct {
Traders []TraderConfig `json:"traders"` Traders []TraderConfig `json:"traders"`
UseDefaultCoins bool `json:"use_default_coins"` // 是否使用默认主流币种列表 UseDefaultCoins bool `json:"use_default_coins"` // 是否使用默认主流币种列表
DefaultCoins []string `json:"default_coins"` // 默认主流币种池 DefaultCoins []string `json:"default_coins"` // 默认主流币种池
CoinPoolAPIURL string `json:"coin_pool_api_url"` CoinPoolAPIURL string `json:"coin_pool_api_url"`
OITopAPIURL string `json:"oi_top_api_url"` OITopAPIURL string `json:"oi_top_api_url"`
APIServerPort int `json:"api_server_port"` APIServerPort int `json:"api_server_port"`
@@ -83,13 +84,13 @@ func LoadConfig(filename string) (*Config, error) {
if len(config.DefaultCoins) == 0 { if len(config.DefaultCoins) == 0 {
config.DefaultCoins = []string{ config.DefaultCoins = []string{
"BTCUSDT", "BTCUSDT",
"ETHUSDT", "ETHUSDT",
"SOLUSDT", "SOLUSDT",
"BNBUSDT", "BNBUSDT",
"XRPUSDT", "XRPUSDT",
"DOGEUSDT", "DOGEUSDT",
"ADAUSDT", "ADAUSDT",
"HYPEUSDT", "HYPEUSDT",
} }
} }

2
go.sum
View File

@@ -74,6 +74,8 @@ github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 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/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=

View File

@@ -40,6 +40,7 @@ func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string,
BinanceAPIKey: cfg.BinanceAPIKey, BinanceAPIKey: cfg.BinanceAPIKey,
BinanceSecretKey: cfg.BinanceSecretKey, BinanceSecretKey: cfg.BinanceSecretKey,
HyperliquidPrivateKey: cfg.HyperliquidPrivateKey, HyperliquidPrivateKey: cfg.HyperliquidPrivateKey,
HyperliquidWalletAddr: cfg.HyperliquidWalletAddr,
HyperliquidTestnet: cfg.HyperliquidTestnet, HyperliquidTestnet: cfg.HyperliquidTestnet,
AsterUser: cfg.AsterUser, AsterUser: cfg.AsterUser,
AsterSigner: cfg.AsterSigner, AsterSigner: cfg.AsterSigner,

View File

@@ -29,6 +29,7 @@ type AutoTraderConfig struct {
// Hyperliquid配置 // Hyperliquid配置
HyperliquidPrivateKey string HyperliquidPrivateKey string
HyperliquidWalletAddr string
HyperliquidTestnet bool HyperliquidTestnet bool
// Aster配置 // Aster配置
@@ -138,7 +139,7 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
trader = NewFuturesTrader(config.BinanceAPIKey, config.BinanceSecretKey) trader = NewFuturesTrader(config.BinanceAPIKey, config.BinanceSecretKey)
case "hyperliquid": case "hyperliquid":
log.Printf("🏦 [%s] 使用Hyperliquid交易", config.Name) log.Printf("🏦 [%s] 使用Hyperliquid交易", config.Name)
trader, err = NewHyperliquidTrader(config.HyperliquidPrivateKey, config.HyperliquidTestnet) trader, err = NewHyperliquidTrader(config.HyperliquidPrivateKey, config.HyperliquidWalletAddr, config.HyperliquidTestnet)
if err != nil { if err != nil {
return nil, fmt.Errorf("初始化Hyperliquid交易器失败: %w", err) return nil, fmt.Errorf("初始化Hyperliquid交易器失败: %w", err)
} }

View File

@@ -2,7 +2,6 @@ package trader
import ( import (
"context" "context"
"crypto/ecdsa"
"fmt" "fmt"
"log" "log"
"strconv" "strconv"
@@ -20,7 +19,7 @@ type HyperliquidTrader struct {
} }
// NewHyperliquidTrader 创建Hyperliquid交易器 // NewHyperliquidTrader 创建Hyperliquid交易器
func NewHyperliquidTrader(privateKeyHex string, testnet bool) (*HyperliquidTrader, error) { func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool) (*HyperliquidTrader, error) {
// 解析私钥 // 解析私钥
privateKey, err := crypto.HexToECDSA(privateKeyHex) privateKey, err := crypto.HexToECDSA(privateKeyHex)
if err != nil { if err != nil {
@@ -33,13 +32,13 @@ func NewHyperliquidTrader(privateKeyHex string, testnet bool) (*HyperliquidTrade
apiURL = hyperliquid.TestnetAPIURL apiURL = hyperliquid.TestnetAPIURL
} }
// 从私钥生成钱包地址 // // 从私钥生成钱包地址
pubKey := privateKey.Public() // pubKey := privateKey.Public()
publicKeyECDSA, ok := pubKey.(*ecdsa.PublicKey) // publicKeyECDSA, ok := pubKey.(*ecdsa.PublicKey)
if !ok { // if !ok {
return nil, fmt.Errorf("无法转换公钥") // return nil, fmt.Errorf("无法转换公钥")
} // }
walletAddr := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() // walletAddr := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
ctx := context.Background() ctx := context.Background()
@@ -99,9 +98,9 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) {
// 钱包余额(已实现)= AccountValue - 未实现盈亏 // 钱包余额(已实现)= AccountValue - 未实现盈亏
walletBalance := accountValue - totalUnrealizedPnl walletBalance := accountValue - totalUnrealizedPnl
result["totalWalletBalance"] = walletBalance // 钱包余额(已实现部分) result["totalWalletBalance"] = walletBalance // 钱包余额(已实现部分)
result["availableBalance"] = accountValue - totalMarginUsed // 可用余额 result["availableBalance"] = accountValue - totalMarginUsed // 可用余额
result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏 result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏
log.Printf("✓ Hyperliquid API返回: 账户净值=%.2f, 钱包余额=%.2f, 可用=%.2f, 未实现盈亏=%.2f", log.Printf("✓ Hyperliquid API返回: 账户净值=%.2f, 钱包余额=%.2f, 可用=%.2f, 未实现盈亏=%.2f",
accountValue, accountValue,
@@ -515,8 +514,8 @@ func (t *HyperliquidTrader) SetStopLoss(symbol string, positionSide string, quan
order := hyperliquid.CreateOrderRequest{ order := hyperliquid.CreateOrderRequest{
Coin: coin, Coin: coin,
IsBuy: isBuy, IsBuy: isBuy,
Size: roundedQuantity, // 使用四舍五入后的数量 Size: roundedQuantity, // 使用四舍五入后的数量
Price: roundedStopPrice, // 使用处理后的价格 Price: roundedStopPrice, // 使用处理后的价格
OrderType: hyperliquid.OrderType{ OrderType: hyperliquid.OrderType{
Trigger: &hyperliquid.TriggerOrderType{ Trigger: &hyperliquid.TriggerOrderType{
TriggerPx: roundedStopPrice, TriggerPx: roundedStopPrice,
@@ -552,8 +551,8 @@ func (t *HyperliquidTrader) SetTakeProfit(symbol string, positionSide string, qu
order := hyperliquid.CreateOrderRequest{ order := hyperliquid.CreateOrderRequest{
Coin: coin, Coin: coin,
IsBuy: isBuy, IsBuy: isBuy,
Size: roundedQuantity, // 使用四舍五入后的数量 Size: roundedQuantity, // 使用四舍五入后的数量
Price: roundedTakeProfitPrice, // 使用处理后的价格 Price: roundedTakeProfitPrice, // 使用处理后的价格
OrderType: hyperliquid.OrderType{ OrderType: hyperliquid.OrderType{
Trigger: &hyperliquid.TriggerOrderType{ Trigger: &hyperliquid.TriggerOrderType{
TriggerPx: roundedTakeProfitPrice, TriggerPx: roundedTakeProfitPrice,
@@ -577,7 +576,7 @@ func (t *HyperliquidTrader) SetTakeProfit(symbol string, positionSide string, qu
func (t *HyperliquidTrader) FormatQuantity(symbol string, quantity float64) (string, error) { func (t *HyperliquidTrader) FormatQuantity(symbol string, quantity float64) (string, error) {
coin := convertSymbolToHyperliquid(symbol) coin := convertSymbolToHyperliquid(symbol)
szDecimals := t.getSzDecimals(coin) szDecimals := t.getSzDecimals(coin)
// 使用szDecimals格式化数量 // 使用szDecimals格式化数量
formatStr := fmt.Sprintf("%%.%df", szDecimals) formatStr := fmt.Sprintf("%%.%df", szDecimals)
return fmt.Sprintf(formatStr, quantity), nil return fmt.Sprintf(formatStr, quantity), nil
@@ -604,13 +603,13 @@ func (t *HyperliquidTrader) getSzDecimals(coin string) int {
// roundToSzDecimals 将数量四舍五入到正确的精度 // roundToSzDecimals 将数量四舍五入到正确的精度
func (t *HyperliquidTrader) roundToSzDecimals(coin string, quantity float64) float64 { func (t *HyperliquidTrader) roundToSzDecimals(coin string, quantity float64) float64 {
szDecimals := t.getSzDecimals(coin) szDecimals := t.getSzDecimals(coin)
// 计算倍数10^szDecimals // 计算倍数10^szDecimals
multiplier := 1.0 multiplier := 1.0
for i := 0; i < szDecimals; i++ { for i := 0; i < szDecimals; i++ {
multiplier *= 10.0 multiplier *= 10.0
} }
// 四舍五入 // 四舍五入
return float64(int(quantity*multiplier+0.5)) / multiplier return float64(int(quantity*multiplier+0.5)) / multiplier
} }
@@ -621,9 +620,9 @@ func (t *HyperliquidTrader) roundPriceToSigfigs(price float64) float64 {
if price == 0 { if price == 0 {
return 0 return 0
} }
const sigfigs = 5 // Hyperliquid标准5位有效数字 const sigfigs = 5 // Hyperliquid标准5位有效数字
// 计算价格的数量级 // 计算价格的数量级
var magnitude float64 var magnitude float64
if price < 0 { if price < 0 {
@@ -631,7 +630,7 @@ func (t *HyperliquidTrader) roundPriceToSigfigs(price float64) float64 {
} else { } else {
magnitude = price magnitude = price
} }
// 计算需要的倍数 // 计算需要的倍数
multiplier := 1.0 multiplier := 1.0
for magnitude >= 10 { for magnitude >= 10 {
@@ -642,12 +641,12 @@ func (t *HyperliquidTrader) roundPriceToSigfigs(price float64) float64 {
magnitude *= 10 magnitude *= 10
multiplier *= 10 multiplier *= 10
} }
// 应用有效数字精度 // 应用有效数字精度
for i := 0; i < sigfigs-1; i++ { for i := 0; i < sigfigs-1; i++ {
multiplier *= 10 multiplier *= 10
} }
// 四舍五入 // 四舍五入
rounded := float64(int(price*multiplier+0.5)) / multiplier rounded := float64(int(price*multiplier+0.5)) / multiplier
return rounded return rounded

View File

@@ -4,6 +4,7 @@ import react from '@vitejs/plugin-react'
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
host: '0.0.0.0',
port: 3000, port: 3000,
proxy: { proxy: {
'/api': { '/api': {