SIP_GoldBees_Backend/backend/app/services/paper_broker_service.py
2026-02-01 13:57:30 +00:00

192 lines
6.0 KiB
Python

import os
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[3]
if str(PROJECT_ROOT) not in sys.path:
sys.path.append(str(PROJECT_ROOT))
from indian_paper_trading_strategy.engine.broker import PaperBroker
from indian_paper_trading_strategy.engine.state import load_state, save_state
from indian_paper_trading_strategy.engine.db import engine_context, insert_engine_event
from app.services.db import run_with_retry
from app.services.run_service import get_active_run_id, get_running_run_id
_logged_path = False
def _broker():
global _logged_path
state = load_state(mode="PAPER")
initial_cash = float(state.get("initial_cash", 0))
broker = PaperBroker(initial_cash=initial_cash)
if not _logged_path:
_logged_path = True
print(
"PaperBroker store path:",
{
"cwd": os.getcwd(),
"paper_store_path": str(broker.store_path) if hasattr(broker, "store_path") else "NO_STORE_PATH",
"abs_store_path": os.path.abspath(str(broker.store_path)) if hasattr(broker, "store_path") else "N/A",
},
)
return broker
def get_paper_broker(user_id: str):
run_id = get_active_run_id(user_id)
with engine_context(user_id, run_id):
return _broker()
def get_funds(user_id: str):
run_id = get_active_run_id(user_id)
with engine_context(user_id, run_id):
return _broker().get_funds()
def get_positions(user_id: str):
run_id = get_active_run_id(user_id)
with engine_context(user_id, run_id):
positions = _broker().get_positions()
enriched = []
for item in positions:
qty = float(item.get("qty", 0))
avg = float(item.get("avg_price", 0))
ltp = float(item.get("last_price", 0))
pnl = (ltp - avg) * qty
pnl_pct = ((ltp - avg) / avg * 100) if avg else 0.0
enriched.append(
{
**item,
"pnl": pnl,
"pnl_pct": pnl_pct,
}
)
return enriched
def get_orders(user_id: str):
run_id = get_active_run_id(user_id)
with engine_context(user_id, run_id):
return _broker().get_orders()
def get_trades(user_id: str):
run_id = get_active_run_id(user_id)
with engine_context(user_id, run_id):
return _broker().get_trades()
def get_equity_curve(user_id: str):
run_id = get_active_run_id(user_id)
with engine_context(user_id, run_id):
broker = _broker()
points = broker.get_equity_curve()
if not points:
return []
state = load_state(mode="PAPER")
initial_cash = float(state.get("initial_cash", 0))
response = []
for point in points:
equity = float(point.get("equity", 0))
pnl = point.get("pnl")
if pnl is None:
pnl = equity - float(initial_cash)
response.append(
{
"timestamp": point.get("timestamp"),
"equity": equity,
"pnl": float(pnl),
}
)
return response
def add_cash(user_id: str, amount: float):
if amount <= 0:
raise ValueError("Amount must be positive")
run_id = get_running_run_id(user_id)
if not run_id:
raise ValueError("Strategy must be running to add cash")
def _op(cur, _conn):
with engine_context(user_id, run_id):
state = load_state(mode="PAPER", cur=cur, for_update=True)
initial_cash = float(state.get("initial_cash", 0))
broker = PaperBroker(initial_cash=initial_cash)
store = broker._load_store(cur=cur, for_update=True)
cash = float(store.get("cash", 0)) + amount
store["cash"] = cash
broker._save_store(store, cur=cur)
state["cash"] = cash
state["initial_cash"] = initial_cash + amount
state["total_invested"] = float(state.get("total_invested", 0)) + amount
save_state(
state,
mode="PAPER",
cur=cur,
emit_event=True,
event_meta={"source": "add_cash"},
)
insert_engine_event(
cur,
"CASH_ADDED",
data={"amount": amount, "cash": cash},
)
return state
return run_with_retry(_op)
def reset_paper_state(user_id: str):
run_id = get_active_run_id(user_id)
def _op(cur, _conn):
with engine_context(user_id, run_id):
cur.execute(
"DELETE FROM strategy_log WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM engine_event WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM paper_equity_curve WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM paper_trade WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM paper_order WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM paper_position WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM paper_broker_account WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM mtm_ledger WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM event_ledger WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
cur.execute(
"DELETE FROM engine_state_paper WHERE user_id = %s AND run_id = %s",
(user_id, run_id),
)
insert_engine_event(cur, "PAPER_RESET", data={})
run_with_retry(_op)