s-ttp commited on
Commit
7507198
Β·
verified Β·
1 Parent(s): 9a0feab

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -7,23 +7,22 @@ sdk: gradio
7
  sdk_version: 5.50.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: Autonomous agentic fund - built to beat the S&P 500
11
  ---
12
 
13
  # DeepQuant
14
 
15
- A **fully autonomous fund** running end-to-end on an Alpaca **paper** account β€” no human in the loop. Under the hood, a hybrid architecture: a deterministic quant engine
16
- executes a 20-year-validated, regime-aware multi-asset strategy, while an **agentic AI layer** supervises it,
17
- narrates it, and β€” within strict evidence gates β€” recalibrates it.
18
 
19
- **Division of labour, by design:**
20
- - **Deterministic:** stock ranking (MarketFM, ~11,800-name survivorship-free universe), order execution over the
21
- Alpaca API, risk rails (long-only, 10% cap, no leverage), daily statistical health checks vs the strategy's own
22
- backtest distribution.
23
- - **Agentic (Claude Opus 5):** a weekly role-separated crew β€” Analyst β†’ adversarial Critic β†’ PM β€” that assesses
24
- the evidence and decides to hold, adopt a better-validated strategy variant, or pause trading; every action is
25
- re-checked by a code validator (full walk-forward acceptance criteria, 90-day rate limit, hard rails).
26
- - **MarketGuruLLM** (fine-tuned Qwen2.5-14B) narrates every rebalance and decision in plain English.
27
 
28
- The page is **read-only**: live positions, the fund vs the S&P 500 since inception, agent health, the latest decision
29
- with its reasoning, and a dry-run rebalance preview. Backtested results, paper trading β€” not investment advice.
 
7
  sdk_version: 5.50.0
8
  app_file: app.py
9
  pinned: false
10
+ short_description: The AI-managed fund - researched, run and governed by AI
11
  ---
12
 
13
  # DeepQuant
14
 
15
+ **The AI-managed fund** β€” its strategy was *discovered by AI research agents* through 150+ walk-forward-validated
16
+ strategy configurations over 21 years of survivorship-free data, and it is *run and governed by AI* end-to-end on a
17
+ live Alpaca **paper** account with no human in the loop.
18
 
19
+ - **The secret recipe:** ~60 U.S. large caps selected monthly by a proprietary AI-researched quality signal, plus a
20
+ crisis-only defense (40% into bonds + gold, only in confirmed severe bears). The exact signal is undisclosed;
21
+ every trade and result is published live.
22
+ - **AI governance:** a weekly AI investment committee (Analyst β†’ adversarial Critic β†’ PM on Claude Opus 5) reviews
23
+ the fund against its own 20-year envelope; changes require walk-forward proof via a code validator. Failure β†’ HOLD.
24
+ - **Deterministic where it must be:** execution, risk rails (long-only, 10% cap, no leverage), reconciliation.
25
+ - **MarketGuruLLM** narrates every move in plain English; daily AI self-diagnosis is published openly.
 
26
 
27
+ Strategy v2, live since September 2026 (v1's full record is archived). Simulated research results, paper trading β€”
28
+ not investment advice.
__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
@@ -137,6 +137,12 @@ def refresh():
137
  "Weight": f"{mv / eq * 100:.1f}%" if eq else "β€”",
138
  "Unrealized P&L": f'${float(p["unrealized_pl"]):,.2f} ({float(p["unrealized_plpc"]) * 100:+.1f}%)',
139
  })
 
 
 
 
 
 
140
  df = pd.DataFrame(rows) if rows else pd.DataFrame([{"Symbol": "β€” no open positions β€”"}])
141
  curve, cnote = build_curve()
142
  stamp = f"_Last updated {now} Β· auto-refreshes every 30 min_"
@@ -205,14 +211,16 @@ def load_governance():
205
  .replace("impl_alert", "implementation-drift alert").replace("SPY/MTUM", "the S&P 500 and the momentum factor"))
206
  if len(why) > 420:
207
  why = why[:420].rsplit(" ", 1)[0] + "…"
