You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
564 lines
19 KiB
Python
564 lines
19 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from apps.web.routers import intraday
|
|
|
|
|
|
MOMENTUM_CONFIG = """\
|
|
_meta:
|
|
name: Leader Intraday Momentum
|
|
description: Test momentum config
|
|
id: 7
|
|
strategy_mode: momentum
|
|
strategy:
|
|
entry_minutes_after_open: 10
|
|
exit_minutes_before_close: 5
|
|
stop_loss_pct: null
|
|
trailing_stop_pct: -0.06
|
|
min_morning_gain_pct: 0.02
|
|
max_morning_gain_pct: null
|
|
min_entry_volume: 250000
|
|
ticker_cooldown_days: 0
|
|
top_n: 5
|
|
initial_capital: 10000.0
|
|
slippage_bps: 5.0
|
|
market_regime_spy_threshold: null
|
|
universe:
|
|
source: midcap
|
|
min_price: 10.0
|
|
backtest:
|
|
lookback_trading_days: 200
|
|
pre_screen_threshold: 0.02
|
|
cache:
|
|
enabled: true
|
|
dir: data/cache/intraday
|
|
output:
|
|
dir: runs/intraday
|
|
verbose: false
|
|
"""
|
|
|
|
MOMENTUM_YAML_UNIVERSE_CONFIG = """\
|
|
_meta:
|
|
name: Leader Intraday Momentum
|
|
description: Test momentum config
|
|
id: 7
|
|
strategy_mode: momentum
|
|
strategy:
|
|
entry_minutes_after_open: 10
|
|
exit_minutes_before_close: 5
|
|
stop_loss_pct: null
|
|
trailing_stop_pct: -0.06
|
|
min_morning_gain_pct: 0.02
|
|
max_morning_gain_pct: null
|
|
min_entry_volume: 250000
|
|
ticker_cooldown_days: 0
|
|
top_n: 5
|
|
initial_capital: 10000.0
|
|
slippage_bps: 5.0
|
|
market_regime_spy_threshold: null
|
|
universe:
|
|
source: yaml
|
|
symbols_file: configs/symbols_midcap_smallmid.yaml
|
|
min_price: 10.0
|
|
backtest:
|
|
lookback_trading_days: 200
|
|
pre_screen_threshold: 0.02
|
|
cache:
|
|
enabled: true
|
|
dir: data/cache/intraday
|
|
output:
|
|
dir: runs/intraday
|
|
verbose: false
|
|
"""
|
|
|
|
ORB_ADVANCED_CONFIG = """\
|
|
_meta:
|
|
name: ORB Advanced
|
|
description: Advanced ORB config
|
|
id: 42
|
|
strategy_mode: orb
|
|
orb_strategy:
|
|
orb_minutes: 5
|
|
sim_bar_minutes: 5
|
|
entry_direction: long_only
|
|
order_timeout_minutes: 45
|
|
min_price: 15.0
|
|
min_avg_dollar_volume: 40000000
|
|
min_premarket_dollar_vol: 2500000
|
|
min_breakout_rel_vol: 1.2
|
|
min_atr_14: 0.50
|
|
min_rvol: 1.5
|
|
max_candidates: 20
|
|
risk_per_trade_pct: 0.05
|
|
max_position_pct: 0.70
|
|
atr_stop_multiplier: 0.75
|
|
breakeven_at_r: 1.0
|
|
trailing_at_r: 1.0
|
|
trailing_stop_atr_multiplier: 0.8
|
|
daily_max_loss_pct: 0.05
|
|
max_stops_per_day: 5
|
|
settlement_days: 1
|
|
max_gap_pct: 0.04
|
|
compound_returns: false
|
|
daily_budget_reset: true
|
|
daily_bar_snapshot_id: orb_daily_v46_20260423
|
|
prior_event_snapshot_id: orb_pead_d10_v46_20260423
|
|
universe:
|
|
source: broad
|
|
backtest:
|
|
lookback_trading_days: 200
|
|
cache:
|
|
enabled: true
|
|
dir: data/cache/intraday
|
|
output:
|
|
dir: runs/intraday_orb
|
|
verbose: false
|
|
"""
|
|
|
|
ORB_EXTENDS_CHILD_CONFIG = """\
|
|
extends: orb_advanced_base.yaml
|
|
_meta:
|
|
name: ORB Extends Child
|
|
description: Child config
|
|
id: 43
|
|
orb_strategy:
|
|
min_rvol: 3.0
|
|
"""
|
|
|
|
|
|
def _write_config(tmp_path: Path, slug: str, text: str = MOMENTUM_CONFIG) -> Path:
|
|
strategies_dir = tmp_path / "configs" / "intraday" / "strategies"
|
|
strategies_dir.mkdir(parents=True, exist_ok=True)
|
|
path = strategies_dir / f"{slug}.yaml"
|
|
path.write_text(text)
|
|
return path
|
|
|
|
|
|
def test_load_user_strategy_supports_momentum(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "leader_intraday_momentum")
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
payload = intraday._load_user_strategy("leader_intraday_momentum")
|
|
|
|
assert payload is not None
|
|
assert payload["strategy_mode"] == "momentum"
|
|
assert payload["output_dir"] == "runs/intraday"
|
|
assert payload["top_n"] == 5
|
|
assert payload["min_morning_gain_pct"] == 0.02
|
|
|
|
|
|
def test_load_user_strategy_exposes_orb_advanced_fields(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced", ORB_ADVANCED_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
payload = intraday._load_user_strategy("orb_advanced")
|
|
|
|
assert payload is not None
|
|
assert payload["strategy_mode"] == "orb"
|
|
assert payload["universe"] == "broad"
|
|
assert payload["min_price"] == 15.0
|
|
assert payload["min_avg_dollar_volume"] == 40000000
|
|
assert payload["min_premarket_dollar_vol"] == 2500000
|
|
assert payload["min_breakout_rel_vol"] == 1.2
|
|
assert payload["daily_budget_reset"] is True
|
|
|
|
|
|
def test_create_comp_strategy_materializes_deployment_overlay(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced", ORB_ADVANCED_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
generated = intraday.create_comp_strategy("orb_advanced")
|
|
generated_path = tmp_path / "configs" / "intraday" / "strategies" / f"{generated['slug']}.yaml"
|
|
raw = intraday.yaml.safe_load(generated_path.read_text())
|
|
|
|
assert generated["slug"] == "orb_advanced_comp"
|
|
assert generated["name"] == "ORB Advanced Comp"
|
|
assert generated["compound_returns"] is True
|
|
assert generated["daily_budget_reset"] is False
|
|
assert generated["single_trade_loss_cap_basis"] == "equity"
|
|
assert generated["is_comp_equity_deployment"] is True
|
|
assert raw["orb_strategy"]["compound_returns"] is True
|
|
assert raw["orb_strategy"]["daily_budget_reset"] is False
|
|
assert raw["orb_strategy"]["single_trade_loss_cap_basis"] == "equity"
|
|
assert raw["_meta"]["parent"] is None
|
|
assert raw["_meta"]["deployment_kind"] == "compound_equity_losscap"
|
|
assert raw["_meta"]["source_slug"] == "orb_advanced"
|
|
|
|
|
|
def test_load_strategy_yaml_rejects_extends(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced_base", ORB_ADVANCED_CONFIG)
|
|
_write_config(tmp_path, "orb_extends_child", ORB_EXTENDS_CHILD_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
with pytest.raises(ValueError, match="extends.*no longer supported"):
|
|
intraday._load_strategy_yaml_standalone(
|
|
tmp_path / "configs" / "intraday" / "strategies" / "orb_extends_child.yaml"
|
|
)
|
|
|
|
|
|
def test_create_comp_strategy_refreshes_existing_generated_variant(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced", ORB_ADVANCED_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
first = intraday.create_comp_strategy("orb_advanced")
|
|
first_path = tmp_path / "configs" / "intraday" / "strategies" / f"{first['slug']}.yaml"
|
|
raw = intraday.yaml.safe_load(first_path.read_text())
|
|
raw["orb_strategy"]["min_rvol"] = 9.9
|
|
first_path.write_text(intraday.yaml.dump(raw, default_flow_style=False, sort_keys=False))
|
|
|
|
second = intraday.create_comp_strategy("orb_advanced")
|
|
refreshed = intraday.yaml.safe_load(first_path.read_text())
|
|
|
|
assert second["slug"] == first["slug"]
|
|
assert second["id"] == first["id"]
|
|
assert refreshed["orb_strategy"]["min_rvol"] == 1.5
|
|
assert refreshed["orb_strategy"]["single_trade_loss_cap_basis"] == "equity"
|
|
|
|
|
|
def test_load_user_strategy_does_not_load_extends(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced_base", ORB_ADVANCED_CONFIG)
|
|
_write_config(tmp_path, "orb_extends_child", ORB_EXTENDS_CHILD_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
payload = intraday._load_user_strategy("orb_extends_child")
|
|
|
|
assert payload is None
|
|
|
|
|
|
def test_update_strategy_preserves_unedited_orb_advanced_fields(monkeypatch, tmp_path: Path) -> None:
|
|
path = _write_config(tmp_path, "orb_advanced", ORB_ADVANCED_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
updated = intraday.update_strategy(
|
|
"orb_advanced",
|
|
intraday.UpdateStrategyRequest(name="ORB Advanced Updated"),
|
|
)
|
|
raw = intraday.yaml.safe_load(path.read_text())
|
|
|
|
assert updated["name"] == "ORB Advanced Updated"
|
|
assert raw["orb_strategy"]["min_price"] == 15.0
|
|
assert raw["orb_strategy"]["min_avg_dollar_volume"] == 40000000
|
|
assert raw["orb_strategy"]["min_premarket_dollar_vol"] == 2500000
|
|
assert raw["orb_strategy"]["daily_bar_snapshot_id"] == "orb_daily_v46_20260423"
|
|
assert raw["orb_strategy"]["prior_event_snapshot_id"] == "orb_pead_d10_v46_20260423"
|
|
|
|
|
|
def test_copy_strategy_preserves_orb_advanced_fields(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced", ORB_ADVANCED_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
copied = intraday.copy_strategy("orb_advanced")
|
|
copied_path = tmp_path / "configs" / "intraday" / "strategies" / f"{copied['slug']}.yaml"
|
|
raw = intraday.yaml.safe_load(copied_path.read_text())
|
|
|
|
assert copied["name"] == "ORB Advanced (copy)"
|
|
assert raw["orb_strategy"]["min_price"] == 15.0
|
|
assert raw["orb_strategy"]["min_avg_dollar_volume"] == 40000000
|
|
assert raw["orb_strategy"]["min_premarket_dollar_vol"] == 2500000
|
|
assert raw["orb_strategy"]["daily_bar_snapshot_id"] == "orb_daily_v46_20260423"
|
|
|
|
|
|
def test_copy_strategy_rejects_extends(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced_base", ORB_ADVANCED_CONFIG)
|
|
_write_config(tmp_path, "orb_extends_child", ORB_EXTENDS_CHILD_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
|
|
with pytest.raises(intraday.HTTPException) as exc_info:
|
|
intraday.copy_strategy("orb_extends_child")
|
|
|
|
assert exc_info.value.status_code == 400
|
|
|
|
|
|
def test_submit_intraday_backtest_uses_config_strategy_mode(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "leader_intraday_momentum")
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
monkeypatch.setattr(intraday, "get_runs_dir", lambda: tmp_path / "runs")
|
|
intraday._tasks.clear()
|
|
intraday._tasks_initialized = True
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
class DummyProc:
|
|
pid = 4242
|
|
|
|
def fake_popen(cmd, stdout=None, stderr=None, cwd=None): # noqa: ANN001
|
|
captured["cmd"] = cmd
|
|
captured["cwd"] = cwd
|
|
return DummyProc()
|
|
|
|
class DummyThread:
|
|
def __init__(self, target=None, args=None, daemon=None): # noqa: ANN001
|
|
captured["thread_target"] = target
|
|
captured["thread_args"] = args
|
|
|
|
def start(self) -> None:
|
|
captured["thread_started"] = True
|
|
|
|
monkeypatch.setattr(intraday.subprocess, "Popen", fake_popen)
|
|
monkeypatch.setattr(intraday.threading, "Thread", DummyThread)
|
|
|
|
req = intraday.IntradayBacktestRequest(
|
|
config="leader_intraday_momentum",
|
|
universe="midcap",
|
|
start_date="2026-01-02",
|
|
end_date="2026-03-31",
|
|
compound_returns=False,
|
|
)
|
|
resp = intraday.submit_intraday_backtest(req)
|
|
|
|
task = intraday._tasks[resp["task_id"]]
|
|
cmd = captured["cmd"]
|
|
|
|
assert task["strategy_mode"] == "momentum"
|
|
assert task["output_dir"] == "runs/intraday"
|
|
assert "--strategy" in cmd
|
|
assert cmd[cmd.index("--strategy") + 1] == "momentum"
|
|
assert "--output-dir" in cmd
|
|
assert cmd[cmd.index("--output-dir") + 1] == str(tmp_path / "runs" / "intraday")
|
|
|
|
|
|
def test_submit_intraday_backtest_preserves_yaml_strategy_universe(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "leader_intraday_momentum_yaml", MOMENTUM_YAML_UNIVERSE_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
monkeypatch.setattr(intraday, "get_runs_dir", lambda: tmp_path / "runs")
|
|
intraday._tasks.clear()
|
|
intraday._tasks_initialized = True
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
class DummyProc:
|
|
pid = 4242
|
|
|
|
def fake_popen(cmd, stdout=None, stderr=None, cwd=None): # noqa: ANN001
|
|
captured["cmd"] = cmd
|
|
captured["cwd"] = cwd
|
|
return DummyProc()
|
|
|
|
class DummyThread:
|
|
def __init__(self, target=None, args=None, daemon=None): # noqa: ANN001
|
|
captured["thread_target"] = target
|
|
captured["thread_args"] = args
|
|
|
|
def start(self) -> None:
|
|
captured["thread_started"] = True
|
|
|
|
monkeypatch.setattr(intraday.subprocess, "Popen", fake_popen)
|
|
monkeypatch.setattr(intraday.threading, "Thread", DummyThread)
|
|
|
|
req = intraday.IntradayBacktestRequest(
|
|
config="leader_intraday_momentum_yaml",
|
|
universe="yaml",
|
|
start_date="2026-01-02",
|
|
end_date="2026-03-31",
|
|
compound_returns=False,
|
|
)
|
|
resp = intraday.submit_intraday_backtest(req)
|
|
|
|
task = intraday._tasks[resp["task_id"]]
|
|
cmd = captured["cmd"]
|
|
|
|
assert task["universe"] == "yaml"
|
|
assert task["universe_label"] == "yaml:symbols_midcap_smallmid.yaml"
|
|
assert "--strategy" in cmd
|
|
assert cmd[cmd.index("--strategy") + 1] == "momentum"
|
|
assert "--universe" not in cmd
|
|
|
|
|
|
def test_submit_intraday_backtest_accepts_broad_universe(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "leader_intraday_momentum")
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
monkeypatch.setattr(intraday, "get_runs_dir", lambda: tmp_path / "runs")
|
|
intraday._tasks.clear()
|
|
intraday._tasks_initialized = True
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
class DummyProc:
|
|
pid = 4242
|
|
|
|
def fake_popen(cmd, stdout=None, stderr=None, cwd=None): # noqa: ANN001
|
|
captured["cmd"] = cmd
|
|
captured["cwd"] = cwd
|
|
return DummyProc()
|
|
|
|
class DummyThread:
|
|
def __init__(self, target=None, args=None, daemon=None): # noqa: ANN001
|
|
captured["thread_target"] = target
|
|
captured["thread_args"] = args
|
|
|
|
def start(self) -> None:
|
|
captured["thread_started"] = True
|
|
|
|
monkeypatch.setattr(intraday.subprocess, "Popen", fake_popen)
|
|
monkeypatch.setattr(intraday.threading, "Thread", DummyThread)
|
|
|
|
req = intraday.IntradayBacktestRequest(
|
|
config="leader_intraday_momentum",
|
|
universe="broad",
|
|
start_date="2026-01-02",
|
|
end_date="2026-03-31",
|
|
compound_returns=False,
|
|
)
|
|
resp = intraday.submit_intraday_backtest(req)
|
|
|
|
task = intraday._tasks[resp["task_id"]]
|
|
cmd = captured["cmd"]
|
|
|
|
assert task["universe"] == "broad"
|
|
assert task["universe_label"] == "broad"
|
|
assert "--universe" in cmd
|
|
assert cmd[cmd.index("--universe") + 1] == "broad"
|
|
|
|
|
|
def test_submit_intraday_backtest_records_command_and_compound_flags(monkeypatch, tmp_path: Path) -> None:
|
|
_write_config(tmp_path, "orb_advanced", ORB_ADVANCED_CONFIG)
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
monkeypatch.setattr(intraday, "get_runs_dir", lambda: tmp_path / "runs")
|
|
intraday._tasks.clear()
|
|
intraday._tasks_initialized = True
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
class DummyProc:
|
|
pid = 4242
|
|
|
|
def fake_popen(cmd, stdout=None, stderr=None, cwd=None): # noqa: ANN001
|
|
captured["cmd"] = cmd
|
|
captured["cwd"] = cwd
|
|
return DummyProc()
|
|
|
|
class DummyThread:
|
|
def __init__(self, target=None, args=None, daemon=None): # noqa: ANN001
|
|
captured["thread_target"] = target
|
|
captured["thread_args"] = args
|
|
|
|
def start(self) -> None:
|
|
captured["thread_started"] = True
|
|
|
|
monkeypatch.setattr(intraday.subprocess, "Popen", fake_popen)
|
|
monkeypatch.setattr(intraday.threading, "Thread", DummyThread)
|
|
|
|
req = intraday.IntradayBacktestRequest(
|
|
config="orb_advanced",
|
|
universe="midlarge",
|
|
days=200,
|
|
compound_returns=True,
|
|
daily_budget_reset=False,
|
|
)
|
|
resp = intraday.submit_intraday_backtest(req)
|
|
|
|
task = intraday._tasks[resp["task_id"]]
|
|
cmd = captured["cmd"]
|
|
log_text = (tmp_path / "runs" / ".intraday_tasks" / f"{resp['task_id']}.log").read_text()
|
|
|
|
assert task["command"] == cmd
|
|
assert "--compound-returns" in cmd
|
|
assert "--no-daily-budget-reset" in cmd
|
|
assert "Command:" in log_text
|
|
assert "--compound-returns" in log_text
|
|
assert "--no-daily-budget-reset" in log_text
|
|
|
|
|
|
def test_result_summary_from_file_includes_loss_containment_fields(tmp_path: Path) -> None:
|
|
result_path = tmp_path / "intraday_result.json"
|
|
result_path.write_text(
|
|
"""{
|
|
"metrics": {
|
|
"total_return_pct": 0.0688,
|
|
"max_drawdown_pct": -0.1325,
|
|
"sharpe_ratio": 1.23,
|
|
"win_rate": 0.569,
|
|
"total_trades": 116,
|
|
"final_equity": 10688.0,
|
|
"calmar_ratio": 0.52,
|
|
"loss_containment_score": 43.78,
|
|
"avg_loss_day_pct": -0.0168,
|
|
"tail_loss_20_pct": -0.0377,
|
|
"worst_day_return_pct": -0.0672
|
|
}
|
|
}"""
|
|
)
|
|
|
|
summary = intraday._result_summary_from_file(result_path)
|
|
|
|
assert summary is not None
|
|
assert summary["return_pct"] == 0.0688
|
|
assert summary["loss_containment_score"] == 43.78
|
|
assert summary["avg_loss_day_pct"] == -0.0168
|
|
assert summary["tail_loss_20_pct"] == -0.0377
|
|
assert summary["worst_day_return_pct"] == -0.0672
|
|
|
|
|
|
def test_detect_result_file_from_log_prefers_explicit_saved_path(tmp_path: Path) -> None:
|
|
good = tmp_path / "runs" / "intraday_orb" / "intraday_good.json"
|
|
good.parent.mkdir(parents=True, exist_ok=True)
|
|
good.write_text("{}")
|
|
|
|
bad = tmp_path / "runs" / "intraday_orb" / "intraday_bad.json"
|
|
bad.write_text("{}")
|
|
|
|
log_path = tmp_path / "runs" / ".intraday_tasks" / "task.log"
|
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
log_path.write_text(
|
|
"\n".join([
|
|
f"Results saved to: {bad}",
|
|
f"Results saved to: {good}",
|
|
])
|
|
)
|
|
|
|
assert intraday._detect_result_file_from_log(log_path) == good
|
|
|
|
|
|
def test_try_resolve_dead_task_repairs_completed_result_from_log(monkeypatch, tmp_path: Path) -> None:
|
|
monkeypatch.setattr(intraday, "get_project_root", lambda: tmp_path)
|
|
monkeypatch.setattr(intraday, "get_runs_dir", lambda: tmp_path / "runs")
|
|
intraday._tasks.clear()
|
|
intraday._tasks_initialized = True
|
|
|
|
results_dir = tmp_path / "runs" / "intraday_orb"
|
|
results_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
wrong_result = results_dir / "intraday_wrong.json"
|
|
wrong_result.write_text(
|
|
json.dumps({"metrics": {"total_return_pct": -0.1177, "max_drawdown_pct": -0.1765}})
|
|
)
|
|
right_result = results_dir / "intraday_right.json"
|
|
right_result.write_text(
|
|
json.dumps({"metrics": {"total_return_pct": 1.302, "max_drawdown_pct": -0.11}})
|
|
)
|
|
|
|
task_id = "task-123"
|
|
log_path = tmp_path / "runs" / ".intraday_tasks" / f"{task_id}.log"
|
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
log_path.write_text(f"Results saved to: {right_result}\n")
|
|
|
|
intraday._tasks[task_id] = {
|
|
"task_id": task_id,
|
|
"status": "completed",
|
|
"pid": None,
|
|
"started_at": "2026-04-22T23:28:35.765827+00:00",
|
|
"finished_at": "2026-04-23T00:33:45.996122+00:00",
|
|
"returncode": None,
|
|
"error": None,
|
|
"result_file": str(wrong_result),
|
|
"result_summary": {"return_pct": -0.1177, "max_dd_pct": -0.1765},
|
|
"output_dir": "runs/intraday_orb",
|
|
}
|
|
intraday._persist_task(intraday._tasks[task_id])
|
|
|
|
intraday._try_resolve_dead_task(task_id)
|
|
|
|
task = intraday._tasks[task_id]
|
|
persisted = json.loads((tmp_path / "runs" / ".intraday_tasks" / f"{task_id}.task.json").read_text())
|
|
|
|
assert task["result_file"] == str(right_result)
|
|
assert task["result_summary"]["return_pct"] == 1.302
|
|
assert task["result_summary"]["max_dd_pct"] == -0.11
|
|
assert task["returncode"] == 0
|
|
assert persisted["result_file"] == str(right_result)
|
|
assert persisted["result_summary"]["return_pct"] == 1.302
|