mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
feat: port NOFXi agent module onto latest dev base (#1485)
* feat: integrate NOFXi agent into dev * Enhance NOFXi agent workflow and diagnostics
This commit is contained in:
+59
-18
@@ -24,6 +24,31 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (at *AutoTrader) logTag() string {
|
||||
if at == nil {
|
||||
return "[trader_id=unknown]"
|
||||
}
|
||||
if at.name != "" {
|
||||
return fmt.Sprintf("[trader_id=%s trader_name=%s]", at.id, at.name)
|
||||
}
|
||||
return fmt.Sprintf("[trader_id=%s]", at.id)
|
||||
}
|
||||
|
||||
func (at *AutoTrader) logInfof(format string, args ...interface{}) {
|
||||
values := append([]interface{}{at.logTag()}, args...)
|
||||
logger.Infof("%s "+format, values...)
|
||||
}
|
||||
|
||||
func (at *AutoTrader) logWarnf(format string, args ...interface{}) {
|
||||
values := append([]interface{}{at.logTag()}, args...)
|
||||
logger.Warnf("%s "+format, values...)
|
||||
}
|
||||
|
||||
func (at *AutoTrader) logErrorf(format string, args ...interface{}) {
|
||||
values := append([]interface{}{at.logTag()}, args...)
|
||||
logger.Errorf("%s "+format, values...)
|
||||
}
|
||||
|
||||
// AutoTraderConfig auto trading configuration (simplified version - AI makes all decisions)
|
||||
type AutoTraderConfig struct {
|
||||
// Trader identification
|
||||
@@ -381,8 +406,8 @@ func (at *AutoTrader) Run() error {
|
||||
at.startTime = time.Now()
|
||||
|
||||
logger.Info("🚀 AI-driven automatic trading system started")
|
||||
logger.Infof("💰 Initial balance: %.2f USDT", at.initialBalance)
|
||||
logger.Infof("⚙️ Scan interval: %v", at.config.ScanInterval)
|
||||
at.logInfof("💰 Initial balance: %.2f USDT", at.initialBalance)
|
||||
at.logInfof("⚙️ Scan interval: %v", at.config.ScanInterval)
|
||||
logger.Info("🤖 AI will make full decisions on leverage, position size, stop loss/take profit, etc.")
|
||||
|
||||
// Pre-launch checks for claw402 users
|
||||
@@ -397,7 +422,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "lighter" {
|
||||
if lighterTrader, ok := at.trader.(*lighter.LighterTraderV2); ok && at.store != nil {
|
||||
lighterTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] Lighter order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 Lighter order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +430,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "hyperliquid" {
|
||||
if hyperliquidTrader, ok := at.trader.(*hyperliquid.HyperliquidTrader); ok && at.store != nil {
|
||||
hyperliquidTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] Hyperliquid order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 Hyperliquid order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,7 +438,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "bybit" {
|
||||
if bybitTrader, ok := at.trader.(*bybit.BybitTrader); ok && at.store != nil {
|
||||
bybitTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] Bybit order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 Bybit order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +446,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "okx" {
|
||||
if okxTrader, ok := at.trader.(*okx.OKXTrader); ok && at.store != nil {
|
||||
okxTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] OKX order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 OKX order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +454,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "bitget" {
|
||||
if bitgetTrader, ok := at.trader.(*bitget.BitgetTrader); ok && at.store != nil {
|
||||
bitgetTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] Bitget order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 Bitget order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +462,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "aster" {
|
||||
if asterTrader, ok := at.trader.(*aster.AsterTrader); ok && at.store != nil {
|
||||
asterTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] Aster order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 Aster order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,7 +470,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "binance" {
|
||||
if binanceTrader, ok := at.trader.(*binance.FuturesTrader); ok && at.store != nil {
|
||||
binanceTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] Binance order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 Binance order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +478,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "gate" {
|
||||
if gateTrader, ok := at.trader.(*gate.GateTrader); ok && at.store != nil {
|
||||
gateTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] Gate order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 Gate order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +486,7 @@ func (at *AutoTrader) Run() error {
|
||||
if at.exchange == "kucoin" {
|
||||
if kucoinTrader, ok := at.trader.(*kucoin.KuCoinTrader); ok && at.store != nil {
|
||||
kucoinTrader.StartOrderSync(at.id, at.exchangeID, at.exchange, at.store, 30*time.Second)
|
||||
logger.Infof("🔄 [%s] KuCoin order+position sync enabled (every 30s)", at.name)
|
||||
at.logInfof("🔄 KuCoin order+position sync enabled (every 30s)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,9 +496,9 @@ func (at *AutoTrader) Run() error {
|
||||
// Check if this is a grid trading strategy
|
||||
isGridStrategy := at.IsGridStrategy()
|
||||
if isGridStrategy {
|
||||
logger.Infof("🔲 [%s] Grid trading strategy detected, initializing grid...", at.name)
|
||||
at.logInfof("🔲 Grid trading strategy detected, initializing grid...")
|
||||
if err := at.InitializeGrid(); err != nil {
|
||||
logger.Errorf("❌ [%s] Failed to initialize grid: %v", at.name, err)
|
||||
at.logErrorf("❌ Failed to initialize grid: %v", err)
|
||||
return fmt.Errorf("grid initialization failed: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -481,11 +506,11 @@ func (at *AutoTrader) Run() error {
|
||||
// Execute immediately on first run
|
||||
if isGridStrategy {
|
||||
if err := at.RunGridCycle(); err != nil {
|
||||
logger.Infof("❌ Grid execution failed: %v", err)
|
||||
at.logErrorf("❌ Grid execution failed: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := at.runCycle(); err != nil {
|
||||
logger.Infof("❌ Execution failed: %v", err)
|
||||
at.logErrorf("❌ Execution failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,15 +527,15 @@ func (at *AutoTrader) Run() error {
|
||||
case <-ticker.C:
|
||||
if isGridStrategy {
|
||||
if err := at.RunGridCycle(); err != nil {
|
||||
logger.Infof("❌ Grid execution failed: %v", err)
|
||||
at.logErrorf("❌ Grid execution failed: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := at.runCycle(); err != nil {
|
||||
logger.Infof("❌ Execution failed: %v", err)
|
||||
at.logErrorf("❌ Execution failed: %v", err)
|
||||
}
|
||||
}
|
||||
case <-at.stopMonitorCh:
|
||||
logger.Infof("[%s] ⏹ Stop signal received, exiting automatic trading main loop", at.name)
|
||||
at.logInfof("⏹ Stop signal received, exiting automatic trading main loop")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -590,6 +615,22 @@ func (at *AutoTrader) GetSystemPromptTemplate() string {
|
||||
return "strategy"
|
||||
}
|
||||
|
||||
// GetCandidateCoins returns the current candidate coin set from the trader's strategy engine.
|
||||
func (at *AutoTrader) GetCandidateCoins() ([]kernel.CandidateCoin, error) {
|
||||
if at.strategyEngine == nil {
|
||||
return nil, fmt.Errorf("strategy engine not configured")
|
||||
}
|
||||
return at.strategyEngine.GetCandidateCoins()
|
||||
}
|
||||
|
||||
// GetStrategyConfig returns the current strategy config used by the trader.
|
||||
func (at *AutoTrader) GetStrategyConfig() *store.StrategyConfig {
|
||||
if at.strategyEngine == nil {
|
||||
return at.config.StrategyConfig
|
||||
}
|
||||
return at.strategyEngine.GetConfig()
|
||||
}
|
||||
|
||||
// GetStore gets data store (for external access to decision records, etc.)
|
||||
func (at *AutoTrader) GetStore() *store.Store {
|
||||
return at.store
|
||||
|
||||
+30
-30
@@ -24,7 +24,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
running := at.isRunning
|
||||
at.isRunningMutex.RUnlock()
|
||||
if !running {
|
||||
logger.Infof("⏹ Trader is stopped, aborting cycle #%d", at.callCount)
|
||||
at.logInfof("⏹ Trader is stopped, aborting cycle #%d", at.callCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
// 1. Check if trading needs to be stopped
|
||||
if time.Now().Before(at.stopUntil) {
|
||||
remaining := at.stopUntil.Sub(time.Now())
|
||||
logger.Infof("⏸ Risk control: Trading paused, remaining %.0f minutes", remaining.Minutes())
|
||||
at.logWarnf("⏸ Risk control: Trading paused, remaining %.0f minutes", remaining.Minutes())
|
||||
record.Success = false
|
||||
record.ErrorMessage = fmt.Sprintf("Risk control paused, remaining %.0f minutes", remaining.Minutes())
|
||||
at.saveDecision(record)
|
||||
@@ -59,6 +59,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
// 4. Collect trading context
|
||||
ctx, err := at.buildTradingContext()
|
||||
if err != nil {
|
||||
at.logErrorf("failed to build trading context: %v", err)
|
||||
record.Success = false
|
||||
record.ErrorMessage = fmt.Sprintf("Failed to build trading context: %v", err)
|
||||
at.saveDecision(record)
|
||||
@@ -71,7 +72,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
|
||||
// If no candidate coins available, log but do not error
|
||||
if len(ctx.CandidateCoins) == 0 {
|
||||
logger.Infof("ℹ️ No candidate coins available, skipping this cycle")
|
||||
at.logInfof("ℹ️ No candidate coins available, skipping this cycle")
|
||||
record.Success = true // Not an error, just no candidate coins
|
||||
record.ExecutionLog = append(record.ExecutionLog, "No candidate coins available, cycle skipped")
|
||||
record.AccountState = store.AccountSnapshot{
|
||||
@@ -90,16 +91,16 @@ func (at *AutoTrader) runCycle() error {
|
||||
record.CandidateCoins = append(record.CandidateCoins, coin.Symbol)
|
||||
}
|
||||
|
||||
logger.Infof("📊 Account equity: %.2f USDT | Available: %.2f USDT | Positions: %d",
|
||||
at.logInfof("📊 Account equity: %.2f USDT | Available: %.2f USDT | Positions: %d",
|
||||
ctx.Account.TotalEquity, ctx.Account.AvailableBalance, ctx.Account.PositionCount)
|
||||
|
||||
// 5. Use strategy engine to call AI for decision
|
||||
logger.Infof("🤖 Requesting AI analysis and decision... [Strategy Engine]")
|
||||
at.logInfof("🤖 Requesting AI analysis and decision... [Strategy Engine]")
|
||||
aiDecision, err := kernel.GetFullDecisionWithStrategy(ctx, at.mcpClient, at.strategyEngine, "balanced")
|
||||
|
||||
if aiDecision != nil && aiDecision.AIRequestDurationMs > 0 {
|
||||
record.AIRequestDurationMs = aiDecision.AIRequestDurationMs
|
||||
logger.Infof("⏱️ AI call duration: %.2f seconds", float64(record.AIRequestDurationMs)/1000)
|
||||
at.logInfof("⏱️ AI call duration: %.2f seconds", float64(record.AIRequestDurationMs)/1000)
|
||||
record.ExecutionLog = append(record.ExecutionLog,
|
||||
fmt.Sprintf("AI call duration: %d ms", record.AIRequestDurationMs))
|
||||
}
|
||||
@@ -119,7 +120,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
// Record AI charge (track cost regardless of decision outcome)
|
||||
if aiDecision != nil && at.store != nil {
|
||||
if chargeErr := at.store.AICharge().Record(at.id, at.aiModel, at.config.AIModel); chargeErr != nil {
|
||||
logger.Warnf("⚠️ Failed to record AI charge: %v", chargeErr)
|
||||
at.logWarnf("⚠️ Failed to record AI charge: %v", chargeErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +133,9 @@ func (at *AutoTrader) runCycle() error {
|
||||
if at.consecutiveAIFailures >= 3 && !at.safeMode {
|
||||
at.safeMode = true
|
||||
at.safeModeReason = fmt.Sprintf("AI failed %d consecutive times: %v", at.consecutiveAIFailures, err)
|
||||
logger.Errorf("🛡️ [%s] SAFE MODE ACTIVATED — AI failed %d times in a row. No new positions will be opened. Existing positions are protected with current stop-loss settings.",
|
||||
at.name, at.consecutiveAIFailures)
|
||||
logger.Errorf("🛡️ [%s] Reason: %v", at.name, err)
|
||||
logger.Errorf("🛡️ [%s] Action: Will keep trying AI each cycle. Safe mode auto-deactivates when AI recovers.", at.name)
|
||||
at.logErrorf("🛡️ SAFE MODE ACTIVATED — AI failed %d times in a row. No new positions will be opened. Existing positions are protected with current stop-loss settings.", at.consecutiveAIFailures)
|
||||
at.logErrorf("🛡️ Reason: %v", err)
|
||||
at.logErrorf("🛡️ Action: Will keep trying AI each cycle. Safe mode auto-deactivates when AI recovers.")
|
||||
}
|
||||
|
||||
// Print system prompt and AI chain of thought (output even with errors for debugging)
|
||||
@@ -159,7 +159,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
|
||||
// In safe mode, don't return error — keep the loop running to retry next cycle
|
||||
if at.safeMode {
|
||||
logger.Warnf("🛡️ [%s] Safe mode: skipping this cycle, will retry in %v", at.name, at.config.ScanInterval)
|
||||
at.logWarnf("🛡️ Safe mode: skipping this cycle, will retry in %v", at.config.ScanInterval)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -168,11 +168,11 @@ func (at *AutoTrader) runCycle() error {
|
||||
|
||||
// AI succeeded — reset failure counter and deactivate safe mode
|
||||
if at.consecutiveAIFailures > 0 {
|
||||
logger.Infof("✅ [%s] AI recovered after %d consecutive failures", at.name, at.consecutiveAIFailures)
|
||||
at.logInfof("✅ AI recovered after %d consecutive failures", at.consecutiveAIFailures)
|
||||
}
|
||||
at.consecutiveAIFailures = 0
|
||||
if at.safeMode {
|
||||
logger.Infof("🛡️ [%s] SAFE MODE DEACTIVATED — AI is working again. Resuming normal trading.", at.name)
|
||||
at.logInfof("🛡️ SAFE MODE DEACTIVATED — AI is working again. Resuming normal trading.")
|
||||
at.safeMode = false
|
||||
at.safeModeReason = ""
|
||||
}
|
||||
@@ -219,7 +219,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
running = at.isRunning
|
||||
at.isRunningMutex.RUnlock()
|
||||
if !running {
|
||||
logger.Infof("⏹ Trader stopped before decision execution, aborting cycle #%d", at.callCount)
|
||||
at.logInfof("⏹ Trader stopped before decision execution, aborting cycle #%d", at.callCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -228,14 +228,14 @@ func (at *AutoTrader) runCycle() error {
|
||||
filtered := make([]kernel.Decision, 0)
|
||||
for _, d := range sortedDecisions {
|
||||
if d.Action == "open_long" || d.Action == "open_short" {
|
||||
logger.Warnf("🛡️ [%s] Safe mode: BLOCKED %s %s (no new positions allowed)", at.name, d.Action, d.Symbol)
|
||||
at.logWarnf("🛡️ Safe mode: BLOCKED %s %s (no new positions allowed)", d.Action, d.Symbol)
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, d)
|
||||
}
|
||||
sortedDecisions = filtered
|
||||
if len(sortedDecisions) == 0 {
|
||||
logger.Infof("🛡️ [%s] Safe mode: all decisions were open positions, nothing to execute", at.name)
|
||||
at.logInfof("🛡️ Safe mode: all decisions were open positions, nothing to execute")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
running = at.isRunning
|
||||
at.isRunningMutex.RUnlock()
|
||||
if !running {
|
||||
logger.Infof("⏹ Trader stopped during decision execution, aborting remaining decisions")
|
||||
at.logInfof("⏹ Trader stopped during decision execution, aborting remaining decisions")
|
||||
break
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
}
|
||||
|
||||
if err := at.executeDecisionWithRecord(&d, &actionRecord); err != nil {
|
||||
logger.Infof("❌ Failed to execute decision (%s %s): %v", d.Symbol, d.Action, err)
|
||||
at.logErrorf("❌ Failed to execute decision (%s %s): %v", d.Symbol, d.Action, err)
|
||||
actionRecord.Error = err.Error()
|
||||
record.ExecutionLog = append(record.ExecutionLog, fmt.Sprintf("❌ %s %s failed: %v", d.Symbol, d.Action, err))
|
||||
} else {
|
||||
@@ -280,7 +280,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
|
||||
// 9. Save decision record
|
||||
if err := at.saveDecision(record); err != nil {
|
||||
logger.Infof("⚠ Failed to save decision record: %v", err)
|
||||
at.logWarnf("⚠ Failed to save decision record: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -417,12 +417,12 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
||||
// 3. Use strategy engine to get candidate coins (must have strategy engine)
|
||||
var candidateCoins []kernel.CandidateCoin
|
||||
if at.strategyEngine == nil {
|
||||
logger.Infof("⚠️ [%s] No strategy engine configured, skipping candidate coins", at.name)
|
||||
at.logWarnf("⚠️ No strategy engine configured, skipping candidate coins")
|
||||
} else {
|
||||
coins, err := at.strategyEngine.GetCandidateCoins()
|
||||
if err != nil {
|
||||
// Log warning but don't fail - equity snapshot should still be saved
|
||||
logger.Infof("⚠️ [%s] Failed to get candidate coins: %v (will use empty list)", at.name, err)
|
||||
at.logWarnf("⚠️ Failed to get candidate coins: %v (will use empty list)", err)
|
||||
} else {
|
||||
candidateCoins = coins
|
||||
logger.Infof("📋 [%s] Strategy engine fetched candidate coins: %d", at.name, len(candidateCoins))
|
||||
@@ -473,7 +473,7 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
||||
// Get recent 10 closed trades for AI context
|
||||
recentTrades, err := at.store.Position().GetRecentTrades(at.id, 10)
|
||||
if err != nil {
|
||||
logger.Infof("⚠️ [%s] Failed to get recent trades: %v", at.name, err)
|
||||
at.logWarnf("⚠️ Failed to get recent trades: %v", err)
|
||||
} else {
|
||||
logger.Infof("📊 [%s] Found %d recent closed trades for AI context", at.name, len(recentTrades))
|
||||
for _, trade := range recentTrades {
|
||||
@@ -503,11 +503,11 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
||||
// Get trading statistics for AI context
|
||||
stats, err := at.store.Position().GetFullStats(at.id)
|
||||
if err != nil {
|
||||
logger.Infof("⚠️ [%s] Failed to get trading stats: %v", at.name, err)
|
||||
at.logWarnf("⚠️ Failed to get trading stats: %v", err)
|
||||
} else if stats == nil {
|
||||
logger.Infof("⚠️ [%s] GetFullStats returned nil", at.name)
|
||||
at.logWarnf("⚠️ GetFullStats returned nil")
|
||||
} else if stats.TotalTrades == 0 {
|
||||
logger.Infof("⚠️ [%s] GetFullStats returned 0 trades (traderID=%s)", at.name, at.id)
|
||||
at.logWarnf("⚠️ GetFullStats returned 0 trades")
|
||||
} else {
|
||||
ctx.TradingStats = &kernel.TradingStats{
|
||||
TotalTrades: stats.TotalTrades,
|
||||
@@ -523,7 +523,7 @@ func (at *AutoTrader) buildTradingContext() (*kernel.Context, error) {
|
||||
at.name, stats.TotalTrades, stats.WinRate, stats.ProfitFactor, stats.SharpeRatio, stats.MaxDrawdownPct)
|
||||
}
|
||||
} else {
|
||||
logger.Infof("⚠️ [%s] Store is nil, cannot get recent trades", at.name)
|
||||
at.logWarnf("⚠️ Store is nil, cannot get recent trades")
|
||||
}
|
||||
|
||||
// 8. Get quantitative data (if enabled in strategy config)
|
||||
@@ -630,15 +630,15 @@ func (at *AutoTrader) checkClaw402Balance() {
|
||||
if at.claw402WalletAddr != "" {
|
||||
balance, err := wallet.QueryUSDCBalance(at.claw402WalletAddr)
|
||||
if err != nil {
|
||||
logger.Warnf("⚠️ [%s] Failed to query USDC balance: %v", at.name, err)
|
||||
at.logWarnf("⚠️ Failed to query USDC balance: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if balance < 1.0 {
|
||||
logger.Warnf("⚠️ [%s] Low USDC balance: $%.2f — AI may stop soon!", at.name, balance)
|
||||
at.logWarnf("⚠️ Low USDC balance: $%.2f — AI may stop soon!", balance)
|
||||
}
|
||||
if balance <= 0 {
|
||||
logger.Errorf("🚨 [%s] USDC balance is ZERO — AI calls will fail!", at.name)
|
||||
at.logErrorf("🚨 USDC balance is ZERO — AI calls will fail!")
|
||||
}
|
||||
|
||||
runway := float64(0)
|
||||
|
||||
Reference in New Issue
Block a user