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 通过
98 lines
3.2 KiB
Python
98 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
from app.api import screener as screener_api
|
|
|
|
|
|
class _MonitorEngine:
|
|
def __init__(self, results=None):
|
|
self.results = results or {}
|
|
|
|
def latest_strategy_results(self):
|
|
return self.results
|
|
|
|
|
|
def _request(tmp_path, monitor_results=None):
|
|
repo = SimpleNamespace(store=SimpleNamespace(data_dir=tmp_path))
|
|
state = SimpleNamespace(repo=repo, monitor_engine=_MonitorEngine(monitor_results))
|
|
return SimpleNamespace(app=SimpleNamespace(state=state))
|
|
|
|
|
|
def test_cached_summary_omits_rows_and_counts_realtime_expirations(monkeypatch, tmp_path):
|
|
cached = {
|
|
"as_of": "2026-07-20",
|
|
"results": {
|
|
"strategy_a": {
|
|
"as_of": "2026-07-20",
|
|
"total": 2,
|
|
"rows": [{"symbol": "000001.SZ"}, {"symbol": "000002.SZ"}],
|
|
},
|
|
},
|
|
"today_ever_rows": {
|
|
"strategy_a": {
|
|
"000001.SZ": {"symbol": "000001.SZ"},
|
|
"600000.SH": {"symbol": "600000.SH"},
|
|
},
|
|
},
|
|
"updated_at": 1,
|
|
}
|
|
realtime = {
|
|
"strategy_a": {
|
|
"as_of": "2026-07-20",
|
|
"total": 2,
|
|
"rows": [{"symbol": "000002.SZ"}, {"symbol": "300001.SZ"}],
|
|
},
|
|
}
|
|
monkeypatch.setattr(screener_api.strategy_cache, "read_cache", lambda *_args: cached)
|
|
|
|
payload = screener_api.get_cached_summary(_request(tmp_path, realtime))
|
|
|
|
assert payload["results"] == {
|
|
"strategy_a": {"total": 2, "as_of": "2026-07-20", "computed_at": None}
|
|
}
|
|
assert payload["today_ever_counts"] == {"strategy_a": 4}
|
|
assert "rows" not in payload["results"]["strategy_a"]
|
|
|
|
|
|
def test_cached_result_returns_only_requested_rows_with_ext_and_strategy_membership(monkeypatch, tmp_path):
|
|
cached = {
|
|
"as_of": "2026-07-20",
|
|
"results": {
|
|
"strategy_a": {
|
|
"as_of": "2026-07-20",
|
|
"total": 1,
|
|
"rows": [{"symbol": "000001.SZ"}],
|
|
},
|
|
"strategy_b": {
|
|
"as_of": "2026-07-20",
|
|
"total": 2,
|
|
"rows": [{"symbol": "000001.SZ"}, {"symbol": "600000.SH"}],
|
|
},
|
|
},
|
|
"today_ever_rows": {
|
|
"strategy_a": {
|
|
"000001.SZ": {"symbol": "000001.SZ"},
|
|
"000002.SZ": {"symbol": "000002.SZ"},
|
|
},
|
|
},
|
|
"updated_at": 1,
|
|
}
|
|
monkeypatch.setattr(screener_api.strategy_cache, "read_cache", lambda *_args: cached)
|
|
monkeypatch.setattr(
|
|
screener_api,
|
|
"_load_ext_value_maps",
|
|
lambda *_args, **_kwargs: {"concept.concept": {"000001.SZ": "银行", "000002.SZ": "科技"}},
|
|
)
|
|
|
|
payload = screener_api.get_cached_result(
|
|
"strategy_a",
|
|
_request(tmp_path),
|
|
ext_columns="concept.concept",
|
|
)
|
|
|
|
assert payload["result"]["strategy"] == "strategy_a"
|
|
assert payload["result"]["rows"] == [{"symbol": "000001.SZ", "concept.concept": "银行"}]
|
|
assert payload["today_ever_rows"]["000002.SZ"]["concept.concept"] == "科技"
|
|
assert payload["strategy_ids_by_symbol"] == {"000001.SZ": ["strategy_a", "strategy_b"]}
|