s-ttp commited on
Commit
e48ec63
·
verified ·
1 Parent(s): f79f99b

Upload folder using huggingface_hub

Browse files
__pycache__/app.cpython-312.pyc CHANGED
Binary files a/__pycache__/app.cpython-312.pyc and b/__pycache__/app.cpython-312.pyc differ
 
__pycache__/rebalance_engine.cpython-312.pyc ADDED
Binary file (7.92 kB). View file
 
app.py CHANGED
@@ -14,6 +14,7 @@ import gradio as gr
14
  import pandas as pd
15
 
16
  import alpaca_client as ac
 
17
 
18
  try:
19
  import certifi
@@ -106,6 +107,42 @@ def refresh():
106
  f"_Last updated {now} · auto-refreshes every 30 min_")
107
 
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  with gr.Blocks(title="DeepQuant — live paper monitor", theme=gr.themes.Soft()) as demo:
110
  gr.Markdown(
111
  "# 🤖 DeepQuant\n"
@@ -121,22 +158,40 @@ with gr.Blocks(title="DeepQuant — live paper monitor", theme=gr.themes.Soft())
121
  "higher Sharpe, smaller drawdown.\n"
122
  "<sub>Phase 0 — live paper-account monitor below; autonomous weekly execution rolls out next. "
123
  "Backtested results, paper trading — not investment advice.</sub>")
124
- stats = gr.Markdown()
125
- gr.Markdown("### Holdings")
126
- tbl = gr.Dataframe(wrap=True, interactive=False)
127
- gr.Markdown("### Equity vs S&P 500")
128
- plot = gr.LinePlot(x="date", y="value", color="series", height=320,
129
- y_title="Account value ($)", x_title="")
130
- note = gr.Markdown(visible=False)
131
- with gr.Row():
132
- updated = gr.Markdown("_loading…_")
133
- btn = gr.Button("↻ Refresh now", scale=0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  timer = gr.Timer(1800)
135
 
136
- outs = [stats, tbl, plot, note, updated]
137
- demo.load(refresh, None, outs)
138
- btn.click(refresh, None, outs)
139
- timer.tick(refresh, None, outs)
 
 
 
 
140
 
141
  if __name__ == "__main__":
142
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
 
14
  import pandas as pd
15
 
16
  import alpaca_client as ac
17
+ import rebalance_engine as eng
18
 
19
  try:
20
  import certifi
 
107
  f"_Last updated {now} · auto-refreshes every 30 min_")
108
 
109
 
110
+ def rebalance_preview():
111
+ tok = os.environ.get("HF_TOKEN")
112
+ if not tok:
113
+ return "_Set the `HF_TOKEN` secret so DeepQuant can read the MarketFM target._", pd.DataFrame(), pd.DataFrame()
114
+ try:
115
+ tgt = eng.get_target(tok)
116
+ acct = ac.get_account()
117
+ eq = float(acct["equity"])
118
+ positions = {p["symbol"]: float(p["market_value"]) for p in ac.get_positions()}
119
+ orders = eng.compute_orders(tgt["weights"], positions, eq)
120
+ except Exception as e:
121
+ return f"### ⚠️ Could not build preview\n`{type(e).__name__}: {str(e)[:180]}`", pd.DataFrame(), pd.DataFrame()
122
+ reg = tgt["regime"]
123
+ mode = "🟢 OFFENSE" if reg["offense"] else "🛡️ DEFENSE"
124
+ sma = (f"SPY 10-wk {reg['sma10']:,.0f} vs 40-wk {reg['sma40']:,.0f}" if reg.get("sma10") else reg.get("warn", ""))
125
+ mix = "50 stocks · 100% equity" if reg["offense"] else "60% stocks + 20% AGG + 20% GLD"
126
+ nb = sum(1 for o in orders if o["side"] == "BUY")
127
+ ns = sum(1 for o in orders if o["side"] == "SELL")
128
+ traded = sum(abs(o["delta"]) for o in orders)
129
+ summ = (f"**{len(orders)} orders** — {nb} buys, {ns} sells · gross traded **${traded:,.0f}** "
130
+ f"({traded / eq * 100:.0f}% turnover) · no-trade band skips deltas < ${0.005 * eq:,.0f}"
131
+ if orders else "**No trades needed** — the account already matches the target within the no-trade band.")
132
+ banner = (f"## {mode}\n**Regime:** {sma} · equity **${eq:,.0f}** · target: {mix}\n\n{summ}\n\n"
133
+ f"<sub>Preview only — **no orders are placed**. This is exactly what the agent would submit at the next "
134
+ f"Monday-after-open rebalance (notional market orders, sells before buys).</sub>")
135
+ tdf = pd.DataFrame([{"Ticker": r["ticker"], "Sector": r["sector"], "Target %": f'{r["weight"] * 100:.1f}%',
136
+ "Target $": f'${r["weight"] * eq:,.0f}'} for r in tgt["rows"]])
137
+ if orders:
138
+ odf = pd.DataFrame([{"Symbol": o["symbol"], "Side": ("🔻 SELL" if o["side"] == "SELL" else "🟢 BUY"),
139
+ "Trade $": f'${abs(o["delta"]):,.0f}', "Now": f'{o["cur_w"] * 100:.1f}%',
140
+ "→ Target": f'{o["tgt_w"] * 100:.1f}%'} for o in orders])
141
+ else:
142
+ odf = pd.DataFrame([{"Symbol": "— already at target —"}])
143
+ return banner, tdf, odf
144
+
145
+
146
  with gr.Blocks(title="DeepQuant — live paper monitor", theme=gr.themes.Soft()) as demo:
