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.

80 lines
3.0 KiB
Python

"""ORB auto-scheduler standalone daemon.
Runs independently of the web server so HMR/server restarts don't kill the scheduler.
Started and stopped by the web API via subprocess management.
Usage (usually via web API, but can be run manually):
python -m apps.orb_trader.daemon --db-path data/paper/orb.db
python -m apps.orb_trader.daemon --db-path data/paper/orb.db --sessions "Default,Session2"
python -m apps.orb_trader.daemon --db-path data/paper/orb.db --dry-run
"""
from __future__ import annotations
import argparse
import asyncio
import json
import logging
import time
from pathlib import Path
def main() -> None:
parser = argparse.ArgumentParser(description="ORB auto-scheduler daemon")
parser.add_argument("--sessions", default="",
help="Comma-separated session names (empty = all active)")
parser.add_argument("--db-path", required=True, help="Path to ORB SQLite DB")
parser.add_argument("--dry-run", action="store_true", help="Dry run (no real orders)")
args = parser.parse_args()
# Route Python logger output to a file so engine logs are not lost
# (daemon's stdout/stderr are /dev/null when started from the web server)
log_file = Path(args.db_path).parent / "orb_daemon.log"
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s"))
logging.root.addHandler(file_handler)
logging.root.setLevel(logging.INFO)
sessions = [s.strip() for s in args.sessions.split(",") if s.strip()]
trigger_dir = Path(args.db_path).parent
async def _run() -> None:
from apps.web.orb_trading_service import ORBAutoScheduler
sched = ORBAutoScheduler()
sched.start(sessions=sessions, db_path=args.db_path, dry_run=args.dry_run)
async def _poll_triggers() -> None:
"""Pick up run_session_now commands written by the web API."""
while True:
await asyncio.sleep(3)
for tf in sorted(trigger_dir.glob(".orb_trigger_*.json")):
try:
raw = tf.read_text()
tf.unlink()
cmd = json.loads(raw)
# Ignore stale triggers (> 60 s old)
if time.time() - cmd.get("ts", 0) > 60:
continue
if cmd.get("command") == "run_session_now":
session = cmd.get("session", "")
if session:
asyncio.create_task(sched.run_session_now(session))
except Exception:
pass
asyncio.create_task(_poll_triggers())
try:
if sched._task:
await sched._task
except asyncio.CancelledError:
pass
try:
asyncio.run(_run())
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()