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.

126 lines
4.9 KiB
Python

"""Direct backtest subprocess runner.
Invoked by the web server as a subprocess:
python -m apps.web.direct_runner TASK_ID CONFIG_PATH CAPITAL START_DATE END_DATE RESULT_FILE [--parking PRESET] [--idle-alpha PRESET] [--form4-sleeve PRESET] [--ownership-sleeve PRESET] [--risk-off-sleeve PRESET] [--non-core-allocator-v2-mode MODE]
Runs run_backtest_session_sync() and saves the result JSON to RESULT_FILE.
Exits 0 on success, non-zero on failure.
"""
from __future__ import annotations
import datetime as dt
import json
import sys
from pathlib import Path
def _json_default(obj):
if isinstance(obj, (dt.date, dt.datetime)):
return obj.isoformat()
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
def main():
if len(sys.argv) < 7:
print(
"Usage: direct_runner TASK_ID CONFIG_PATH CAPITAL START_DATE END_DATE RESULT_FILE [--parking PRESET] [--idle-alpha PRESET] [--form4-sleeve PRESET] [--ownership-sleeve PRESET] [--risk-off-sleeve PRESET] [--non-core-allocator-v2-mode MODE]",
file=sys.stderr,
)
sys.exit(2)
task_id = sys.argv[1]
config_path = sys.argv[2]
capital = float(sys.argv[3])
start_date = dt.date.fromisoformat(sys.argv[4])
end_date = dt.date.fromisoformat(sys.argv[5])
result_file = Path(sys.argv[6])
# Optional flags
parking_preset = None
idle_alpha_preset = None
form4_sleeve_preset = None
ownership_sleeve_preset = None
risk_off_alpha_sleeve_preset = None
non_core_allocator_v2_mode = None
snapshot_id_override = None
fixed_capital = False
daily_budget_reset = False
remaining = sys.argv[7:]
i = 0
while i < len(remaining):
if remaining[i] == "--parking" and i + 1 < len(remaining):
parking_preset = remaining[i + 1]
i += 2
elif remaining[i] == "--idle-alpha" and i + 1 < len(remaining):
idle_alpha_preset = remaining[i + 1]
i += 2
elif remaining[i] == "--form4-sleeve" and i + 1 < len(remaining):
form4_sleeve_preset = remaining[i + 1]
i += 2
elif remaining[i] == "--ownership-sleeve" and i + 1 < len(remaining):
ownership_sleeve_preset = remaining[i + 1]
i += 2
elif remaining[i] == "--risk-off-sleeve" and i + 1 < len(remaining):
risk_off_alpha_sleeve_preset = remaining[i + 1]
i += 2
elif remaining[i] == "--non-core-allocator-v2-mode" and i + 1 < len(remaining):
non_core_allocator_v2_mode = remaining[i + 1]
i += 2
elif remaining[i] == "--snapshot-id" and i + 1 < len(remaining):
snapshot_id_override = remaining[i + 1]
i += 2
elif remaining[i] == "--fixed-capital":
fixed_capital = True
i += 1
elif remaining[i] == "--daily-budget-reset":
daily_budget_reset = True
i += 1
else:
i += 1
print(f"[direct] {task_id} · {Path(config_path).stem} · {start_date}{end_date}" +
(f" · parking={parking_preset}" if parking_preset else "") +
(f" · idle_alpha={idle_alpha_preset}" if idle_alpha_preset else "") +
(f" · form4={form4_sleeve_preset}" if form4_sleeve_preset else "") +
(f" · ownership={ownership_sleeve_preset}" if ownership_sleeve_preset else "") +
(f" · risk_off={risk_off_alpha_sleeve_preset}" if risk_off_alpha_sleeve_preset else "") +
(f" · ncav2={non_core_allocator_v2_mode}" if non_core_allocator_v2_mode else "") +
(f" · snapshot={snapshot_id_override}" if snapshot_id_override else "") +
(" · fixed_capital" if fixed_capital else "") +
(" · daily_budget_reset" if daily_budget_reset else ""))
sys.stdout.flush()
from apps.paper_trader.backtest_sim import run_backtest_session_sync
result = run_backtest_session_sync(
session_name=Path(config_path).stem,
config_path=config_path,
initial_equity=capital,
start_date=start_date,
end_date=end_date,
parking_preset=parking_preset,
idle_alpha_preset=idle_alpha_preset,
form4_sleeve_preset=form4_sleeve_preset,
ownership_sleeve_preset=ownership_sleeve_preset,
risk_off_alpha_sleeve_preset=risk_off_alpha_sleeve_preset,
non_core_allocator_v2_mode=non_core_allocator_v2_mode,
snapshot_id_override=snapshot_id_override,
fixed_capital_sizing=fixed_capital,
daily_budget_reset=daily_budget_reset,
)
result_file.parent.mkdir(parents=True, exist_ok=True)
result_file.write_text(json.dumps(result, default=_json_default, indent=2))
s = result.get("summary", {})
print(
f"[direct] done · return={s.get('return_pct', 0):+.2f}% "
f"trades={s.get('trade_count', 0)} "
f"sharpe={s.get('sharpe', 0):.2f}"
)
sys.stdout.flush()
if __name__ == "__main__":
main()