Files
quant-trading/scripts/run_daily.py
Gahow Wang f6b46d4d68 chore: track precompute and ops scripts; move monitor proxy into .env
The monitor start script used to embed the proxy URL with credentials,
which kept scripts/ out of git. It now reads QUANT_MARKET_PROXY from the
untracked repo-root .env.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 11:35:59 +08:00

117 lines
4.1 KiB
Python

"""Daily live-trading update for all registered strategies.
Iterates STRATEGY_REGISTRY and calls trader.cmd_auto for each, sharing
fetched price panels across strategies (we still call cmd_auto so the
state-file format stays consistent with manual runs).
Usage:
uv run python scripts/run_daily.py [--market us|cn|all] [--capital 10000]
[--only NAME ...] [--skip NAME ...]
Designed for cron:
# 5pm ET weekdays, after US close
0 17 * * 1-5 cd /path/to/quant && uv run python scripts/run_daily.py --market us
# 4pm CST weekdays, after CN close
0 16 * * 1-5 cd /path/to/quant && uv run python scripts/run_daily.py --market cn
"""
from __future__ import annotations
import argparse
import os
import sys
import time
import types
from datetime import datetime
# Make the project root importable when invoked as `python scripts/run_daily.py`
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
# We change into the project root so trader's relative `data/` paths resolve.
os.chdir(PROJECT_ROOT)
from trader import ( # noqa: E402
ETF_STRATEGY_UNIVERSES,
STRATEGY_REGISTRY,
cmd_auto,
)
from bridge import STRATEGY_META # noqa: E402
def _strategy_markets(name: str) -> list[str]:
"""Return the markets a strategy targets (per its META declaration)."""
meta = STRATEGY_META.get(name, {"markets": ["us"]})
return list(meta.get("markets") or ["us"])
def _make_args(market: str, name: str, capital: float) -> argparse.Namespace:
"""Build the argparse namespace cmd_auto expects."""
return argparse.Namespace(
market=market,
strategy=name,
capital=capital,
tx_cost=0.001,
fixed_fee=0.0, # cmd_auto falls back to MARKET_FEES + tier
integer_shares=True, # match the existing live state convention
)
def main() -> int:
parser = argparse.ArgumentParser(description="Daily live-trading update")
parser.add_argument("--market", default="all",
help="Which market(s): 'us', 'cn', 'all' (default: all)")
parser.add_argument("--capital", type=float, default=10_000,
help="Initial capital for any newly-initialized strategy (default: 10000)")
parser.add_argument("--only", nargs="+", default=None,
help="If set, only run these strategy names")
parser.add_argument("--skip", nargs="+", default=None,
help="Strategy names to skip (e.g. broken ones)")
parser.add_argument("--dry-run", action="store_true",
help="Print what would be run without executing")
args = parser.parse_args()
markets = ["us", "cn"] if args.market == "all" else [args.market]
only = set(args.only) if args.only else None
skip = set(args.skip) if args.skip else set()
plan: list[tuple[str, str]] = []
for name in STRATEGY_REGISTRY:
if only and name not in only:
continue
if name in skip:
continue
for mk in _strategy_markets(name):
if mk in markets:
plan.append((mk, name))
print(f"[run_daily] {datetime.now().isoformat(timespec='seconds')} "
f"will process {len(plan)} (market, strategy) pairs")
if args.dry_run:
for mk, name in plan:
print(f" [DRY] {mk:<3} {name}")
return 0
failures = []
t0 = time.time()
for mk, name in plan:
ns = _make_args(mk, name, args.capital)
try:
cmd_auto(ns)
except SystemExit:
raise
except Exception as e: # noqa: BLE001
failures.append((mk, name, repr(e)))
print(f"[run_daily] FAILED {mk} {name}: {e}", file=sys.stderr)
elapsed = time.time() - t0
print(f"[run_daily] done in {elapsed:.1f}s — {len(plan) - len(failures)} ok, {len(failures)} failed")
for mk, name, err in failures:
print(f" fail {mk} {name}: {err}", file=sys.stderr)
return 0 if not failures else 1
if __name__ == "__main__":
sys.exit(main())