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

364 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
多策略组合回测 — 目标: 1000 USDT/月
思路: 同时运行多个不同参数的 EMA 策略,它们在不同时间段产生信号,
彼此信号不重叠时各自独立开仓,等于"多个策略并行跑"
条件:
- 每笔: 100 USDT 保证金, 100x 杠杆, 名义 10,000 USDT
- 90% 返佣
- 最低持仓 > 3 分钟
- ETH 合约, 2025 全年
测试方案:
A) 单策略加大仓位 (500U/1000U)
B) 多策略组合 (3-5个不同参数策略并行)
C) 降低 ATR 门槛 + 更宽止损
D) 综合最优方案
"""
import sys, time, datetime, sqlite3
from pathlib import Path
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_strategy(data, fp, sp, bp, atr_min, sl_pct, mh, notional=10000.0):
"""单策略回测,返回交易列表"""
ef=EMA(fp); es=EMA(sp); eb=EMA(bp)
H=[]; L=[]; C=[]
pf_=None; ps_=None
pos=0; op=0.0; ot=None; pend=None
hsl=sl_pct*1.5; AP=14
FEE_RATE=0.0006; REB_RATE=0.90; MIN_H=200
trades=[]
for dt,o_,h_,l_,c_ in data:
p=c_; H.append(h_); L.append(l_); C.append(p)
fast=ef.update(p); slow=es.update(p); big=eb.update(p)
atr_pct=0.0
if len(H)>AP+1:
s=0.0
for i in range(-AP,0):
tr=H[i]-L[i]; d1=abs(H[i]-C[i-1]); d2=abs(L[i]-C[i-1])
if d1>tr: tr=d1
if d2>tr: tr=d2
s+=tr
atr_pct=s/(AP*p) if p>0 else 0
cu=pf_ is not None and pf_<=ps_ and fast>slow
cd=pf_ is not None and pf_>=ps_ and fast<slow
pf_=fast; ps_=slow
if pos!=0 and ot is not None:
pp=(p-op)/op if pos==1 else (op-p)/op
hsec=(dt-ot).total_seconds()
if -pp>=hsl:
pnl=notional*pp; fee=notional*FEE_RATE*2; reb=fee*REB_RATE
trades.append((pos, op, p, pnl, fee, reb, hsec, ot, dt))
pos=0; op=0; ot=None; pend=None; continue
if hsec>=MIN_H:
dc=False
if -pp>=sl_pct: dc=True
elif hsec>=mh: dc=True
elif pos==1 and cd: dc=True
elif pos==-1 and cu: dc=True
elif pend=='cl' and pos==1: dc=True
elif pend=='cs' and pos==-1: dc=True
if dc:
pnl=notional*pp; fee=notional*FEE_RATE*2; reb=fee*REB_RATE
trades.append((pos, op, p, pnl, fee, reb, hsec, ot, dt))
pos=0; op=0; ot=None; pend=None
if atr_pct>=atr_min:
if (cd or fast<slow) and p<big: pos=-1; op=p; ot=dt
elif (cu or fast>slow) and p>big: pos=1; op=p; ot=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 p>big: pos=1; op=p; ot=dt
elif cd and p<big: pos=-1; op=p; ot=dt
if pos!=0:
p=data[-1][4]; dt=data[-1][0]
pp=(p-op)/op if pos==1 else (op-p)/op
pnl=notional*pp; fee=notional*FEE_RATE*2; reb=fee*REB_RATE
trades.append((pos, op, p, pnl, fee, reb, (dt-ot).total_seconds(), ot, dt))
return trades
def analyze(trades, label, notional=10000.0):
"""分析交易结果,返回摘要字典"""
if not trades:
return {'label': label, 'n': 0, 'net': 0, 'pnl': 0, 'reb': 0, 'fee': 0, 'dd': 0, 'monthly': {}}
n = len(trades)
total_pnl = sum(t[3] for t in trades)
total_fee = sum(t[4] for t in trades)
total_reb = sum(t[5] for t in trades)
net = total_pnl - (total_fee - total_reb)
# 最大回撤
cum=0; peak=0; dd=0
for t in trades:
cum += t[3] - (t[4] - t[5])
if cum > peak: peak = cum
if peak - cum > dd: dd = peak - cum
# 月度
monthly = {}
for t in trades:
k = t[8].strftime('%Y-%m')
if k not in monthly: monthly[k] = {'n': 0, 'net': 0}
monthly[k]['n'] += 1
monthly[k]['net'] += t[3] - (t[4] - t[5])
wr = len([t for t in trades if t[3]>0]) / n * 100
return {'label': label, 'n': n, 'net': net, 'pnl': total_pnl, 'reb': total_reb,
'fee': total_fee, 'dd': dd, 'wr': wr, 'monthly': monthly}
def merge_trades(all_trade_lists):
"""合并多个策略的交易(检查时间冲突:同一时间只能有一个持仓)"""
# 简单合并:按开仓时间排序,跳过与已有持仓重叠的交易
all_trades = []
for trades in all_trade_lists:
for t in trades:
all_trades.append(t)
all_trades.sort(key=lambda x: x[7]) # 按开仓时间排序
merged = []
last_close = None
for t in all_trades:
open_time = t[7]
close_time = t[8]
if last_close is None or open_time >= last_close:
merged.append(t)
last_close = close_time
return merged
def print_comparison(results):
"""打印对比表"""
print(f"\n{'='*110}", flush=True)
print(f" COMPARISON: Target = 1000 USDT/month = 12,000 USDT/year", flush=True)
print(f"{'='*110}", flush=True)
print(f" {'方案':<40} {'交易数':>6} {'年净利':>10} {'月均':>8} {'胜率':>6} {'返佣':>10} {'最大回撤':>10} {'达标':>4}", flush=True)
print(f" {'-'*104}", flush=True)
for r in results:
monthly_avg = r['net'] / 12
ok = "Yes" if monthly_avg >= 1000 else "No"
print(f" {r['label']:<40} {r['n']:>6} {r['net']:>+10.0f} {monthly_avg:>+8.0f} {r.get('wr',0):>5.1f}% {r['reb']:>10.0f} {r['dd']:>10.0f} {ok:>4}", flush=True)
print(f"{'='*110}", flush=True)
# 打印最佳方案的月度明细
best = max(results, key=lambda x: x['net'])
print(f"\n 最佳方案: {best['label']}", flush=True)
print(f" 年净利: {best['net']:+.0f} USDT | 月均: {best['net']/12:+.0f} USDT\n", flush=True)
if best['monthly']:
print(f" {'月份':<8} {'笔数':>5} {'净利润':>10}", flush=True)
print(f" {'-'*28}", flush=True)
for m in sorted(best['monthly'].keys()):
d = best['monthly'][m]
print(f" {m:<8} {d['n']:>5} {d['net']:>+10.0f}", flush=True)
print(f" {'-'*28}", flush=True)
print(f" {'合计':<8} {best['n']:>5} {best['net']:>+10.0f}", flush=True)
def main():
print("Loading data...", flush=True)
data = load()
print(f"{len(data)} bars loaded\n", flush=True)
NOTIONAL = 10000.0 # 100U * 100x
results = []
# ============================
# 方案 1: 原始策略(基线)
# ============================
t1 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, NOTIONAL)
results.append(analyze(t1, "A1: EMA(8/21) ATR>0.3% [基线]", NOTIONAL))
print(f" A1 done: {len(t1)} trades", flush=True)
# ============================
# 方案 2: 加大仓位 - 500U (5倍)
# ============================
t2 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 50000.0)
results.append(analyze(t2, "A2: 基线 x5 (500U保证金)", 50000.0))
print(f" A2 done: {len(t2)} trades", flush=True)
# ============================
# 方案 3: 加大仓位 - 1000U (10倍)
# ============================
t3 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 100000.0)
results.append(analyze(t3, "A3: 基线 x10 (1000U保证金)", 100000.0))
print(f" A3 done: {len(t3)} trades", flush=True)
# ============================
# 方案 4: 降低ATR到0.15%(更多交易)
# ============================
t4 = run_strategy(data, 8, 21, 120, 0.0015, 0.004, 1800, NOTIONAL)
results.append(analyze(t4, "B1: ATR>0.15% (更频繁)", NOTIONAL))
print(f" B1 done: {len(t4)} trades", flush=True)
# ============================
# 方案 5: ATR>0.1% + 宽止损0.8%
# ============================
t5 = run_strategy(data, 8, 21, 120, 0.001, 0.008, 1800, NOTIONAL)
results.append(analyze(t5, "B2: ATR>0.1% SL=0.8%", NOTIONAL))
print(f" B2 done: {len(t5)} trades", flush=True)
# ============================
# 方案 6: ATR>0.2% + SL=0.8% + 更长持仓3600s
# ============================
t6 = run_strategy(data, 8, 21, 120, 0.002, 0.008, 3600, NOTIONAL)
results.append(analyze(t6, "B3: ATR>0.2% SL=0.8% MH=3600", NOTIONAL))
print(f" B3 done: {len(t6)} trades", flush=True)
# ============================
# 方案 7: 多策略组合3个不同EMA参数并行无时间重叠
# ============================
s1 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, NOTIONAL)
s2 = run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, NOTIONAL)
s3 = run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, NOTIONAL)
merged3 = merge_trades([s1, s2, s3])
results.append(analyze(merged3, "C1: 3策略组合 (不重叠)", NOTIONAL))
print(f" C1 done: {len(s1)}+{len(s2)}+{len(s3)} -> {len(merged3)} merged", flush=True)
# ============================
# 方案 8: 5策略组合
# ============================
s4 = run_strategy(data, 20, 55, 120, 0.002, 0.006, 1800, NOTIONAL)
s5 = run_strategy(data, 8, 34, 200, 0.002, 0.005, 1800, NOTIONAL)
merged5 = merge_trades([s1, s2, s3, s4, s5])
results.append(analyze(merged5, "C2: 5策略组合 (不重叠)", NOTIONAL))
print(f" C2 done: -> {len(merged5)} merged", flush=True)
# ============================
# 方案 9: 3策略组合 + 加大仓位到 300U
# ============================
s1b = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 30000.0)
s2b = run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, 30000.0)
s3b = run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, 30000.0)
merged3b = merge_trades([s1b, s2b, s3b])
results.append(analyze(merged3b, "D1: 3策略+300U仓位", 30000.0))
print(f" D1 done: -> {len(merged3b)} merged", flush=True)
# ============================
# 方案 10: 3策略组合 + 500U仓位
# ============================
s1c = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 50000.0)
s2c = run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, 50000.0)
s3c = run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, 50000.0)
merged3c = merge_trades([s1c, s2c, s3c])
results.append(analyze(merged3c, "D2: 3策略+500U仓位", 50000.0))
print(f" D2 done: -> {len(merged3c)} merged", flush=True)
# ============================
# 方案 11: 多策略并行(允许同时持仓,各策略独立运行)
# ============================
# 如果账户允许同时持有多个仓位(不同策略各自独立)
s1_r = analyze(s1, "sub1")
s2_r = analyze(run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, NOTIONAL), "sub2")
s3_r = analyze(run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, NOTIONAL), "sub3")
s4_r = analyze(run_strategy(data, 20, 55, 120, 0.002, 0.006, 1800, NOTIONAL), "sub4")
s5_r = analyze(run_strategy(data, 8, 34, 200, 0.002, 0.005, 1800, NOTIONAL), "sub5")
# 合并月度(允许重叠 = 各自独立计算再加总)
parallel_net = s1_r['net'] + s2_r['net'] + s3_r['net'] + s4_r['net'] + s5_r['net']
parallel_reb = s1_r['reb'] + s2_r['reb'] + s3_r['reb'] + s4_r['reb'] + s5_r['reb']
parallel_fee = s1_r['fee'] + s2_r['fee'] + s3_r['fee'] + s4_r['fee'] + s5_r['fee']
parallel_pnl = s1_r['pnl'] + s2_r['pnl'] + s3_r['pnl'] + s4_r['pnl'] + s5_r['pnl']
parallel_n = s1_r['n'] + s2_r['n'] + s3_r['n'] + s4_r['n'] + s5_r['n']
parallel_dd = max(s1_r['dd'], s2_r['dd'], s3_r['dd'], s4_r['dd'], s5_r['dd']) * 2 # 保守估计
# 合并月度
all_months = set()
for sr in [s1_r, s2_r, s3_r, s4_r, s5_r]:
all_months.update(sr['monthly'].keys())
parallel_monthly = {}
for m in all_months:
n_m = 0; net_m = 0
for sr in [s1_r, s2_r, s3_r, s4_r, s5_r]:
if m in sr['monthly']:
n_m += sr['monthly'][m]['n']
net_m += sr['monthly'][m]['net']
parallel_monthly[m] = {'n': n_m, 'net': net_m}
results.append({
'label': "E1: 5策略并行(允许同时持仓) 100U each",
'n': parallel_n, 'net': parallel_net, 'pnl': parallel_pnl,
'reb': parallel_reb, 'fee': parallel_fee, 'dd': parallel_dd,
'wr': 0, 'monthly': parallel_monthly
})
print(f" E1 done: {parallel_n} total trades (parallel)", flush=True)
# ============================
# 方案 12: 5策略并行 + 200U仓位
# ============================
N2 = 20000.0
ps1 = analyze(run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, N2), "p1")
ps2 = analyze(run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, N2), "p2")
ps3 = analyze(run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, N2), "p3")
ps4 = analyze(run_strategy(data, 20, 55, 120, 0.002, 0.006, 1800, N2), "p4")
ps5 = analyze(run_strategy(data, 8, 34, 200, 0.002, 0.005, 1800, N2), "p5")
p_net = ps1['net']+ps2['net']+ps3['net']+ps4['net']+ps5['net']
p_reb = ps1['reb']+ps2['reb']+ps3['reb']+ps4['reb']+ps5['reb']
p_fee = ps1['fee']+ps2['fee']+ps3['fee']+ps4['fee']+ps5['fee']
p_pnl = ps1['pnl']+ps2['pnl']+ps3['pnl']+ps4['pnl']+ps5['pnl']
p_n = ps1['n']+ps2['n']+ps3['n']+ps4['n']+ps5['n']
p_dd = max(ps1['dd'],ps2['dd'],ps3['dd'],ps4['dd'],ps5['dd'])*2
p_monthly = {}
for m in all_months:
n_m=0; net_m=0
for sr in [ps1,ps2,ps3,ps4,ps5]:
if m in sr['monthly']:
n_m+=sr['monthly'][m]['n']; net_m+=sr['monthly'][m]['net']
p_monthly[m] = {'n': n_m, 'net': net_m}
results.append({
'label': "E2: 5策略并行 200U each",
'n': p_n, 'net': p_net, 'pnl': p_pnl,
'reb': p_reb, 'fee': p_fee, 'dd': p_dd,
'wr': 0, 'monthly': p_monthly
})
print(f" E2 done: {p_n} total trades (parallel 200U)", flush=True)
# ============================
# 打印对比
# ============================
print_comparison(results)
# 保存
csv = Path(__file__).parent.parent / 'multi_strategy_results.csv'
with open(csv, 'w', encoding='utf-8-sig') as f:
f.write("方案,交易数,年净利,月均,返佣,最大回撤\n")
for r in results:
f.write(f"{r['label']},{r['n']},{r['net']:.0f},{r['net']/12:.0f},{r['reb']:.0f},{r['dd']:.0f}\n")
print(f"\nSaved: {csv}", flush=True)
if __name__ == '__main__':
main()