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

81 lines
2.4 KiB
Python

from typing import Any, Dict
from pathlib import Path
import sys
from fastapi import APIRouter, Request
from app.services.paper_broker_service import get_paper_broker
from app.services.tenant import get_request_user_id
from app.services.run_service import get_active_run_id
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.append(str(PROJECT_ROOT))
from indian_paper_trading_strategy.engine.db import engine_context
from indian_paper_trading_strategy.engine.data import fetch_live_price
from market import get_ltp
from indian_paper_trading_strategy.engine.state import load_state
router = APIRouter(prefix="/api/paper", tags=["paper-mtm"])
@router.get("/mtm")
def paper_mtm(request: Request) -> Dict[str, Any]:
user_id = get_request_user_id(request)
run_id = get_active_run_id(user_id, mode="PAPER")
with engine_context(user_id, run_id):
broker = get_paper_broker(user_id)
positions = broker.get_positions()
state = load_state(mode="PAPER")
cash = float(state.get("cash", 0))
initial_cash = float(state.get("initial_cash", 0))
ltp_payload = get_ltp(allow_cache=True)
ltp_map = ltp_payload["ltp"]
mtm_positions = []
positions_value = 0.0
for pos in positions:
symbol = pos.get("symbol")
if not symbol:
continue
qty = float(pos.get("qty", 0))
avg_price = float(pos.get("avg_price", 0))
ltp = ltp_map.get(symbol)
if ltp is None:
try:
ltp = fetch_live_price(symbol, allow_cache=True)
except Exception:
ltp = float(pos.get("last_price") or pos.get("avg_price") or 0)
pnl = (ltp - avg_price) * qty
positions_value += qty * ltp
mtm_positions.append(
{
"symbol": symbol,
"qty": qty,
"avg_price": avg_price,
"ltp": ltp,
"pnl": pnl,
}
)
equity = cash + positions_value
unrealized_pnl = equity - float(initial_cash)
return {
"ts": ltp_payload["ts"],
"initial_cash": initial_cash,
"cash": cash,
"positions_value": positions_value,
"equity": equity,
"unrealized_pnl": unrealized_pnl,
"positions": mtm_positions,
"price_stale": ltp_payload.get("stale_any", False),
"price_source": ltp_payload.get("source", {}),
}