155 lines
4.4 KiB
Python
155 lines
4.4 KiB
Python
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from indian_paper_trading_strategy.engine.db import db_connection, insert_engine_event, run_with_retry, get_context
|
|
from indian_paper_trading_strategy.engine.time_utils import normalize_logical_time
|
|
|
|
ENGINE_ROOT = Path(__file__).resolve().parents[1]
|
|
STORAGE_DIR = ENGINE_ROOT / "storage"
|
|
MTM_FILE = STORAGE_DIR / "mtm_ledger.csv"
|
|
|
|
MTM_INTERVAL_SECONDS = 60
|
|
|
|
def _log_mtm_in_tx(
|
|
cur,
|
|
nifty_units,
|
|
gold_units,
|
|
nifty_price,
|
|
gold_price,
|
|
total_invested,
|
|
ts,
|
|
logical_time=None,
|
|
user_id: str | None = None,
|
|
run_id: str | None = None,
|
|
):
|
|
scope_user, scope_run = get_context(user_id, run_id)
|
|
logical_ts = normalize_logical_time(logical_time or ts)
|
|
nifty_value = nifty_units * nifty_price
|
|
gold_value = gold_units * gold_price
|
|
portfolio_value = nifty_value + gold_value
|
|
pnl = portfolio_value - total_invested
|
|
|
|
row = {
|
|
"timestamp": ts.isoformat(),
|
|
"logical_time": logical_ts.isoformat(),
|
|
"nifty_units": nifty_units,
|
|
"gold_units": gold_units,
|
|
"nifty_price": nifty_price,
|
|
"gold_price": gold_price,
|
|
"nifty_value": nifty_value,
|
|
"gold_value": gold_value,
|
|
"portfolio_value": portfolio_value,
|
|
"total_invested": total_invested,
|
|
"pnl": pnl,
|
|
}
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO mtm_ledger (
|
|
user_id,
|
|
run_id,
|
|
timestamp,
|
|
logical_time,
|
|
nifty_units,
|
|
gold_units,
|
|
nifty_price,
|
|
gold_price,
|
|
nifty_value,
|
|
gold_value,
|
|
portfolio_value,
|
|
total_invested,
|
|
pnl
|
|
)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
ON CONFLICT DO NOTHING
|
|
""",
|
|
(
|
|
scope_user,
|
|
scope_run,
|
|
ts,
|
|
logical_ts,
|
|
row["nifty_units"],
|
|
row["gold_units"],
|
|
row["nifty_price"],
|
|
row["gold_price"],
|
|
row["nifty_value"],
|
|
row["gold_value"],
|
|
row["portfolio_value"],
|
|
row["total_invested"],
|
|
row["pnl"],
|
|
),
|
|
)
|
|
if cur.rowcount:
|
|
insert_engine_event(cur, "MTM_UPDATED", data=row, ts=ts)
|
|
return portfolio_value, pnl
|
|
|
|
def log_mtm(
|
|
nifty_units,
|
|
gold_units,
|
|
nifty_price,
|
|
gold_price,
|
|
total_invested,
|
|
*,
|
|
cur=None,
|
|
logical_time=None,
|
|
user_id: str | None = None,
|
|
run_id: str | None = None,
|
|
):
|
|
ts = logical_time or datetime.now(timezone.utc)
|
|
if cur is not None:
|
|
return _log_mtm_in_tx(
|
|
cur,
|
|
nifty_units,
|
|
gold_units,
|
|
nifty_price,
|
|
gold_price,
|
|
total_invested,
|
|
ts,
|
|
logical_time=logical_time,
|
|
user_id=user_id,
|
|
run_id=run_id,
|
|
)
|
|
|
|
def _op(cur, _conn):
|
|
return _log_mtm_in_tx(
|
|
cur,
|
|
nifty_units,
|
|
gold_units,
|
|
nifty_price,
|
|
gold_price,
|
|
total_invested,
|
|
ts,
|
|
logical_time=logical_time,
|
|
user_id=user_id,
|
|
run_id=run_id,
|
|
)
|
|
|
|
return run_with_retry(_op)
|
|
|
|
def _get_last_mtm_ts(user_id: str | None = None, run_id: str | None = None):
|
|
scope_user, scope_run = get_context(user_id, run_id)
|
|
with db_connection() as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"SELECT MAX(timestamp) FROM mtm_ledger WHERE user_id = %s AND run_id = %s",
|
|
(scope_user, scope_run),
|
|
)
|
|
row = cur.fetchone()
|
|
if not row or row[0] is None:
|
|
return None
|
|
return row[0].astimezone().replace(tzinfo=None)
|
|
|
|
def should_log_mtm(df, now, user_id: str | None = None, run_id: str | None = None):
|
|
if df is None:
|
|
last_ts = _get_last_mtm_ts(user_id=user_id, run_id=run_id)
|
|
if last_ts is None:
|
|
return True
|
|
return (now - last_ts).total_seconds() >= MTM_INTERVAL_SECONDS
|
|
if getattr(df, "empty", False):
|
|
return True
|
|
try:
|
|
last_ts = datetime.fromisoformat(str(df.iloc[-1]["timestamp"]))
|
|
except Exception:
|
|
return True
|
|
return (now - last_ts).total_seconds() >= MTM_INTERVAL_SECONDS
|
|
|