1、新增傅里叶策略
2、新增策略管理、策略重启功能
This commit is contained in:
6
strategy_manager/config/main.json
Normal file
6
strategy_manager/config/main.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"strategy_root": "AUTO_DETECT",
|
||||
"logs_dir": "logs",
|
||||
"status_file": "status.json",
|
||||
"pid_dir": "pids"
|
||||
}
|
||||
287
strategy_manager/core/manager.py
Normal file
287
strategy_manager/core/manager.py
Normal file
@@ -0,0 +1,287 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import psutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List
|
||||
from datetime import datetime
|
||||
|
||||
# ==================== 动态路径配置 ====================
|
||||
from core.path_utils import add_project_root_to_path
|
||||
|
||||
# 添加项目根路径到sys.path
|
||||
PROJECT_ROOT = add_project_root_to_path()
|
||||
|
||||
|
||||
# ==================================================
|
||||
|
||||
class StrategyManager:
|
||||
def __init__(self, config_path: str = "config/main.json"):
|
||||
self.config = self._load_main_config(config_path)
|
||||
self.strategies_dir = Path("strategies")
|
||||
self.logs_dir = Path(self.config["logs_dir"])
|
||||
self.status_file = Path(self.config["status_file"])
|
||||
self.pid_dir = Path(self.config["pid_dir"])
|
||||
|
||||
# 创建目录
|
||||
self.logs_dir.mkdir(exist_ok=True)
|
||||
self.pid_dir.mkdir(exist_ok=True)
|
||||
|
||||
self.strategies: Dict[str, Dict[str, Any]] = {}
|
||||
self.load_strategies()
|
||||
|
||||
def _load_main_config(self, config_path: str) -> Dict[str, Any]:
|
||||
path = Path(config_path)
|
||||
if not path.exists():
|
||||
return {
|
||||
"logs_dir": "logs",
|
||||
"status_file": "status.json",
|
||||
"pid_dir": "pids"
|
||||
}
|
||||
with open(path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
def load_strategies(self):
|
||||
"""递归扫描 strategies/ 目录,查找 .config 文件"""
|
||||
self.strategies = {}
|
||||
if not self.strategies_dir.exists():
|
||||
print("[ERROR] 策略配置目录不存在: {}".format(self.strategies_dir))
|
||||
return
|
||||
|
||||
for config_file in self.strategies_dir.rglob("*.config"):
|
||||
try:
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
required = ['name', 'strategy_class', 'enabled', 'engine_params', 'strategy_params']
|
||||
for field in required:
|
||||
if field not in config:
|
||||
raise ValueError("配置缺少必要字段: {}".format(field))
|
||||
|
||||
relative_path = config_file.relative_to(self.strategies_dir)
|
||||
strategy_name = relative_path.parent.name
|
||||
symbol = config_file.stem
|
||||
strategy_key = "{}_{}".format(strategy_name, symbol)
|
||||
|
||||
self.strategies[strategy_key] = {
|
||||
"strategy_name": strategy_name,
|
||||
"symbol": symbol,
|
||||
"config_file": str(config_file),
|
||||
"config": config,
|
||||
"status": "stopped",
|
||||
"pid": None,
|
||||
"started_at": None,
|
||||
"uptime": None
|
||||
}
|
||||
except Exception as e:
|
||||
print("[ERROR] 加载配置失败 {}: {}".format(config_file, e))
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""获取完整状态"""
|
||||
self._refresh_status()
|
||||
return {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"total": len(self.strategies),
|
||||
"running": sum(1 for s in self.strategies.values() if s["status"] == "running"),
|
||||
"strategies": self.strategies
|
||||
}
|
||||
|
||||
def _refresh_status(self):
|
||||
"""
|
||||
刷新进程状态 - 双重验证
|
||||
1. 检查PID文件是否存在
|
||||
2. 检查进程是否存在
|
||||
3. 验证进程名是否为python(防止PID复用)
|
||||
"""
|
||||
for name, info in self.strategies.items():
|
||||
pid_file = self.pid_dir / "{}.pid".format(name)
|
||||
|
||||
if pid_file.exists():
|
||||
try:
|
||||
with open(pid_file, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
# 双重验证
|
||||
if psutil.pid_exists(pid):
|
||||
try:
|
||||
proc = psutil.Process(pid)
|
||||
# 验证进程名是否包含python
|
||||
if "python" in proc.name().lower():
|
||||
# 验证成功,更新为运行中
|
||||
info["status"] = "running"
|
||||
info["pid"] = pid
|
||||
if info["started_at"]:
|
||||
started = datetime.fromisoformat(info["started_at"])
|
||||
uptime = datetime.now() - started
|
||||
info["uptime"] = str(uptime).split('.')[0]
|
||||
continue # 跳过清理逻辑
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
# 进程已死或无权访问,继续清理
|
||||
pass
|
||||
|
||||
# PID不存在或验证失败,清理
|
||||
self._cleanup_stopped_strategy(name, pid_file)
|
||||
except Exception as e:
|
||||
print("[WARNING] 刷新状态失败 {}: {}".format(name, e))
|
||||
self._cleanup_stopped_strategy(name, pid_file)
|
||||
else:
|
||||
info["status"] = "stopped"
|
||||
info["pid"] = None
|
||||
info["started_at"] = None
|
||||
info["uptime"] = None
|
||||
|
||||
def _is_running(self, name: str) -> bool:
|
||||
"""
|
||||
检查策略是否运行中 - 实时刷新状态
|
||||
确保与status命令结果一致
|
||||
"""
|
||||
# 先刷新状态确保最新
|
||||
self._refresh_status()
|
||||
|
||||
info = self.strategies[name]
|
||||
if not info["pid"]:
|
||||
return False
|
||||
|
||||
pid_file = self.pid_dir / "{}.pid".format(name)
|
||||
if not pid_file.exists():
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(pid_file, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
# 双重验证
|
||||
if psutil.pid_exists(pid):
|
||||
try:
|
||||
proc = psutil.Process(pid)
|
||||
return "python" in proc.name().lower()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
return False
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def stop_strategy(self, name: str, timeout: int = 30) -> bool:
|
||||
"""停止单个策略"""
|
||||
if name not in self.strategies:
|
||||
print("[ERROR] 策略不存在: {}".format(name))
|
||||
return False
|
||||
|
||||
# 再次检查状态(确保最新)
|
||||
if not self._is_running(name):
|
||||
print("[WARNING] 策略未运行: {}".format(name))
|
||||
return False
|
||||
|
||||
info = self.strategies[name]
|
||||
|
||||
try:
|
||||
pid = info["pid"]
|
||||
process = psutil.Process(pid)
|
||||
|
||||
print("\n[INFO] 正在停止: {} (PID: {})...".format(name, pid))
|
||||
|
||||
# 优雅终止
|
||||
process.terminate()
|
||||
|
||||
try:
|
||||
process.wait(timeout=timeout)
|
||||
print("[SUCCESS] 已停止: {}".format(name))
|
||||
except psutil.TimeoutExpired:
|
||||
print("[WARNING] 超时,强制终止: {}".format(name))
|
||||
process.kill()
|
||||
process.wait()
|
||||
|
||||
# 清理状态
|
||||
self._cleanup_stopped_strategy(name, self.pid_dir / "{}.pid".format(name))
|
||||
self._save_status()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print("[ERROR] 停止失败 {}: {}".format(name, e))
|
||||
return False
|
||||
|
||||
def restart_strategy(self, name: str) -> bool:
|
||||
"""重启策略"""
|
||||
print("\n[INFO] 正在重启: {}".format(name))
|
||||
self.stop_strategy(name)
|
||||
time.sleep(2)
|
||||
return self.start_strategy(name)
|
||||
|
||||
def start_all(self):
|
||||
"""启动所有启用的策略"""
|
||||
print("\n" + "=" * 100)
|
||||
print("正在启动所有启用的策略...")
|
||||
print("=" * 100)
|
||||
|
||||
started = []
|
||||
for name, info in self.strategies.items():
|
||||
if info["config"]["enabled"] and not self._is_running(name):
|
||||
if self.start_strategy(name):
|
||||
started.append(name)
|
||||
|
||||
print("\n[SUCCESS] 成功启动 {} 个策略".format(len(started)))
|
||||
if started:
|
||||
print("策略: {}".format(", ".join(started)))
|
||||
|
||||
def stop_all(self):
|
||||
"""停止所有运行的策略"""
|
||||
print("\n" + "=" * 100)
|
||||
print("正在停止所有运行的策略...")
|
||||
print("=" * 100)
|
||||
|
||||
stopped = []
|
||||
for name in self.strategies.keys():
|
||||
if self._is_running(name):
|
||||
if self.stop_strategy(name):
|
||||
stopped.append(name)
|
||||
|
||||
print("\n[SUCCESS] 成功停止 {} 个策略".format(len(stopped)))
|
||||
if stopped:
|
||||
print("策略: {}".format(", ".join(stopped)))
|
||||
|
||||
def _cleanup_stopped_strategy(self, name: str, pid_file: Path):
|
||||
"""清理已停止的策略状态"""
|
||||
pid_file.unlink(missing_ok=True)
|
||||
info = self.strategies[name]
|
||||
info["status"] = "stopped"
|
||||
info["pid"] = None
|
||||
info["started_at"] = None
|
||||
info["uptime"] = None
|
||||
|
||||
def _save_status(self):
|
||||
"""状态持久化"""
|
||||
status = self.get_status()
|
||||
with open(self.status_file, 'w') as f:
|
||||
json.dump(status, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def print_status_table(status: Dict[str, Any]):
|
||||
"""格式化打印状态表格"""
|
||||
print("\n" + "=" * 130)
|
||||
print("策略状态总览 (更新时间: {})".format(status['timestamp']))
|
||||
print("总计: {} | 运行中: {} | 已停止: {}".format(
|
||||
status['total'], status['running'], status['total'] - status['running']
|
||||
))
|
||||
print("=" * 130)
|
||||
|
||||
if not status["strategies"]:
|
||||
print("未找到任何策略")
|
||||
return
|
||||
|
||||
print(
|
||||
"配置标识 策略名称 状态 PID 运行时长 启动时间")
|
||||
print("-" * 130)
|
||||
|
||||
for name, info in status["strategies"].items():
|
||||
status_text = "RUNNING" if info["status"] == "running" else "STOPPED"
|
||||
pid_text = str(info["pid"]) if info["pid"] else "-"
|
||||
uptime_text = info["uptime"] if info["uptime"] else "-"
|
||||
started_text = info["started_at"][:19] if info["started_at"] else "-"
|
||||
|
||||
print("{:<35} {:<40} {:<10} {:<10} {:<15} {:<25}".format(
|
||||
name, info['config']['name'], status_text, pid_text, uptime_text, started_text
|
||||
))
|
||||
|
||||
print("=" * 130)
|
||||
76
strategy_manager/core/path_utils.py
Normal file
76
strategy_manager/core/path_utils.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_project_root():
|
||||
"""
|
||||
获取项目根路径(strategy_manager 的父目录)
|
||||
项目结构:
|
||||
project/
|
||||
├── futures_trading_strategies/
|
||||
└── strategy_manager/
|
||||
"""
|
||||
# strategy_manager/core/path_utils.py -> strategy_manager -> project
|
||||
return Path(__file__).parent.parent.parent
|
||||
|
||||
|
||||
def get_strategy_root(config_path: str = "config/main.json"):
|
||||
"""
|
||||
动态获取策略代码根路径
|
||||
优先级:
|
||||
1. config/main.json 中配置的绝对路径
|
||||
2. config/main.json 中配置的相对路径(相对于项目根)
|
||||
3. AUTO_DETECT: 自动探测项目根目录下的 futures_trading_strategies
|
||||
"""
|
||||
config_file = Path(config_path)
|
||||
strategy_root = None
|
||||
|
||||
# 读取配置
|
||||
if config_file.exists():
|
||||
import json
|
||||
try:
|
||||
with open(config_file, 'r') as f:
|
||||
config = json.load(f)
|
||||
strategy_root = config.get("strategy_root", "AUTO_DETECT")
|
||||
except:
|
||||
strategy_root = "AUTO_DETECT"
|
||||
else:
|
||||
strategy_root = "AUTO_DETECT"
|
||||
|
||||
# 如果是绝对路径,直接返回
|
||||
if strategy_root and Path(strategy_root).is_absolute():
|
||||
return Path(strategy_root)
|
||||
|
||||
# 如果是相对路径,相对于项目根
|
||||
project_root = get_project_root()
|
||||
if strategy_root and strategy_root != "AUTO_DETECT":
|
||||
return project_root / strategy_root
|
||||
|
||||
# AUTO_DETECT模式: 探测项目根目录下的 futures_trading_strategies
|
||||
auto_detect = project_root / "futures_trading_strategies"
|
||||
if auto_detect.exists():
|
||||
return auto_detect
|
||||
|
||||
# 如果失败,抛出错误
|
||||
raise RuntimeError(
|
||||
"无法自动探测策略代码路径,请在 config/main.json 中配置:\n"
|
||||
'"strategy_root": "/path/to/your/futures_trading_strategies"'
|
||||
)
|
||||
|
||||
|
||||
def add_project_root_to_path():
|
||||
"""将项目根路径添加到 sys.path"""
|
||||
project_root = get_project_root()
|
||||
if str(project_root) not in sys.path:
|
||||
sys.path.insert(0, str(project_root))
|
||||
print(f"[INFO] 已添加项目根路径到sys.path: {project_root}")
|
||||
return project_root
|
||||
|
||||
|
||||
def add_strategy_root_to_path():
|
||||
"""将策略根路径添加到 sys.path"""
|
||||
strategy_root = get_strategy_root()
|
||||
if str(strategy_root) not in sys.path:
|
||||
sys.path.insert(0, str(strategy_root))
|
||||
print(f"[INFO] 已添加策略根路径到sys.path: {strategy_root}")
|
||||
return strategy_root
|
||||
92
strategy_manager/launcher.py
Normal file
92
strategy_manager/launcher.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import sys
|
||||
import json
|
||||
import signal
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
|
||||
# ==================== 动态路径配置 ====================
|
||||
from core.path_utils import add_project_root_to_path
|
||||
|
||||
# 添加项目根路径到sys.path
|
||||
PROJECT_ROOT = add_project_root_to_path()
|
||||
print(f"[INFO] 项目根路径: {PROJECT_ROOT}")
|
||||
print(f"[INFO] Python路径: {sys.path[:3]}")
|
||||
|
||||
|
||||
# ====================================================
|
||||
|
||||
def load_strategy_class(class_path: str):
|
||||
"""动态加载策略类"""
|
||||
try:
|
||||
# class_path: "futures_trading_strategies.FG.TrendlineBreakoutStrategy.DualModeTrendlineHawkesStrategy2.DualModeTrendlineHawkesStrategy"
|
||||
module_path, class_name = class_path.rsplit('.', 1)
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, class_name)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] 加载策略类失败 {class_path}: {e}")
|
||||
print(f"[ERROR] 请检查项目根路径是否正确: {PROJECT_ROOT}")
|
||||
print(f"[ERROR] 当前sys.path: {sys.path}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def run_strategy(config_path: str):
|
||||
"""通过配置文件运行策略"""
|
||||
# 1. 加载配置
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
print(f"[INFO] [{config['name']}] 正在启动...")
|
||||
|
||||
# 2. 动态加载策略类
|
||||
strategy_class = load_strategy_class(config["strategy_class"])
|
||||
|
||||
# 3. 创建API
|
||||
from tqsdk import TqApi, TqAuth, TqKq
|
||||
api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
||||
|
||||
# 4. 准备策略参数
|
||||
strategy_params = config["strategy_params"].copy()
|
||||
strategy_params["main_symbol"] = config["engine_params"]["symbol"].split(".")[-1]
|
||||
|
||||
# 5. 创建引擎
|
||||
from src.tqsdk_real_engine import TqsdkEngine
|
||||
|
||||
engine = TqsdkEngine(
|
||||
strategy_class=strategy_class,
|
||||
strategy_params=strategy_params,
|
||||
api=api,
|
||||
symbol=config["engine_params"]["symbol"],
|
||||
duration_seconds=config["engine_params"]["duration_seconds"],
|
||||
roll_over_mode=config["engine_params"]["roll_over_mode"],
|
||||
history_length=config["engine_params"]["history_length"],
|
||||
close_bar_delta=timedelta(**config["engine_params"]["close_bar_delta"])
|
||||
)
|
||||
|
||||
# 6. 信号处理
|
||||
def signal_handler(sig, frame):
|
||||
print(f"\n[INFO] [{config['name']}] 收到停止信号 {sig},正在关闭...")
|
||||
api.close()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
# 7. 运行
|
||||
try:
|
||||
print(f"[INFO] [{config['name']}] 开始运行")
|
||||
engine.run()
|
||||
except Exception as e:
|
||||
print(f"[ERROR] [{config['name']}] 运行出错: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
api.close()
|
||||
print(f"[INFO] [{config['name']}] 已停止")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3 or sys.argv[1] != "--config":
|
||||
print("使用方法: python launcher.py --config <config_file>")
|
||||
sys.exit(1)
|
||||
|
||||
run_strategy(sys.argv[2])
|
||||
205
strategy_manager/restart_daemon.py
Normal file
205
strategy_manager/restart_daemon.py
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
极简重启守护进程 - 不持有策略状态,只监控和重启
|
||||
目标:崩溃后不影响策略,重启后可无缝接管
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import psutil
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import threading
|
||||
import subprocess
|
||||
|
||||
|
||||
class RestartDaemon:
|
||||
"""重启守护进程 - 定时重启策略子进程"""
|
||||
|
||||
# 每日重启时间点
|
||||
RESTART_TIMES = ["08:50", "20:50"]
|
||||
|
||||
def __init__(self, pid_dir="pids", log_dir="logs"):
|
||||
self.pid_dir = Path(pid_dir)
|
||||
self.log_dir = Path(log_dir)
|
||||
self.logger = self._setup_logger()
|
||||
self.running = False
|
||||
self.thread = None
|
||||
|
||||
# 确保目录存在
|
||||
self.pid_dir.mkdir(exist_ok=True)
|
||||
|
||||
def _setup_logger(self):
|
||||
"""配置日志"""
|
||||
self.log_dir.mkdir(exist_ok=True)
|
||||
log_file = self.log_dir / "restart_daemon.log"
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(log_file, encoding='utf-8'),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
return logging.getLogger("RestartDaemon")
|
||||
|
||||
def start(self):
|
||||
"""启动守护进程"""
|
||||
if self.running:
|
||||
self.logger.warning("⚠️ 守护进程已在运行")
|
||||
return
|
||||
|
||||
self.running = True
|
||||
self.thread = threading.Thread(target=self._check_loop, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
self.logger.info("=" * 80)
|
||||
self.logger.info("✅ 重启守护进程已启动")
|
||||
self.logger.info("⏰ 监控时间点: {}".format(", ".join(self.RESTART_TIMES)))
|
||||
self.logger.info("📂 PID目录: {}".format(self.pid_dir.absolute()))
|
||||
self.logger.info("=" * 80)
|
||||
|
||||
# 主线程阻塞(保持进程运行)
|
||||
try:
|
||||
self.logger.info("📌 进程已常驻,按 Ctrl+C 退出...")
|
||||
while self.running:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
self.logger.info("\n⏹️ 收到退出信号,正在停止...")
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
"""停止守护进程"""
|
||||
self.running = False
|
||||
if self.thread:
|
||||
self.thread.join(timeout=5)
|
||||
self.logger.info("✅ 重启守护进程已停止")
|
||||
|
||||
def _check_loop(self):
|
||||
"""每分钟检查一次重启时间"""
|
||||
last_restart_date = {t: None for t in self.RESTART_TIMES}
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
now = datetime.now()
|
||||
current_time = now.strftime("%H:%M")
|
||||
current_date = now.date()
|
||||
|
||||
# 检查是否到达重启时间点
|
||||
if current_time in self.RESTART_TIMES:
|
||||
# 防重复:检查今天是否已执行
|
||||
if last_restart_date[current_time] != current_date:
|
||||
last_restart_date[current_time] = current_date
|
||||
self._perform_restart(current_time)
|
||||
|
||||
time.sleep(60) # 每分钟检查一次
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error("❌ 检查循环出错: {}".format(e))
|
||||
self.logger.error("=" * 80)
|
||||
time.sleep(60) # 出错后等待1分钟继续
|
||||
|
||||
def _perform_restart(self, time_point: str):
|
||||
"""执行重启"""
|
||||
self.logger.info("\n" + "=" * 80)
|
||||
self.logger.info("⏰ 到达重启时间: {}".format(time_point))
|
||||
self.logger.info("=" * 80)
|
||||
|
||||
# 1. 扫描所有PID文件
|
||||
pid_files = list(self.pid_dir.glob("*.pid"))
|
||||
if not pid_files:
|
||||
self.logger.info("⚠️ 未发现运行中的策略")
|
||||
return
|
||||
|
||||
self.logger.info("📋 发现 {} 个策略需要重启".format(len(pid_files)))
|
||||
|
||||
# 2. 停止所有策略
|
||||
stopped_count = 0
|
||||
for pid_file in pid_files:
|
||||
try:
|
||||
with open(pid_file, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
if psutil.pid_exists(pid):
|
||||
proc = psutil.Process(pid)
|
||||
self.logger.info("⏹️ 停止策略 PID {}: {}".format(pid, proc.name()))
|
||||
proc.terminate()
|
||||
|
||||
try:
|
||||
proc.wait(timeout=30)
|
||||
self.logger.info("✅ 已优雅停止 PID {}".format(pid))
|
||||
stopped_count += 1
|
||||
except psutil.TimeoutExpired:
|
||||
proc.kill()
|
||||
self.logger.info("🔥 强制终止 PID {}".format(pid))
|
||||
stopped_count += 1
|
||||
else:
|
||||
self.logger.warning("⚠️ PID文件存在但进程已死: {}".format(pid))
|
||||
except Exception as e:
|
||||
self.logger.error("❌ 停止失败 {}: {}".format(pid_file, e))
|
||||
|
||||
if stopped_count == 0:
|
||||
self.logger.warning("⚠️ 未成功停止任何策略")
|
||||
return
|
||||
|
||||
# 3. 等待资源释放
|
||||
self.logger.info("\n⏳ 等待2秒资源释放...")
|
||||
time.sleep(2)
|
||||
|
||||
# 4. 重新启动策略
|
||||
self.logger.info("\n🚀 重新启动所有策略...")
|
||||
restarted_count = 0
|
||||
for pid_file in pid_files:
|
||||
try:
|
||||
# 从PID文件名推导配置路径
|
||||
# DualModeTrendlineHawkesStrategy2_FG.pid -> strategies/DualModeTrendlineHawkesStrategy2/FG.config
|
||||
name = pid_file.stem
|
||||
if '_' not in name:
|
||||
self.logger.error("❌ PID文件名格式错误: {}".format(name))
|
||||
continue
|
||||
|
||||
strategy_name, symbol = name.split('_', 1)
|
||||
config_file = Path("strategies") / strategy_name / "{}.config".format(symbol)
|
||||
|
||||
if not config_file.exists():
|
||||
self.logger.error("❌ 配置文件不存在: {}".format(config_file))
|
||||
continue
|
||||
|
||||
# 启动新进程(不阻塞,立即返回)
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, "launcher.py", "--config", str(config_file)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=Path.cwd()
|
||||
)
|
||||
|
||||
self.logger.info("✅ 启动新进程 PID {}: {}".format(process.pid, config_file.name))
|
||||
restarted_count += 1
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error("❌ 启动失败: {}".format(e))
|
||||
|
||||
# 5. 统计结果
|
||||
self.logger.info("\n" + "=" * 80)
|
||||
self.logger.info("📊 重启统计:")
|
||||
self.logger.info(" 停止成功: {}个".format(stopped_count))
|
||||
self.logger.info(" 启动成功: {}个".format(restarted_count))
|
||||
|
||||
if stopped_count == restarted_count and stopped_count > 0:
|
||||
self.logger.info("✅ 所有策略重启成功")
|
||||
else:
|
||||
self.logger.warning("⚠️ 部分策略重启失败")
|
||||
|
||||
self.logger.info("=" * 80)
|
||||
|
||||
|
||||
def main():
|
||||
"""主入口"""
|
||||
daemon = RestartDaemon()
|
||||
daemon.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
81
strategy_manager/start.py
Normal file
81
strategy_manager/start.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from core.manager import StrategyManager, print_status_table
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="策略管理系统 - 批量管理你的交易策略",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
# 查看所有策略状态
|
||||
python start.py status
|
||||
|
||||
# 启动所有策略
|
||||
python start.py start --all
|
||||
|
||||
# 启动单个策略(使用标识符)
|
||||
python start.py start -n DualModeTrendlineHawkesStrategy2_FG
|
||||
|
||||
# 停止单个策略
|
||||
python start.py stop -n DualModeTrendlineHawkesStrategy2_FG
|
||||
|
||||
# 重启策略
|
||||
python start.py restart -n DualModeTrendlineHawkesStrategy2_FG
|
||||
|
||||
# 查看日志(最近50行)
|
||||
python start.py logs -n DualModeTrendlineHawkesStrategy2_FG -t 50
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("action", choices=["start", "stop", "restart", "status", "logs"])
|
||||
parser.add_argument("-n", "--name", help="策略标识符(策略名_品种)")
|
||||
parser.add_argument("-a", "--all", action="store_true", help="对所有策略执行操作")
|
||||
parser.add_argument("-c", "--config", default="config/main.json", help="主配置文件路径")
|
||||
parser.add_argument("-t", "--tail", type=int, default=30, help="查看日志末尾行数")
|
||||
|
||||
args = parser.parse_args()
|
||||
manager = StrategyManager(args.config)
|
||||
|
||||
if args.action == "status":
|
||||
status = manager.get_status()
|
||||
print_status_table(status)
|
||||
|
||||
elif args.action in ["start", "stop"]:
|
||||
if args.all:
|
||||
manager.start_all() if args.action == "start" else manager.stop_all()
|
||||
elif args.name:
|
||||
manager.start_strategy(args.name) if args.action == "start" else manager.stop_strategy(args.name)
|
||||
else:
|
||||
print("❌ 错误: 请指定策略名称 (-n) 或使用 --all")
|
||||
sys.exit(1)
|
||||
|
||||
elif args.action == "restart":
|
||||
if not args.name:
|
||||
print("❌ 错误: 重启操作必须指定策略名称 (-n)")
|
||||
sys.exit(1)
|
||||
manager.restart_strategy(args.name)
|
||||
|
||||
elif args.action == "logs":
|
||||
if not args.name:
|
||||
print("❌ 错误: 查看日志必须指定策略名称 (-n)")
|
||||
sys.exit(1)
|
||||
|
||||
log_file = Path("logs") / f"{args.name}.log"
|
||||
if not log_file.exists():
|
||||
print(f"⚠️ 日志文件不存在: {log_file}")
|
||||
return
|
||||
|
||||
print(f"\n📄 {args.name} 的日志 (最近{args.tail}行):")
|
||||
print("=" * 80)
|
||||
with open(log_file, 'r') as f:
|
||||
lines = f.readlines()[-args.tail:]
|
||||
for line in lines:
|
||||
print(line.rstrip())
|
||||
print("=" * 80)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
21
strategy_manager/strategies/TestConnectionStrategy/FG.config
Normal file
21
strategy_manager/strategies/TestConnectionStrategy/FG.config
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "test策略",
|
||||
"version": "1.0",
|
||||
"enabled": true,
|
||||
|
||||
"strategy_class": "futures_trading_strategies.FG.TrendlineBreakoutStrategy.DualModeTrendlineHawkesStrategy2.DualModeTrendlineHawkesStrategy",
|
||||
|
||||
"engine_params": {
|
||||
"symbol": "KQ.m@CZCE.FG",
|
||||
"duration_seconds": 900,
|
||||
"roll_over_mode": true,
|
||||
"history_length": 1000,
|
||||
"close_bar_delta": {"minutes": 58}
|
||||
},
|
||||
|
||||
"strategy_params": {
|
||||
"main_symbol": "FG",
|
||||
"trade_volume": 2,
|
||||
"enable_log": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user