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:
shinchan-zhai
2026-03-21 19:59:00 +08:00
parent fd77f2df3e
commit 4e4b4ceed7
2 changed files with 48 additions and 0 deletions
+45
View File
@@ -124,9 +124,20 @@ func (at *AutoTrader) runCycle() error {
}
if err != nil {
at.consecutiveAIFailures++
record.Success = false
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)
if aiDecision != nil {
logger.Info("\n" + strings.Repeat("=", 70) + "\n")
@@ -145,9 +156,27 @@ func (at *AutoTrader) runCycle() error {
}
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)
}
// 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
// logger.Infof("\n" + strings.Repeat("=", 70))
// logger.Infof("📋 System prompt [template: %s]", at.systemPromptTemplate)
@@ -194,6 +223,22 @@ func (at *AutoTrader) runCycle() error {
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
for _, d := range sortedDecisions {
// Check if trader is stopped before each decision (allow immediate stop during execution)