208
- CONMAP = {"abs10_sec5": "10% cap Β· ≀5/sector", "rel12b2_sec5": "index-aware caps (≀12%, index wt +2%) Β· ≀5/sector",
209
- "abs10_sec6": "10% cap Β· ≀6/sector"}
210
- sig = " + ".join(SIGMAP.get(x, x) for x in (inc.get("signal") or []))
 
 
211
  return (f"### 🀝 Strategy governance β€” the crew's latest decision\n"
212
  f"**{act.strip()}** β€” {why}\n"
213
  f"- **Tracking:** 8-week gap vs S&P {tr.get('gap_8w_pp', 'β€”')}pp β€” the {tr.get('pctile_8w', 'β€”')}th percentile "
214
  f"of the strategy's 20-year history Β· implementation drift {tr.get('impl_drift_pp', 'β€”')}pp vs its own simulation\n"
215
- f"- **Live strategy (v{inc.get('version', '?')}):** {sig} selection Β· {inc.get('regime', '?')} trend rule Β· "
216
  f"defense = {DEFMAP.get(inc.get('defense'), inc.get('defense'))} Β· {CONMAP.get(inc.get('construction'), '10% cap Β· ≀5/sector')} Β· rebalanced every {inc.get('cadence_weeks', 1)} week(s)\n"
217
  f"- **Change policy:** decided weekly by an agentic crew β€” Analyst β†’ adversarial Critic β†’ PM (Claude Opus 5) β€” "
218
  f"gated by a code validator. A variant is adopted only if it beats the incumbent on the full 20-year "
@@ -263,7 +271,7 @@ def rebalance_preview():
263
  reg = tgt["regime"]
264
  mode = "🟒 OFFENSE" if reg["offense"] else "πŸ›‘οΈ DEFENSE"
265
  sma = (f"SPY 10-wk {reg['sma10']:,.0f} vs 40-wk {reg['sma40']:,.0f}" if reg.get("sma10") else reg.get("warn", ""))
266
- mix = "50 stocks Β· 100% equity" if reg["offense"] else "60% stocks + 20% AGG + 20% GLD"
267
  nb = sum(1 for o in orders if o["side"] == "BUY")
268
  ns = sum(1 for o in orders if o["side"] == "SELL")
269
  traded = sum(abs(o["delta"]) for o in orders)
@@ -273,43 +281,50 @@ def rebalance_preview():
273
  banner = (f"## {mode}\n**Regime:** {sma} Β· equity **${eq:,.0f}** Β· target: {mix}\n\n{summ}\n\n"
274
  f"<sub>Preview only β€” **no orders are placed**. This is exactly what the agent would submit at the next "
275
  f"Monday-after-open rebalance (notional market orders, sells before buys).</sub>")
276
- tdf = pd.DataFrame([{"Ticker": r["ticker"], "Sector": r["sector"], "Target %": f'{r["weight"] * 100:.1f}%',
277
- "Target $": f'${r["weight"] * eq:,.0f}'} for r in tgt["rows"]])
 
 
 
278
  if orders:
279
- odf = pd.DataFrame([{"Symbol": o["symbol"], "Side": ("πŸ”» SELL" if o["side"] == "SELL" else "🟒 BUY"),
280
- "Trade $": f'${abs(o["delta"]):,.0f}', "Now": f'{o["cur_w"] * 100:.1f}%',
281
- "β†’ Target": f'{o["tgt_w"] * 100:.1f}%'} for o in orders])
 
282
  else:
283
- odf = pd.DataFrame([{"Symbol": "β€” already at target β€”"}])
284
  return banner, tdf, odf
285
 
286
 
287
  with gr.Blocks(title="DeepQuant β€” live paper monitor", theme=gr.themes.Soft()) as demo:
288
  gr.Markdown(
289
  "# πŸ€– DeepQuant\n"
290
- "### A fully autonomous, agentic fund β€” built to beat the S&P 500 across market cycles\n"
291
- "DeepQuant runs a 20-year-validated, regime-aware multi-asset strategy **end-to-end on a live paper account** β€” "
292
- "it selects, executes, monitors, explains, recalibrates and even pauses itself, **with no human in the loop**. Under the hood it is a **hybrid "
293
- "of deterministic and agentic AI frameworks**, with every responsibility assigned by one principle: **deterministic code wherever correctness must be provable, "
294
- "agentic AI wherever judgment adds value** β€” never the other way around.\n\n"
295
- "**The stack:**\n"
296
- "- 🧠 **[MarketFM](https://huggingface.co/spaces/s-ttp/marketfm-ranker)** β€” a survivorship-free quant model "
297
- "(~11,800 US stocks, 21 point-in-time factors) ranks the cross-section and sets the target portfolio\n"
298
- "- βš™οΈ **Deterministic execution engine** β€” turns targets into orders over the **Alpaca API**: weekly rebalances, "
299
- "market-open checks, notional market orders, idempotent reconciliation β€” inside uncrossable rails "
300
- "(long-only, 10% position cap, no leverage, cash buffer)\n"
301
- "- 🩺 **Deterministic supervision** β€” daily health checks locate live performance *inside the strategy's own "
302
- "20-year backtest distribution* and watch guardrails, drift and pipeline freshness\n"
303
- "- 🀝 **Agentic decision crew** (Claude Opus 5) β€” every week an **Analyst** assesses the evidence, an adversarial "
304
- "**Critic** stress-tests it, and a **PM** decides: hold, adopt a better-validated strategy variant, or pause "
305
- "trading β€” each action gated by a **code validator** (walk-forward acceptance criteria, 90-day rate limit, hard rails)\n"
306
- "- πŸ’¬ **[MarketGuruLLM](https://huggingface.co/s-ttp/qwen25-14b-fin-lora)** β€” a fine-tuned analyst model narrates "
307
- "every rebalance and every decision in plain English\n\n"
308
- "**The strategy:** large-cap **momentum + quality** while the market trend is up; when it turns down, de-risk into "
309
- "a blend of **stocks + bonds + gold** (60 / 20 / 20). Backtested 2002–2026, net of costs: **~14% / yr vs the "
310
- "S&P 500's ~11%**, higher Sharpe, βˆ’35% max drawdown vs the index's βˆ’49%.\n"
311
- "<sub>Fully autonomous, paper trading, this page is read-only. Backtested results β€” past performance β‰  future. "
312
- "Not investment advice.</sub>")
 
 
 
313
  with gr.Tabs():
314
  with gr.Tab("πŸ“Š Live account"):
315
  stats = gr.Markdown()
@@ -319,7 +334,8 @@ with gr.Blocks(title="DeepQuant β€” live paper monitor", theme=gr.themes.Soft())
319
  plot = gr.LinePlot(x="date", y="value", color="series", height=320,
320
  y_title="Account value ($)", x_title="")
321
  note = gr.Markdown(visible=False)
322
- gr.Markdown("### πŸ“‹ Current holdings")
 
323
  tbl = gr.Dataframe(wrap=True, interactive=False)
324
  gov = gr.Markdown()
325
  with gr.Row():
@@ -333,10 +349,10 @@ with gr.Blocks(title="DeepQuant β€” live paper monitor", theme=gr.themes.Soft())
333
  pbtn = gr.Button("⟳ Recompute preview", scale=0)
334
  with gr.Row():
335
  with gr.Column():
336
- gr.Markdown("**Target portfolio**")
337
  tgt_tbl = gr.Dataframe(interactive=False, wrap=True)
338
  with gr.Column():
339
- gr.Markdown("**Proposed orders** β€” sells first, then buys")
340
  ord_tbl = gr.Dataframe(interactive=False, wrap=True)
341
  with gr.Accordion("🧬 Why hybrid? Who decides what β€” and why", open=False):
342
  gr.Markdown(
 
137
  "Weight": f"{mv / eq * 100:.1f}%" if eq else "β€”",
138
  "Unrealized P&L": f'${float(p["unrealized_pl"]):,.2f} ({float(p["unrealized_plpc"]) * 100:+.1f}%)',
139
  })
