feat: 适应度门槛95+swing_point策略+多项改进

- 新增适应度门槛: min_backtest_fitness=95, 适应度<95暂停开仓
- 新增 SwingPointRetest 策略替代 Turtle
- 新增 monday_reset.py 周重置脚本
- exit_rules: 拖尾止损相对回撤模式
- market_state: 趋势检测优化
- position: 一票制并发锁+合约规格缓存
- optimize: Optuna 替代 DEAP 遗传算法
- realtime_trader: 适应度门槛+同向递增
- weights: 动态权重管理
- cron_optimize: PYTHONPATH 修复
- .gitignore: 排除生成文件
This commit is contained in:
silencesdg
2026-05-21 20:26:55 +08:00
parent 218efef538
commit e1691c3c41
14 changed files with 699 additions and 449 deletions
+4 -3
View File
@@ -1,5 +1,6 @@
#!/bin/bash
# cron 环境修复:设置正确的 PYTHONPATH 后运行优化器
export PYTHONPATH="/home/songkl/.hermes/profiles/bot1/home/.local/lib/python3.13/site-packages:$PYTHONPATH"
# 每日自动优化 + 重启 EA
# 设置 PYTHONPATH 确保 cron 环境下能找到 user-site packages(如 deap, moocore 等)
export PYTHONPATH="$HOME/.local/lib/python$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')/site-packages:$PYTHONPATH"
cd /home/songkl/mt5_python_ea_suite
exec /usr/bin/python3 scripts/daily_optimize.py "$@"
exec python3 scripts/daily_optimize.py "$@"
+39 -23
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""每日自动优化 — 遗传算法跑参数 → 写入config → 重启实盘EA
"""每日自动优化 — 遗传算法跑参数 → 写入config → EA热加载生效(无需重启)
通过 cron 调用: python3 scripts/daily_optimize.py
"""
@@ -22,12 +22,12 @@ RESTART_SIGNAL = PROJECT_DIR / ".restart_signal"
def run_optimizer():
"""运行遗传算法优化,返回 best_params dict"""
"""运行遗传算法优化,返回 (best_params dict, fitness)"""
from execution.optimize import run_optimizer as _run
logger.info("🧬 开始遗传算法优化...")
best_params, fitness = _run()
logger.info(f"✅ 优化完成 适应度={fitness:.2f}")
return best_params
return best_params, fitness
def backup_config():
@@ -39,18 +39,13 @@ def backup_config():
logger.info(f"📦 已备份配置: {dst}")
def update_config(best_params: dict):
def update_config(best_params: dict, fitness: float = 0.0):
"""将优化结果写回 config.py"""
content = CONFIG_PATH.read_text(encoding="utf-8")
# ═══ RISK_CONFIG ═══
# ═══ RISK_CONFIG — 手动设定,不进优化器 ═══
# optimizer.py PARAM_SPACE 已移除风控基因,此 map 清空)
risk_map = {
"stop_loss_pct": "stop_loss_pct",
"profit_retracement_pct": "profit_retracement_pct",
"min_profit_for_trailing": "min_profit_for_trailing",
"take_profit_pct": "take_profit_pct",
"max_holding_minutes": "max_holding_minutes",
"min_profit_for_time_exit": "min_profit_for_time_exit",
}
for opt_key, cfg_key in risk_map.items():
if opt_key in best_params:
@@ -115,8 +110,11 @@ def update_config(best_params: dict):
"momentum_breakout_momentum_period": ("momentum_breakout", "momentum_period"),
# KDJStrategy
"kdj_period": ("kdj", "period"),
# TurtleStrategy
"turtle_period": ("turtle", "period"),
# SwingPointRetestStrategy
"swing_point_left_bars": ("swing_point", "left_bars"),
"swing_point_right_bars": ("swing_point", "right_bars"),
"swing_point_tolerance_pct": ("swing_point", "tolerance_pct"),
"swing_point_num_swings": ("swing_point", "num_swings"),
# DailyBreakoutStrategy
"daily_breakout_bars_count": ("daily_breakout", "bars_count"),
# WaveTheoryStrategy
@@ -151,7 +149,7 @@ def update_config(best_params: dict):
"weight_MomentumBreakoutStrategy": "momentum_breakout",
"weight_MACDStrategy": "macd",
"weight_KDJStrategy": "kdj",
"weight_TurtleStrategy": "turtle",
"weight_SwingPointRetestStrategy": "swing_point",
"weight_DailyBreakoutStrategy": "daily_breakout",
"weight_WaveTheoryStrategy": "wave_theory",
}
@@ -213,6 +211,13 @@ def update_config(best_params: dict):
content
)
# ═══ LAST_OPTIMIZATION_FITNESS ═══
content = re.sub(
r'LAST_OPTIMIZATION_FITNESS\s*=\s*[\d.\-e]+',
f'LAST_OPTIMIZATION_FITNESS = {fitness:.2f}',
content
)
CONFIG_PATH.write_text(content, encoding="utf-8")
logger.info("✏️ 配置已更新")
@@ -222,11 +227,24 @@ def restart_ea():
import subprocess
logger.info("🔄 重启 EA...")
# 杀旧进程
# 杀旧进程,等锁释放再启动新的
subprocess.run(["pkill", "-f", "python.*run/realtime.py"], capture_output=True)
import time; time.sleep(2)
subprocess.run(["pkill", "-9", "-f", "python.*run/realtime.py"], capture_output=True)
time.sleep(1)
# 确认旧进程已死 + 锁已释放(轮询最多等 5 秒)
lock_file = PROJECT_DIR / ".ea.lock"
import fcntl as _fcntl
for _ in range(10):
time.sleep(0.5)
try:
fd = os.open(str(lock_file), os.O_RDONLY)
_fcntl.flock(fd, _fcntl.LOCK_EX | _fcntl.LOCK_NB)
os.close(fd) # 立即释放,只是测试
break
except (BlockingIOError, OSError):
pass
else:
logger.warning("⚠️ 旧进程锁未释放,强制启动(旧进程可能僵死)")
# 清空旧交易记录
for f in PROJECT_DIR.glob("realtime_trades_*"):
@@ -259,20 +277,18 @@ def main():
# 2. 运行优化
try:
best_params = run_optimizer()
best_params, fitness = run_optimizer()
except Exception as e:
logger.error(f"优化失败: {e}")
import traceback
traceback.print_exc()
return 1
# 3. 写入 config.py
update_config(best_params)
# 3. 写入 config.py(含适应度)
update_config(best_params, fitness)
# 4. 触发重启
restart_ea()
logger.info("✅ 每日优化流程完成")
# 4. ★ EA 通过 config.reload() 自动热加载,无需重启
logger.info("✅ 每日优化流程完成 (EA 将在下个周期自动读取新配置)")
return 0
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""开市后全平持仓 + 清峰值 + 重启 EA"""
import requests, json, time, sys, os, subprocess
API = "http://192.168.1.5:5555/api"
SYMBOL = "XAUUSDz"
PROJECT_DIR = "/home/songkl/mt5_python_ea_suite"
def log(msg):
print(f"[{time.strftime('%H:%M:%S')}] {msg}")
# 1. 等市场开市(重试最多30次,间隔10秒)
log("等待市场开市...")
for i in range(30):
try:
r = requests.get(f"{API}/account", timeout=5)
if r.status_code == 200:
acct = r.json()
if acct.get("trade_allowed"):
log("✅ 市场已开市")
break
except:
pass
time.sleep(10)
else:
log("❌ 等待超时,市场未开市")
sys.exit(1)
# 2. 获取所有持仓并平仓
log("获取持仓...")
r = requests.get(f"{API}/positions/{SYMBOL}")
positions = r.json().get("positions", [])
log(f"{len(positions)}")
total_pnl = 0
for p in positions:
resp = requests.post(f"{API}/close",
json={"ticket": p["ticket"], "symbol": SYMBOL, "volume": p["volume"]})
ok = resp.json().get("success", False)
total_pnl += p["profit"]
log(f"{'' if ok else ''} {p['ticket']} ${p['profit']:+.2f}")
log(f"总盈亏: ${total_pnl:+.2f}")
# 3. 清峰值数据
peak_file = os.path.join(PROJECT_DIR, "position_peaks.json")
if os.path.exists(peak_file):
os.remove(peak_file)
log("✅ 峰值数据已清除")
# 4. 杀掉旧 EA,启动新 EA
subprocess.run(["pkill", "-9", "-f", "python.*run/realtime.py"], capture_output=True)
time.sleep(2)
log_file = os.path.join(PROJECT_DIR, "logs", "strategy.log")
with open(log_file, "a") as f:
proc = subprocess.Popen(
[sys.executable, "run/realtime.py"],
cwd=PROJECT_DIR,
stdout=f, stderr=subprocess.STDOUT,
)
log(f"✅ EA 已重启 PID={proc.pid}")