1
This commit is contained in:
parent
a03bb0e8f3
commit
69327a6668
146
scripts/clear_cooldown.py
Normal file
146
scripts/clear_cooldown.py
Normal file
|
|
@ -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()
|
||||||
|
|
@ -861,6 +861,13 @@ class RiskManager:
|
||||||
consecutive_losses = 0
|
consecutive_losses = 0
|
||||||
for trade in recent_trades:
|
for trade in recent_trades:
|
||||||
pnl = float(trade.get('pnl', 0) or 0)
|
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: # 亏损
|
if pnl < 0: # 亏损
|
||||||
consecutive_losses += 1
|
consecutive_losses += 1
|
||||||
else: # 盈利,中断连续亏损
|
else: # 盈利,中断连续亏损
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user