feat(trader): add automatic balance sync every 30 minutes

## Summary
Automatically sync trader's initial_balance when exchange balance changes
significantly (>5%), eliminating the need for manual intervention after
deposits/withdrawals.

## Changes

### trader/auto_trader.go
- Add fields: lastBalanceSyncTime, database, userID
- Add autoSyncBalanceIfNeeded() method:
  * Check every 30 minutes (配合3分钟扫描周期,约10次扫描触发一次)
  * Query exchange balance via trader.GetBalance()
  * Update if change >5%
  * Log changes with emoji indicators
- Integrate into runCycle() before trading decisions

### manager/trader_manager.go
- Update NewAutoTrader() calls to pass database and userID
- Update method signatures:
  * addTraderFromDB()
  * AddTraderFromDB()
  * loadSingleTrader()

## Configuration

- Check interval: 30 minutes
- Change threshold: 5%
- Automatic detection and update

## Benefits

 完全自动化,无需手动调用API
 30分钟检查间隔,及时又不频繁
 智能判断(变化>5%才更新)
 充值/提现后自动更新initial_balance
 前端P&L显示自动修正

## Example Logs

🔄 [DS-BF] 开始自动检查余额变化...
🔔 [DS-BF] 检测到余额大幅变化: 693.00 → 3000.00 USDT (333.33%)
 [DS-BF] 已自动同步余额到数据库

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZhouYongyou
2025-11-04 20:31:40 +08:00
parent e36ffc5a18
commit e8f05d1761
2 changed files with 80 additions and 13 deletions

View File

@@ -118,10 +118,13 @@ type AutoTrader struct {
positionFirstSeenTime map[string]int64 // 持仓首次出现时间 (symbol_side -> timestamp毫秒)
lastPositions map[string]*PositionSnapshot // 上一个周期的持仓快照 (symbol_side -> snapshot)
newsProcessor []news.Provider // 新闻
lastBalanceSyncTime time.Time // 上次余额同步时间
database *config.Database // 数据库引用(用于自动更新余额)
userID string // 用户ID
}
// NewAutoTrader 创建自动交易器
func NewAutoTrader(traderConfig AutoTraderConfig) (*AutoTrader, error) {
func NewAutoTrader(traderConfig AutoTraderConfig, db *config.Database, userID string) (*AutoTrader, error) {
// 设置默认值
if traderConfig.ID == "" {
traderConfig.ID = "default_trader"
@@ -261,6 +264,9 @@ func NewAutoTrader(traderConfig AutoTraderConfig) (*AutoTrader, error) {
positionFirstSeenTime: make(map[string]int64),
lastPositions: make(map[string]*PositionSnapshot),
newsProcessor: newsProcessor,
lastBalanceSyncTime: time.Now(), // 初始化为当前时间
database: db,
userID: userID,
}, nil
}
@@ -298,6 +304,64 @@ func (at *AutoTrader) Stop() {
log.Println("⏹ 自动交易系统停止")
}
// autoSyncBalanceIfNeeded 自动同步余额每30分钟检查一次变化>5%才更新)
func (at *AutoTrader) autoSyncBalanceIfNeeded() {
// 距离上次同步不足30分钟跳过
if time.Since(at.lastBalanceSyncTime) < 30*time.Minute {
return
}
log.Printf("🔄 [%s] 开始自动检查余额变化...", at.name)
// 查询实际余额
balanceInfo, err := at.trader.GetBalance()
if err != nil {
log.Printf("⚠️ [%s] 查询余额失败: %v", at.name, err)
at.lastBalanceSyncTime = time.Now() // 即使失败也更新时间,避免频繁重试
return
}
// 提取可用余额
var actualBalance float64
if availableBalance, ok := balanceInfo["available_balance"].(float64); ok && availableBalance > 0 {
actualBalance = availableBalance
} else if availableBalance, ok := balanceInfo["availableBalance"].(float64); ok && availableBalance > 0 {
actualBalance = availableBalance
} else if totalBalance, ok := balanceInfo["balance"].(float64); ok && totalBalance > 0 {
actualBalance = totalBalance
} else {
log.Printf("⚠️ [%s] 无法提取可用余额", at.name)
at.lastBalanceSyncTime = time.Now()
return
}
oldBalance := at.initialBalance
changePercent := ((actualBalance - oldBalance) / oldBalance) * 100
// 变化超过5%才更新
if math.Abs(changePercent) > 5.0 {
log.Printf("🔔 [%s] 检测到余额大幅变化: %.2f → %.2f USDT (%.2f%%)",
at.name, oldBalance, actualBalance, changePercent)
// 更新内存中的 initialBalance
at.initialBalance = actualBalance
// 更新数据库
if at.database != nil {
err := at.database.UpdateTraderInitialBalance(at.userID, at.id, actualBalance)
if err != nil {
log.Printf("❌ [%s] 更新数据库失败: %v", at.name, err)
} else {
log.Printf("✅ [%s] 已自动同步余额到数据库", at.name)
}
}
} else {
log.Printf("✓ [%s] 余额变化不大 (%.2f%%),无需更新", at.name, changePercent)
}
at.lastBalanceSyncTime = time.Now()
}
// runCycle 运行一个交易周期使用AI全权决策
func (at *AutoTrader) runCycle() error {
at.callCount++
@@ -329,7 +393,10 @@ func (at *AutoTrader) runCycle() error {
log.Println("📅 日盈亏已重置")
}
// 3. 收集交易上下文
// 3. 自动同步余额每30分钟检查一次充值/提现后自动更新)
at.autoSyncBalanceIfNeeded()
// 4. 收集交易上下文
ctx, err := at.buildTradingContext()
if err != nil {
record.Success = false