Files
lm_code/交易/bitmart-最终回测.py
Your Name b5af5b07f3 哈哈
2026-02-15 02:16:45 +08:00

294 lines
11 KiB
Python

"""
最终回测 - Top 3 盈利参数 + 不同仓位大小
Top 3 profitable combos from scan:
1. EMA(8/21/120) ATR>0.30% SL=0.4% MH=1800s → +2.88%
2. EMA(30/80/200) ATR>0.20% SL=0.8% MH=3600s → +2.53%
3. EMA(8/21/120) ATR>0.20% SL=0.8% MH=1800s → +1.94%
每组参数测试 risk_pct = [0.005, 0.01, 0.02, 0.03, 0.05]
输出详细月度报告和交易明细
"""
import sys, time, datetime, sqlite3, statistics
from pathlib import Path
from dataclasses import dataclass
from typing import List
@dataclass
class Trade:
open_time: datetime.datetime
close_time: datetime.datetime
direction: str
open_price: float
close_price: float
size: float
pnl: float
pnl_pct: float
fee: float
rebate: float
hold_seconds: float
close_reason: str
class EMA:
__slots__ = ('k', 'v')
def __init__(self, p):
self.k = 2.0 / (p + 1); self.v = None
def update(self, x):
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
return self.v
def load():
db = Path(__file__).parent.parent / 'models' / 'database.db'
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
conn = sqlite3.connect(str(db))
rows = conn.cursor().execute(
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
).fetchall()
conn.close()
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
def run_detailed(data, fp, sp, bp, atr_min, sl_pct, mh, risk_pct=0.005,
leverage=50, taker_fee=0.0006, rebate_rate=0.90, min_hold=200):
bal = 1000.0; pos = 0; op_ = 0.0; ot_ = None; ps_ = 0.0; pend = None
ef = EMA(fp); es = EMA(sp); eb = EMA(bp)
H = []; L = []; C = []
pf = None; pslow = None
trades: List[Trade] = []
hsl = sl_pct * 1.5
eq_curve = []; peak = 1000.0; max_dd = 0.0
def _close(price, dt, reason):
nonlocal bal, pos, op_, ot_, ps_, pend
pp = (price - op_) / op_ if pos == 1 else (op_ - price) / op_
pnl = ps_ * pp
cv = ps_ * (1 + pp); cf = cv * taker_fee; of = ps_ * taker_fee
tf = of + cf; rb = tf * rebate_rate
bal += pnl - cf + rb
hs = (dt - ot_).total_seconds()
trades.append(Trade(ot_, dt, 'long' if pos == 1 else 'short',
op_, price, ps_, pnl, pp, tf, rb, hs, reason))
pos = 0; op_ = 0; ot_ = None; ps_ = 0; pend = None
def _open(d, price, dt):
nonlocal bal, pos, op_, ot_, ps_
ns = bal * risk_pct * leverage
if ns < 1: return
bal -= ns * taker_fee
pos = 1 if d == 'L' else -1; op_ = price; ot_ = dt; ps_ = ns
for i, (dt, o, h, l, c) in enumerate(data):
H.append(h); L.append(l); C.append(c)
fast = ef.update(c); slow = es.update(c); big = eb.update(c)
atr_pct = 0.0
AP = 14
if len(H) > AP + 1:
s = 0.0
for j in range(-AP, 0):
tr = H[j] - L[j]; d1 = abs(H[j] - C[j-1]); d2 = abs(L[j] - C[j-1])
if d1 > tr: tr = d1
if d2 > tr: tr = d2
s += tr
atr_pct = s / (AP * c) if c > 0 else 0
cu = pf is not None and pf <= pslow and fast > slow
cd = pf is not None and pf >= pslow and fast < slow
pf = fast; pslow = slow
if pos != 0 and ot_:
pp = (c - op_) / op_ if pos == 1 else (op_ - c) / op_
hsec = (dt - ot_).total_seconds()
if -pp >= hsl:
_close(c, dt, f"hard_SL({pp*100:+.2f}%)")
continue
if hsec >= min_hold:
dc = False; reason = ""
if -pp >= sl_pct: dc = True; reason = f"SL({pp*100:+.2f}%)"
elif hsec >= mh: dc = True; reason = f"timeout({hsec:.0f}s)"
elif pos == 1 and cd: dc = True; reason = "cross_rev"
elif pos == -1 and cu: dc = True; reason = "cross_rev"
elif pend == 'cl' and pos == 1: dc = True; reason = "delayed_cross"
elif pend == 'cs' and pos == -1: dc = True; reason = "delayed_cross"
if dc:
_close(c, dt, reason)
if atr_pct >= atr_min:
if (cd or fast < slow) and c < big: _open('S', c, dt)
elif (cu or fast > slow) and c > big: _open('L', c, dt)
continue
else:
if pos == 1 and cd: pend = 'cl'
elif pos == -1 and cu: pend = 'cs'
if pos == 0 and atr_pct >= atr_min:
if cu and c > big: _open('L', c, dt)
elif cd and c < big: _open('S', c, dt)
# equity tracking every hour
if i % 60 == 0:
eq = bal
if pos != 0 and op_ > 0:
pp = (c - op_) / op_ if pos == 1 else (op_ - c) / op_
eq += ps_ * pp
eq_curve.append((dt, eq))
if eq > peak: peak = eq
dd = (peak - eq) / peak if peak > 0 else 0
if dd > max_dd: max_dd = dd
if pos != 0: _close(data[-1][4], data[-1][0], "backtest_end")
return trades, bal, max_dd, eq_curve
def print_report(name, trades, balance, max_dd, init=1000.0):
if not trades:
print(f"\n[{name}] No trades.\n", flush=True)
return
n = len(trades)
wins = [t for t in trades if t.pnl > 0]
losses = [t for t in trades if t.pnl <= 0]
wr = len(wins) / n * 100
net = balance - init
tot_pnl = sum(t.pnl for t in trades)
tot_fee = sum(t.fee for t in trades)
tot_reb = sum(t.rebate for t in trades)
avg_hold = statistics.mean([t.hold_seconds for t in trades])
vol = sum(t.size for t in trades) * 2
pf_n = sum(t.pnl for t in wins) if wins else 0
pf_d = abs(sum(t.pnl for t in losses)) if losses else 0
pf = pf_n / pf_d if pf_d > 0 else float('inf')
long_t = [t for t in trades if t.direction == 'long']
short_t = [t for t in trades if t.direction == 'short']
long_wr = len([t for t in long_t if t.pnl > 0]) / len(long_t) * 100 if long_t else 0
short_wr = len([t for t in short_t if t.pnl > 0]) / len(short_t) * 100 if short_t else 0
reasons = {}
for t in trades:
r = t.close_reason.split('(')[0]
reasons[r] = reasons.get(r, 0) + 1
print(f"\n{'='*70}", flush=True)
print(f" [{name}]", flush=True)
print(f"{'='*70}", flush=True)
print(f" Initial: {init:>10.2f} USDT", flush=True)
print(f" Final: {balance:>10.2f} USDT", flush=True)
print(f" Net P&L: {net:>+10.2f} USDT ({net/init*100:+.2f}%)", flush=True)
print(f" Max Drawdown: {max_dd*100:>9.2f}%", flush=True)
print(f"\n Trades: {n:>6} (Long {len(long_t)} WR={long_wr:.0f}% | Short {len(short_t)} WR={short_wr:.0f}%)", flush=True)
print(f" Win Rate: {wr:>5.1f}%", flush=True)
print(f" Profit Factor:{pf:>6.2f}", flush=True)
print(f" Avg Hold: {avg_hold:>5.0f}s ({avg_hold/60:.1f}min)", flush=True)
print(f"\n Dir P&L: {tot_pnl:>+10.2f}", flush=True)
print(f" Total Fee: {tot_fee:>10.2f}", flush=True)
print(f" Rebate(90%): {tot_reb:>+10.2f}", flush=True)
print(f" Net Fee: {tot_fee - tot_reb:>10.2f}", flush=True)
print(f" Volume: {vol:>10.0f}", flush=True)
if wins:
print(f"\n Avg Win: {statistics.mean([t.pnl for t in wins]):>+10.4f}", flush=True)
if losses:
print(f" Avg Loss: {statistics.mean([t.pnl for t in losses]):>+10.4f}", flush=True)
print(f" Best: {max(t.pnl for t in trades):>+10.4f}", flush=True)
print(f" Worst: {min(t.pnl for t in trades):>+10.4f}", flush=True)
print(f"\n Close Reasons:", flush=True)
for r, c in sorted(reasons.items(), key=lambda x: -x[1]):
print(f" {r:<20} {c:>5} ({c/n*100:.1f}%)", flush=True)
# Monthly
print(f"\n {'Month':<8} {'Trd':>5} {'DirPnL':>9} {'Rebate':>9} {'Net':>9} {'WR':>6}", flush=True)
print(f" {'-'*50}", flush=True)
monthly = {}
for t in trades:
k = t.close_time.strftime('%Y-%m')
if k not in monthly:
monthly[k] = {'n': 0, 'pnl': 0, 'reb': 0, 'fee': 0, 'w': 0}
monthly[k]['n'] += 1
monthly[k]['pnl'] += t.pnl
monthly[k]['reb'] += t.rebate
monthly[k]['fee'] += t.fee
if t.pnl > 0: monthly[k]['w'] += 1
for m in sorted(monthly.keys()):
d = monthly[m]
net_m = d['pnl'] - d['fee'] + d['reb']
wr_m = d['w'] / d['n'] * 100 if d['n'] > 0 else 0
print(f" {m:<8} {d['n']:>5} {d['pnl']:>+9.2f} {d['reb']:>9.2f} {net_m:>+9.2f} {wr_m:>5.1f}%", flush=True)
print(f"{'='*70}", flush=True)
def main():
print("Loading data...", flush=True)
data = load()
print(f"{len(data)} bars\n", flush=True)
# Top 3 parameter combos
configs = [
{"name": "A", "fp": 8, "sp": 21, "bp": 120, "atr": 0.003, "sl": 0.004, "mh": 1800},
{"name": "B", "fp": 30, "sp": 80, "bp": 200, "atr": 0.002, "sl": 0.008, "mh": 3600},
{"name": "C", "fp": 8, "sp": 21, "bp": 120, "atr": 0.002, "sl": 0.008, "mh": 1800},
]
risk_levels = [0.005, 0.01, 0.02, 0.03, 0.05]
print(f"{'='*100}", flush=True)
print(f" RISK LEVEL COMPARISON", flush=True)
print(f"{'='*100}", flush=True)
print(f" {'Config':<50} {'Risk%':>6} {'Net%':>7} {'Net$':>9} {'Trades':>7} {'MaxDD':>7}", flush=True)
print(f" {'-'*96}", flush=True)
best_result = None
best_net = -9999
for cfg in configs:
for rp in risk_levels:
label = f"EMA({cfg['fp']}/{cfg['sp']}/{cfg['bp']}) ATR>{cfg['atr']*100:.1f}% SL={cfg['sl']*100:.1f}% MH={cfg['mh']}"
trades, bal, mdd, eq = run_detailed(
data, cfg['fp'], cfg['sp'], cfg['bp'],
cfg['atr'], cfg['sl'], cfg['mh'], risk_pct=rp
)
net = bal - 1000.0
mk = " <<<" if net > 0 else ""
print(f" {label:<50} {rp*100:>5.1f}% {net/10:>+6.2f}% {net:>+8.2f} {len(trades):>7} {mdd*100:>6.2f}%{mk}", flush=True)
if net > best_net:
best_net = net
best_result = (cfg, rp, trades, bal, mdd, eq)
print(f"{'='*100}\n", flush=True)
# Detailed report for best
if best_result:
cfg, rp, trades, bal, mdd, eq = best_result
label = f"EMA({cfg['fp']}/{cfg['sp']}/{cfg['bp']}) ATR>{cfg['atr']*100:.1f}% SL={cfg['sl']*100:.1f}% MH={cfg['mh']} Risk={rp*100:.1f}%"
print_report(f"BEST: {label}", trades, bal, mdd)
# Save trades CSV
csv = Path(__file__).parent.parent / 'best_trades.csv'
with open(csv, 'w', encoding='utf-8-sig') as f:
f.write("open_time,close_time,dir,open_px,close_px,size,pnl,pnl_pct,fee,rebate,hold_sec,reason\n")
for t in trades:
f.write(f"{t.open_time},{t.close_time},{t.direction},"
f"{t.open_price:.2f},{t.close_price:.2f},{t.size:.2f},"
f"{t.pnl:.4f},{t.pnl_pct*100:.4f}%,{t.fee:.4f},{t.rebate:.4f},"
f"{t.hold_seconds:.0f},{t.close_reason}\n")
print(f"\nBest trades saved: {csv}", flush=True)
# Save equity curve
eq_csv = Path(__file__).parent.parent / 'best_equity.csv'
with open(eq_csv, 'w', encoding='utf-8-sig') as f:
f.write("time,equity\n")
for dt, e in eq:
f.write(f"{dt},{e:.2f}\n")
print(f"Equity curve saved: {eq_csv}", flush=True)
if __name__ == '__main__':
main()