DeepQuant / app.py
s-ttp's picture
Upload folder using huggingface_hub
939e9fa verified
Raw
History Blame Contribute Delete
12.8 kB
"""DeepQuant β€” live paper-account monitor (Phase 0, READ-ONLY).
Shows the Alpaca PAPER account: equity, cash, today's P&L, open positions, and an
equity curve vs the S&P 500. No order code exists in this phase β€” pure observation.
Auto-refreshes every 30 minutes.
"""
import datetime as dt
import json
import os
import ssl
import urllib.request
from zoneinfo import ZoneInfo
_ET = ZoneInfo("America/New_York")
import gradio as gr
import pandas as pd
from huggingface_hub import hf_hub_download
import alpaca_client as ac
import rebalance_engine as eng
try:
import certifi
_CTX = ssl.create_default_context(cafile=certifi.where())
except Exception:
_CTX = ssl.create_default_context()
AV = os.environ.get("ALPHAVANTAGE_API_KEY", "")
def fetch_spy_daily():
if not AV:
return None
try:
u = f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=SPY&outputsize=compact&apikey={AV}"
d = json.load(urllib.request.urlopen(u, timeout=25, context=_CTX)).get("Time Series (Daily)", {})
return {dt.date.fromisoformat(k): float(v["5. adjusted close"]) for k, v in d.items()}
except Exception:
return None
def _curve_pts(h):
ts, eqv = h.get("timestamp", []), h.get("equity", [])
return [(dt.datetime.fromtimestamp(t, _ET).replace(tzinfo=None), v) for t, v in zip(ts, eqv) if v]
def _mk_df(rows):
df = pd.DataFrame(rows)
df["date"] = pd.to_datetime(df["date"]).astype("datetime64[ns]")
return df
def fetch_spy_intraday():
if not AV:
return None
try:
u = f"https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=SPY&interval=15min&outputsize=compact&apikey={AV}"
d = json.load(urllib.request.urlopen(u, timeout=25, context=_CTX)).get("Time Series (15min)", {})
return {dt.datetime.strptime(k, "%Y-%m-%d %H:%M:%S"): float(v["4. close"]) for k, v in d.items()}
except Exception:
return None
def build_curve():
"""Returns (dataframe_or_None, note). Daily fund-vs-SPY once there's history; intraday today (both lines) for a young account."""
try:
daily = _curve_pts(ac.get_portfolio_history("1M", "1D"))
except Exception:
daily = []
if len(daily) >= 4: # enough daily history β†’ daily fund vs SPY
base = daily[0][1]
rows = [{"date": d, "series": "DeepQuant (paper)", "value": round(v, 2)} for d, v in daily]
spy = fetch_spy_daily()
if spy:
sd, first = sorted(spy), None
for d, _v in daily:
prior = [x for x in sd if x <= d.date()]
if not prior:
continue
px = spy[prior[-1]]
first = first or px
rows.append({"date": d, "series": "S&P 500 (SPY)", "value": round(base * px / first, 2)})
return _mk_df(rows), ""
try: # young account β†’ today's intraday, fund AND SPY
intr = _curve_pts(ac.get_portfolio_history("1D", "15Min"))
except Exception:
intr = []
if len(intr) < 2:
return None, "Equity curve builds once the account has trading history."
base, lo, hi = intr[0][1], intr[0][0], intr[-1][0]
rows = [{"date": d, "series": "DeepQuant (paper)", "value": round(v, 2)} for d, v in intr]
spy = fetch_spy_intraday()
if spy:
inr = sorted(k for k in spy if lo - dt.timedelta(minutes=20) <= k <= hi + dt.timedelta(minutes=20))
if inr:
first = spy[inr[0]]
for k in inr:
rows.append({"date": k, "series": "S&P 500 (SPY)", "value": round(base * spy[k] / first, 2)})
return _mk_df(rows), "Today's intraday β€” fund vs S&P 500. The multi-day curve builds over the coming sessions."
def refresh():
now = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
a = ac.get_account()
clk = ac.get_clock()
pos = ac.get_positions()
except Exception as e:
return (f"### ⚠️ Could not reach Alpaca\n`{type(e).__name__}: {str(e)[:160]}`",
pd.DataFrame(), gr.update(visible=False), gr.update(value="β€”", visible=True),
f"_Last checked {now}_")
eq, last = float(a["equity"]), float(a.get("last_equity", a["equity"]))
cash = float(a["cash"])
dpl = eq - last
dpct = (dpl / last * 100) if last else 0.0
mkt = "🟒 open" if clk.get("is_open") else "πŸ”΄ closed"
nxt = str(clk.get("next_open", ""))[:16].replace("T", " ")
invested = eq - cash
summary = (
f"## ${eq:,.2f}\n"
f"**Today's P&L** {dpl:+,.2f} ({dpct:+.2f}%) Β· account **{a.get('status')}** Β· market {mkt}"
+ ("" if clk.get("is_open") else f" (next open {nxt})") + "\n\n"
f"| Cash | Invested | Open positions | Currency |\n|---|---|---|---|\n"
f"| ${cash:,.2f} | ${invested:,.2f} | {len(pos)} | {a.get('currency','USD')} |\n\n"
f"<sub>Long-only strategy: margin and short-selling are never used, regardless of account permissions.</sub>"
)
rows = []
for p in pos:
qty, mv = float(p["qty"]), float(p["market_value"])
rows.append({
"Symbol": p["symbol"],
"Qty": round(qty, 4),
"Avg entry": f'${float(p["avg_entry_price"]):,.2f}',
"Last": f'${float(p["current_price"]):,.2f}',
"Mkt value": f"${mv:,.2f}",
"Weight": f"{mv / eq * 100:.1f}%" if eq else "β€”",
"Unrealized P&L": f'${float(p["unrealized_pl"]):,.2f} ({float(p["unrealized_plpc"]) * 100:+.1f}%)',
})
df = pd.DataFrame(rows) if rows else pd.DataFrame([{"Symbol": "β€” no open positions β€”"}])
curve, cnote = build_curve()
stamp = f"_Last updated {now} Β· auto-refreshes every 30 min_"
if curve is None:
return (summary, df, gr.update(visible=False), gr.update(value=f"_{cnote}_", visible=True), stamp)
note_upd = gr.update(value=f"_{cnote}_", visible=True) if cnote else gr.update(visible=False)
return (summary, df, gr.update(value=curve, visible=True), note_upd, stamp)
def load_rebalance():
tok = os.environ.get("HF_TOKEN")
if not tok:
return ""
try:
log = json.load(open(hf_hub_download("s-ttp/marketfm-panel", "deepquant_log.json", repo_type="dataset",
token=tok, force_download=True)))
if not log:
return ""
e = log[-1]
mode = "🟒 OFFENSE" if e.get("mode") == "OFFENSE" else "πŸ›‘οΈ DEFENSE"
head = (f"### πŸ—“οΈ Latest rebalance β€” {e.get('date','')} Β· {mode}\n"
f"{e.get('buys',0)} buys Β· {e.get('sells',0)} sells Β· {e.get('turnover_pct',0)}% turnover Β· "
f"{e.get('n_positions','β€”')} holdings\n\n")
if e.get("narration"):
return head + f"> πŸ’¬ *{e['narration']}*\n\n<sub>β€” {e.get('narrated_by','MarketGuruLLM')}</sub>"
return head + f"<sub>{e.get('summary','')}</sub>"
except Exception:
return ""
def rebalance_preview():
tok = os.environ.get("HF_TOKEN")
if not tok:
return "_Set the `HF_TOKEN` secret so DeepQuant can read the MarketFM target._", pd.DataFrame(), pd.DataFrame()
try:
tgt = eng.get_target(tok)
acct = ac.get_account()
eq = float(acct["equity"])
positions = {p["symbol"]: float(p["market_value"]) for p in ac.get_positions()}
orders = eng.compute_orders(tgt["weights"], positions, eq)
except Exception as e:
return f"### ⚠️ Could not build preview\n`{type(e).__name__}: {str(e)[:180]}`", pd.DataFrame(), pd.DataFrame()
reg = tgt["regime"]
mode = "🟒 OFFENSE" if reg["offense"] else "πŸ›‘οΈ DEFENSE"
sma = (f"SPY 10-wk {reg['sma10']:,.0f} vs 40-wk {reg['sma40']:,.0f}" if reg.get("sma10") else reg.get("warn", ""))
mix = "50 stocks Β· 100% equity" if reg["offense"] else "60% stocks + 20% AGG + 20% GLD"
nb = sum(1 for o in orders if o["side"] == "BUY")
ns = sum(1 for o in orders if o["side"] == "SELL")
traded = sum(abs(o["delta"]) for o in orders)
summ = (f"**{len(orders)} orders** β€” {nb} buys, {ns} sells Β· gross traded **${traded:,.0f}** "
f"({traded / eq * 100:.0f}% turnover) Β· no-trade band skips deltas < ${0.005 * eq:,.0f}"
if orders else "**No trades needed** β€” the account already matches the target within the no-trade band.")
banner = (f"## {mode}\n**Regime:** {sma} Β· equity **${eq:,.0f}** Β· target: {mix}\n\n{summ}\n\n"
f"<sub>Preview only β€” **no orders are placed**. This is exactly what the agent would submit at the next "
f"Monday-after-open rebalance (notional market orders, sells before buys).</sub>")
tdf = pd.DataFrame([{"Ticker": r["ticker"], "Sector": r["sector"], "Target %": f'{r["weight"] * 100:.1f}%',
"Target $": f'${r["weight"] * eq:,.0f}'} for r in tgt["rows"]])
if orders:
odf = pd.DataFrame([{"Symbol": o["symbol"], "Side": ("πŸ”» SELL" if o["side"] == "SELL" else "🟒 BUY"),
"Trade $": f'${abs(o["delta"]):,.0f}', "Now": f'{o["cur_w"] * 100:.1f}%',
"β†’ Target": f'{o["tgt_w"] * 100:.1f}%'} for o in orders])
else:
odf = pd.DataFrame([{"Symbol": "β€” already at target β€”"}])
return banner, tdf, odf
with gr.Blocks(title="DeepQuant β€” live paper monitor", theme=gr.themes.Soft()) as demo:
gr.Markdown(
"# πŸ€– DeepQuant\n"
"### An agent that autonomously **executes** a strategy backtested to beat the S&P 500 over 20+ years\n"
"DeepQuant runs a regime-aware, **multi-asset** strategy end-to-end β€” using the **Alpaca API to both execute "
"*and* monitor** every trade on a live paper account.\n\n"
"**Under the hood:**\n"
"- 🧠 **[MarketFM](https://huggingface.co/spaces/s-ttp/marketfm-ranker)** β€” a survivorship-free quant model β€” ranks the universe and sets the target portfolio\n"
"- πŸ’¬ **[MarketGuruLLM](https://huggingface.co/s-ttp/qwen25-14b-fin-lora)** β€” a fine-tuned analyst LLM β€” reasons over and narrates every rebalance\n"
"- ⚑ **Alpaca API** β€” the brokerage rails the **quant execution engine** drives to place orders and stream live fills, positions and P&L\n\n"
"**The strategy:** large-cap **momentum + quality** while the trend is up; when it turns down it de-risks into a "
"blend of **stocks + bonds + gold** (60 / 20 / 20). Backtested 2002–2026: **~14% / yr vs the S&P 500's ~11%**, "
"higher Sharpe, smaller drawdown.\n"
"<sub>Phase 0 β€” live paper-account monitor below; autonomous weekly execution rolls out next. "
"Backtested results, paper trading β€” not investment advice.</sub>")
with gr.Tabs():
with gr.Tab("πŸ“Š Live account"):
stats = gr.Markdown()
rebal = gr.Markdown()
gr.Markdown("### Holdings")
tbl = gr.Dataframe(wrap=True, interactive=False)
gr.Markdown("### Equity vs S&P 500")
plot = gr.LinePlot(x="date", y="value", color="series", height=320,
y_title="Account value ($)", x_title="")
note = gr.Markdown(visible=False)
with gr.Row():
updated = gr.Markdown("_loading…_")
btn = gr.Button("↻ Refresh now", scale=0)
with gr.Tab("πŸ”„ Rebalance preview"):
gr.Markdown("What the agent **would** trade at the next rebalance β€” computed live from the MarketFM target "
"and your current account. **Nothing is placed** (Phase 1, dry-run).")
pv_banner = gr.Markdown()
pbtn = gr.Button("⟳ Recompute preview", scale=0)
with gr.Row():
with gr.Column():
gr.Markdown("**Target portfolio**")
tgt_tbl = gr.Dataframe(interactive=False, wrap=True)
with gr.Column():
gr.Markdown("**Proposed orders** β€” sells first, then buys")
ord_tbl = gr.Dataframe(interactive=False, wrap=True)
timer = gr.Timer(1800)
acct_outs = [stats, tbl, plot, note, updated]
demo.load(refresh, None, acct_outs)
btn.click(refresh, None, acct_outs)
timer.tick(refresh, None, acct_outs)
demo.load(load_rebalance, None, rebal)
btn.click(load_rebalance, None, rebal)
timer.tick(load_rebalance, None, rebal)
pv_outs = [pv_banner, tgt_tbl, ord_tbl]
demo.load(rebalance_preview, None, pv_outs)
pbtn.click(rebalance_preview, None, pv_outs)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)