mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
- 策略: 引擎只加载 data/strategies/{custom,ai,composite} 与研究模板目录,
原 25 个内置策略一次性迁移为用户自定义文件(ID 不变, 引用零迁移);
研究模板移至 app/strategy/research, 打包 spec 同步; 新装环境策略库为空
- 信号: 管线不再硬编码任何信号表达式; 涨跌停判定价成为运行时列
limit_up_price/limit_down_price(生效价∓0.005 容差), 20 个信号转为
data/user_data/custom_signals 定义(显式 column 沿用 signal_* 原列名);
依赖解析/注入顺序/当日路径 prev-join 全部定义驱动; 语义统一为历史路径
口径(顺带修正当日路径 BOLL 比较符与翘板/炸板空值两处既有偏差)
- 前端: 信号库单列表(全部可编辑), SignalPicker/监控选项/信号标签全走
定义接口; 回测页新增免责声明(模拟性质/数据源准确性不作保证/不构成
投资建议)
- 测试: 25 个策略夹具与 20 个信号种子入库 tests/fixtures, 受影响测试
全部自包含化(不依赖运行目录用户数据); 77 万行 MA 死叉新旧表达式对拍
逐行一致; 回测实跑 45 笔卖点信号出场验证矩阵链路
- 同批包含工作区既有进度: 策略结果 ext 列同日投影修复(含测试)、数据源插件、fuyao/auth/api_gateway/data_integrity 调整、README 信号库
描述与 strategy.md 导入路径补充
验证: 后端 2592 passed(空数据目录 CI 模拟 2588 passed, 4 例为模拟环境
伪影复验排除); 前端 vitest 125 passed + tsc 0 错 + vite build 通过
85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""策略详情 _strategy_detail 触发器合并回归测试.
|
|
|
|
回归点 (bug): entry_signals / exit_signals 原先直接返回策略源文件默认值,
|
|
没有合并 overrides, 导致用户在卡片弹窗里新选的买卖触发器 tag 保存后回显丢失.
|
|
|
|
契约:
|
|
- 无 overrides → 返回策略默认 entry_signals / exit_signals
|
|
- 有 overrides → 返回 overrides 里保存的 list (即使为空, 也代表用户主动清空)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from app.api.strategy import _strategy_detail
|
|
from app.strategy.engine import StrategyDef
|
|
|
|
|
|
def _make_strategy(
|
|
entry_signals: list[str],
|
|
exit_signals: list[str],
|
|
) -> StrategyDef:
|
|
"""构造最小可用的 StrategyDef (只填必填字段)."""
|
|
return StrategyDef(
|
|
meta={"id": "test_strat", "name": "测试策略"},
|
|
basic_filter={"enabled": True},
|
|
entry_signals=list(entry_signals),
|
|
exit_signals=list(exit_signals),
|
|
stop_loss=-0.08,
|
|
trailing_stop=None,
|
|
trailing_take_profit_activate=None,
|
|
trailing_take_profit_drawdown=None,
|
|
max_hold_days=10,
|
|
filter_fn=None,
|
|
filter_history_fn=None,
|
|
lookback_days=60,
|
|
source="custom",
|
|
)
|
|
|
|
|
|
def test_no_overrides_returns_default_signals():
|
|
"""无 overrides 时返回策略源文件默认的触发器."""
|
|
s = _make_strategy(
|
|
entry_signals=["signal_ma20_breakout", "signal_n_day_high"],
|
|
exit_signals=["signal_ma20_breakdown"],
|
|
)
|
|
detail = _strategy_detail(s, overrides=None)
|
|
assert "alerts" not in detail
|
|
assert detail["entry_signals"] == ["signal_ma20_breakout", "signal_n_day_high"]
|
|
assert detail["exit_signals"] == ["signal_ma20_breakdown"]
|
|
|
|
|
|
def test_overrides_signals_are_reflected():
|
|
"""核心回归: 用户保存了更多触发器, 详情必须回显保存值而非默认值."""
|
|
s = _make_strategy(
|
|
entry_signals=["signal_ma20_breakout"],
|
|
exit_signals=["signal_ma20_breakdown"],
|
|
)
|
|
overrides = {
|
|
"entry_signals": ["signal_ma20_breakout", "signal_macd_golden", "signal_n_day_high"],
|
|
"exit_signals": ["signal_ma20_breakdown", "signal_macd_dead"],
|
|
}
|
|
detail = _strategy_detail(s, overrides=overrides)
|
|
assert detail["entry_signals"] == overrides["entry_signals"]
|
|
assert detail["exit_signals"] == overrides["exit_signals"]
|
|
|
|
|
|
def test_empty_override_signals_reflected():
|
|
"""用户主动清空所有触发器 → 保存空 list, 详情应回显空 (而非回退默认)."""
|
|
s = _make_strategy(
|
|
entry_signals=["signal_ma20_breakout"],
|
|
exit_signals=["signal_ma20_breakdown"],
|
|
)
|
|
detail = _strategy_detail(s, overrides={"entry_signals": [], "exit_signals": []})
|
|
assert detail["entry_signals"] == []
|
|
assert detail["exit_signals"] == []
|
|
|
|
|
|
def test_partial_override_keeps_other_default():
|
|
"""只覆盖 entry_signals, exit_signals 保持默认 (key 不在 overrides 里)."""
|
|
s = _make_strategy(
|
|
entry_signals=["signal_ma20_breakout"],
|
|
exit_signals=["signal_ma20_breakdown"],
|
|
)
|
|
detail = _strategy_detail(s, overrides={"entry_signals": ["signal_x"]})
|
|
assert detail["entry_signals"] == ["signal_x"]
|
|
assert detail["exit_signals"] == ["signal_ma20_breakdown"]
|