mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
87f67f2da1
## Features - Add full Aster DEX integration with Binance-compatible API - Support Web3 authentication with API wallet system - Add comprehensive Aster integration guide (ASTER_INTEGRATION.md) - Add example Aster configuration (config.aster.example.json) ## Bug Fixes - Fix precision error (code -1111) for all order types - Implement proper float-to-string conversion with exchange precision - Add automatic precision fetching from /exchangeInfo endpoint - Remove trailing zeros from formatted values ## Documentation - Update README.md with Aster quick start guide - Add detailed setup instructions for creating API wallet - Include troubleshooting FAQ and security best practices - Update core features to mention three supported exchanges ## Technical Details - Added formatFloatWithPrecision() helper function - Updated all order functions to use proper precision formatting - Added precision logging for debugging - Fully backward compatible with existing configurations Closes #[issue number if applicable]
172 lines
4.5 KiB
Go
172 lines
4.5 KiB
Go
package manager
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"nofx/config"
|
|
"nofx/trader"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TraderManager 管理多个trader实例
|
|
type TraderManager struct {
|
|
traders map[string]*trader.AutoTrader // key: trader ID
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewTraderManager 创建trader管理器
|
|
func NewTraderManager() *TraderManager {
|
|
return &TraderManager{
|
|
traders: make(map[string]*trader.AutoTrader),
|
|
}
|
|
}
|
|
|
|
// AddTrader 添加一个trader
|
|
func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, leverage config.LeverageConfig) error {
|
|
tm.mu.Lock()
|
|
defer tm.mu.Unlock()
|
|
|
|
if _, exists := tm.traders[cfg.ID]; exists {
|
|
return fmt.Errorf("trader ID '%s' 已存在", cfg.ID)
|
|
}
|
|
|
|
// 构建AutoTraderConfig
|
|
traderConfig := trader.AutoTraderConfig{
|
|
ID: cfg.ID,
|
|
Name: cfg.Name,
|
|
AIModel: cfg.AIModel,
|
|
Exchange: cfg.Exchange,
|
|
BinanceAPIKey: cfg.BinanceAPIKey,
|
|
BinanceSecretKey: cfg.BinanceSecretKey,
|
|
HyperliquidPrivateKey: cfg.HyperliquidPrivateKey,
|
|
HyperliquidTestnet: cfg.HyperliquidTestnet,
|
|
AsterUser: cfg.AsterUser,
|
|
AsterSigner: cfg.AsterSigner,
|
|
AsterPrivateKey: cfg.AsterPrivateKey,
|
|
CoinPoolAPIURL: coinPoolURL,
|
|
UseQwen: cfg.AIModel == "qwen",
|
|
DeepSeekKey: cfg.DeepSeekKey,
|
|
QwenKey: cfg.QwenKey,
|
|
CustomAPIURL: cfg.CustomAPIURL,
|
|
CustomAPIKey: cfg.CustomAPIKey,
|
|
CustomModelName: cfg.CustomModelName,
|
|
ScanInterval: cfg.GetScanInterval(),
|
|
InitialBalance: cfg.InitialBalance,
|
|
BTCETHLeverage: leverage.BTCETHLeverage, // 使用配置的杠杆倍数
|
|
AltcoinLeverage: leverage.AltcoinLeverage, // 使用配置的杠杆倍数
|
|
MaxDailyLoss: maxDailyLoss,
|
|
MaxDrawdown: maxDrawdown,
|
|
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
|
}
|
|
|
|
// 创建trader实例
|
|
at, err := trader.NewAutoTrader(traderConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("创建trader失败: %w", err)
|
|
}
|
|
|
|
tm.traders[cfg.ID] = at
|
|
log.Printf("✓ Trader '%s' (%s) 已添加", cfg.Name, cfg.AIModel)
|
|
return nil
|
|
}
|
|
|
|
// GetTrader 获取指定ID的trader
|
|
func (tm *TraderManager) GetTrader(id string) (*trader.AutoTrader, error) {
|
|
tm.mu.RLock()
|
|
defer tm.mu.RUnlock()
|
|
|
|
t, exists := tm.traders[id]
|
|
if !exists {
|
|
return nil, fmt.Errorf("trader ID '%s' 不存在", id)
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// GetAllTraders 获取所有trader
|
|
func (tm *TraderManager) GetAllTraders() map[string]*trader.AutoTrader {
|
|
tm.mu.RLock()
|
|
defer tm.mu.RUnlock()
|
|
|
|
result := make(map[string]*trader.AutoTrader)
|
|
for id, t := range tm.traders {
|
|
result[id] = t
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetTraderIDs 获取所有trader ID列表
|
|
func (tm *TraderManager) GetTraderIDs() []string {
|
|
tm.mu.RLock()
|
|
defer tm.mu.RUnlock()
|
|
|
|
ids := make([]string, 0, len(tm.traders))
|
|
for id := range tm.traders {
|
|
ids = append(ids, id)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// StartAll 启动所有trader
|
|
func (tm *TraderManager) StartAll() {
|
|
tm.mu.RLock()
|
|
defer tm.mu.RUnlock()
|
|
|
|
log.Println("🚀 启动所有Trader...")
|
|
for id, t := range tm.traders {
|
|
go func(traderID string, at *trader.AutoTrader) {
|
|
log.Printf("▶️ 启动 %s...", at.GetName())
|
|
if err := at.Run(); err != nil {
|
|
log.Printf("❌ %s 运行错误: %v", at.GetName(), err)
|
|
}
|
|
}(id, t)
|
|
}
|
|
}
|
|
|
|
// StopAll 停止所有trader
|
|
func (tm *TraderManager) StopAll() {
|
|
tm.mu.RLock()
|
|
defer tm.mu.RUnlock()
|
|
|
|
log.Println("⏹ 停止所有Trader...")
|
|
for _, t := range tm.traders {
|
|
t.Stop()
|
|
}
|
|
}
|
|
|
|
// GetComparisonData 获取对比数据
|
|
func (tm *TraderManager) GetComparisonData() (map[string]interface{}, error) {
|
|
tm.mu.RLock()
|
|
defer tm.mu.RUnlock()
|
|
|
|
comparison := make(map[string]interface{})
|
|
traders := make([]map[string]interface{}, 0, len(tm.traders))
|
|
|
|
for _, t := range tm.traders {
|
|
account, err := t.GetAccountInfo()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
status := t.GetStatus()
|
|
|
|
traders = append(traders, map[string]interface{}{
|
|
"trader_id": t.GetID(),
|
|
"trader_name": t.GetName(),
|
|
"ai_model": t.GetAIModel(),
|
|
"total_equity": account["total_equity"],
|
|
"total_pnl": account["total_pnl"],
|
|
"total_pnl_pct": account["total_pnl_pct"],
|
|
"position_count": account["position_count"],
|
|
"margin_used_pct": account["margin_used_pct"],
|
|
"call_count": status["call_count"],
|
|
"is_running": status["is_running"],
|
|
})
|
|
}
|
|
|
|
comparison["traders"] = traders
|
|
comparison["count"] = len(traders)
|
|
|
|
return comparison, nil
|
|
}
|