147
  gr.Markdown(
148
  "# 🤖 DeepQuant\n"
 
158
  "higher Sharpe, smaller drawdown.\n"
159
  "<sub>Phase 0 — live paper-account monitor below; autonomous weekly execution rolls out next. "
160
  "Backtested results, paper trading — not investment advice.</sub>")
161
+ with gr.Tabs():
162
+ with gr.Tab("📊 Live account"):
163
+ stats = gr.Markdown()
164
+ gr.Markdown("### Holdings")
165
+ tbl = gr.Dataframe(wrap=True, interactive=False)
166
+ gr.Markdown("### Equity vs S&P 500")
167
+ plot = gr.LinePlot(x="date", y="value", color="series", height=320,
168
+ y_title="Account value ($)", x_title="")
169
+ note = gr.Markdown(visible=False)
170
+ with gr.Row():
171
+ updated = gr.Markdown("_loading…_")
172
+ btn = gr.Button("↻ Refresh now", scale=0)
173
+ with gr.Tab("🔄 Rebalance preview"):
174
+ gr.Markdown("What the agent **would** trade at the next rebalance — computed live from the MarketFM target "
175
+ "and your current account. **Nothing is placed** (Phase 1, dry-run).")
176
+ pv_banner = gr.Markdown()
177
+ pbtn = gr.Button("⟳ Recompute preview", scale=0)
178
+ with gr.Row():
179
+ with gr.Column():
180
+ gr.Markdown("**Target portfolio**")
181
+ tgt_tbl = gr.Dataframe(interactive=False, wrap=True)
182
+ with gr.Column():
183
+ gr.Markdown("**Proposed orders** — sells first, then buys")
184
+ ord_tbl = gr.Dataframe(interactive=False, wrap=True)
185
  timer = gr.Timer(1800)
186
 
187
+ acct_outs = [stats, tbl, plot, note, updated]
188
+ demo.load(refresh, None, acct_outs)
189
+ btn.click(refresh, None, acct_outs)
190
+ timer.tick(refresh, None, acct_outs)
191
+
192
+ pv_outs = [pv_banner, tgt_tbl, ord_tbl]
193
+ demo.load(rebalance_preview, None, pv_outs)
194
+ pbtn.click(rebalance_preview, None, pv_outs)
195
 
196
  if __name__ == "__main__":
