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>
This commit is contained in:
2026-09-25 11:35:59 +08:00
parent 6dc623b4a1
commit f6b46d4d68
5 changed files with 585 additions and 0 deletions

116
scripts/run_daily.py Normal file
View File

@@ -0,0 +1,116 @@
"""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())

21
scripts/run_daily.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Daily live-trading update entrypoint for cron.
#
# Examples:
# 0 17 * * 1-5 /path/to/quant/scripts/run_daily.sh us >> /var/log/quant_us.log 2>&1
# 0 16 * * 1-5 /path/to/quant/scripts/run_daily.sh cn >> /var/log/quant_cn.log 2>&1
#
# The script changes into the quant project root and calls run_daily.py via uv.
# Defaults to "all" markets when no argument is passed.
set -euo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
cd "$PROJECT_ROOT"
MARKET="${1:-all}"
shift || true
exec uv run python scripts/run_daily.py --market "$MARKET" "$@"

23
scripts/start-monitor.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Long-running live-trader monitor (US + CN, all strategies).
# Started by the quant-monitor.service systemd --user unit; replaces the
# old "tmux new -s quant; uv run python trader.py monitor" workflow.
set -euo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
cd "$PROJECT_ROOT"
# uv lives in ~/.local/bin, which is not on systemd's default PATH.
export PATH="/home/gahow/.local/bin:$PATH"
# Market data is fetched through the lab proxy (same as interactive `proxyOn`).
# QUANT_MARKET_PROXY lives in the untracked repo-root .env.
if [[ -f .env ]]; then
set -a; source .env; set +a
fi
if [[ -n "${QUANT_MARKET_PROXY:-}" ]]; then
export http_proxy="$QUANT_MARKET_PROXY" https_proxy="$QUANT_MARKET_PROXY" all_proxy="$QUANT_MARKET_PROXY"
fi
exec uv run python trader.py monitor