diff --git a/.kilocode/rules/formatting.md b/.kilocode/rules/formatting.md new file mode 100644 index 0000000..93a66f8 --- /dev/null +++ b/.kilocode/rules/formatting.md @@ -0,0 +1,96 @@ +# 代码格式规则 (Formatting Rules) + +本项目遵循以下代码格式规范: + +## 1. Python 代码规范 + +### 1.1 缩进 +- 使用 **4个空格** 进行缩进 +- 不使用 Tab 键 + +### 1.2 行长度 +- 每行代码最大长度:**100 字符** +- 文档字符串最大长度:**80 字符** + +### 1.3 空行 +- 类定义之间:**2个空行** +- 方法定义之间:**1个空行** +- 函数定义之间:**2个空行** +- 逻辑段落之间:**1个空行** + +### 1.4 导入顺序 +```python +# 标准库导入 +import os +import sys +from datetime import datetime + +# 第三方库导入 +import numpy as np +import pandas as pd + +# 本地项目导入 +from src.backtest_engine import BacktestEngine +from src.data_manager import DataManager +``` + +### 1.5 空格使用 +- 在逗号后添加空格:`func(a, b, c)` +- 在运算符两侧添加空格:`a + b` +- 不要在括号内添加空格:`func(a)` 而不是 `func( a )` +- 函数参数列表内不要有多余空格 + +## 2. 命名规范 + +### 2.1 变量命名 +- 使用 **小写字母 + 下划线**:`trade_volume`, `stop_loss_points` +- 私有变量使用前缀下划线:`_internal_cache` + +### 2.2 常量命名 +- 使用 **全大写 + 下划线**:`MAX_POSITION`, `DEFAULT_STOP_LOSS` + +### 2.3 函数命名 +- 使用 **小写字母 + 下划线**:`calculate_metrics()`, `get_bar_history()` + +### 2.4 类命名 +- 使用 **大驼峰命名法 (PascalCase)**:`SimpleLimitBuyStrategy`, `BacktestEngine` + +## 3. 文档字符串规范 + +### 3.1 所有公共类和方法必须有文档字符串 +```python +class SimpleLimitBuyStrategy(Strategy): + """ + 一个基于当前K线Open、前1根和前7根K线Range计算优势价格进行限价买入的策略。 + + 具备以下特点: + - 每根K线开始时取消上一根K线未成交的订单 + - 最多只能有一个开仓挂单和一个持仓 + - 包含简单的止损和止盈逻辑 + + Args: + context: 模拟器实例 + symbol: 交易合约代码 + trade_volume: 单笔交易量 + """ +``` + +### 3.2 复杂逻辑必须添加注释说明 + +## 4. 类型提示规范 + +- 推荐为函数参数和返回值添加类型提示 +- 使用 `from typing` 导入类型 +- 循环导入时使用 `TYPE_CHECKING`: +```python +from typing import Dict, Any, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from src.backtest_engine import BacktestContext +``` + +## 5. Git 提交规范 + +- 提交信息使用中文或英文 +- 格式:`[类型] 描述` +- 类型:feat, fix, docs, style, refactor, test, chore \ No newline at end of file diff --git a/.kilocode/rules/naming_conventions.md b/.kilocode/rules/naming_conventions.md new file mode 100644 index 0000000..9dfb834 --- /dev/null +++ b/.kilocode/rules/naming_conventions.md @@ -0,0 +1,129 @@ +# 命名约定规则 (Naming Conventions) + +本项目定义了一套统一的命名约定,以确保代码库的一致性和可读性。 + +## 1. 文件命名规范 + +### 1.1 Python 文件 +- 使用 **小写字母 + 下划线**:`backtest_engine.py`, `data_manager.py` +- 避免使用连字符或空格 +- 测试文件以 `test_` 前缀开头:`test_backtest_engine.py` + +### 1.2 配置文件 +- 使用小写字母和下划线:`config.json`, `strategy_params.yaml` +- 环境配置文件使用 `.env` 或 `.env.{environment}` 格式 + +### 1.3 数据文件 +- 遵循 `{数据类型}_{日期范围}.{扩展名}` 格式 +- 示例:`btc_ohlcv_2023_2024.csv`, `strategy_results_202401.json` + +## 2. 变量命名规范 + +### 2.1 普通变量 +- 使用 **小写字母 + 下划线**(snake_case) +- 使用描述性名称,避免缩写 +- ✅ 正确示例:`current_price`, `trade_volume`, `stop_loss_price` +- ❌ 错误示例:`cp`, `tv`, `sl` + +### 2.2 布尔变量 +- 使用 `is_`, `has_`, `are_` 等前缀 +- ✅ 正确示例:`is_valid`, `has_position`, `are_orders_filled` +- ❌ 错误示例:`valid`, `position`, `filled` + +### 2.3 私有变量 +- 使用单下划线前缀(约定俗成的私有) +- ✅ 正确示例:`_internal_cache`, `_last_order_id` +- ❌ 错误示例:`__private_var`(除非确实需要 name mangling) + +### 2.4 临时变量 +- 循环变量可使用单字母或简短名称 +- ✅ 正确示例:`for i in range(n):`, `for bar in bars:` + +## 3. 常量命名规范 + +### 3.1 全局常量 +- 使用 **全大写字母 + 下划线**(SCREAMING_SNAKE_CASE) +- 定义在文件顶部或单独的 `constants.py` 模块中 +- ✅ 正确示例:`MAX_POSITION = 5`, `DEFAULT_COMMISSION_RATE = 0.0003` +- ❌ 错误示例:`max_position`, `default_commission_rate` + +### 3.2 配置常量 +- 使用全大写字母和下划线 +- ✅ 正确示例:`SYMBOL_FUTURES_SUFFIX = ".FG"` + +## 4. 函数命名规范 + +### 4.1 普通函数 +- 使用 **小写字母 + 下划线**(snake_case) +- 函数名应清晰表达其功能 +- ✅ 正确示例:`calculate_metrics()`, `get_bar_history()`, `send_order()` +- ❌ 错误示例:`calc()`, `getData()`, `send()` + +### 4.2 返回布尔值的函数 +- 使用 `is_`, `has_`, `are_` 等前缀 +- ✅ 正确示例:`is_trending_up()`, `has_active_orders()`, `are_positions_valid()` + +### 4.3 私有函数 +- 使用单下划线前缀 +- ✅ 正确示例:`_validate_params()`, `_calculate_pnl()` + +## 5. 类命名规范 + +### 5.1 公共类 +- 使用 **大驼峰命名法**(PascalCase) +- ✅ 正确示例:`BacktestEngine`, `SimpleLimitBuyStrategy`, `DataManager` +- ❌ 错误示例:`backtest_engine`, `simple_limit_buy_strategy` + +### 5.2 抽象基类 +- 使用 `ABC` 后缀或 `Base` 前缀 +- ✅ 正确示例:`Strategy(ABC)`, `BaseIndicator` + +### 5.3 异常类 +- 使用 `Error` 或 `Exception` 后缀 +- ✅ 正确示例:`BacktestError`, `OrderExecutionException` + +## 6. 特定领域命名规范 + +### 6.1 交易相关 +- 方向:`BUY`, `SELL`, `CLOSE_LONG`, `CLOSE_SHORT` +- 订单类型:`LIMIT`, `MARKET`, `STOP` +- 持仓:`LONG`, `SHORT`, `FLAT` + +### 6.2 策略参数 +- 使用描述性名称,包含参数含义 +- ✅ 正确示例:`stop_loss_points`, `take_profit_points`, `range_factor` +- ❌ 错误示例:`sl`, `tp`, `rf` + +### 6.3 K线数据 +- 时间周期:`1m`, `5m`, `15m`, `1h`, `4h`, `1d` +- OHLCV:`open`, `high`, `low`, `close`, `volume` + +## 7. 数据库/缓存命名规范 + +### 7.1 缓存键 +- 使用冒号分隔的层次结构 +- ✅ 正确示例:`strategy:rb:positions`, `market:btc:price` + +### 7.2 日志文件 +- 使用 `{策略名}/{品种}.log` 格式 +- ✅ 正确示例:`logs/SpectralTrendStrategy/rb.log` + +## 8. 命名一致性检查清单 + +在提交代码前,请确认以下检查项: + +- [ ] 所有变量名使用 snake_case +- [ ] 所有类名使用 PascalCase +- [ ] 所有常量使用 SCREAMING_SNAKE_CASE +- [ ] 函数名清晰表达功能 +- [ ] 命名具有描述性,避免模糊缩写 +- [ ] 私有成员使用下划线前缀 +- [ ] 布尔变量使用适当的前缀 + +## 9. 命名反模式(应避免) + +- ❌ 使用单个字母 `l`, `O`, `I` 作为变量名(容易与数字 1, 0 混淆) +- ❌ 使用魔法数字或字符串(应定义为常量) +- ❌ 使用不一致的命名风格混合 +- ❌ 使用过于通用的名称如 `data`, `info`, `temp` +- ❌ 使用项目保留名称如 `strategy`, `engine`, `manager` \ No newline at end of file diff --git a/.kilocode/rules/restricted_files.md b/.kilocode/rules/restricted_files.md new file mode 100644 index 0000000..66ca8e2 --- /dev/null +++ b/.kilocode/rules/restricted_files.md @@ -0,0 +1,126 @@ +# 受限文件规则 (Restricted Files Rules) + +本项目定义了以下受限文件和目录,这些内容 **禁止访问、不允许访问、更不允许修改**。 + +## 1. 受限目录列表 + +### 1.1 策略目录 - `futures_trading_strategies/` +**状态:** 🚫 **完全禁止访问** +- 目录路径:`futures_trading_strategies/` +- 说明:包含所有期货交易策略的历史版本和实验代码 +- 禁止操作: + - ❌ 读取文件内容 + - ❌ 修改文件内容 + - ❌ 删除文件 + - ❌ 创建新文件 + - ❌ 查看目录结构 + +### 1.2 策略源文件目录 - `src/strategies/` +**状态:** 🚫 **完全禁止访问** +- 目录路径:`src/strategies/` +- 说明:包含所有核心策略实现文件 +- 包含内容: + - `base_strategy.py` - 策略基类 + - `SimpleLimitBuyStrategy.py` - 简单限价买入策略 + - `OpenTwoFactorStrategy.py` - 双因子开仓策略 + - `OpenTwoFactorStrategyDouble.py` - 双因子开仓策略(双倍版本) + - `smc_pure_h1_long_strategy.py` - SMC H1 长线策略 + - `utils.py` - 工具函数 + - 以及其他所有策略文件和子目录 +- 禁止操作: + - ❌ 读取文件内容 + - ❌ 修改文件内容 + - ❌ 删除文件 + - ❌ 创建新文件 + - ❌ 查看目录结构 + +### 1.3 策略管理目录 - `strategy_manager/` +**状态:** ⚠️ **部分禁止访问** +- 目录路径:`strategy_manager/` +- 说明:包含策略管理和运行的核心组件 +- **子目录 `strategy_manager/strategies/` 完全禁止访问** 🚫 +- 其他文件和目录可以正常访问 + +**策略配置子目录(禁止访问):** +- `strategy_manager/strategies/` - 策略配置文件目录 + - ❌ 读取文件内容 + - ❌ 修改文件内容 + - ❌ 删除文件 + - ❌ 创建新文件 + - ❌ 查看目录结构 + +**其他可访问的目录和文件:** +- `strategy_manager/launcher.py` - 策略启动器 ✅ +- `strategy_manager/start.py` - 启动脚本 ✅ +- `strategy_manager/restart_daemon.py` - 重启守护进程 ✅ +- `strategy_manager/web_backend.py` - Web后端 ✅ +- `strategy_manager/status.json` - 状态文件 ✅ +- `strategy_manager/config/` - 配置目录 ✅ +- `strategy_manager/core/` - 核心模块 ✅ +- `strategy_manager/logs/` - 日志目录 ✅ +- `strategy_manager/pids/` - PID目录 ✅ + +## 2. 特殊文件保护 + +### 2.1 策略核心文件 +以下文件受到特别保护: +- `src/strategies/base_strategy.py` - 策略基类,包含邮件通知等核心功能 +- `src/strategies/utils.py` - 策略工具函数 +- `strategy_manager/launcher.py` - 策略启动器 +- `strategy_manager/web_backend.py` - Web后端服务 + +### 2.2 配置文件保护 +- `strategy_manager/config/` 目录下的所有配置文件 +- `strategy_manager/status.json` - 策略状态文件 + +## 3. 违规处理 + +### 3.1 自动拒绝 +系统将自动拒绝任何对受限目录和文件的访问尝试,包括: +- 读取操作 (read_file) +- 写入操作 (write_to_file, edit_file) +- 搜索操作 (search_files) +- 列表操作 (list_files) +- 删除操作 (delete_file) + +**重要说明:** 即使在对话中用户主动提供了受限文件的内容,AI 助手也不应该读取、分析或引用这些内容。所有受限文件的内容都应被视为不可访问的机密信息。 + +### 3.2 错误提示 +当尝试访问受限资源时,系统将返回明确的错误信息: +``` +错误:尝试访问受限资源 [文件/目录路径] +原因:此文件/目录被标记为受保护 +建议:请勿尝试访问或修改这些核心策略文件 +``` + +## 4. 例外情况 + +以下情况需要特殊授权: +- 紧急修复生产环境问题 +- 安全漏洞修补 +- 性能优化(需要详细审查) + +**注意:** 即使在例外情况下,也必须先获得项目管理员的明确授权。 + +## 5. 规则说明 + +这些限制规则的目的是: +1. **保护策略资产**:防止核心交易策略被意外或恶意修改 +2. **确保系统稳定性**:避免因修改关键文件导致系统崩溃 +3. **维护代码质量**:确保只有经过充分测试的代码才能部署 +4. **分离关注点**:开发工作与生产环境严格分离 + +**警告:** 违反这些规则可能导致: +- 策略失效 +- 资金损失 +- 系统故障 +- 项目责任追究 + + +# Restricted files + +Files in the list contain sensitive data, they MUST NOT be read + +- strategy_manager/strategies/ +- futures_trading_strategies/ +- src/strategies/ \ No newline at end of file diff --git a/ma_channel_event_probability.png b/ma_channel_event_probability.png deleted file mode 100644 index d7c3664..0000000 Binary files a/ma_channel_event_probability.png and /dev/null differ diff --git a/plans/strategy_manager_optimization_plan.md b/plans/strategy_manager_optimization_plan.md new file mode 100644 index 0000000..255f265 --- /dev/null +++ b/plans/strategy_manager_optimization_plan.md @@ -0,0 +1,493 @@ +# 策略管理优化方案 + +## 1. 需求概述 + +### 1.1 问题背景 +- 策略启动后如果出现异常会停止进程 +- 需要机制确保重要策略能够自动重启 + +### 1.2 优化目标 +1. **白名单机制**: 本地文件保存希望自动启动的策略列表 +2. **Web管理**: 界面可以管理白名单策略 +3. **定时启动**: 每个工作日 8:58 自动尝试启动白名单中未运行的策略 +4. **单日单次**: 每天只尝试一次,避免重复尝试 +5. **手动控制**: 手动停止后不自动重启,需要用户重新操作 +6. **双状态展示**: 每个策略显示"是否在白名单"和"是否运行中" + +--- + +## 2. 系统架构 + +### 2.1 新增/修改文件 + +| 文件 | 操作 | 说明 | +|------|------|------| +| `config/whitelist.json` | 新增 | 白名单配置文件 | +| `core/whitelist_manager.py` | 新增 | 白名单管理器 | +| `core/manager.py` | 修改 | 集成白名单功能 | +| `web_backend.py` | 修改 | 新增白名单管理 API | +| `frontend/` | 修改 | 前端界面添加白名单管理 | + +### 2.2 核心数据结构 + +#### 白名单配置 (`config/whitelist.json`) +```json +{ + "version": "1.0", + "last_auto_start_date": "2024-01-25", + "strategies": { + "DualModeTrendlineHawkesStrategy2_FG": { + "enabled": true, + "added_at": "2024-01-25T10:00:00", + "added_by": "web" + }, + "SpectralTrendStrategy_rb": { + "enabled": true, + "added_at": "2024-01-25T10:00:00", + "added_by": "web" + } + } +} +``` + +#### 策略状态结构扩展 +```python +# 在 StrategyManager.strategies 字典中新增字段 +{ + "strategy_key": { + # ... 现有字段 ... + "in_whitelist": True, # 是否在白名单中 + "whitelist_enabled": True, # 白名单中是否启用 + "last_auto_start_attempt": "2024-01-25T08:58:00", # 上次自动启动尝试 + "auto_start_success": False # 上次自动启动是否成功 + } +} +``` + +--- + +## 3. 详细设计 + +### 3.1 白名单管理器 (`core/whitelist_manager.py`) + +```python +class WhitelistManager: + """白名单管理器""" + + def __init__(self, config_path: str = "config/whitelist.json"): + self.config_path = Path(config_path) + self.data = self._load() + + def _load(self) -> Dict: + """加载白名单配置""" + if not self.config_path.exists(): + return {"version": "1.0", "strategies": {}} + with open(self.config_path, 'r') as f: + return json.load(f) + + def _save(self): + """保存白名单配置""" + self.config_path.parent.mkdir(exist_ok=True) + with open(self.config_path, 'w') as f: + json.dump(self.data, f, indent=2, ensure_ascii=False) + + def add(self, strategy_key: str, enabled: bool = True) -> bool: + """添加策略到白名单""" + if strategy_key in self.data["strategies"]: + return False # 已存在 + self.data["strategies"][strategy_key] = { + "enabled": enabled, + "added_at": datetime.now().isoformat(), + "added_by": "web" + } + self._save() + return True + + def remove(self, strategy_key: str) -> bool: + """从白名单移除策略""" + if strategy_key not in self.data["strategies"]: + return False + del self.data["strategies"][strategy_key] + self._save() + return True + + def set_enabled(self, strategy_key: str, enabled: bool) -> bool: + """设置策略在白名单中的启用状态""" + if strategy_key not in self.data["strategies"]: + return False + self.data["strategies"][strategy_key]["enabled"] = enabled + self._save() + return True + + def get_all(self) -> Dict[str, Dict]: + """获取所有白名单策略""" + return self.data.get("strategies", {}) + + def is_in_whitelist(self, strategy_key: str) -> bool: + """检查策略是否在白名单中""" + return strategy_key in self.data.get("strategies", {}) + + def is_enabled_in_whitelist(self, strategy_key: str) -> bool: + """检查策略是否在白名单中且已启用""" + if strategy_key not in self.data.get("strategies", {}): + return False + return self.data["strategies"][strategy_key].get("enabled", False) + + def update_last_auto_start_date(self, date_str: str): + """更新最后自动启动日期""" + self.data["last_auto_start_date"] = date_str + self._save() + + def should_auto_start_today(self) -> bool: + """检查今天是否应该自动启动""" + today = datetime.now().date().isoformat() + return self.data.get("last_auto_start_date") != today +``` + +### 3.2 修改 `core/manager.py` + +#### 新增功能 +```python +class StrategyManager: + def __init__(self, config_path: str = "config/main.json"): + # ... 现有代码 ... + self.whitelist_manager = WhitelistManager() + + def get_status(self) -> Dict[str, Any]: + """获取完整状态(扩展白名单信息)""" + status = super().get_status() + + # 添加白名单信息 + for name, info in status["strategies"].items(): + info["in_whitelist"] = self.whitelist_manager.is_in_whitelist(name) + info["whitelist_enabled"] = self.whitelist_manager.is_enabled_in_whitelist(name) + + status["whitelist_auto_start_today"] = self.whitelist_manager.should_auto_start_today() + + return status + + def add_to_whitelist(self, name: str) -> bool: + """添加策略到白名单""" + return self.whitelist_manager.add(name, enabled=True) + + def remove_from_whitelist(self, name: str) -> bool: + """从白名单移除策略""" + return self.whitelist_manager.remove(name) + + def set_whitelist_enabled(self, name: str, enabled: bool) -> bool: + """设置白名单中策略的启用状态""" + return self.whitelist_manager.set_enabled(name, enabled) + + def start_strategy(self, name: str, auto_start: bool = False) -> bool: + """ + 启动策略 + + Args: + name: 策略标识符 + auto_start: 是否为自动启动(自动启动不会更新最后尝试日期) + """ + # ... 现有启动逻辑 ... + + # 如果是手动启动,清除自动启动相关的标记 + if not auto_start: + if name in self.strategies: + self.strategies[name]["last_auto_start_attempt"] = None + self.strategies[name]["auto_start_success"] = None + + return success + + def auto_start_whitelist_strategies(self) -> Dict[str, bool]: + """ + 自动启动白名单中所有未运行的策略 + 一天只执行一次 + + Returns: + Dict[str, bool]: 每个策略的启动结果 + """ + if not self.whitelist_manager.should_auto_start_today(): + return {} + + results = {} + whitelist = self.whitelist_manager.get_all() + + for name, config in whitelist.items(): + if not config.get("enabled", True): + continue + + if name not in self.strategies: + continue + + # 检查是否已在运行 + if self._is_running(name): + results[name] = True + continue + + # 尝试启动 + success = self.start_strategy(name, auto_start=True) + results[name] = success + + # 更新策略状态 + if name in self.strategies: + self.strategies[name]["last_auto_start_attempt"] = datetime.now().isoformat() + self.strategies[name]["auto_start_success"] = success + + # 更新日期 + self.whitelist_manager.update_last_auto_start_date( + datetime.now().date().isoformat() + ) + + return results +``` + +### 3.3 修改 `web_backend.py` + +#### 新增 API 端点 +```python +# ============ 白名单管理 API ============ + +@app.get("/api/whitelist") +def get_whitelist(): + """获取白名单列表""" + whitelist = manager.whitelist_manager.get_all() + return { + "whitelist": whitelist, + "auto_start_today": manager.whitelist_manager.should_auto_start_today() + } + +@app.post("/api/whitelist/{name}/add") +def add_to_whitelist(name: str): + """添加策略到白名单""" + if manager.add_to_whitelist(name): + return {"success": True} + raise HTTPException(400, "添加失败,策略可能已存在") + +@app.post("/api/whitelist/{name}/remove") +def remove_from_whitelist(name: str): + """从白名单移除策略""" + if manager.remove_from_whitelist(name): + return {"success": True} + raise HTTPException(400, "移除失败,策略可能不在白名单中") + +@app.post("/api/whitelist/{name}/enable") +def enable_in_whitelist(name: str): + """启用白名单中的策略""" + if manager.set_whitelist_enabled(name, True): + return {"success": True} + raise HTTPException(400, "操作失败") + +@app.post("/api/whitelist/{name}/disable") +def disable_in_whitelist(name: str): + """禁用白名单中的策略""" + if manager.set_whitelist_enabled(name, False): + return {"success": True} + raise HTTPException(400, "操作失败") + +@app.post("/api/whitelist/auto-start") +def trigger_auto_start(): + """手动触发白名单自动启动(用于测试)""" + results = manager.auto_start_whitelist_strategies() + return { + "success": True, + "results": results, + "count": len(results) + } + +# ============ 修改现有的状态 API ============ + +@app.get("/api/status") +def get_status(): + """获取策略状态(包含白名单信息)""" + status_data = manager.get_status() + status_data['git_info'] = get_git_commit_info() + return status_data +``` + +#### 修改定时任务 +```python +@app.on_event("startup") +async def start_scheduler(): + # ... 现有的 08:58, 20:58 重启任务 ... + + # 新增:白名单自动启动任务(仅 08:58) + scheduler.add_job( + auto_start_whitelist_task, + CronTrigger(hour=8, minute=58), + id="whitelist_auto_start", + replace_existing=True + ) + logger.info("📅 白名单自动启动任务已添加 (计划时间: 08:58)") + + +def auto_start_whitelist_task(): + """ + 白名单自动启动任务 + """ + logger.info("⏰ [白名单任务] 触发自动启动...") + + results = manager.auto_start_whitelist_strategies() + + if not results: + logger.info("⏰ [白名单任务] 今天已执行过或无需启动") + return + + success_count = sum(1 for v in results.values() if v) + fail_count = len(results) - success_count + + logger.info(f"⏰ [白名单任务] 完成: 成功 {success_count}, 失败 {fail_count}") + + for name, success in results.items(): + if success: + logger.info(f"✅ [白名单任务] {name} 启动成功") + else: + logger.warning(f"❌ [白名单任务] {name} 启动失败") +``` + +### 3.4 修改 `start.py` + +#### 新增 CLI 命令 +```python +def main(): + # ... 现有的参数解析 ... + + parser.add_argument("--whitelist", action="store_true", help="白名单操作模式") + + # 白名单子命令 + whitelist_parser = subparsers.add_parser("whitelist", help="白名单管理") + whitelist_parser.add_argument("action", choices=["add", "remove", "list", "enable", "disable"]) + whitelist_parser.add_argument("-n", "--name", help="策略标识符") +``` + +### 3.5 前端界面修改 + +#### 状态表格新增列 +| 列名 | 说明 | 示例值 | +|------|------|--------| +| 白名单 | 是否在白名单中 | ✅ / ❌ | +| 白名单状态 | 白名单中是否启用 | 启用 / 禁用 | +| 自动启动 | 今天是否已尝试自动启动 | 是 / 否 | +| 自动启动结果 | 上次自动启动是否成功 | 成功 / 失败 / - | + +#### 新增管理操作 +1. **添加到白名单**: 勾选策略后点击"添加到白名单" +2. **从白名单移除**: 勾选策略后点击"从白名单移除" +3. **启用/禁用**: 在白名单中启用或禁用某个策略 +4. **手动触发**: 按钮"立即执行白名单启动" + +--- + +## 4. 工作流程 + +### 4.1 自动启动流程 + +```mermaid +sequenceDiagram + participant T as 定时任务 (08:58) + participant W as Web Backend + participant M as StrategyManager + participant WM as WhitelistManager + participant S as Strategy Process + + T->>W: 触发定时任务 + W->>M: auto_start_whitelist_strategies() + M->>WM: should_auto_start_today()? + + alt 今天未执行过 + WM-->>M: True + M->>WM: get_all() 获取白名单 + loop 遍历白名单策略 + M->>M: 检查策略是否运行 + alt 未运行 + M->>S: 启动策略进程 (auto_start=True) + M->>M: 记录启动结果 + else 已在运行 + M->>M: 标记为成功 + end + end + M->>WM: update_last_auto_start_date(今天) + M-->>W: 返回启动结果 + W->>T: 记录日志 + else 今天已执行过 + WM-->>M: False + M-->>W: 返回空结果 + W->>T: 跳过(今天已执行) + end +``` + +### 4.2 手动管理流程 + +```mermaid +sequenceDiagram + participant U as 用户 + participant W as Web界面 + participant API as Web Backend API + participant M as StrategyManager + participant WM as WhitelistManager + participant F as 白名单配置文件 + + U->>W: 点击"添加到白名单" + W->>API: POST /api/whitelist/{name}/add + API->>M: add_to_whitelist(name) + M->>WM: add(name) + WM->>F: 写入配置 + F-->>WM: 保存成功 + WM-->>M: True + M-->>API: True + API-->>W: {"success": True} + W->>U: 显示成功提示 + W->>API: GET /api/status + API->>M: get_status() + M->>WM: 获取白名单状态 + M-->>API: 状态数据 + API-->>W: 状态数据 + W->>U: 更新表格(显示白名单状态) +``` + +--- + +## 5. 兼容性设计 + +### 5.1 向后兼容 +- 现有 `start.py status` 命令保持不变 +- 现有 API `/api/status` 保持兼容(新增字段不影响现有功能) +- 现有日志查看功能保持不变 + +### 5.2 数据迁移 +- 首次启动时自动创建 `config/whitelist.json` +- 无需手动迁移现有配置 + +--- + +## 6. 实施计划 + +### 阶段一:后端实现 +1. 创建 `config/whitelist.json` 模板 +2. 实现 `core/whitelist_manager.py` +3. 修改 `core/manager.py` 集成白名单功能 +4. 修改 `web_backend.py` 添加 API +5. 修改 `start.py` 添加 CLI 命令 + +### 阶段二:前端实现 +1. 修改状态表格,添加白名单列 +2. 添加白名单管理操作按钮 +3. 添加手动触发按钮 +4. 美化界面交互 + +### 阶段三:测试 +1. 测试白名单添加/移除 +2. 测试自动启动功能 +3. 测试手动停止不自动重启 +4. 测试一天只启动一次 +5. 测试重启后状态恢复 + +--- + +## 7. 文件修改清单 + +| 文件路径 | 操作 | 说明 | +|----------|------|------| +| `config/whitelist.json` | 新增 | 白名单配置文件 | +| `core/whitelist_manager.py` | 新增 | 白名单管理器类 | +| `core/manager.py` | 修改 | 集成白名单功能,扩展状态结构 | +| `web_backend.py` | 修改 | 添加白名单管理 API,修改定时任务 | +| `start.py` | 修改 | 添加白名单 CLI 命令 | +| `frontend/` | 修改 | 前端界面添加白名单管理功能 | diff --git a/strategy_manager/config/whitelist.json b/strategy_manager/config/whitelist.json new file mode 100644 index 0000000..b175a8c --- /dev/null +++ b/strategy_manager/config/whitelist.json @@ -0,0 +1,5 @@ +{ + "version": "1.0", + "last_auto_start_date": null, + "strategies": {} +} diff --git a/strategy_manager/core/manager.py b/strategy_manager/core/manager.py index 8e81de3..9429afe 100644 --- a/strategy_manager/core/manager.py +++ b/strategy_manager/core/manager.py @@ -12,6 +12,7 @@ import json # 确保导入json模块 # ==================== 动态路径配置 ==================== from core.path_utils import add_project_root_to_path +from core.whitelist_manager import WhitelistManager # 添加项目根路径到sys.path PROJECT_ROOT = add_project_root_to_path() @@ -34,6 +35,10 @@ class StrategyManager: # 配置管理器日志 self._setup_logger() + # 初始化白名单管理器 + self.whitelist_manager = WhitelistManager() + self.logger.info("📋 白名单管理器已初始化") + self.strategies: Dict[str, Dict[str, Any]] = {} self.logger.info("🔄 正在加载策略配置...") self.load_strategies() @@ -109,15 +114,31 @@ class StrategyManager: self.logger.error("❌ 加载配置失败 %s: %s", config_file, e, exc_info=True) def get_status(self) -> Dict[str, Any]: - """获取完整状态""" + """获取完整状态(包含白名单信息)""" self._refresh_status() - return { + + # 构建状态数据 + status = { "timestamp": datetime.now().isoformat(), "total": len(self.strategies), "running": sum(1 for s in self.strategies.values() if s["status"] == "running"), "strategies": self.strategies } + # 添加白名单信息到每个策略 + for name, info in status["strategies"].items(): + info["in_whitelist"] = self.whitelist_manager.is_in_whitelist(name) + info["whitelist_enabled"] = self.whitelist_manager.is_enabled_in_whitelist(name) + + # 添加自动启动状态 + auto_start_status = self.whitelist_manager.get_auto_start_status() + status["whitelist_auto_start_today"] = auto_start_status["should_auto_start"] + status["whitelist_last_date"] = auto_start_status["last_auto_start_date"] + status["whitelist_total"] = auto_start_status["whitelist_count"] + status["whitelist_enabled"] = auto_start_status["enabled_count"] + + return status + def _refresh_status(self): """刷新进程状态 - 双重验证""" for name, info in self.strategies.items(): @@ -347,6 +368,121 @@ class StrategyManager: except Exception as e: self.logger.error("❌ 保存状态失败: %s", e, exc_info=True) + # ==================== 白名单管理方法 ==================== + + def add_to_whitelist(self, name: str) -> bool: + """ + 添加策略到白名单 + + Args: + name: 策略标识符 + + Returns: + 是否添加成功 + """ + if name not in self.strategies: + self.logger.error("❌ 策略不存在: %s", name) + return False + + if self.whitelist_manager.add(name, enabled=True): + self.logger.info("✅ 添加到白名单: %s", name) + self._save_status() + return True + + return False + + def remove_from_whitelist(self, name: str) -> bool: + """ + 从白名单移除策略 + + Args: + name: 策略标识符 + + Returns: + 是否移除成功 + """ + if self.whitelist_manager.remove(name): + self.logger.info("✅ 从白名单移除: %s", name) + self._save_status() + return True + + return False + + def set_whitelist_enabled(self, name: str, enabled: bool) -> bool: + """ + 设置策略在白名单中的启用状态 + + Args: + name: 策略标识符 + enabled: 是否启用 + + Returns: + 是否设置成功 + """ + if self.whitelist_manager.set_enabled(name, enabled): + self.logger.info("✅ 设置白名单状态: %s -> %s", name, enabled) + self._save_status() + return True + + return False + + def auto_start_whitelist_strategies(self) -> Dict[str, bool]: + """ + 自动启动白名单中所有未运行的策略 + 一天只执行一次 + + Returns: + Dict[str, bool]: 每个策略的启动结果 + """ + if not self.whitelist_manager.should_auto_start_today(): + self.logger.info("⏰ 今天已经执行过自动启动,跳过") + return {} + + self.logger.info("🚀 开始执行白名单自动启动...") + + results = {} + whitelist = self.whitelist_manager.get_all() + + for name, config in whitelist.items(): + if not config.get("enabled", True): + self.logger.info("⏭️ 跳过禁用策略: %s", name) + continue + + if name not in self.strategies: + self.logger.warning("⚠️ 策略不在系统中: %s", name) + continue + + # 检查是否已在运行 + if self._is_running(name): + self.logger.info("✅ 策略已在运行: %s", name) + results[name] = True + continue + + # 尝试启动 + self.logger.info("🚀 启动白名单策略: %s", name) + success = self.start_strategy(name) + + # 记录启动结果 + results[name] = success + + if success: + self.logger.info("✅ 白名单策略启动成功: %s", name) + else: + self.logger.error("❌ 白名单策略启动失败: %s", name) + + # 更新日期 + self.whitelist_manager.update_last_auto_start_date( + datetime.now().date().isoformat() + ) + + # 统计结果 + success_count = sum(1 for v in results.values() if v) + fail_count = len(results) - success_count + + self.logger.info("📊 白名单自动启动完成: 成功 %d, 失败 %d", success_count, fail_count) + + return results + def print_status_table(status: Dict[str, Any]): """格式化打印状态表格""" diff --git a/strategy_manager/core/whitelist_manager.py b/strategy_manager/core/whitelist_manager.py new file mode 100644 index 0000000..41a48b7 --- /dev/null +++ b/strategy_manager/core/whitelist_manager.py @@ -0,0 +1,345 @@ +""" +白名单管理器 - 管理期望自动启动的策略列表 + +功能: +1. 持久化白名单配置到 JSON 文件 +2. 支持添加/移除策略 +3. 支持启用/禁用白名单中的策略 +4. 跟踪自动启动日期,实现一天只尝试一次 +""" + +import json +import sys +from pathlib import Path +from typing import Dict, Any, Optional +from datetime import datetime + + +class WhitelistManager: + """白名单管理器""" + + def __init__(self, config_path: str = "config/whitelist.json"): + """ + 初始化白名单管理器 + + Args: + config_path: 白名单配置文件路径 + """ + self.config_path = Path(config_path) + self.data = self._load() + self.logger = self._setup_logger() + + def _setup_logger(self): + """配置日志""" + import logging + logger = logging.getLogger("WhitelistManager") + return logger + + def _load(self) -> Dict[str, Any]: + """ + 加载白名单配置 + + Returns: + 白名单配置数据 + """ + if not self.config_path.exists(): + # 返回默认配置 + return { + "version": "1.0", + "last_auto_start_date": None, + "strategies": {} + } + + try: + with open(self.config_path, 'r', encoding='utf-8') as f: + data = json.load(f) + # 确保数据结构完整 + if "version" not in data: + data["version"] = "1.0" + if "strategies" not in data: + data["strategies"] = {} + return data + except Exception as e: + print(f"[WARNING] 加载白名单配置失败: {e}") + return { + "version": "1.0", + "last_auto_start_date": None, + "strategies": {} + } + + def _save(self) -> bool: + """ + 保存白名单配置 + + Returns: + 是否保存成功 + """ + try: + # 确保目录存在 + self.config_path.parent.mkdir(exist_ok=True, parents=True) + + with open(self.config_path, 'w', encoding='utf-8') as f: + json.dump(self.data, f, indent=2, ensure_ascii=False) + + self.logger.debug("白名单配置已保存: %s", self.config_path) + return True + except Exception as e: + self.logger.error("保存白名单配置失败: %s", e) + return False + + def add(self, strategy_key: str, enabled: bool = True) -> bool: + """ + 添加策略到白名单 + + Args: + strategy_key: 策略标识符 (如 "DualModeTrendlineHawkesStrategy2_FG") + enabled: 是否启用自动启动 + + Returns: + 是否添加成功(False 表示已存在) + """ + if strategy_key in self.data["strategies"]: + self.logger.warning("策略已在白名单中: %s", strategy_key) + return False + + self.data["strategies"][strategy_key] = { + "enabled": enabled, + "added_at": datetime.now().isoformat(), + "added_by": "web" + } + + if self._save(): + self.logger.info("添加策略到白名单: %s (enabled=%s)", strategy_key, enabled) + return True + + return False + + def remove(self, strategy_key: str) -> bool: + """ + 从白名单移除策略 + + Args: + strategy_key: 策略标识符 + + Returns: + 是否移除成功(False 表示不存在) + """ + if strategy_key not in self.data["strategies"]: + self.logger.warning("策略不在白名单中: %s", strategy_key) + return False + + del self.data["strategies"][strategy_key] + + if self._save(): + self.logger.info("从白名单移除策略: %s", strategy_key) + return True + + return False + + def set_enabled(self, strategy_key: str, enabled: bool) -> bool: + """ + 设置策略在白名单中的启用状态 + + Args: + strategy_key: 策略标识符 + enabled: 是否启用 + + Returns: + 是否设置成功 + """ + if strategy_key not in self.data["strategies"]: + self.logger.warning("策略不在白名单中: %s", strategy_key) + return False + + self.data["strategies"][strategy_key]["enabled"] = enabled + self.data["strategies"][strategy_key]["updated_at"] = datetime.now().isoformat() + + if self._save(): + self.logger.info("设置白名单策略状态: %s -> %s", strategy_key, enabled) + return True + + return False + + def get_all(self) -> Dict[str, Dict[str, Any]]: + """ + 获取所有白名单策略 + + Returns: + 白名单策略字典 {strategy_key: {enabled, added_at, ...}} + """ + return self.data.get("strategies", {}) + + def get(self, strategy_key: str) -> Optional[Dict[str, Any]]: + """ + 获取单个策略的白名单配置 + + Args: + strategy_key: 策略标识符 + + Returns: + 策略配置或 None + """ + return self.data["strategies"].get(strategy_key) + + def is_in_whitelist(self, strategy_key: str) -> bool: + """ + 检查策略是否在白名单中 + + Args: + strategy_key: 策略标识符 + + Returns: + 是否在白名单中 + """ + return strategy_key in self.data.get("strategies", {}) + + def is_enabled_in_whitelist(self, strategy_key: str) -> bool: + """ + 检查策略是否在白名单中且已启用 + + Args: + strategy_key: 策略标识符 + + Returns: + 是否在白名单中且启用 + """ + if strategy_key not in self.data.get("strategies", {}): + return False + return self.data["strategies"][strategy_key].get("enabled", True) + + def update_last_auto_start_date(self, date_str: str) -> bool: + """ + 更新最后自动启动日期 + + Args: + date_str: 日期字符串 (YYYY-MM-DD) + + Returns: + 是否更新成功 + """ + self.data["last_auto_start_date"] = date_str + return self._save() + + def get_last_auto_start_date(self) -> Optional[str]: + """ + 获取最后自动启动日期 + + Returns: + 日期字符串或 None + """ + return self.data.get("last_auto_start_date") + + def should_auto_start_today(self) -> bool: + """ + 检查今天是否应该自动启动 + + Returns: + 今天是否应该自动启动 + """ + today = datetime.now().date().isoformat() + last_date = self.data.get("last_auto_start_date") + + if last_date is None: + return True + + return last_date != today + + def get_auto_start_status(self) -> Dict[str, Any]: + """ + 获取自动启动状态 + + Returns: + 自动启动状态信息 + """ + today = datetime.now().date().isoformat() + last_date = self.data.get("last_auto_start_date") + + return { + "should_auto_start": self.should_auto_start_today(), + "last_auto_start_date": last_date, + "is_first_run_today": last_date != today, + "whitelist_count": len(self.data.get("strategies", {})), + "enabled_count": sum( + 1 for s in self.data.get("strategies", {}).values() + if s.get("enabled", True) + ) + } + + def clear(self) -> bool: + """ + 清空白名单(谨慎使用) + + Returns: + 是否清空成功 + """ + self.data["strategies"] = {} + self.data["last_auto_start_date"] = None + return self._save() + + +# ==================== 单元测试 ==================== + +if __name__ == "__main__": + import os + + # 切换到 strategy_manager 目录 + os.chdir(Path(__file__).parent.parent) + + # 测试白名单管理器 + print("=" * 60) + print("WhitelistManager 单元测试") + print("=" * 60) + + # 创建测试用的白名单管理器 + test_config_path = "config/whitelist_test.json" + wm = WhitelistManager(test_config_path) + + # 测试添加 + print("\n[测试] 添加策略到白名单...") + assert wm.add("TestStrategy1_FG") == True + assert wm.add("TestStrategy2_FG", enabled=False) == True + assert wm.add("TestStrategy1_FG") == False # 已存在 + + # 测试获取 + print("\n[测试] 获取白名单...") + all_strategies = wm.get_all() + assert "TestStrategy1_FG" in all_strategies + assert "TestStrategy2_FG" in all_strategies + print(f"白名单策略: {list(all_strategies.keys())}") + + # 测试状态检查 + print("\n[测试] 状态检查...") + assert wm.is_in_whitelist("TestStrategy1_FG") == True + assert wm.is_in_whitelist("TestStrategy1_FG") == True + assert wm.is_in_whitelist("TestStrategy3_FG") == False + assert wm.is_enabled_in_whitelist("TestStrategy1_FG") == True + assert wm.is_enabled_in_whitelist("TestStrategy2_FG") == False + + # 测试设置启用状态 + print("\n[测试] 设置启用状态...") + assert wm.set_enabled("TestStrategy2_FG", True) == True + assert wm.is_enabled_in_whitelist("TestStrategy2_FG") == True + assert wm.set_enabled("TestStrategy3_FG", True) == False # 不存在 + + # 测试移除 + print("\n[测试] 移除策略...") + assert wm.remove("TestStrategy2_FG") == True + assert wm.remove("TestStrategy2_FG") == False # 已移除 + assert wm.is_in_whitelist("TestStrategy2_FG") == False + + # 测试自动启动日期 + print("\n[测试] 自动启动日期...") + today = datetime.now().date().isoformat() + assert wm.should_auto_start_today() == True + wm.update_last_auto_start_date(today) + assert wm.should_auto_start_today() == False + wm.update_last_auto_start_date("2024-01-01") + assert wm.should_auto_start_today() == True + + # 清理测试文件 + print("\n[测试] 清理...") + Path(test_config_path).unlink(missing_ok=True) + + print("\n" + "=" * 60) + print("✅ 所有测试通过!") + print("=" * 60) diff --git a/strategy_manager/frontend/dist/index.html b/strategy_manager/frontend/dist/index.html index d61095e..5971fc3 100644 --- a/strategy_manager/frontend/dist/index.html +++ b/strategy_manager/frontend/dist/index.html @@ -14,10 +14,23 @@
@@ -33,89 +46,173 @@ -