mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-17 09:24:36 +08:00
feat: safe mode — auto-protect positions when AI fails 3+ times
- Track consecutive AI failures
- After 3 failures: activate safe mode (no new positions, close/hold only)
- Auto-deactivate when AI recovers
- Keep loop running in safe mode (retry each cycle)
- Log clearly: 🛡️ SAFE MODE ACTIVATED/DEACTIVATED
This commit is contained in:
@@ -148,6 +148,9 @@ type AutoTrader struct {
|
|||||||
userID string // User ID
|
userID string // User ID
|
||||||
gridState *GridState // Grid trading state (only used when StrategyType == "grid_trading")
|
gridState *GridState // Grid trading state (only used when StrategyType == "grid_trading")
|
||||||
claw402WalletAddr string // Claw402 wallet address (derived from private key at start)
|
claw402WalletAddr string // Claw402 wallet address (derived from private key at start)
|
||||||
|
consecutiveAIFailures int // Consecutive AI call failures
|
||||||
|
safeMode bool // Safe mode: no new positions, protect existing ones
|
||||||
|
safeModeReason string // Why safe mode was activated
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAutoTrader creates an automatic trader
|
// NewAutoTrader creates an automatic trader
|
||||||
|
|||||||
@@ -124,9 +124,20 @@ func (at *AutoTrader) runCycle() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
at.consecutiveAIFailures++
|
||||||
record.Success = false
|
record.Success = false
|
||||||
record.ErrorMessage = fmt.Sprintf("Failed to get AI decision: %v", err)
|
record.ErrorMessage = fmt.Sprintf("Failed to get AI decision: %v", err)
|
||||||
|
|
||||||
|
// Activate safe mode after 3 consecutive failures
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// Print system prompt and AI chain of thought (output even with errors for debugging)
|
// Print system prompt and AI chain of thought (output even with errors for debugging)
|
||||||
if aiDecision != nil {
|
if aiDecision != nil {
|
||||||
logger.Info("\n" + strings.Repeat("=", 70) + "\n")
|
logger.Info("\n" + strings.Repeat("=", 70) + "\n")
|
||||||
@@ -145,9 +156,27 @@ func (at *AutoTrader) runCycle() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
at.saveDecision(record)
|
at.saveDecision(record)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Errorf("failed to get AI decision: %w", err)
|
return fmt.Errorf("failed to get AI decision: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.consecutiveAIFailures = 0
|
||||||
|
if at.safeMode {
|
||||||
|
logger.Infof("🛡️ [%s] SAFE MODE DEACTIVATED — AI is working again. Resuming normal trading.", at.name)
|
||||||
|
at.safeMode = false
|
||||||
|
at.safeModeReason = ""
|
||||||
|
}
|
||||||
|
|
||||||
// // 5. Print system prompt
|
// // 5. Print system prompt
|
||||||
// logger.Infof("\n" + strings.Repeat("=", 70))
|
// logger.Infof("\n" + strings.Repeat("=", 70))
|
||||||
// logger.Infof("📋 System prompt [template: %s]", at.systemPromptTemplate)
|
// logger.Infof("📋 System prompt [template: %s]", at.systemPromptTemplate)
|
||||||
@@ -194,6 +223,22 @@ func (at *AutoTrader) runCycle() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safe mode: filter out open positions, only allow close/hold
|
||||||
|
if at.safeMode {
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Execute decisions and record results
|
// Execute decisions and record results
|
||||||
for _, d := range sortedDecisions {
|
for _, d := range sortedDecisions {
|
||||||
// Check if trader is stopped before each decision (allow immediate stop during execution)
|
// Check if trader is stopped before each decision (allow immediate stop during execution)
|
||||||
|
|||||||
Reference in New Issue
Block a user