Files
tick-stock-panel/backend/tests/backtest/test_optimizer_api.py
T
lytem28 b6cf0495e1 feat: complete matrix-native backtest engine
Unify strategy execution across backtest, screener, and monitoring; isolate backtest workloads in spawn workers; and add shared matrix caching plus valid-bar indicator acceleration.
2026-07-16 12:17:27 +08:00

89 lines
3.1 KiB
Python

"""优化器 API job_key 契约测试 — 守护 stream 与 cancel 的 key 对齐 (仿 PR3 C1 教训)。"""
from __future__ import annotations
from app.api.backtest import _OPT_BT_FIELDS, _make_opt_job_key, _opt_backtest_kwargs
def _sig(bt: dict) -> str:
return "|".join(f"{k}={bt[k]}" for k in _OPT_BT_FIELDS)
def test_job_key_deterministic():
bt = _opt_backtest_kwargs("open_t+1", 0.0002, None, None, 5.0, 10, 1.0, 1e6, "equal", "position", 5)
sig = _sig(bt)
k1 = _make_opt_job_key("s", None, None, None, '{"p":[1,2]}', "sortino", None, sig)
k2 = _make_opt_job_key("s", None, None, None, '{"p":[1,2]}', "sortino", None, sig)
assert k1 == k2
def test_job_key_distinguishes_grid_and_objective():
bt = _opt_backtest_kwargs("open_t+1", 0.0002, None, None, 5.0, 10, 1.0, 1e6, "equal", "position", 5)
sig = _sig(bt)
base = _make_opt_job_key("s", None, None, None, '{"p":[1,2]}', "sortino", None, sig)
assert base != _make_opt_job_key("s", None, None, None, '{"p":[1,3]}', "sortino", None, sig) # grid 不同
assert base != _make_opt_job_key("s", None, None, None, '{"p":[1,2]}', "sharpe", None, sig) # objective 不同
assert base != _make_opt_job_key(
"s", None, None, None, '{"p":[1,2]}', "sortino", None, sig,
matrix_cache_max_mb=256,
)
def test_cancel_looks_up_job_by_echoed_key():
"""重构后: cancel 直接用 stream 回吐的 job_key 查表, 不再重算参数。
这消除了'两侧重算必须逐字段一致'的脆弱契约 (PR3 C1 / direction 空串失配的根因)。
"""
import asyncio
from app.api.backtest import _BacktestJob, _running_jobs, optimize_cancel
class _Req:
def __init__(self, body):
self._body = body
async def json(self):
return self._body
key = "optkey_test_1"
job = _BacktestJob(key)
_running_jobs[key] = job
try:
# 用回吐的 key 取消 → 命中并 set cancel_event
res = asyncio.run(optimize_cancel(_Req({"job_key": key})))
assert res["ok"] is True
assert job.cancel_event.is_set()
# 已完成任务再取消 → ok False
job.done = True
res2 = asyncio.run(optimize_cancel(_Req({"job_key": key})))
assert res2["ok"] is False
# 未知 key → ok False, 不抛异常
res3 = asyncio.run(optimize_cancel(_Req({"job_key": "nonexistent"})))
assert res3["ok"] is False
finally:
_running_jobs.pop(key, None)
def test_finished_job_is_proactively_removed_after_ttl(monkeypatch):
from app.api import backtest as api
class _ImmediateTimer:
daemon = False
def __init__(self, interval, callback):
self.interval = interval
self.callback = callback
def start(self):
self.callback()
monkeypatch.setattr(api.threading, "Timer", _ImmediateTimer)
job = api._BacktestJob("finished-job")
api._running_jobs[job.key] = job
api._finish_job(job, result={"ok": True})
assert job.done is True
assert job.result == {"ok": True}
assert job.key not in api._running_jobs