mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 22:34:18 +08:00
Unify strategy execution across backtest, screener, and monitoring; isolate backtest workloads in spawn workers; and add shared matrix caching plus valid-bar indicator acceleration.
89 lines
3.1 KiB
Python
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
|