Files
nofx/manager/trader_manager.go
T
nobody 87f67f2da1 feat: Add Aster DEX exchange support + fix precision issues
## 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]
2025-10-30 00:27:33 +08:00

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
}