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.
75 lines
3.1 KiB
Python
75 lines
3.1 KiB
Python
"""Theses tab: each agent's overnight picks, with committee verdicts badged inline."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import streamlit as st
|
|
|
|
from agents.schemas import AgentOutput
|
|
from orchestration.committee import load_verdicts
|
|
from web.views.common import S, trade_dates
|
|
|
|
_BADGE = {"approve": "✅", "reduce": "⚠️ reduced", "veto": "🚫 VETOED"}
|
|
|
|
|
|
def _committee_memo(date: str) -> str:
|
|
path = S.output_dir / date / "_committee.json"
|
|
if not path.exists():
|
|
return ""
|
|
try:
|
|
return str(json.loads(path.read_text()).get("_memo", ""))
|
|
except Exception:
|
|
return ""
|
|
|
|
|
|
def render() -> None:
|
|
dates = trade_dates()
|
|
if not dates:
|
|
st.info("No nightly output yet. Run: `uv run python -m orchestration.run_nightly --compressed`")
|
|
return
|
|
date = st.selectbox("Trade date", dates)
|
|
verdicts = load_verdicts(date)
|
|
memo = _committee_memo(date)
|
|
if memo:
|
|
with st.expander("🏛️ Investment Committee memo", expanded=False):
|
|
st.write(memo)
|
|
for (agent, ticker), v in verdicts.items():
|
|
if v["verdict"] != "approve":
|
|
st.caption(f"{_BADGE.get(v['verdict'], v['verdict'])} **{ticker}** ({agent}) — {v['reason']}")
|
|
|
|
day_dir = S.output_dir / date
|
|
files = sorted(f for f in day_dir.glob("*.json") if not f.name.startswith("_"))
|
|
cols = st.columns(max(len(files), 1))
|
|
for col, f in zip(cols, files):
|
|
try:
|
|
out = AgentOutput.model_validate_json(f.read_text())
|
|
except Exception as e:
|
|
col.error(f"{f.name}: {e}")
|
|
continue
|
|
with col:
|
|
st.markdown(f"### {out.agent_name}")
|
|
st.caption(f"status **{out.status.value}** · {len(out.picks)} picks · "
|
|
f"{out.steps_used} steps · {out.wall_seconds:.0f}s")
|
|
for p in out.picks:
|
|
v = verdicts.get((out.persona_id, p.ticker))
|
|
badge = f" · {_BADGE[v['verdict']]}" if v and v["verdict"] != "approve" else ""
|
|
inst = " · 🎯 option" if p.instrument.value == "option" else ""
|
|
with st.expander(f"{p.ticker} · conv {p.conviction:.2f} · {p.time_horizon.value}{inst}{badge}"):
|
|
st.write(p.thesis)
|
|
if p.instrument.value == "option":
|
|
st.caption(f"{p.option_right} {p.contract_symbol} · strike {p.strike} · exp {p.expiry}")
|
|
st.caption(
|
|
f"weight {p.requested_weight_pct:.0%} · stop {p.stop_loss_pct} · "
|
|
f"take {p.take_profit_pct} · {p.direction.value}"
|
|
)
|
|
if p.catalysts:
|
|
st.caption("catalysts: " + ", ".join(p.catalysts))
|
|
if v and v["verdict"] != "approve":
|
|
st.warning(f"committee: {v['verdict']} — {v['reason']}")
|
|
if out.reasoning_trace:
|
|
with st.expander("🔍 reasoning trace"):
|
|
st.code("\n".join(out.reasoning_trace), language="text")
|
|
if out.errors:
|
|
st.warning("errors: " + "; ".join(out.errors))
|