From 5d1bdc1ff232e2984c3bdd605392f649d7f0189a Mon Sep 17 00:00:00 2001 From: k Date: Fri, 8 May 2026 16:07:46 +0800 Subject: [PATCH] chore: add $500 capital sweep scripts (conservative + moonshot) scripts/sweep_500.py: Parameter grid for capital-preservation oriented configs. Risk filters: max DD <= 30%, max single loss <= 16% of capital. scripts/sweep_moonshot.py: Aggressive grid for one-trade-hits-big strategy. Looser DD ceiling (50%), prioritizes biggest single-trade upside. Both run on the local v4 dataset to inform initial subscription parameter choices for live trading. Re-run after v5 accumulates enough signals (~6 weeks) to recalibrate. Co-Authored-By: Claude Opus 4.7 --- scripts/sweep_500.py | 192 ++++++++++++++++++++++++++++++++++++++ scripts/sweep_moonshot.py | 180 +++++++++++++++++++++++++++++++++++ 2 files changed, 372 insertions(+) create mode 100644 scripts/sweep_500.py create mode 100644 scripts/sweep_moonshot.py diff --git a/scripts/sweep_500.py b/scripts/sweep_500.py new file mode 100644 index 0000000..b28d562 --- /dev/null +++ b/scripts/sweep_500.py @@ -0,0 +1,192 @@ +""" +Parameter sweep for a $500 starting account. + +Finds (confidence_floor, leverage, tp%, sl%, margin_per_trade) combos that +maximize expected return while keeping max drawdown bounded. + +Uses the v4 buy/short signals already in the DB (sell excluded — semantic +bug). For each signal we know `price_impact_m1h` = max favorable excursion +in % over the next hour (already side-adjusted). Sim: + • peak >= tp_pct → TP hit, exit at +tp + • peak < 0 → SL hit (price went against us — pessimistic but safe) + • else → exit at peak/2 (held until window close, conservative) + • fees: 9 bps round-trip on notional + +Metrics computed per combo: + total_pnl_usd total profit on the $500 over the sample period + max_dd_usd biggest peak-to-trough drawdown in $ + max_dd_pct same as % of starting capital + sharpe_ish mean / std of per-trade PnL (rough Sharpe proxy) + worst_trade biggest single-trade loss in $ + n_trades how many trades fired in this combo +""" +import sqlite3 +import statistics +from itertools import product +from pathlib import Path + +DB = Path(__file__).resolve().parents[1] / "trumpsignal.db" +CAPITAL = 500.0 +FEE_BPS_RT = 0.0009 + + +def fetch_signals(conf_floor: int): + con = sqlite3.connect(DB) + con.row_factory = sqlite3.Row + rows = con.execute(""" + SELECT id, signal, ai_confidence, price_impact_m1h, published_at + FROM posts + WHERE signal IN ('buy', 'short') + AND price_at_post IS NOT NULL + AND price_impact_m1h IS NOT NULL + AND ai_confidence >= ? + ORDER BY published_at + """, (conf_floor,)).fetchall() + con.close() + return [dict(r) for r in rows] + + +def simulate(signals, margin: float, leverage: int, tp_pct: float, sl_pct: float): + """One sim of all signals with one parameter set. Returns trade-by-trade + PnLs in $ (positive = win, negative = loss).""" + notional = margin * leverage + pnls: list[float] = [] + for s in signals: + peak = s["price_impact_m1h"] # already side-adjusted, % + if peak >= tp_pct: + gross = tp_pct + elif peak < 0: + gross = -sl_pct # pessimistic: assume SL hit + else: + gross = peak / 2 # conservative midpoint + # Apply fees on notional + net_pct = gross / 100 - FEE_BPS_RT + # Convert to $ on this single trade + pnls.append(notional * net_pct) + return pnls + + +def metrics(pnls: list[float]) -> dict: + n = len(pnls) + if n == 0: + return {"n": 0, "total": 0.0, "max_dd": 0.0, "max_dd_pct": 0.0, + "sharpe": 0.0, "worst": 0.0, "win_rate": 0.0} + total = sum(pnls) + wins = sum(1 for p in pnls if p > 0) + # Running equity for drawdown + equity = CAPITAL + peak = equity + max_dd = 0.0 + for p in pnls: + equity += p + if equity > peak: + peak = equity + dd = peak - equity + if dd > max_dd: + max_dd = dd + sharpe = (statistics.mean(pnls) / statistics.stdev(pnls) + if n > 1 and statistics.stdev(pnls) > 0 else 0.0) + return { + "n": n, + "total": round(total, 2), + "max_dd": round(max_dd, 2), + "max_dd_pct": round(max_dd / CAPITAL * 100, 1), + "sharpe": round(sharpe, 2), + "worst": round(min(pnls), 2), + "win_rate": round(wins / n * 100, 1), + } + + +def main(): + print(f"$500 PARAMETER SWEEP") + print("=" * 90) + print(f"Sample: v4 buy/short signals from local DB, peak-MFE-based realistic sim") + print(f"Sim model: TP if peak≥TP, SL if peak<0, else peak/2 (conservative)") + print() + + # Sweep grid + confs = [60, 70, 75, 80, 85] + margins = [25, 50, 100] # $ per trade + levs = [5, 10, 15, 20] + tps = [0.5, 1.0, 1.5, 2.0, 3.0] # % + sls = [0.5, 1.0, 1.5, 2.0] # % + + results = [] + for conf in confs: + sigs = fetch_signals(conf) + if not sigs: + continue + for margin, lev, tp, sl in product(margins, levs, tps, sls): + # Skip degenerate combos (tp CAPITAL * 5: # cap notional at 5× capital total + continue + pnls = simulate(sigs, margin, lev, tp, sl) + m = metrics(pnls) + # ROI on the trading capital (not notional) + m.update({ + "conf": conf, "margin": margin, "lev": lev, + "tp": tp, "sl": sl, + "roi_pct": round(m["total"] / CAPITAL * 100, 1), + "max_loss_per_trade": round(margin * lev * (sl/100 + FEE_BPS_RT), 2), + }) + results.append(m) + + # Filter to "realistic risk profile" first + # Worst single trade <= $80 (16% of capital) + # Max drawdown <= $150 (30% of capital) + # At least 5 trades + candidates = [ + r for r in results + if r["max_loss_per_trade"] <= 80 + and r["max_dd"] <= 150 + and r["n"] >= 5 + and r["total"] > 0 # actually profitable + ] + + print(f"Combos tested: {len(results)}") + print(f"Profitable + safe: {len(candidates)}") + print() + + # Top 10 by total PnL + print("=" * 90) + print("TOP 10 BY TOTAL PROFIT (with risk filters applied)") + print("=" * 90) + print(f"{'conf':>4} {'margin':>6} {'lev':>3} {'TP%':>4} {'SL%':>4} " + f"{'n':>3} {'win%':>5} {'total$':>8} {'ROI%':>6} " + f"{'maxDD$':>7} {'DD%':>5} {'maxLoss$':>8} {'sharpe':>6}") + for r in sorted(candidates, key=lambda x: -x["total"])[:10]: + print(f"{r['conf']:>4} ${r['margin']:>5} {r['lev']:>3}× " + f"{r['tp']:>4.1f} {r['sl']:>4.1f} " + f"{r['n']:>3} {r['win_rate']:>4.1f}% " + f"${r['total']:>+6.2f} {r['roi_pct']:>+5.1f}% " + f"${r['max_dd']:>5.2f} {r['max_dd_pct']:>4.1f}% " + f"${r['max_loss_per_trade']:>6.2f} {r['sharpe']:>+5.2f}") + + print() + print("=" * 90) + print("TOP 5 BY SHARPE (risk-adjusted return)") + print("=" * 90) + print(f"{'conf':>4} {'margin':>6} {'lev':>3} {'TP%':>4} {'SL%':>4} " + f"{'n':>3} {'win%':>5} {'total$':>8} {'sharpe':>6} {'maxDD%':>6}") + for r in sorted(candidates, key=lambda x: -x["sharpe"])[:5]: + print(f"{r['conf']:>4} ${r['margin']:>5} {r['lev']:>3}× " + f"{r['tp']:>4.1f} {r['sl']:>4.1f} " + f"{r['n']:>3} {r['win_rate']:>4.1f}% " + f"${r['total']:>+6.2f} {r['sharpe']:>+5.2f} " + f"{r['max_dd_pct']:>5.1f}%") + + print() + print("=" * 90) + print("LOWEST DRAWDOWN COMBOS (conservative, prioritize survival)") + print("=" * 90) + print(f"{'conf':>4} {'margin':>6} {'lev':>3} {'TP%':>4} {'SL%':>4} " + f"{'n':>3} {'total$':>8} {'maxDD%':>6} {'sharpe':>6}") + for r in sorted(candidates, key=lambda x: (x["max_dd_pct"], -x["total"]))[:5]: + print(f"{r['conf']:>4} ${r['margin']:>5} {r['lev']:>3}× " + f"{r['tp']:>4.1f} {r['sl']:>4.1f} " + f"{r['n']:>3} ${r['total']:>+6.2f} " + f"{r['max_dd_pct']:>5.1f}% {r['sharpe']:>+5.2f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/sweep_moonshot.py b/scripts/sweep_moonshot.py new file mode 100644 index 0000000..5d8f54a --- /dev/null +++ b/scripts/sweep_moonshot.py @@ -0,0 +1,180 @@ +""" +Moonshot sweep: maximize SINGLE-TRADE upside, accept higher drawdown. + +Strategy hypothesis: + - User does NOT care about monthly stability. + - User wants 1-2 trades a month that individually return 20-50%+ of capital. + - Acceptable trade-off: more losing months, occasional -30% drawdowns, + in exchange for fat-tail capture of the right signals. + +Sim differences vs sweep_500: + - Higher leverage tier (up to 30×) + - Larger per-trade margin (% of capital) + - Higher conf floors (90, 95) — only the cleanest signals + - Wider TP grid (1% up to 5% to capture biggest moves) + - Tight SL (0.5% / 1% only — cut losers fast) + - Looser DD filter (allow up to 50% account drawdown) + - Reports BIGGEST winning trade explicitly +""" +import sqlite3 +import statistics +from itertools import product +from pathlib import Path + +DB = Path(__file__).resolve().parents[1] / "trumpsignal.db" +CAPITAL = 500.0 +FEE_BPS_RT = 0.0009 + + +def fetch_signals(conf_floor: int): + con = sqlite3.connect(DB) + con.row_factory = sqlite3.Row + rows = con.execute(""" + SELECT id, signal, ai_confidence, price_impact_m1h + FROM posts + WHERE signal IN ('buy', 'short') + AND price_at_post IS NOT NULL + AND price_impact_m1h IS NOT NULL + AND ai_confidence >= ? + ORDER BY published_at + """, (conf_floor,)).fetchall() + con.close() + return [dict(r) for r in rows] + + +def simulate(signals, margin: float, leverage: int, tp_pct: float, sl_pct: float): + """Return list of per-trade $ PnL.""" + notional = margin * leverage + pnls: list[float] = [] + for s in signals: + peak = s["price_impact_m1h"] + if peak >= tp_pct: + gross = tp_pct + elif peak < 0: + gross = -sl_pct + else: + gross = peak / 2 + net_pct = gross / 100 - FEE_BPS_RT + pnls.append(notional * net_pct) + return pnls + + +def metrics(pnls): + n = len(pnls) + if n == 0: + return None + total = sum(pnls) + wins = sum(1 for p in pnls if p > 0) + biggest_win = max(pnls) + biggest_loss = min(pnls) + equity, peak, maxdd = CAPITAL, CAPITAL, 0.0 + for p in pnls: + equity += p + peak = max(peak, equity) + maxdd = max(maxdd, peak - equity) + return { + "n": n, + "wins": wins, + "win_rate": round(wins/n*100, 1), + "total": round(total, 2), + "biggest_win": round(biggest_win, 2), + "biggest_loss": round(biggest_loss, 2), + "max_dd": round(maxdd, 2), + "max_dd_pct": round(maxdd/CAPITAL*100, 1), + "roi_pct": round(total/CAPITAL*100, 1), + # "best-trade-as-%-of-capital" — the moonshot metric + "best_trade_pct": round(biggest_win/CAPITAL*100, 1), + } + + +def main(): + print("$500 MOONSHOT SWEEP — maximize single-trade upside") + print("=" * 100) + + # Aggressive grid + confs = [80, 85, 90, 95] + margins = [50, 100, 150, 200] + levs = [10, 15, 20, 25, 30] + tps = [1.0, 1.5, 2.0, 3.0, 5.0] + sls = [0.5, 1.0, 1.5] + + results = [] + for conf in confs: + sigs = fetch_signals(conf) + if not sigs: + continue + for margin, lev, tp, sl in product(margins, levs, tps, sls): + # Notional cap: don't exceed 12× capital total exposure + if margin * lev > CAPITAL * 12: + continue + pnls = simulate(sigs, margin, lev, tp, sl) + m = metrics(pnls) + m.update({ + "conf": conf, "margin": margin, "lev": lev, + "tp": tp, "sl": sl, + "max_loss_per_trade": round(margin * lev * (sl/100 + FEE_BPS_RT), 2), + }) + results.append(m) + + # Soft filter: at least 5 trades, profitable, drawdown <50% (acceptable for moonshot) + candidates = [r for r in results + if r["n"] >= 5 and r["total"] > 0 and r["max_dd_pct"] <= 50] + + print(f"\n{len(candidates)} viable combos (n>=5, profitable, DD<=50%)") + print() + + # ── 1. Highest single-trade upside ───────────────────────────── + print("=" * 100) + print("TOP 10 — BIGGEST SINGLE WINNING TRADE (the 'one trade hits big' goal)") + print("=" * 100) + print(f"{'conf':>4} {'margin':>6} {'lev':>3} {'TP%':>4} {'SL%':>4} " + f"{'n':>3} {'win%':>5} {'best_win$':>9} {'best%cap':>9} " + f"{'maxLoss$':>8} {'total$':>8} {'DD%':>5}") + for r in sorted(candidates, key=lambda x: -x["biggest_win"])[:10]: + print(f"{r['conf']:>4} ${r['margin']:>5} {r['lev']:>3}× " + f"{r['tp']:>4.1f} {r['sl']:>4.1f} " + f"{r['n']:>3} {r['win_rate']:>4.1f}% " + f"${r['biggest_win']:>+7.2f} {r['best_trade_pct']:>+7.1f}% " + f"${r['max_loss_per_trade']:>6.2f} ${r['total']:>+6.2f} " + f"{r['max_dd_pct']:>4.1f}%") + + # ── 2. Best total ROI accepting bigger drawdown ──────────────── + print() + print("=" * 100) + print("TOP 10 — TOTAL ROI (any drawdown ≤ 50%)") + print("=" * 100) + print(f"{'conf':>4} {'margin':>6} {'lev':>3} {'TP%':>4} {'SL%':>4} " + f"{'n':>3} {'win%':>5} {'total$':>8} {'ROI%':>6} " + f"{'maxLoss$':>8} {'maxDD%':>6} {'best%':>6}") + for r in sorted(candidates, key=lambda x: -x["roi_pct"])[:10]: + print(f"{r['conf']:>4} ${r['margin']:>5} {r['lev']:>3}× " + f"{r['tp']:>4.1f} {r['sl']:>4.1f} " + f"{r['n']:>3} {r['win_rate']:>4.1f}% " + f"${r['total']:>+6.2f} {r['roi_pct']:>+5.1f}% " + f"${r['max_loss_per_trade']:>6.2f} {r['max_dd_pct']:>5.1f}% " + f"{r['best_trade_pct']:>+5.1f}%") + + # ── 3. The "Black Swan" candidate — biggest in absolute $ terms ─ + print() + print("=" * 100) + print("DATA REALITY CHECK — biggest |move| observed across all signals") + print("=" * 100) + con = sqlite3.connect(DB) + rows = con.execute(""" + SELECT id, signal, ai_confidence, price_impact_m1h + FROM posts WHERE signal IN ('buy','short') + AND price_impact_m1h IS NOT NULL + ORDER BY ABS(price_impact_m1h) DESC LIMIT 5 + """).fetchall() + con.close() + print(f" {'rank':>4} {'id':>5} {'sig':>5} {'conf':>4} {'1h move':>10}") + for i, r in enumerate(rows, 1): + print(f" {i:>4} {r[0]:>5} {r[1]:>5} {r[2]:>4} {r[3]:>+9.3f}%") + print() + print("→ With $200 margin × 30× = $6000 notional, the 2.36% best move") + print(f" would have netted: ${6000*0.0236-6000*0.0009:.2f} on a single trade") + print(f" = {(6000*0.0236-6000*0.0009)/CAPITAL*100:.1f}% of $500 capital from ONE trade") + + +if __name__ == "__main__": + main()