Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016DvdRZkyCy9NJpHYDdPqmS
57 lines
1.9 KiB
Python
57 lines
1.9 KiB
Python
"""get_portfolio — persona-scoped. Each agent sees ONLY its own paper account's holdings
|
|
(written by execution/reconcile.py, keyed by persona id), so picks build on that book.
|
|
Built per-persona via make_get_portfolio() in tools/build_toolset.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from langchain_core.tools import tool
|
|
|
|
from config.settings import get_settings
|
|
from state import db as statedb
|
|
|
|
|
|
def make_get_portfolio(account: str):
|
|
"""Return a get_portfolio tool bound to one account (persona id)."""
|
|
|
|
@tool
|
|
def get_portfolio() -> str:
|
|
"""Return YOUR current paper-account holdings, unrealized PnL, and cash.
|
|
|
|
Use this BEFORE proposing trades so you account for what you already own.
|
|
"""
|
|
path = get_settings().db_path
|
|
if not path.exists():
|
|
return "No portfolio data yet (fresh account, no trades recorded)."
|
|
try:
|
|
con = statedb.connect(path)
|
|
snap = statedb.latest_snapshot(con, account or None)
|
|
con.close()
|
|
except Exception as e:
|
|
return f"No portfolio data yet ({e})."
|
|
if snap is None:
|
|
return "No holdings yet (fresh account, all cash)."
|
|
|
|
lines = [
|
|
f"As of {snap['ts']}: equity=${snap['equity']:,.2f}, "
|
|
f"cash=${snap['cash']:,.2f}, buying_power=${snap['buying_power']:,.2f}"
|
|
]
|
|
try:
|
|
positions = json.loads(snap["positions_json"] or "[]")
|
|
except Exception:
|
|
positions = []
|
|
if not positions:
|
|
lines.append("Holdings: none (all cash).")
|
|
else:
|
|
lines.append("Holdings:")
|
|
for p in positions:
|
|
lines.append(
|
|
f" {p.get('symbol')}: {p.get('qty')} @ {p.get('avg_entry')} "
|
|
f"-> mv={p.get('market_value')}, unrealized_pl={p.get('unrealized_pl')}"
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
return get_portfolio
|