Upload folder using huggingface_hub
Browse files- __pycache__/app.cpython-312.pyc +0 -0
- __pycache__/rebalance_engine.cpython-312.pyc +0 -0
- app.py +22 -1
- rebalance_engine.py +64 -18
__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
CHANGED
|
Binary files a/__pycache__/rebalance_engine.cpython-312.pyc and b/__pycache__/rebalance_engine.cpython-312.pyc differ
|
|
|
app.py
CHANGED
|
@@ -157,6 +157,21 @@ def load_health():
|
|
| 157 |
lines = [f"### 🩺 Agent health — {icon.get(r.get('overall'), '⚪')} {r.get('overall')} <sub>(checked {r.get('date')})</sub>"]
|
| 158 |
for c in r.get("checks", []):
|
| 159 |
lines.append(f"- {icon.get(c['status'], '⚪')} **{c['name']}** — {c['detail']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
lines.append("<sub>Deterministic daily checks: live tracking gap located in the 20-year backtest distribution, "
|
| 161 |
"risk guardrails, and pipeline freshness. Flags only — the supervisor never trades.</sub>")
|
| 162 |
return "\n".join(lines)
|
|
@@ -174,6 +189,11 @@ def load_rebalance():
|
|
| 174 |
if not log:
|
| 175 |
return ""
|
| 176 |
e = log[-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
mode = "🟢 OFFENSE" if e.get("mode") == "OFFENSE" else "🛡️ DEFENSE"
|
| 178 |
head = (f"### 🗓️ Latest rebalance — {e.get('date','')} · {mode}\n"
|
| 179 |
f"{e.get('buys',0)} buys · {e.get('sells',0)} sells · {e.get('turnover_pct',0)}% turnover · "
|
|
@@ -231,7 +251,8 @@ with gr.Blocks(title="DeepQuant — live paper monitor", theme=gr.themes.Soft())
|
|
| 231 |
"**Under the hood:**\n"
|
| 232 |
"- 🧠 **[MarketFM](https://huggingface.co/spaces/s-ttp/marketfm-ranker)** — a survivorship-free quant model — ranks the universe and sets the target portfolio\n"
|
| 233 |
"- 💬 **[MarketGuruLLM](https://huggingface.co/s-ttp/qwen25-14b-fin-lora)** — a fine-tuned analyst LLM — reasons over and narrates every rebalance\n"
|
| 234 |
-
"- ⚡ **Alpaca API** — the brokerage rails the **quant execution engine** drives to place orders and stream live fills, positions and P&L\n
|
|
|
|
| 235 |
"**The strategy:** large-cap **momentum + quality** while the trend is up; when it turns down it de-risks into a "
|
| 236 |
"blend of **stocks + bonds + gold** (60 / 20 / 20). Backtested 2002–2026: **~14% / yr vs the S&P 500's ~11%**, "
|
| 237 |
"higher Sharpe, smaller drawdown.\n"
|
|
|
|
| 157 |
lines = [f"### 🩺 Agent health — {icon.get(r.get('overall'), '⚪')} {r.get('overall')} <sub>(checked {r.get('date')})</sub>"]
|
| 158 |
for c in r.get("checks", []):
|
| 159 |
lines.append(f"- {icon.get(c['status'], '⚪')} **{c['name']}** — {c['detail']}")
|
| 160 |
+
try:
|
| 161 |
+
rr = json.load(open(hf_hub_download("s-ttp/marketfm-panel", "recalibration_report.json", repo_type="dataset",
|
| 162 |
+
token=tok, force_download=True)))
|
| 163 |
+
tr = rr.get("tracking", {})
|
| 164 |
+
inc = rr.get("incumbent", {})
|
| 165 |
+
flag = "🔁" if rr.get("changed") else ("🟡" if rr.get("triggered") else "🟢")
|
| 166 |
+
lines.append(f"- {flag} **Autonomous recalibration agent** (weekly) — 8-wk gap vs SPY "
|
| 167 |
+
f"{tr.get('gap_8w_pp', '—')}pp ({tr.get('pctile_8w', '—')}th pctile of backtest) · "
|
| 168 |
+
f"implementation drift {tr.get('impl_drift_pp', '—')}pp · {rr.get('decision', '')[:160]}")
|
| 169 |
+
lines.append(f"<sub>Live strategy config v{inc.get('version', '?')}: signal {inc.get('signal')} · regime "
|
| 170 |
+
f"{inc.get('regime')} · defense {inc.get('defense')} · cadence {inc.get('cadence_weeks')}w. "
|
| 171 |
+
f"Recalibration only adopts a variant that beats the incumbent on a full 20-year walk-forward, "
|
| 172 |
+
f"net of cost, across folds — never a re-fit to recent weeks; max one change per 90 days.</sub>")
|
| 173 |
+
except Exception:
|
| 174 |
+
pass
|
| 175 |
lines.append("<sub>Deterministic daily checks: live tracking gap located in the 20-year backtest distribution, "
|
| 176 |
"risk guardrails, and pipeline freshness. Flags only — the supervisor never trades.</sub>")
|
| 177 |
return "\n".join(lines)
|
|
|
|
| 189 |
if not log:
|
| 190 |
return ""
|
| 191 |
e = log[-1]
|
| 192 |
+
if e.get("type") == "recalibration":
|
| 193 |
+
head = f"### 🔁 Strategy recalibrated — {e.get('date','')}\n{e.get('summary','')}\n\n"
|
| 194 |
+
if e.get("narration"):
|
| 195 |
+
return head + f"> 💬 *{e['narration']}*\n\n<sub>— {e.get('narrated_by','MarketGuruLLM')}</sub>"
|
| 196 |
+
return head
|
| 197 |
mode = "🟢 OFFENSE" if e.get("mode") == "OFFENSE" else "🛡️ DEFENSE"
|
| 198 |
head = (f"### 🗓️ Latest rebalance — {e.get('date','')} · {mode}\n"
|
| 199 |
f"{e.get('buys',0)} buys · {e.get('sells',0)} sells · {e.get('turnover_pct',0)}% turnover · "
|
|
|
|
| 251 |
"**Under the hood:**\n"
|
| 252 |
"- 🧠 **[MarketFM](https://huggingface.co/spaces/s-ttp/marketfm-ranker)** — a survivorship-free quant model — ranks the universe and sets the target portfolio\n"
|
| 253 |
"- 💬 **[MarketGuruLLM](https://huggingface.co/s-ttp/qwen25-14b-fin-lora)** — a fine-tuned analyst LLM — reasons over and narrates every rebalance\n"
|
| 254 |
+
"- ⚡ **Alpaca API** — the brokerage rails the **quant execution engine** drives to place orders and stream live fills, positions and P&L\n"
|
| 255 |
+
"- 🔁 **Autonomous recalibration** — tracks live vs backtest over rolling 8-week windows; on statistically unusual deviation it re-runs the full walk-forward due diligence and adopts a better-validated variant on its own\n\n"
|
| 256 |
"**The strategy:** large-cap **momentum + quality** while the trend is up; when it turns down it de-risks into a "
|
| 257 |
"blend of **stocks + bonds + gold** (60 / 20 / 20). Backtested 2002–2026: **~14% / yr vs the S&P 500's ~11%**, "
|
| 258 |
"higher Sharpe, smaller drawdown.\n"
|
rebalance_engine.py
CHANGED
|
@@ -42,24 +42,51 @@ def cap_weights(w, mw=MAX_W):
|
|
| 42 |
return w
|
| 43 |
|
| 44 |
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
try:
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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"] =
|
| 63 |
df = df.sort_values("score", ascending=False)
|
| 64 |
picked, sec = [], {}
|
| 65 |
for _, r in df.iterrows():
|
|
@@ -75,18 +102,37 @@ def equity_sleeve(token):
|
|
| 75 |
for r, wi in zip(picked, w)]
|
| 76 |
|
| 77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
def get_target(token):
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
| 81 |
if reg["offense"]:
|
| 82 |
rows = [dict(x) for x in sleeve]
|
| 83 |
else:
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
rows
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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, invest_frac=0.995, force_sells=False):
|
|
|
|
| 42 |
return w
|
| 43 |
|
| 44 |
|
| 45 |
+
DEFAULT_CFG = {"signal": ["n_mom_12_1", "n_roe"], "regime": "cross10/40",
|
| 46 |
+
"defense": "mom60_agg20_gld20", "cadence_weeks": 1, "version": 1}
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def load_config(token):
|
| 50 |
+
"""Live strategy config written by the autonomous recalibration agent (falls back to the incumbent)."""
|
| 51 |
try:
|
| 52 |
+
c = json.load(open(hf_hub_download(PANEL_REPO, "strategy_config.json", repo_type="dataset", token=token, force_download=True)))
|
| 53 |
+
return {**DEFAULT_CFG, **{k: c[k] for k in DEFAULT_CFG if k in c}}
|
| 54 |
+
except Exception:
|
| 55 |
+
return dict(DEFAULT_CFG)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _spy_series():
|
| 59 |
+
u = f"https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY_ADJUSTED&symbol=SPY&apikey={AV}"
|
| 60 |
+
ts = json.load(urllib.request.urlopen(u, timeout=25, context=_CTX))["Weekly Adjusted Time Series"]
|
| 61 |
+
return pd.Series({k: float(v["5. adjusted close"]) for k, v in ts.items()}).sort_index()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def detect_regime(rule="cross10/40"):
|
| 65 |
+
try:
|
| 66 |
+
s = _spy_series()
|
| 67 |
+
p = float(s.iloc[-1]); s10 = float(s.rolling(10).mean().iloc[-1]); s20 = float(s.rolling(20).mean().iloc[-1])
|
| 68 |
+
s40 = float(s.rolling(40).mean().iloc[-1]); p_prev = float(s.iloc[-2]); s40_prev = float(s.rolling(40).mean().iloc[-2])
|
| 69 |
+
if rule == "single40":
|
| 70 |
+
off = p >= s40
|
| 71 |
+
elif rule == "dual20/40":
|
| 72 |
+
off = True if (p >= s20 and p >= s40) else (False if (p < s20 and p < s40) else (s10 >= s40))
|
| 73 |
+
elif rule == "hyst5":
|
| 74 |
+
off = True if p > 1.05 * s40 else (False if p < 0.95 * s40 else (s10 >= s40))
|
| 75 |
+
elif rule == "confirm2":
|
| 76 |
+
cur, prev = p >= s40, p_prev >= s40_prev
|
| 77 |
+
off = cur if cur == prev else (s10 >= s40)
|
| 78 |
+
else: # cross10/40 (incumbent)
|
| 79 |
+
off = s10 >= s40
|
| 80 |
+
return {"offense": bool(off), "rule": rule, "sma10": s10, "sma40": s40, "spy": p}
|
| 81 |
except Exception as e:
|
| 82 |
+
return {"offense": True, "rule": rule, "sma10": None, "sma40": None, "spy": None,
|
| 83 |
"warn": f"SPY fetch failed ({type(e).__name__}); defaulting to OFFENSE"}
|
| 84 |
|
| 85 |
|
| 86 |
+
def _rank_sleeve(df, cols, sign=1):
|
|
|
|
|
|
|
| 87 |
df = df[df["marketcap"].notna() & (df["marketcap"] > 0)].copy()
|
| 88 |
df = df.sort_values("marketcap", ascending=False).head(SEG_K)
|
| 89 |
+
df["score"] = sum(sign * df[c].fillna(0) for c in cols)
|
| 90 |
df = df.sort_values("score", ascending=False)
|
| 91 |
picked, sec = [], {}
|
| 92 |
for _, r in df.iterrows():
|
|
|
|
| 102 |
for r, wi in zip(picked, w)]
|
| 103 |
|
| 104 |
|
| 105 |
+
def equity_sleeve(token, cols=None):
|
| 106 |
+
cols = cols or DEFAULT_CFG["signal"]
|
| 107 |
+
p = hf_hub_download(PANEL_REPO, "marketfm_snapshot.parquet", repo_type="dataset", token=token)
|
| 108 |
+
return _rank_sleeve(pd.read_parquet(p), cols)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
def get_target(token):
|
| 112 |
+
cfg = load_config(token)
|
| 113 |
+
reg = detect_regime(cfg["regime"])
|
| 114 |
+
snap = pd.read_parquet(hf_hub_download(PANEL_REPO, "marketfm_snapshot.parquet", repo_type="dataset", token=token))
|
| 115 |
+
sleeve = _rank_sleeve(snap, cfg["signal"])
|
| 116 |
if reg["offense"]:
|
| 117 |
rows = [dict(x) for x in sleeve]
|
| 118 |
else:
|
| 119 |
+
d = cfg["defense"]
|
| 120 |
+
if d == "agg50_gld50":
|
| 121 |
+
rows = []
|
| 122 |
+
eq_frac, agg_w, gld_w = 0.0, 0.5, 0.5
|
| 123 |
+
elif d == "lowvol":
|
| 124 |
+
rows = _rank_sleeve(snap, ["n_vol_13w"], sign=-1)
|
| 125 |
+
eq_frac, agg_w, gld_w = 1.0, 0.0, 0.0
|
| 126 |
+
elif d == "mom40_agg30_gld30":
|
| 127 |
+
rows = [dict(x, weight=0.4 * x["weight"]) for x in sleeve]; eq_frac, agg_w, gld_w = 0.4, 0.3, 0.3
|
| 128 |
+
else: # mom60_agg20_gld20 (incumbent)
|
| 129 |
+
rows = [dict(x, weight=DEF_EQ * x["weight"]) for x in sleeve]; eq_frac, agg_w, gld_w = DEF_EQ, DEF_AGG, DEF_GLD
|
| 130 |
+
for sym, wv in [("AGG", agg_w), ("GLD", gld_w)]:
|
| 131 |
+
if wv > 0:
|
| 132 |
+
nm, sct = ETF[sym]
|
| 133 |
+
rows.append({"ticker": sym, "name": nm, "sector": sct, "weight": wv})
|
| 134 |
weights = {r["ticker"]: r["weight"] for r in rows}
|
| 135 |
+
return {"regime": reg, "weights": weights, "rows": rows, "config": cfg}
|
| 136 |
|
| 137 |
|
| 138 |
def compute_orders(weights, positions, equity, band_pct=0.005, invest_frac=0.995, force_sells=False):
|