Files
lm_code/交易/bitmart-快速优化.py
Your Name b5af5b07f3 哈哈
2026-02-15 02:16:45 +08:00

290 lines
9.1 KiB
Python
Raw 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.

"""
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()