From 69327a6668163a985bba22f7dbd0d51da693a987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Fri, 13 Feb 2026 08:27:05 +0800 Subject: [PATCH] 1 --- scripts/clear_cooldown.py | 146 +++++++++++++++++++++++++++++++++ trading_system/risk_manager.py | 7 ++ 2 files changed, 153 insertions(+) create mode 100644 scripts/clear_cooldown.py diff --git a/scripts/clear_cooldown.py b/scripts/clear_cooldown.py new file mode 100644 index 0000000..660bc5d --- /dev/null +++ b/scripts/clear_cooldown.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +清理交易冷却时间脚本 +用于手动重置某个交易对的“连续亏损冷却”状态。 +原理:将该交易对最近一次亏损交易的平仓时间修改为“冷却时间之前”,使其不再触发冷却逻辑。 + +用法: + python scripts/clear_cooldown.py [symbol] + + 例如: + python scripts/clear_cooldown.py BTCUSDT # 清理 BTCUSDT 的冷却 + python scripts/clear_cooldown.py # 列出当前处于冷却中的交易对 +""" +import sys +import os +import time +from datetime import datetime, timedelta +import logging + +# Add backend to path +sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'backend')) + +from database.connection import db +from database.models import Trade, TradingConfig + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +def get_cooldown_config(account_id): + """获取冷却配置""" + try: + enabled = TradingConfig.get_value("SYMBOL_LOSS_COOLDOWN_ENABLED", True, account_id=account_id) + max_losses = int(TradingConfig.get_value("SYMBOL_MAX_CONSECUTIVE_LOSSES", 2, account_id=account_id)) + cooldown_sec = int(TradingConfig.get_value("SYMBOL_LOSS_COOLDOWN_SEC", 3600, account_id=account_id)) + return enabled, max_losses, cooldown_sec + except Exception as e: + logger.error(f"获取配置失败: {e}") + return True, 2, 3600 + +def check_cooldown(symbol, account_id, max_losses, cooldown_sec): + """检查是否处于冷却中""" + recent_trades = Trade.get_all(symbol=symbol, status='closed', account_id=account_id) + # Sort by exit_time desc + recent_trades = sorted( + recent_trades, + key=lambda x: (x.get('exit_time') or x.get('entry_time') or 0), + reverse=True + )[:max_losses + 1] + + if not recent_trades: + return False, None, 0 + + now = int(time.time()) + latest_trade = recent_trades[0] + latest_exit_time = latest_trade.get('exit_time') or latest_trade.get('entry_time') or 0 + + # Check time window + if now - latest_exit_time < cooldown_sec: + consecutive_losses = 0 + for trade in recent_trades: + pnl = float(trade.get('pnl', 0) or 0) + exit_reason = str(trade.get('exit_reason', '') or '').lower() + + # 2026-02-13 优化:手动平仓(manual)不计入连续亏损 + if exit_reason == 'manual' or exit_reason == 'manual_close': + continue + + if pnl < 0: + consecutive_losses += 1 + else: + break + + if consecutive_losses >= max_losses: + remaining = cooldown_sec - (now - latest_exit_time) + return True, latest_trade, remaining + + return False, None, 0 + +def clear_cooldown(symbol, account_id, cooldown_sec): + """清理冷却(修改平仓时间)""" + is_cooling, latest_trade, remaining = check_cooldown( + symbol, account_id, + *get_cooldown_config(account_id)[1:] + ) + + if not is_cooling: + logger.info(f"{symbol} 当前未处于冷却状态。") + return + + logger.info(f"正在清理 {symbol} 的冷却状态...") + logger.info(f" 当前剩余冷却: {remaining} 秒") + logger.info(f" 最近交易ID: {latest_trade['id']}, 平仓时间: {latest_trade.get('exit_time')}") + + # Calculate new exit time: now - cooldown_sec - 60s (buffer) + now = int(time.time()) + new_exit_time = now - cooldown_sec - 60 + + try: + # Update database + sql = "UPDATE trades SET exit_time = %s WHERE id = %s" + db.execute_update(sql, (new_exit_time, latest_trade['id'])) + + logger.info(f"✅ 已成功清理 {symbol} 的冷却状态!") + logger.info(f" 修改交易 #{latest_trade['id']} 平仓时间 -> {new_exit_time} (当前时间-{cooldown_sec+60}s)") + except Exception as e: + logger.error(f"❌ 更新数据库失败: {e}") + +def list_cooldowns(account_id): + """列出所有处于冷却中的交易对""" + logger.info("正在扫描处于冷却中的交易对...") + + enabled, max_losses, cooldown_sec = get_cooldown_config(account_id) + if not enabled: + logger.info("冷却功能未启用 (SYMBOL_LOSS_COOLDOWN_ENABLED=False)") + return + + # Get all distinct symbols traded recently? + # Better: Get all symbols from trades in last cooldown_sec + some buffer + # Simplified: Get distinct symbols from trades + sql = "SELECT symbol FROM trades WHERE account_id = %s AND status = 'closed' GROUP BY symbol ORDER BY MAX(id) DESC LIMIT 100" + rows = db.execute_query(sql, (account_id,)) + symbols = [r['symbol'] for r in rows] + + found = False + for symbol in symbols: + is_cooling, trade, remaining = check_cooldown(symbol, account_id, max_losses, cooldown_sec) + if is_cooling: + found = True + print(f"🔥 {symbol:<10} 冷却剩余: {remaining:>4}s (最近连续亏损)") + + if not found: + logger.info("没有发现处于冷却中的交易对。") + +def main(): + account_id = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1) + + if len(sys.argv) > 1: + symbol = sys.argv[1].upper() + clear_cooldown(symbol, account_id, get_cooldown_config(account_id)[2]) + else: + list_cooldowns(account_id) + print("\n提示: 使用 python scripts/clear_cooldown.py [SYMBOL] 来清理指定币种的冷却。") + +if __name__ == "__main__": + main() diff --git a/trading_system/risk_manager.py b/trading_system/risk_manager.py index e0f3528..b57cc39 100644 --- a/trading_system/risk_manager.py +++ b/trading_system/risk_manager.py @@ -861,6 +861,13 @@ class RiskManager: consecutive_losses = 0 for trade in recent_trades: pnl = float(trade.get('pnl', 0) or 0) + exit_reason = str(trade.get('exit_reason', '') or '').lower() + + # 2026-02-13 优化:手动平仓(manual)通常是人为干预(如发现信号消失或误操作), + # 不应计入策略的连续亏损统计,避免误触发冷却。 + if exit_reason == 'manual' or exit_reason == 'manual_close': + continue + if pnl < 0: # 亏损 consecutive_losses += 1 else: # 盈利,中断连续亏损