- 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>
81 lines
2.4 KiB
Python
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", {}),
|
|
}
|