Thigazhezhilan J 10e262231f feat: make paper and live trading fully independent
- start_strategy filters running check by mode so starting LIVE
  won't clash with an active PAPER run and vice versa
- stop_strategy and resume_strategy accept optional mode param
  so each tab stops/resumes only its own run
- paper_broker_service scopes all run lookups to mode=PAPER
- paper_mtm scopes run lookup to mode=PAPER
- routers/strategy exposes ?mode= query param on /stop and /resume
- run_service get_active_run_id and get_running_run_id already
  support mode filtering (added in previous session)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 22:11:29 +05:30

203 lines
5.6 KiB
Python

import threading
from datetime import datetime, timezone
from uuid import uuid4
from psycopg2.extras import Json
from app.services.db import run_with_retry
_DEFAULT_USER_ID = None
_DEFAULT_LOCK = threading.Lock()
def _utc_now():
return datetime.now(timezone.utc)
def get_default_user_id():
global _DEFAULT_USER_ID
if _DEFAULT_USER_ID:
return _DEFAULT_USER_ID
def _op(cur, _conn):
cur.execute("SELECT id FROM app_user ORDER BY username LIMIT 1")
row = cur.fetchone()
return row[0] if row else None
user_id = run_with_retry(_op)
if user_id:
with _DEFAULT_LOCK:
_DEFAULT_USER_ID = user_id
return user_id
def _default_run_id(user_id: str) -> str:
return f"default_{user_id}"
def ensure_default_run(user_id: str):
run_id = _default_run_id(user_id)
def _op(cur, _conn):
now = _utc_now()
cur.execute(
"""
INSERT INTO strategy_run (
run_id, user_id, created_at, started_at, stopped_at, status, strategy, mode, broker, meta
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (run_id) DO NOTHING
""",
(
run_id,
user_id,
now,
None,
None,
"STOPPED",
None,
None,
None,
Json({}),
),
)
return run_id
return run_with_retry(_op)
def get_active_run_id(user_id: str, mode: str | None = None):
def _op(cur, _conn):
if mode:
cur.execute(
"""
SELECT run_id FROM strategy_run
WHERE user_id = %s AND status = 'RUNNING' AND UPPER(mode) = %s
ORDER BY created_at DESC LIMIT 1
""",
(user_id, mode.strip().upper()),
)
else:
cur.execute(
"""
SELECT run_id FROM strategy_run
WHERE user_id = %s AND status = 'RUNNING'
ORDER BY created_at DESC LIMIT 1
""",
(user_id,),
)
row = cur.fetchone()
if row:
return row[0]
# Fallback: most recent run of same mode (or any mode if mode not specified)
if mode:
cur.execute(
"""
SELECT run_id FROM strategy_run
WHERE user_id = %s AND UPPER(mode) = %s
ORDER BY created_at DESC LIMIT 1
""",
(user_id, mode.strip().upper()),
)
else:
cur.execute(
"""
SELECT run_id FROM strategy_run
WHERE user_id = %s
ORDER BY created_at DESC LIMIT 1
""",
(user_id,),
)
row = cur.fetchone()
if row:
return row[0]
return None
run_id = run_with_retry(_op)
if run_id:
return run_id
return ensure_default_run(user_id)
def get_running_run_id(user_id: str, mode: str | None = None):
def _op(cur, _conn):
if mode:
cur.execute(
"""
SELECT run_id FROM strategy_run
WHERE user_id = %s AND status = 'RUNNING' AND UPPER(mode) = %s
ORDER BY created_at DESC LIMIT 1
""",
(user_id, mode.strip().upper()),
)
else:
cur.execute(
"""
SELECT run_id FROM strategy_run
WHERE user_id = %s AND status = 'RUNNING'
ORDER BY created_at DESC LIMIT 1
""",
(user_id,),
)
row = cur.fetchone()
return row[0] if row else None
return run_with_retry(_op)
def create_strategy_run(user_id: str, strategy: str | None, mode: str | None, broker: str | None, meta: dict | None):
run_id = str(uuid4())
def _op(cur, _conn):
now = _utc_now()
cur.execute(
"""
INSERT INTO strategy_run (
run_id, user_id, created_at, started_at, stopped_at, status, strategy, mode, broker, meta
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""",
(
run_id,
user_id,
now,
now,
None,
"RUNNING",
strategy,
mode,
broker,
Json(meta or {}),
),
)
return run_id
return run_with_retry(_op)
def update_run_status(user_id: str, run_id: str, status: str, meta: dict | None = None):
def _op(cur, _conn):
now = _utc_now()
if status == "RUNNING":
cur.execute(
"""
UPDATE strategy_run
SET status = %s, started_at = COALESCE(started_at, %s), stopped_at = NULL,
meta = COALESCE(meta, '{}'::jsonb) || %s
WHERE run_id = %s AND user_id = %s
""",
(status, now, Json(meta or {}), run_id, user_id),
)
else:
cur.execute(
"""
UPDATE strategy_run
SET status = %s, stopped_at = %s, meta = COALESCE(meta, '{}'::jsonb) || %s
WHERE run_id = %s AND user_id = %s
""",
(status, now, Json(meta or {}), run_id, user_id),
)
return cur.rowcount > 0
return run_with_retry(_op)