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

316 lines
11 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.

"""
盈利信号组合策略 — 只保留回测验证盈利的3种信号
信号1: EMA交叉 (83笔, +1773) — 趋势变化捕捉
信号2: 吞没形态 (1909笔, +2471) — K线反转形态
信号3: BB反弹 (438笔, +171) — 均值回归
去掉: 三分之一(-17290)、PinBar(-1936) — 亏损信号
条件: 同一时间只持1个仓, 100U保证金, 100x杠杆, 90%返佣
"""
import sys, time, datetime, sqlite3
from pathlib import Path
from collections import defaultdict
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 calc_bb(closes, period=20, nstd=2.0):
if len(closes) < period: return None, None, None
rec = closes[-period:]
mid = sum(rec) / period
var = sum((x - mid)**2 for x in rec) / period
std = var ** 0.5
return mid + nstd * std, mid, mid - nstd * std
def calc_rsi(closes, period=14):
if len(closes) < period + 1: return None
g = 0; l = 0
for i in range(-period, 0):
d = closes[i] - closes[i-1]
if d > 0: g += d
else: l -= d
if l == 0: return 100.0
return 100 - 100 / (1 + (g/period)/(l/period))
def run_combo(data, notional, engulf_body_min, engulf_ratio, engulf_sl, engulf_tp,
bb_rsi_long, bb_rsi_short, bb_sl, bb_tp, bb_atr_min,
ema_atr_min, ema_sl):
N = len(data)
FEE = notional * 0.0006 * 2
REB = FEE * 0.9
NFEE = FEE - REB
MIN_HOLD = 200; MAX_HOLD = 1800
ema_f = EMA(8); ema_s = EMA(21); ema_b = EMA(120)
pf_ = None; ps_ = None
CB = []; HB = []; LB = []
pos = 0; op = 0.0; ot = None; st = ""; sl = 0; tp = 0
trades = []
def close_(price, dt_, reason):
nonlocal pos, op, ot, st
pp = (price-op)/op if pos==1 else (op-price)/op
trades.append((st, 'L' if pos==1 else 'S', op, price, notional*pp,
(dt_-ot).total_seconds(), reason, ot, dt_))
pos=0; op=0; ot=None; st=""
for i in range(N):
dt, o_, h_, l_, c_ = data[i]
p = c_
CB.append(p); HB.append(h_); LB.append(l_)
if len(CB) > 300: CB=CB[-300:]; HB=HB[-300:]; LB=LB[-300:]
fast = ema_f.update(p); slow = ema_s.update(p); big = ema_b.update(p)
atr = 0.0
if len(HB) > 15:
s=0
for j in range(-14, 0):
tr=HB[j]-LB[j]; d1=abs(HB[j]-CB[j-1]); d2=abs(LB[j]-CB[j-1])
if d1>tr: tr=d1
if d2>tr: tr=d2
s+=tr
atr = s/(14*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
bb_u, bb_m, bb_l = calc_bb(CB, 20, 2.0)
rsi = calc_rsi(CB, 14)
# 持仓管理
if pos!=0 and ot:
pp=(p-op)/op if pos==1 else (op-p)/op
hsec=(dt-ot).total_seconds()
hsl = max(sl*1.5, 0.006)
if -pp>=hsl: close_(p, dt, "硬止损"); continue
if hsec>=MIN_HOLD:
if -pp>=sl: close_(p, dt, "止损"); continue
if tp>0 and pp>=tp: close_(p, dt, "止盈"); continue
if hsec>=MAX_HOLD: close_(p, dt, "超时"); continue
if st=="EMA":
if pos==1 and cd: close_(p, dt, "EMA反转"); continue
if pos==-1 and cu: close_(p, dt, "EMA反转"); continue
if st=="BB" and bb_m:
if pos==1 and p>=bb_m: close_(p, dt, "BB回中轨"); continue
if pos==-1 and p<=bb_m: close_(p, dt, "BB回中轨"); continue
# 开仓
if pos==0 and i>20:
sig=None; s_sl=0; s_tp=0; s_type=""
# 信号1: EMA
if atr>=ema_atr_min:
if cu and p>big: sig='L'; s_type="EMA"; s_sl=ema_sl; s_tp=0
elif cd and p<big: sig='S'; s_type="EMA"; s_sl=ema_sl; s_tp=0
# 信号2: 吞没
if sig is None and i>0:
pb_o,pb_c = data[i-1][1], data[i-1][4]
cb_o,cb_c = o_, c_
pb_body = abs(pb_c-pb_o)
cb_body = abs(cb_c-cb_o)
cb_pct = cb_body/p if p>0 else 0
if cb_pct>=engulf_body_min and cb_body>pb_body*engulf_ratio:
if pb_c<pb_o and cb_c>cb_o and cb_c>pb_o and cb_o<=pb_c:
if p>big and atr>=0.001:
sig='L'; s_type="吞没"; s_sl=engulf_sl; s_tp=engulf_tp
elif pb_c>pb_o and cb_c<cb_o and cb_c<pb_o and cb_o>=pb_c:
if p<big and atr>=0.001:
sig='S'; s_type="吞没"; s_sl=engulf_sl; s_tp=engulf_tp
# 信号3: BB
if sig is None and bb_u and rsi is not None:
if p<=bb_l and rsi<bb_rsi_long and p>big and atr>=bb_atr_min:
sig='L'; s_type="BB"; s_sl=bb_sl; s_tp=bb_tp
elif p>=bb_u and rsi>bb_rsi_short and p<big and atr>=bb_atr_min:
sig='S'; s_type="BB"; s_sl=bb_sl; s_tp=bb_tp
if sig:
pos=1 if sig=='L' else -1; op=p; ot=dt; st=s_type; sl=s_sl; tp=s_tp
if pos!=0: close_(data[-1][4], data[-1][0], "结束")
return trades
def analyze_print(trades, notional, label=""):
if not trades:
print(f" [{label}] No trades", flush=True); return 0
n = len(trades)
FEE = notional * 0.0006 * 2
REB = FEE * 0.9
NFEE = FEE - REB
total_pnl = sum(t[4] for t in trades)
net = total_pnl - NFEE * n
total_reb = REB * n
wins = len([t for t in trades if t[4]>0])
wr = wins/n*100
by_type = defaultdict(lambda: {'n':0,'pnl':0,'w':0})
for t in trades:
by_type[t[0]]['n']+=1; by_type[t[0]]['pnl']+=t[4]
if t[4]>0: by_type[t[0]]['w']+=1
monthly = defaultdict(lambda: {'n':0,'net':0,'w':0})
for t in trades:
k = t[8].strftime('%Y-%m')
monthly[k]['n']+=1
monthly[k]['net']+=t[4]-NFEE
if t[4]>0: monthly[k]['w']+=1
cum=0; peak=0; dd=0
for t in trades:
cum+=t[4]-NFEE
if cum>peak: peak=cum
if peak-cum>dd: dd=peak-cum
# 月度盈利月数
profit_months = len([m for m in monthly.values() if m['net']>0])
min_month = min(monthly.values(), key=lambda x: x['net'])
max_month = max(monthly.values(), key=lambda x: x['net'])
print(f"\n{'='*75}", flush=True)
print(f" {label}", flush=True)
print(f"{'='*75}", flush=True)
print(f" 年净利: {net:>+10.2f} | 月均: {net/12:>+8.2f} | 交易: {n}笔 | 胜率: {wr:.1f}%", flush=True)
print(f" 返佣: {total_reb:>.0f} | 最大回撤: {dd:>.0f} | 盈利月: {profit_months}/12", flush=True)
print(f"\n 信号拆分:", flush=True)
for st in sorted(by_type.keys()):
d=by_type[st]
nt=d['pnl']-NFEE*d['n']
wt=d['w']/d['n']*100 if d['n']>0 else 0
print(f" {st:<8} {d['n']:>5}笔 净利{nt:>+8.0f} 胜率{wt:.0f}%", flush=True)
print(f"\n 月度:", flush=True)
print(f" {'月份':<8} {'':>4} {'净利':>9} {'胜率':>6}", flush=True)
print(f" {'-'*30}", flush=True)
for m in sorted(monthly.keys()):
d=monthly[m]
wr_m=d['w']/d['n']*100 if d['n']>0 else 0
mark=" <<" if d['net']<0 else ""
print(f" {m:<8} {d['n']:>4} {d['net']:>+9.0f} {wr_m:>5.1f}%{mark}", flush=True)
print(f" {'-'*30}", flush=True)
print(f" {'合计':<8} {n:>4} {net:>+9.0f}", flush=True)
print(f"{'='*75}", flush=True)
return net
def main():
print("Loading...", flush=True)
data = load()
print(f"{len(data)} bars\n", flush=True)
# === 测试多种参数组合 ===
configs = [
# (label, notional, engulf_body_min, engulf_ratio, engulf_sl, engulf_tp,
# bb_rsi_long, bb_rsi_short, bb_sl, bb_tp, bb_atr_min, ema_atr_min, ema_sl)
# 基线:上一轮参数
("v1: 基线", 10000,
0.001, 1.5, 0.004, 0.005,
30, 70, 0.003, 0.002, 0.0008,
0.003, 0.004),
# v2: 更严格的吞没(减少低质量交易)
("v2: 严格吞没", 10000,
0.0015, 2.0, 0.004, 0.006,
30, 70, 0.003, 0.002, 0.0008,
0.003, 0.004),
# v3: 更严格吞没 + 更宽BB RSI
("v3: 严格吞没+宽BB", 10000,
0.0015, 2.0, 0.004, 0.006,
25, 75, 0.003, 0.003, 0.001,
0.003, 0.004),
# v4: 最严格吞没 + 最严BB
("v4: 超严格", 10000,
0.002, 2.5, 0.005, 0.008,
20, 80, 0.004, 0.003, 0.001,
0.003, 0.004),
# v5: 中等吞没 + 更大止盈
("v5: 大止盈", 10000,
0.0012, 1.8, 0.005, 0.008,
28, 72, 0.004, 0.003, 0.001,
0.003, 0.004),
# v6: 去掉BB只用EMA+吞没)
("v6: 仅EMA+吞没", 10000,
0.0015, 2.0, 0.004, 0.006,
999, -999, 0.003, 0.002, 999, # BB不会触发
0.003, 0.004),
# v7: 放宽EMA的ATR
("v7: EMA ATR>0.2%", 10000,
0.0015, 2.0, 0.004, 0.006,
25, 75, 0.003, 0.003, 0.001,
0.002, 0.004),
# v8: v3的最优 x 300U仓位
("v8: v3 x 300U仓位", 30000,
0.0015, 2.0, 0.004, 0.006,
25, 75, 0.003, 0.003, 0.001,
0.003, 0.004),
# v9: v3 x 500U
("v9: v3 x 500U仓位", 50000,
0.0015, 2.0, 0.004, 0.006,
25, 75, 0.003, 0.003, 0.001,
0.003, 0.004),
# v10: v4 x 500U
("v10: v4 x 500U", 50000,
0.002, 2.5, 0.005, 0.008,
20, 80, 0.004, 0.003, 0.001,
0.003, 0.004),
]
best_net = -99999; best_label = ""
summary = []
for cfg in configs:
label = cfg[0]
trades = run_combo(data, *cfg[1:])
net = analyze_print(trades, cfg[1], label)
summary.append((label, cfg[1], len(trades), net))
if net > best_net:
best_net = net; best_label = label
# 总览
print(f"\n\n{'='*80}", flush=True)
print(f" SUMMARY — 目标: 月均 1000 USDT", flush=True)
print(f"{'='*80}", flush=True)
print(f" {'方案':<25} {'名义值':>10} {'交易数':>6} {'年净利':>10} {'月均':>8} {'达标':>4}", flush=True)
print(f" {'-'*72}", flush=True)
for label, notional, n, net in summary:
mavg = net/12
ok = "Yes" if mavg>=1000 else "No"
print(f" {label:<25} {notional:>10,.0f} {n:>6} {net:>+10.0f} {mavg:>+8.0f} {ok:>4}", flush=True)
print(f" {'-'*72}", flush=True)
print(f" Best: {best_label}{best_net:+.0f}/年 = {best_net/12:+.0f}/月", flush=True)
print(f"{'='*80}", flush=True)
if __name__=='__main__':
main()