RiskManager `peak_equity` 文件持久化简化设计
RiskManager peak_equity 文件持久化简化设计
1. 背景与现状
1.1 业务用途
peak_equity:用于RiskManager的最大回撤检查。- 回撤公式:
drawdown = (peak_equity - account_value) / peak_equity,并与config.max_drawdown_pct比较。 - 只有当
account_value低于peak_equity时才会触发回撤超限检查。
1.2 现有实现(与本次优化相关)
文件:src/trading/risk_manager.py
- 文件级常量:
_PEAK_EQUITY_FILE = Path(__file__).parent.parent.parent / ".peak_equity"
- 初始化:
self._peak_equity: float = self._load_peak_equity()
- 启动时恢复:
_load_peak_equity():如.peak_equity存在,则读取 JSON,并解析peak_equity字段。- 成功时日志:
"从文件恢复 peak_equity: $xxx.xx" - 失败时记录 warning,并回退到默认值
0.0。
- 运行时持久化:
_save_peak_equity(self, value):- 使用
tempfile.mkstemp在_PEAK_EQUITY_FILE.parent创建临时文件。 - 写入
{"peak_equity": value}的 JSON。 - 通过
os.replace原子替换_PEAK_EQUITY_FILE。 - 出错时尝试删除临时文件并记录 warning。
- 使用
update_peak_equity(self, account_value):- 若
account_value > self._peak_equity:- 更新内存中的
self._peak_equity。 - 调用
_save_peak_equity(account_value)写入文件。
- 更新内存中的
- 若
- 最大回撤检查:
_check_max_drawdown(self, account_value):- 若
self._peak_equity <= 0:- 若
account_value <= 0:记录错误并拒绝交易。 - 否则:
self._peak_equity = account_value,将当前权益视为初始峰值。
- 若
- 若
account_value > self._peak_equity:更新内存中的self._peak_equity。 - 计算回撤比例
(self._peak_equity - account_value) / self._peak_equity,与self._config.max_drawdown_pct比较。
- 若
关键点:回撤风控本身只依赖进程内存中的
_peak_equity,文件持久化只是为其提供“跨重启初始化值”的额外路径。
2. 设计目标与原则
2.1 设计目标
- 将
peak_equity实现收敛为单一内存状态,不再依赖.peak_equity文件。 - 明确语义:最大回撤约束基于“本次进程生命周期内的最大账户权益”。
- 移除与该持久化相关的 IO、异常处理和隐含状态,降低理解与排错成本。
2.2 设计原则(复杂度优先)
- 只做减法:删除不必要的路径和状态来源,不增加新抽象层/配置项。
- 无历史兼容层:不保留“若旧文件存在则……”之类的兼容逻辑。
- 单一事实来源:
_peak_equity仅由当前进程的业务流程维护,不从外部文件反向注入。 - 行为语义显性化:文档中明确说明“最大回撤只针对本次进程生命周期”,避免未来隐式依赖“跨重启历史峰值”。
3. 目标行为(优化后的语义)
3.1 启动阶段
RiskManager初始化时:self._peak_equity设为0.0。
- 首次调用
_check_max_drawdown(account_value)时:- 若
self._peak_equity <= 0且account_value > 0:- 将
self._peak_equity设置为account_value,作为本次进程的“初始峰值”。
- 将
- 若
account_value <= 0:视为异常数据,保持现有拒绝交易与日志行为。
- 若
3.2 运行阶段
- 每次开仓前:
pre_trade_check仍调用_check_max_drawdown(account_value)。- 若
account_value > self._peak_equity:- 更新内存中的
self._peak_equity为新的高点。
- 更新内存中的
- 否则:
- 按
(self._peak_equity - account_value) / self._peak_equity计算回撤比例,并与配置上限比较。
- 按
- 不再有任何对
.peak_equity的读写或相关日志。
3.3 重启后
- 上一进程的峰值不再被恢复。
- 新进程会在首次有效
account_value时建立自己的起点峰值。 - 最大回撤含义变为:“自本次进程启动以来的最大回撤”。
4. 详细代码变更设计
4.1 删除文件持久化相关逻辑
文件:src/trading/risk_manager.py
删除内容:
- 常量:
_PEAK_EQUITY_FILE = Path(__file__).parent.parent.parent / ".peak_equity"
- 方法:
_load_peak_equity()_save_peak_equity(self, value: float)
- 其他:
- 如
Path、os、tempfile等 import 仅被上述逻辑使用,则一并删除。 - 删除所有包含
"从文件恢复 peak_equity"、"加载 peak_equity 文件失败"、"保存 peak_equity 文件失败"的日志调用。
- 如
效果:
RiskManager不再产生或依赖.peak_equity文件。- 启动路径和运行路径中均不再包含磁盘 IO 与相关异常分支。
4.2 简化 _peak_equity 初始化
- 原实现:
self._peak_equity: float = self._load_peak_equity()
- 新实现:
self._peak_equity: float = 0.0
理由:
_check_max_drawdown已经在self._peak_equity <= 0时,用当前account_value建立初始峰值。- 将初始化设为
0.0即可触发该逻辑,无需额外分支。
4.3 收敛 update_peak_equity 为纯内存更新
- 原实现(逻辑):
- 若
account_value > self._peak_equity:- 更新内存中的
self._peak_equity。 - 调用
_save_peak_equity(account_value)写文件。
- 更新内存中的
- 若
- 新实现:
- 保留加锁与“创新高则更新”的判断;
- 删除对
_save_peak_equity的调用,仅更新内存。
语义:
update_peak_equity成为维护内存峰值的纯函数式副作用(只改内存,不做 IO)。
4.4 保持 _check_max_drawdown 逻辑不变
- 保持以下行为:
self._peak_equity <= 0:account_value <= 0:视为无效数据,记录错误并拒绝交易。account_value > 0:使用当前账户权益初始化峰值。
account_value > self._peak_equity:- 更新内存中的峰值。
- 否则:
- 按当前峰值计算回撤比例并与
max_drawdown_pct比较。
- 按当前峰值计算回撤比例并与
原因:
- 该逻辑是业务规则本身,与持久化方式无关,优化目标仅是移除文件持久化带来的额外复杂度。
5. 行为变化与兼容性分析
5.1 行为差异
- 旧行为:
- 若
.peak_equity存在且有效,重启后_peak_equity从文件中恢复。 - 最大回撤始终相对于“历史最高权益”(可能跨多个进程)。
- 若
- 新行为:
_peak_equity在每个进程生命周期内独立维护。- 重启后重新从首次有效
account_value建立峰值。
结论:
- 新版本的回撤约束强度:对单次进程运行严格,对跨重启放宽。
- 换取的是:实现和运维心智明显简化。
5.2 兼容性与风险
- 若现在的监控、报警或人工流程隐式依赖“跨重启回撤连续性”,新行为可能与其预期不符。
- 本次改动不增加任何配置开关、迁移逻辑或回退逻辑,保证代码路径单一、易于理解。
.peak_equity文件如仍存在:- 运行时不会再被访问。
- 可在人工确认后,由运维手工删除。
6. 测试方案
6.1 单元级测试
建议覆盖以下场景:
-
初始峰值建立
- 给定:
_peak_equity = 0.0,account_value = 1000.0。 - 调用
_check_max_drawdown(1000.0)。 - 期望:
_peak_equity更新为1000.0。- 返回
True(未超限)。
- 给定:
-
创新高更新
- 初始:
_peak_equity = 1000.0。 - 调用
update_peak_equity(1200.0)。 - 期望:
_peak_equity更新为1200.0。- 无任何文件 IO 发生。
- 初始:
-
回撤在限制内
- 初始:
_peak_equity = 1000.0,max_drawdown_pct = 0.2。 - 调用
_check_max_drawdown(900.0)。 - 回撤 10%,期望返回
True。
- 初始:
-
回撤超限
- 初始:
_peak_equity = 1000.0,max_drawdown_pct = 0.2。 - 调用
_check_max_drawdown(750.0)。 - 回撤 25%,期望返回
False,并由上层风控拒绝开仓。
- 初始:
-
重启后的行为
- 模拟“历史上一度峰值 2000”这一事实不再从文件注入。
- 新进程第一次
_check_max_drawdown(1500.0):- 期望
_peak_equity直接初始化为 1500.0,而不是 2000。
- 期望
6.2 集成级测试
与 orchestrator 联动,验证:
- 正常链路仍然会在获取账户权益后调用
update_peak_equity。 - 最大回撤超限时,仍能在日志与风控结果中看到对应的拒绝原因。
- 日志中不再出现与
.peak_equity文件相关的任何信息。
7. 后续扩展建议(仅在有强需求时考虑)
- 如后续有明确写入的产品需求要求“最大回撤必须跨重启严格生效”,可在那时评估重新引入持久化方案:
- 优先复用现有集中式存储(如 DB)的一行记录/一个键,而不是重新启用本地文件。
- 仍然保持“单一实现路径”,避免再添加“文件 vs DB vs 内存”多选逻辑。
- 在没有这类书面需求之前,保持当前简化后的内存实现,避免过早增加抽象和兼容层。