| """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: |
| 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: |
| 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) |
|
|