140
+ rows.sort(key=lambda r: -float(str(r["Mkt value"]).replace("$", "").replace(",", "")))
141
+ hidden = max(0, len(rows) - 10)
142
+ rows = rows[:10]
143
+ if hidden:
144
+ rows.append({"Symbol": f"… +{hidden} more", "Qty": "", "Avg entry": "", "Last": "",
145
+ "Mkt value": "undisclosed", "Weight": "", "Unrealized P&L": ""})
146
  df = pd.DataFrame(rows) if rows else pd.DataFrame([{"Symbol": "β€” no open positions β€”"}])
147
  curve, cnote = build_curve()
148
  stamp = f"_Last updated {now} Β· auto-refreshes every 30 min_"
 
211
  .replace("impl_alert", "implementation-drift alert").replace("SPY/MTUM", "the S&P 500 and the momentum factor"))
212
  if len(why) > 420:
213
  why = why[:420].rsplit(" ", 1)[0] + "…"
214
+ CONMAP = {"n60_nocap": "~60 AI-selected large caps Β· 10% position cap", "n50_nocap": "~50 AI-selected large caps Β· 10% cap",
215
+ "n75_nocap": "~75 AI-selected large caps Β· 10% cap", "abs10_sec5": "10% cap Β· ≀5/sector"}
216
+ REGMAP = {"gate20": "crisis-only severity gate (trend break + deep-drawdown confirmation)",
217
+ "gate15": "severity gate (15%)", "cross10/40": "10/40-week trend crossover"}
218
+ sig = "proprietary AI-researched quality signal (undisclosed)"
219
  return (f"### 🀝 Strategy governance β€” the crew's latest decision\n"
220
  f"**{act.strip()}** β€” {why}\n"
221
  f"- **Tracking:** 8-week gap vs S&P {tr.get('gap_8w_pp', 'β€”')}pp β€” the {tr.get('pctile_8w', 'β€”')}th percentile "
222
  f"of the strategy's 20-year history Β· implementation drift {tr.get('impl_drift_pp', 'β€”')}pp vs its own simulation\n"
223
+ f"- **Live strategy (v{inc.get('version', '?')}):** {sig} Β· {REGMAP.get(inc.get('regime'), inc.get('regime'))} Β· "
224
  f"defense = {DEFMAP.get(inc.get('defense'), inc.get('defense'))} Β· {CONMAP.get(inc.get('construction'), '10% cap Β· ≀5/sector')} Β· rebalanced every {inc.get('cadence_weeks', 1)} week(s)\n"
225
  f"- **Change policy:** decided weekly by an agentic crew β€” Analyst β†’ adversarial Critic β†’ PM (Claude Opus 5) β€” "
226
  f"gated by a code validator. A variant is adopted only if it beats the incumbent on the full 20-year "
 
271
  reg = tgt["regime"]
272
  mode = "🟒 OFFENSE" if reg["offense"] else "πŸ›‘οΈ DEFENSE"
273
  sma = (f"SPY 10-wk {reg['sma10']:,.0f} vs 40-wk {reg['sma40']:,.0f}" if reg.get("sma10") else reg.get("warn", ""))
274
+ mix = "~60 AI-selected stocks Β· 100% equity" if reg["offense"] else "60% stocks + 20% AGG + 20% GLD (crisis defense)"
275
  nb = sum(1 for o in orders if o["side"] == "BUY")
276
  ns = sum(1 for o in orders if o["side"] == "SELL")
277
  traded = sum(abs(o["delta"]) for o in orders)
 
281
  banner = (f"## {mode}\n**Regime:** {sma} Β· equity **${eq:,.0f}** Β· target: {mix}\n\n{summ}\n\n"
282
  f"<sub>Preview only β€” **no orders are placed**. This is exactly what the agent would submit at the next "
283
  f"Monday-after-open rebalance (notional market orders, sells before buys).</sub>")
284
+ import collections as _c
285
+ secagg = _c.defaultdict(float)
286
+ for r in tgt["rows"]: secagg[r["sector"]] += r["weight"]
287
+ tdf = pd.DataFrame([{"Sector": k, "Target %": f"{v*100:.1f}%", "Names": sum(1 for r in tgt["rows"] if r["sector"] == k)}
288
+ for k, v in sorted(secagg.items(), key=lambda x: -x[1])])
289
  if orders:
290
+ buys = sum(o["delta"] for o in orders if o["side"] == "BUY")
291
+ sells = sum(-o["delta"] for o in orders if o["side"] == "SELL")
292
+ odf = pd.DataFrame([{"Side": "🟒 BUY", "Orders": sum(1 for o in orders if o["side"] == "BUY"), "Total": f"${buys:,.0f}"},
293
+ {"Side": "πŸ”» SELL", "Orders": sum(1 for o in orders if o["side"] == "SELL"), "Total": f"${sells:,.0f}"}])
294
  else:
295
+ odf = pd.DataFrame([{"Side": "β€” already at target β€”", "Orders": 0, "Total": "$0"}])
296
  return banner, tdf, odf
297
 
298
 
299
  with gr.Blocks(title="DeepQuant β€” live paper monitor", theme=gr.themes.Soft()) as demo:
300
  gr.Markdown(
301
  "# πŸ€– DeepQuant\n"
302
+ "### The AI-managed fund β€” researched by AI, run by AI, governed by AI\n"
303
+ "DeepQuant is managed end-to-end by artificial intelligence β€” **no human in the loop**. Its strategy was not "
304
+ "designed by a portfolio manager: it was **discovered by AI research agents** that stress-tested more than **150 "
305
+ "strategy configurations across 21 years of survivorship-free market data** β€” every candidate walk-forward "
306
+ "validated, net of costs, across five market eras β€” before converging on the recipe running today.\n\n"
307
+ "**The secret recipe:** the exact selection signal is proprietary. What we disclose: the fund holds **~60 U.S. "
308
+ "large caps chosen by an AI-researched quality signal**, refreshed monthly β€” and a **crisis-only defense**: in "
309
+ "confirmed severe bears (and only then) it shifts 40% into bonds and gold. No reaction to noise, corrections, "
310
+ "or headlines. Every trade and every result is published here, live.\n\n"
311
+ "**The AI stack:**\n"
312
+ "- 🧠 **[MarketFM](https://huggingface.co/spaces/s-ttp/marketfm-ranker)** β€” the AI research platform: ~11,800 US "
313
+ "stocks, 4.1M stock-weeks, 21 point-in-time factors β€” the laboratory where the recipe was found and is re-verified\n"
314
+ "- 🀝 **An AI investment committee** (Claude Opus 5) β€” every week an **Analyst** reviews the fund against its own "
315
+ "20-year statistical envelope, an adversarial **Critic** attacks, and a **PM** rules: hold, adopt a better-proven "
316
+ "variant, or pause β€” every action gated by a **code validator** (walk-forward proof required, rate-limited)\n"
317
+ "- βš™οΈ **Deterministic execution** β€” orders, risk rails (long-only, 10% cap, no leverage), market-hours gates, "
318
+ "self-reconciliation: the parts where correctness must be provable are code, not judgment\n"
319
+ "- 🩺 **AI self-diagnosis** β€” daily health checks published openly; deviation from the strategy's own history is "
320
+ "flagged before anyone asks\n"
321
+ "- πŸ’¬ **[MarketGuruLLM](https://huggingface.co/s-ttp/qwen25-14b-fin-lora)** β€” a fine-tuned analyst model explains "
322
+ "every move in plain English\n\n"
323
+ "**Simulated record of the recipe (2005–2026, walk-forward, net of costs):** ~14%/yr vs the S&P 500's ~11% Β· "
324
+ "beat the index in **14 of 22 years** and **~69% of all rolling 3-year windows** Β· 2008 cushioned to βˆ’29% Β· "
325
+ "verified across 4 of 5 market eras.\n"
326
+ "<sub>Fully autonomous, paper trading, read-only page. Strategy v2, live since September 2026; the v1 record is "
327
+ "archived and available. Simulated results β€” past performance β‰  future. Not investment advice.</sub>")
328
  with gr.Tabs():
329
  with gr.Tab("πŸ“Š Live account"):
330
  stats = gr.Markdown()
 
334
  plot = gr.LinePlot(x="date", y="value", color="series", height=320,
335
  y_title="Account value ($)", x_title="")
336
  note = gr.Markdown(visible=False)
337
+ gr.Markdown("### πŸ“‹ Top 10 holdings\n<sub>Top 10 by value. The full ~60-name book is not disclosed β€” "
338
+ "the selection signal is the fund's proprietary, AI-researched recipe.</sub>")
339
  tbl = gr.Dataframe(wrap=True, interactive=False)
340
  gov = gr.Markdown()
341
  with gr.Row():
 
349
  pbtn = gr.Button("⟳ Recompute preview", scale=0)
350
  with gr.Row():
351
  with gr.Column():
352
+ gr.Markdown("**Target allocation by sector** <sub>(name-level book undisclosed)</sub>")
353
  tgt_tbl = gr.Dataframe(interactive=False, wrap=True)
354
  with gr.Column():
355
+ gr.Markdown("**Proposed orders** β€” aggregate (sells execute first)")
356
  ord_tbl = gr.Dataframe(interactive=False, wrap=True)
357
  with gr.Accordion("🧬 Why hybrid? Who decides what β€” and why", open=False):
358
  gr.Markdown(
rebalance_engine.py CHANGED
@@ -42,14 +42,15 @@ def cap_weights(w, mw=MAX_W):
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, "construction": "abs10_sec5", "version": 1}
47
 
48
 
49
  CONSTRUCTIONS = {
50
- "abs10_sec5": {"rule": "abs", "hard": 0.10, "buffer": 0.0, "sector_cap": 5},
51
- "rel12b2_sec5": {"rule": "rel", "hard": 0.12, "buffer": 0.02, "sector_cap": 5},
52
- "abs10_sec6": {"rule": "abs", "hard": 0.10, "buffer": 0.0, "sector_cap": 6},
 
53
  }
54
 
55
 
@@ -83,12 +84,25 @@ def _spy_series():
83
  return pd.Series({k: float(v["5. adjusted close"]) for k, v in ts.items()}).sort_index()
84
 
85
 
86
- def detect_regime(rule="cross10/40"):
 
 
 
 
 
 
 
 
 
 
87
  try:
88
  s = _spy_series()
89
  p = float(s.iloc[-1]); s10 = float(s.rolling(10).mean().iloc[-1]); s20 = float(s.rolling(20).mean().iloc[-1])
90
  s40 = float(s.rolling(40).mean().iloc[-1]); p_prev = float(s.iloc[-2]); s40_prev = float(s.rolling(40).mean().iloc[-2])
91
- if rule == "single40":
 
 
 
92
  off = p >= s40
93
  elif rule == "dual20/40":
94
  off = True if (p >= s20 and p >= s40) else (False if (p < s20 and p < s40) else (s10 >= s40))
@@ -118,7 +132,7 @@ def _rank_sleeve(df, cols, sign=1, con=None):
118
  continue
119
  sec[r["sector"]] = sec.get(r["sector"], 0) + 1
120
  picked.append(r)
121
- if len(picked) >= SIZE:
122
  break
123
  mc = np.array([r["marketcap"] for r in picked], float)
124
  if con["rule"] == "rel":
 
42
  return w
43
 
44
 
45
+ DEFAULT_CFG = {"signal": ["n_roa"], "regime": "gate20",
46
+ "defense": "mom60_agg20_gld20", "cadence_weeks": 4, "construction": "n60_nocap", "version": 2}
47
 
48
 
49
  CONSTRUCTIONS = {
50
+ "n60_nocap": {"rule": "abs", "hard": 0.10, "buffer": 0.0, "sector_cap": 999, "size": 60},
51
+ "n50_nocap": {"rule": "abs", "hard": 0.10, "buffer": 0.0, "sector_cap": 999, "size": 50},
52
+ "n75_nocap": {"rule": "abs", "hard": 0.10, "buffer": 0.0, "sector_cap": 999, "size": 75},
53
+ "abs10_sec5": {"rule": "abs", "hard": 0.10, "buffer": 0.0, "sector_cap": 5, "size": 50},
54
  }
55
 
56
 
 
84
  return pd.Series({k: float(v["5. adjusted close"]) for k, v in ts.items()}).sort_index()
85
 
86
 
87
+ def _trail_dd(closes, n=52):
88
+ win = list(closes[-n:])
89
+ if len(win) < 26:
90
+ return 0.0
91
+ pk, m = win[0], 0.0
92
+ for p in win:
93
+ pk = max(pk, p); m = max(m, 1 - p/pk)
94
+ return m
95
+
96
+
97
+ def detect_regime(rule="gate20"):
98
  try:
99
  s = _spy_series()
100
  p = float(s.iloc[-1]); s10 = float(s.rolling(10).mean().iloc[-1]); s20 = float(s.rolling(20).mean().iloc[-1])
101
  s40 = float(s.rolling(40).mean().iloc[-1]); p_prev = float(s.iloc[-2]); s40_prev = float(s.rolling(40).mean().iloc[-2])
102
+ if rule in ("gate20", "gate15"):
103
+ thr = 0.20 if rule == "gate20" else 0.15
104
+ off = not ((s10 < s40) and (_trail_dd(list(s.values)) >= thr))
105
+ elif rule == "single40":
106
  off = p >= s40
107
  elif rule == "dual20/40":
108
  off = True if (p >= s20 and p >= s40) else (False if (p < s20 and p < s40) else (s10 >= s40))
 
132
  continue
133
  sec[r["sector"]] = sec.get(r["sector"], 0) + 1
134
  picked.append(r)
135
+ if len(picked) >= con.get("size", SIZE):
136
  break
137
  mc = np.array([r["marketcap"] for r in picked], float)
138
  if con["rule"] == "rel":