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 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-08 16:07:46 +08:00
parent 9872a4cc52
commit 5d1bdc1ff2
2 changed files with 372 additions and 0 deletions
+192
View File
@@ -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<sl with low conviction signals)
if margin * lev > 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()
+180
View File
@@ -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()