feat(execution): per-account Alpaca GTC brackets, guardrails, shorts, options, reconcile
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016DvdRZkyCy9NJpHYDdPqmS
This commit is contained in:
1 parent
aa1ae0c95e
commit
709870b1d5
8 files changed
+813
No files matched your search
+108
@@ -0,0 +1,108 @@
|
||||
"""SQLite state: audit log + context cache, dimensioned by account (persona).
|
||||
|
||||
Alpaca remains the source of truth for live positions; this DB records what each agent
|
||||
proposed and did, and caches each account's latest snapshot so a nightly agent can see its
|
||||
own holdings (tools/portfolio.py) and the dashboard can show all three books.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS runs (
|
||||
run_id TEXT PRIMARY KEY, trade_date TEXT, started_at TEXT, ended_at TEXT, status TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS theses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
trade_date TEXT, agent TEXT, ticker TEXT, direction TEXT, conviction REAL,
|
||||
thesis TEXT, requested_weight_pct REAL, stop_pct REAL, take_pct REAL, horizon TEXT,
|
||||
json_blob TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
trade_date TEXT, agent TEXT, ticker TEXT, side TEXT, qty INTEGER, entry_type TEXT,
|
||||
est_price REAL, stop_price REAL, take_price REAL,
|
||||
alpaca_order_id TEXT, status TEXT, avg_fill_price REAL, submitted_at TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS snapshots (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ts TEXT, account TEXT, equity REAL, cash REAL, buying_power REAL, positions_json TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS positions (
|
||||
symbol TEXT, account TEXT, qty REAL, avg_entry REAL, market_value REAL,
|
||||
unrealized_pl REAL, as_of TEXT, PRIMARY KEY (symbol, account)
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
def _now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def connect(path: str | Path) -> sqlite3.Connection:
|
||||
con = sqlite3.connect(str(path))
|
||||
con.row_factory = sqlite3.Row
|
||||
return con
|
||||
|
||||
|
||||
def init_db(path: str | Path) -> None:
|
||||
con = connect(path)
|
||||
con.executescript(SCHEMA)
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
|
||||
def record_thesis(con, trade_date, agent, pick_dict: dict) -> None:
|
||||
con.execute(
|
||||
"INSERT INTO theses (trade_date, agent, ticker, direction, conviction, thesis, "
|
||||
"requested_weight_pct, stop_pct, take_pct, horizon, json_blob) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(
|
||||
trade_date, agent, pick_dict.get("ticker"), pick_dict.get("direction"),
|
||||
pick_dict.get("conviction"), pick_dict.get("thesis"),
|
||||
pick_dict.get("requested_weight_pct"), pick_dict.get("stop_loss_pct"),
|
||||
pick_dict.get("take_profit_pct"), pick_dict.get("time_horizon"),
|
||||
json.dumps(pick_dict),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def record_order(con, *, trade_date, agent, ticker, side, qty, entry_type, est_price,
|
||||
stop_price, take_price, alpaca_order_id, status, avg_fill_price=None) -> None:
|
||||
con.execute(
|
||||
"INSERT INTO orders (trade_date, agent, ticker, side, qty, entry_type, est_price, "
|
||||
"stop_price, take_price, alpaca_order_id, status, avg_fill_price, submitted_at) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(trade_date, agent, ticker, side, qty, entry_type, est_price, stop_price, take_price,
|
||||
alpaca_order_id, status, avg_fill_price, _now()),
|
||||
)
|
||||
|
||||
|
||||
def record_snapshot(con, *, account, equity, cash, buying_power, positions: list[dict]) -> None:
|
||||
con.execute(
|
||||
"INSERT INTO snapshots (ts, account, equity, cash, buying_power, positions_json) "
|
||||
"VALUES (?,?,?,?,?,?)",
|
||||
(_now(), account, equity, cash, buying_power, json.dumps(positions)),
|
||||
)
|
||||
|
||||
|
||||
def upsert_position(con, *, account, symbol, qty, avg_entry, market_value, unrealized_pl) -> None:
|
||||
con.execute(
|
||||
"INSERT INTO positions (symbol, account, qty, avg_entry, market_value, unrealized_pl, "
|
||||
"as_of) VALUES (?,?,?,?,?,?,?) ON CONFLICT(symbol, account) DO UPDATE SET "
|
||||
"qty=excluded.qty, avg_entry=excluded.avg_entry, market_value=excluded.market_value, "
|
||||
"unrealized_pl=excluded.unrealized_pl, as_of=excluded.as_of",
|
||||
(symbol, account, qty, avg_entry, market_value, unrealized_pl, _now()),
|
||||
)
|
||||
|
||||
|
||||
def latest_snapshot(con, account: str | None = None) -> sqlite3.Row | None:
|
||||
if account:
|
||||
return con.execute(
|
||||
"SELECT * FROM snapshots WHERE account=? ORDER BY ts DESC LIMIT 1", (account,)
|
||||
).fetchone()
|
||||
return con.execute("SELECT * FROM snapshots ORDER BY ts DESC LIMIT 1").fetchone()
|
||||
Reference in new issue
Block a user