192 lines
6.0 KiB
Python
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)
|