Files
trading-agent/web/views/common.py
T
Aryan Singh e1ac514f30 Add dynamic agent registry and options mandate
Introduces a dashboard-managed agent registry (`state/agents.json`) used by nightly research and market-open execution, including support for custom/disabled agents and per-agent time budgets. Expands personas to four agents (adds Maverick), shifts pick targets to include mandatory options, and updates prompts/finalization nudges to enforce option research without hard-failing runs.

Strengthens execution safety by adding option-specific guardrails (conviction floor, premium cap, reserved option slot through trade/day/buying-power caps), validates OCC option symbols in schemas, and normalizes invalid option direction cases. The dashboard was refactored into modular views, adds an Agents admin tab, and improves live portfolio visibility with Alpaca-first data and snapshot fallback.
2026-07-07 22:00:20 -05:00

104 lines
3.2 KiB
Python

"""Shared helpers for the dashboard views: DB access, live Alpaca overview, date listing,
OCC symbol parsing."""
from __future__ import annotations
import re
import sqlite3
import pandas as pd
import streamlit as st
from config.settings import get_settings
S = get_settings()
_OCC_RE = re.compile(r"^([A-Z]+)(\d{6})([CP])(\d{8})$")
def _con() -> sqlite3.Connection | None:
if not S.db_path.exists():
return None
con = sqlite3.connect(str(S.db_path))
con.row_factory = sqlite3.Row
return con
def query_df(sql: str, params: tuple = ()) -> pd.DataFrame:
con = _con()
if con is None:
return pd.DataFrame()
try:
return pd.read_sql_query(sql, con, params=params)
except Exception:
return pd.DataFrame()
finally:
con.close()
def trade_dates() -> list[str]:
if not S.output_dir.exists():
return []
return sorted((p.name for p in S.output_dir.iterdir() if p.is_dir()), reverse=True)
@st.cache_data(ttl=60, show_spinner=False)
def live_overview() -> tuple[pd.DataFrame, pd.DataFrame]:
"""Live per-persona account + position data straight from Alpaca (60s cache).
Returns (accounts_df, positions_df) shaped like the DB tables (account = persona id).
Personas without reachable creds are skipped; duplicate underlying accounts (shared-key
fallback) are deduped by Alpaca account number. Empty frames when fully offline —
callers fall back to the last DB snapshot.
"""
from config.agent_registry import load_personas
from execution.broker import for_persona
acc_rows, pos_rows, seen = [], [], set()
for p in load_personas():
try:
b = for_persona(p.id)
acct = b.get_account()
if acct.account_number in seen:
continue
seen.add(acct.account_number)
acc_rows.append(
{
"account": p.id,
"equity": float(acct.equity),
"cash": float(acct.cash),
"buying_power": float(acct.buying_power),
}
)
for pos in b.get_positions():
pos_rows.append(
{
"account": p.id,
"symbol": pos.symbol,
"qty": float(pos.qty),
"avg_entry": float(pos.avg_entry_price or 0),
"market_value": float(pos.market_value or 0),
"unrealized_pl": float(pos.unrealized_pl or 0),
}
)
except Exception:
continue
return pd.DataFrame(acc_rows), pd.DataFrame(pos_rows)
def describe_symbol(symbol: str) -> str:
"""Human-readable label for a position/order symbol. OCC option symbols become
'AAPL $295 call 2026-07-24'; plain tickers pass through unchanged."""
m = _OCC_RE.match(str(symbol).strip().upper())
if not m:
return symbol
underlying, yymmdd, right, strike = m.groups()
return (
f"{underlying} ${int(strike) / 1000:g} {'call' if right == 'C' else 'put'} "
f"20{yymmdd[:2]}-{yymmdd[2:4]}-{yymmdd[4:]}"
)
def is_option_symbol(symbol: str) -> bool:
return bool(_OCC_RE.match(str(symbol).strip().upper()))