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.
This commit is contained in:
1 parent
b3821abd69
commit
e1ac514f30
36 files changed
+1476
-294
No files matched your search
@@ -23,15 +23,19 @@ from config.settings import get_settings
|
||||
from llm.client import build_llm
|
||||
|
||||
CRO_SYSTEM = (
|
||||
"You are the Chief Risk Officer of a small fund. Three analysts submitted tonight's picks. "
|
||||
"You are the Chief Risk Officer of a small fund. {n} analysts submitted tonight's picks. "
|
||||
"Your job is capital preservation and portfolio coherence — NOT stock picking. Judge each "
|
||||
"pick on: thesis quality vs the market regime, concentration/overlap across the three books, "
|
||||
"pick on: thesis quality vs the market regime, concentration/overlap across the books, "
|
||||
"crowding into one theme, position sizing sanity, and each analyst's recent track record. "
|
||||
"Each analyst has a declared risk style — judge every book against ITS OWN mandate: an "
|
||||
"intentionally aggressive book is not vetoed for being aggressive, only for breaking its "
|
||||
"own discipline (undefined risk, sloppy sizing, no catalyst). "
|
||||
"Be decisive and specific. Output ONLY the requested JSON object."
|
||||
)
|
||||
|
||||
CRO_PROMPT = (
|
||||
"{regime}\n\n"
|
||||
"ANALYST MANDATES (risk styles):\n{styles}\n\n"
|
||||
"ANALYST TRACK RECORDS:\n{records}\n\n"
|
||||
"TONIGHT'S SUBMITTED PICKS:\n{picks}\n\n"
|
||||
'Return a JSON object: {{"memo": "<a 150-250 word committee memo in plain prose: overall '
|
||||
@@ -82,15 +86,27 @@ def run_committee(
|
||||
return {}
|
||||
records_txt = "\n".join(f"[{k}]\n{v or '(no history yet)'}" for k, v in records.items())
|
||||
|
||||
# style-aware review: the CRO judges each book against its own declared mandate
|
||||
try:
|
||||
from config.agent_registry import load_personas
|
||||
|
||||
styles = {p.id: p.risk_style for p in load_personas(include_disabled=True)}
|
||||
except Exception:
|
||||
styles = {}
|
||||
styles_txt = "\n".join(
|
||||
f"- {o.persona_id}: {styles.get(o.persona_id, 'balanced')}" for o in outputs
|
||||
)
|
||||
|
||||
verdicts: dict = {}
|
||||
memo = ""
|
||||
try:
|
||||
resp = build_llm(max_tokens=4096).invoke(
|
||||
[
|
||||
SystemMessage(content=CRO_SYSTEM),
|
||||
SystemMessage(content=CRO_SYSTEM.format(n=len(outputs))),
|
||||
HumanMessage(
|
||||
content=CRO_PROMPT.format(
|
||||
regime=regime or "(regime unavailable)",
|
||||
styles=styles_txt or "(none declared)",
|
||||
records=records_txt,
|
||||
picks=picks_txt,
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ from loguru import logger # noqa: E402
|
||||
|
||||
from agents.graph import run_agent # noqa: E402
|
||||
from agents.schemas import RunManifest # noqa: E402
|
||||
from config.personas import PERSONAS # noqa: E402
|
||||
from config.agent_registry import load_personas # noqa: E402
|
||||
from config.settings import get_settings # noqa: E402
|
||||
from llm import server # noqa: E402
|
||||
from llm.client import resolve_model_id # noqa: E402
|
||||
@@ -71,10 +71,12 @@ def main(compressed: bool = False) -> int:
|
||||
cp.write_manifest(manifest)
|
||||
|
||||
hard_stop = local_hard_stop_ts(s.hard_stop, s.local_tz)
|
||||
per_agent_cap_s = 5 * 60 if compressed else s.per_agent_wallclock_min * 60
|
||||
step_budget = 12 if compressed else s.per_agent_step_budget
|
||||
min_steps = 4 if compressed else s.per_agent_min_steps
|
||||
min_seconds = 60 if compressed else s.per_agent_min_minutes * 60
|
||||
|
||||
# dashboard-managed registry: built-ins + overrides + custom agents, disabled excluded
|
||||
personas = load_personas()
|
||||
logger.info("agents tonight: {}", ", ".join(p.id for p in personas))
|
||||
|
||||
# shared ground truth for all agents tonight (computed, deterministic)
|
||||
regime = market_regime()
|
||||
@@ -83,12 +85,18 @@ def main(compressed: bool = False) -> int:
|
||||
|
||||
outputs = []
|
||||
records: dict[str, str] = {}
|
||||
for i, persona in enumerate(PERSONAS):
|
||||
for i, persona in enumerate(personas):
|
||||
# per-agent budgets (dashboard-editable), falling back to the global settings
|
||||
if compressed:
|
||||
per_agent_cap_s, min_seconds = 5 * 60, 60
|
||||
else:
|
||||
per_agent_cap_s = (persona.time_budget_min or s.per_agent_wallclock_min) * 60
|
||||
min_seconds = (persona.min_research_min or s.per_agent_min_minutes) * 60
|
||||
now = time.time()
|
||||
if now >= hard_stop:
|
||||
logger.warning("hard stop reached; skipping {} remaining agents", len(PERSONAS) - i)
|
||||
logger.warning("hard stop reached; skipping {} remaining agents", len(personas) - i)
|
||||
break
|
||||
deadline = next_deadline(now, hard_stop, len(PERSONAS) - i, per_agent_cap_s)
|
||||
deadline = next_deadline(now, hard_stop, len(personas) - i, per_agent_cap_s)
|
||||
logger.info("--- {} until {} ---", persona.id,
|
||||
datetime.fromtimestamp(deadline).strftime("%H:%M:%S"))
|
||||
if not server.server_up(): # recover if a prior agent left oMLX unresponsive
|
||||
|
||||
Reference in new issue
Block a user