197
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
rebalance_engine.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """DeepQuant — deterministic rebalance engine (Phase 1: compute only, no execution).
2
+
3
+ Reproduces the MarketFM Tactical target portfolio and diffs it against the live account in
4
+ DOLLARS, trading only the change. No order is placed here — compute_orders returns the list
5
+ the agent *would* submit. Execution (Phase 2) consumes this same output, guardrailed.
6
+ """
7
+ import json
8
+ import os
9
+ import ssl
10
+ import urllib.request
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ from huggingface_hub import hf_hub_download
15
+
16
+ try:
17
+ import certifi
18
+ _CTX = ssl.create_default_context(cafile=certifi.where())
19
+ except Exception:
20
+ _CTX = ssl.create_default_context()
21
+
22
+ PANEL_REPO = os.environ.get("MARKETFM_REPO", "s-ttp/marketfm-panel")
23
+ AV = os.environ.get("ALPHAVANTAGE_API_KEY", "")
24
+ SEG_K, SECTOR_CAP, SIZE, MAX_W = 1000, 5, 50, 0.10 # top-1000 by mktcap, ≤5/sector, 50 names, 10% cap
25
+ DEF_EQ, DEF_AGG, DEF_GLD = 0.60, 0.20, 0.20 # defense: 60% stocks + 20% bonds + 20% gold
26
+ ETF = {"AGG": ("iShares Core US Aggregate Bond ETF", "Bonds (ETF)"),
27
+ "GLD": ("SPDR Gold Shares", "Gold (ETF)")}
28
+
29
+
30
+ def cap_weights(w, mw=MAX_W):
31
+ w = np.asarray(w, float)
32
+ for _ in range(30):
33
+ over = w > mw + 1e-9
34
+ if not over.any():
35
+ break
36
+ excess = (w[over] - mw).sum()
37
+ w[over] = mw
38
+ under = ~over
39
+ if w[under].sum() <= 0:
40
+ break
41
+ w[under] += excess * w[under] / w[under].sum()
42
+ return w
43
+
44
+
45
+ def detect_regime():
46
+ try:
47
+ u = f"https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY_ADJUSTED&symbol=SPY&apikey={AV}"
48
+ ts = json.load(urllib.request.urlopen(u, timeout=25, context=_CTX))["Weekly Adjusted Time Series"]
49
+ s = pd.Series({k: float(v["5. adjusted close"]) for k, v in ts.items()}).sort_index()
50
+ sma10, sma40 = s.rolling(10).mean().iloc[-1], s.rolling(40).mean().iloc[-1]
51
+ return {"offense": bool(sma10 >= sma40), "sma10": float(sma10), "sma40": float(sma40), "spy": float(s.iloc[-1])}
52
+ except Exception as e:
53
+ return {"offense": True, "sma10": None, "sma40": None, "spy": None,
54
+ "warn": f"SPY fetch failed ({type(e).__name__}); defaulting to OFFENSE"}
55
+
56
+
57
+ def equity_sleeve(token):
58
+ p = hf_hub_download(PANEL_REPO, "marketfm_snapshot.parquet", repo_type="dataset", token=token)
59
+ df = pd.read_parquet(p)
60
+ df = df[df["marketcap"].notna() & (df["marketcap"] > 0)].copy()
61
+ df = df.sort_values("marketcap", ascending=False).head(SEG_K)
62
+ df["score"] = df["n_mom_12_1"].fillna(0) + df["n_roe"].fillna(0)
63
+ df = df.sort_values("score", ascending=False)
64
+ picked, sec = [], {}
65
+ for _, r in df.iterrows():
66
+ if sec.get(r["sector"], 0) >= SECTOR_CAP:
67
+ continue
68
+ sec[r["sector"]] = sec.get(r["sector"], 0) + 1
69
+ picked.append(r)
70
+ if len(picked) >= SIZE:
71
+ break
72
+ mc = np.array([r["marketcap"] for r in picked], float)
73
+ w = cap_weights(mc / mc.sum())
74
+ return [{"ticker": r["ticker"], "name": r.get("name"), "sector": r["sector"], "weight": float(wi)}
75
+ for r, wi in zip(picked, w)]
76
+
77
+
78
+ def get_target(token):
79
+ reg = detect_regime()
80
+ sleeve = equity_sleeve(token)
81
+ if reg["offense"]:
82
+ rows = [dict(x) for x in sleeve]
83
+ else:
84
+ rows = [dict(x, weight=DEF_EQ * x["weight"]) for x in sleeve]
85
+ for sym, (nm, sct) in ETF.items():
86
+ rows.append({"ticker": sym, "name": nm, "sector": sct,
87
+ "weight": DEF_AGG if sym == "AGG" else DEF_GLD})
88
+ weights = {r["ticker"]: r["weight"] for r in rows}
89
+ return {"regime": reg, "weights": weights, "rows": rows}
90
+
91
+
92
+ def compute_orders(weights, positions, equity, band_pct=0.005):
93
+ """positions: {symbol: market_value $}. Returns order list (sells first), trading only the delta."""
94
+ band = band_pct * equity
95
+ orders = []
96
+ for sym in set(weights) | set(positions):
97
+ tw = weights.get(sym, 0.0)
98
+ tgt = tw * equity
99
+ cur = positions.get(sym, 0.0)
100
+ delta = tgt - cur
101
+ cw = (cur / equity) if equity else 0.0
102
+ if tw == 0 and cur > 0: # dropped from basket → full exit (always)
103
+ orders.append({"symbol": sym, "side": "SELL", "delta": -cur, "cur_w": cw, "tgt_w": 0.0, "exit": True})
104
+ elif cur == 0 and tgt > 1.0: # establish a new target position (always; band can't block it)
105
+ orders.append({"symbol": sym, "side": "BUY", "delta": tgt, "cur_w": 0.0, "tgt_w": tw, "exit": False})
106
+ elif abs(delta) < band: # churn guard: skip small adjustments to EXISTING positions
107
+ continue
108
+ else:
109
+ orders.append({"symbol": sym, "side": "BUY" if delta > 0 else "SELL",
110
+ "delta": delta, "cur_w": cw, "tgt_w": tw, "exit": False})
111
+ orders.sort(key=lambda o: (0 if o["side"] == "SELL" else 1, -abs(o["delta"]))) # sells first
112
+ return orders
requirements.txt CHANGED
@@ -1,3 +1,6 @@
1
  gradio==5.50.0
2
  pandas
 
 
 
3
  certifi
 
1
  gradio==5.50.0
2
  pandas
3
+ numpy
4
+ pyarrow
5
+ huggingface_hub
6
  certifi