""" EMA趋势策略 - 快速参数优化(精简版) 前次发现:EMA(8/21/120) 方向盈利 +327,但10%费用成本 428,净亏 -101。 优化方向:用更长EMA减少交易次数 + 更高ATR过滤提高质量。 """ import sys import time import datetime import sqlite3 from pathlib import Path class EMA: def __init__(self, period): self.k = 2.0 / (period + 1) self.value = None def update(self, price): if self.value is None: self.value = price else: self.value = price * self.k + self.value * (1 - self.k) return self.value def load_data(): db_path = Path(__file__).parent.parent / 'models' / 'database.db' start_ms = int(datetime.datetime(2025, 1, 1).timestamp()) * 1000 end_ms = int(datetime.datetime(2026, 1, 1).timestamp()) * 1000 conn = sqlite3.connect(str(db_path)) rows = conn.cursor().execute( "SELECT id, open, high, low, close FROM bitmart_eth_1m WHERE id >= ? AND id < ? ORDER BY id", (start_ms, end_ms) ).fetchall() conn.close() data = [] for r in rows: data.append(( datetime.datetime.fromtimestamp(r[0] / 1000.0), r[1], r[2], r[3], r[4], # open, high, low, close )) return data def backtest(data, fp, sp, bp, atr_min, sl_pct, mh): bal = 1000.0 pos = 0 op = 0.0 ot = None ps = 0.0 pending = None ef = EMA(fp) es = EMA(sp) eb = EMA(bp) highs = [] lows = [] closes = [] pf = None pslow = None tc = 0 wc = 0 dir_pnl = 0.0 tot_fee = 0.0 tot_reb = 0.0 hsl = sl_pct * 1.5 lev = 50 rp = 0.005 tf_rate = 0.0006 reb_rate = 0.90 min_h = 200 for dt, o_, h_, l_, c_ in data: price = c_ highs.append(h_) lows.append(l_) closes.append(price) fast = ef.update(price) slow = es.update(price) big = eb.update(price) # ATR atr_pct = 0 ap = 14 if len(highs) > ap + 1: trs = [] for i in range(-ap, 0): tr = max(highs[i] - lows[i], abs(highs[i] - closes[i-1]), abs(lows[i] - closes[i-1])) trs.append(tr) atr_pct = (sum(trs) / ap) / price if price > 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: if pos == 1: p = (price - op) / op else: p = (op - price) / op hs = (dt - ot).total_seconds() if -p >= hsl: # close pnl_ = ps * p cv = ps * (1 + p) cf = cv * tf_rate of_ = ps * tf_rate ttf = of_ + cf rb = ttf * reb_rate bal += pnl_ - cf + rb dir_pnl += pnl_ tot_fee += ttf tot_reb += rb tc += 1 if pnl_ > 0: wc += 1 pos = 0; op = 0; ot = None; ps = 0; pending = None continue can_c = hs >= min_h if can_c: do_close = False if -p >= sl_pct: do_close = True elif hs >= mh: do_close = True elif pos == 1 and cd: do_close = True elif pos == -1 and cu: do_close = True elif pending == 'cl' and pos == 1: do_close = True elif pending == 'cs' and pos == -1: do_close = True if do_close: pnl_ = ps * p cv = ps * (1 + p) cf = cv * tf_rate of_ = ps * tf_rate ttf = of_ + cf rb = ttf * reb_rate bal += pnl_ - cf + rb dir_pnl += pnl_ tot_fee += ttf tot_reb += rb tc += 1 if pnl_ > 0: wc += 1 pos = 0; op = 0; ot = None; ps = 0; pending = None # re-enter if atr_pct >= atr_min: if (cd or (fast < slow)) and price < big: ns = bal * rp * lev if ns >= 1: bal -= ns * tf_rate pos = -1; op = price; ot = dt; ps = ns elif (cu or (fast > slow)) and price > big: ns = bal * rp * lev if ns >= 1: bal -= ns * tf_rate pos = 1; op = price; ot = dt; ps = ns continue else: if pos == 1 and cd: pending = 'cl' elif pos == -1 and cu: pending = 'cs' if pos == 0 and atr_pct >= atr_min: if cu and price > big: ns = bal * rp * lev if ns >= 1: bal -= ns * tf_rate pos = 1; op = price; ot = dt; ps = ns elif cd and price < big: ns = bal * rp * lev if ns >= 1: bal -= ns * tf_rate pos = -1; op = price; ot = dt; ps = ns # force close if pos != 0: price = data[-1][4] if pos == 1: p = (price - op) / op else: p = (op - price) / op pnl_ = ps * p cv = ps * (1 + p) cf = cv * tf_rate of_ = ps * tf_rate ttf = of_ + cf rb = ttf * reb_rate bal += pnl_ - cf + rb dir_pnl += pnl_ tot_fee += ttf tot_reb += rb tc += 1 if pnl_ > 0: wc += 1 net = bal - 1000.0 wr = wc / tc * 100 if tc > 0 else 0 return net, tc, wr, dir_pnl, tot_reb, tot_fee - tot_reb def main(): print("Loading data...", flush=True) data = load_data() print(f"Loaded {len(data)} bars", flush=True) # Focused parameter grid (smaller) fast_list = [8, 13, 20] slow_list = [21, 34, 55] big_list = [120, 200] atr_list = [0.0003, 0.0006, 0.001, 0.0015] sl_list = [0.003, 0.005, 0.008] mh_list = [900, 1800, 3600] combos = [] for fp in fast_list: for sp in slow_list: if fp >= sp: continue for bp in big_list: for am in atr_list: for sl in sl_list: for mh in mh_list: combos.append((fp, sp, bp, am, sl, mh)) print(f"\nTotal combos: {len(combos)}", flush=True) results = [] t0 = time.time() for idx, (fp, sp, bp, am, sl, mh) in enumerate(combos): net, tc, wr, dp, reb, nf = backtest(data, fp, sp, bp, am, sl, mh) results.append((net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh)) if (idx + 1) % 20 == 0: elapsed = time.time() - t0 eta = elapsed / (idx + 1) * (len(combos) - idx - 1) print(f" [{idx+1}/{len(combos)}] {elapsed:.0f}s elapsed, ~{eta:.0f}s remaining", flush=True) total_time = time.time() - t0 print(f"\nDone! {len(results)} combos in {total_time:.1f}s\n", flush=True) # Sort by net P&L results.sort(key=lambda x: x[0], reverse=True) # Print results profitable = [r for r in results if r[0] > 0] print(f"Profitable: {len(profitable)} / {len(results)} ({len(profitable)/len(results)*100:.1f}%)\n") print(f"{'='*130}") print(f" TOP 30 RESULTS") print(f"{'='*130}") header = f" {'#':>3} {'Fast':>4} {'Slow':>4} {'Big':>4} {'ATR%':>6} {'SL%':>5} {'MaxH':>5} | {'Net%':>7} {'Net$':>9} {'Trades':>7} {'WR':>6} {'DirPnL':>9} {'Rebate':>9} {'NetFee':>8}" print(header) print(f" {'-'*126}") for i, (net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh) in enumerate(results[:30]): net_pct = net / 10 # net / 1000 * 100 marker = " ***" if net > 0 else "" print(f" {i+1:>3} {fp:>4} {sp:>4} {bp:>4} {am*100:>5.2f}% {sl*100:>4.1f}% {mh:>5} | {net_pct:>+6.2f}% {net:>+8.2f} {tc:>7} {wr:>5.1f}% {dp:>+8.2f} {reb:>8.2f} {nf:>8.2f}{marker}") print(f"{'='*130}") if profitable: print(f"\nALL PROFITABLE:") print(f" {'-'*126}") for i, (net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh) in enumerate(profitable): net_pct = net / 10 print(f" {i+1:>3} EMA({fp}/{sp}/{bp}) ATR>{am*100:.2f}% SL={sl*100:.1f}% MaxH={mh}s | {net_pct:>+6.2f}% {net:>+8.2f} trades={tc} WR={wr:.1f}% DirPnL={dp:+.2f} Rebate={reb:.2f}") # Save CSV csv_path = Path(__file__).parent.parent / 'param_results.csv' with open(csv_path, 'w', encoding='utf-8-sig') as f: f.write("fast,slow,big,atr_min,stop_loss,max_hold,net_pct,net_usd,trades,win_rate,dir_pnl,rebate,net_fee\n") for net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh in results: f.write(f"{fp},{sp},{bp},{am},{sl},{mh},{net/10:.4f},{net:.4f},{tc},{wr:.2f},{dp:.4f},{reb:.4f},{nf:.4f}\n") print(f"\nResults saved: {csv_path}") if __name__ == '__main__': main()