"""获取近一年Top3策略的详细交易记录""" import asyncio, sys, json from datetime import datetime, timedelta, timezone from pathlib import Path _project_root = Path(__file__).resolve().parent.parent.parent if str(_project_root) not in sys.path: sys.path.insert(0, str(_project_root)) from engine.common.config import config from engine.backtest.models import BacktestConfig from engine.example.long_short import LongShortEngine from engine.example.full_comparison import ( VolBreakStrategy, VolBreakConfig, EmaCrossStrategy, EmaCrossConfig, ) from engine.common.base import Signal NOW = datetime.now(timezone.utc) ONE_YEAR_AGO = NOW - timedelta(days=365) INITIAL = 10_000.0 TASKS = [ ("ATR波动率突破 ETHUSDT 4h", VolBreakConfig, VolBreakStrategy, "ETHUSDT", "4h", lambda s: VolBreakConfig(symbol=s, atr_period=14, squeeze_period=20, squeeze_ratio=0.7, atr_stop=2.0)), ("EMA双均线多空 ETHUSDT 1d", EmaCrossConfig, EmaCrossStrategy, "ETHUSDT", "1d", lambda s: EmaCrossConfig(symbol=s, fast=10, slow=50, atr_stop=2.5)), ("EMA双均线多空 BTCUSDT 1d", EmaCrossConfig, EmaCrossStrategy, "BTCUSDT", "1d", lambda s: EmaCrossConfig(symbol=s, fast=10, slow=50, atr_stop=2.5)), ] def fmt_ts(ts_ms): return datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M") async def run_one(label, config_cls, strategy_cls, symbol, interval, mkcfg): sc = mkcfg(symbol) bt = BacktestConfig(symbol=symbol, interval=interval, start_time=ONE_YEAR_AGO, end_time=NOW, initial_capital=INITIAL, warmup_bars=150) engine = LongShortEngine(bt, db_config=config.db) r = await engine.run(strategy_cls, sc) return label, r async def main(): results = [] for task in TASKS: label, r = await run_one(*task) results.append((label, r)) for label, r in results: m = r.metrics trades = r.trades # 配对交易 paired = [] pending = None for t in trades: if t.side == "BUY" and t.pnl is None: pending = {"entry_ts": t.timestamp, "entry_price": t.price, "entry_reason": t.reason} elif t.side == "SELL" and pending and t.pnl is not None: paired.append({**pending, "exit_ts": t.timestamp, "exit_price": t.price, "exit_reason": t.reason, "pnl": t.pnl}) pending = None elif t.side == "SELL" and t.pnl is None: pending = {"entry_ts": t.timestamp, "entry_price": t.price, "entry_reason": t.reason, "short": True} elif t.side == "BUY" and pending and t.pnl is not None and pending.get("short"): paired.append({**pending, "exit_ts": t.timestamp, "exit_price": t.price, "exit_reason": t.reason, "pnl": t.pnl}) pending = None cfg = r.config print(f"\n═══ {label} ═══") print(f" 本金 {INITIAL:,.0f} → 终值 {m.final_equity:,.0f} | {m.total_return_pct:+.1f}% | 年化 {m.annual_return_pct:+.1f}%") print(f" 夏普 {m.sharpe_ratio:.2f} | 回撤 {m.max_drawdown_pct:.1f}% | 胜率 {m.win_rate*100:.1f}% | 盈亏比 {m.profit_factor:.2f} | {m.total_trades}笔") print(f" 日期 {cfg.start_time.date()} ~ {cfg.end_time.date()}") print() print(f" {'#':>3} {'入场时间':<19} {'入场价':>10} {'入场原因':<25} {'出场时间':<19} {'出场价':>10} {'出场原因':<25} {'盈亏':>10}") print(" " + "─" * 130) total_pnl = 0 for i, p in enumerate(paired): total_pnl += p["pnl"] side = "做空" if p.get("short") else "做多" print(f" {i+1:>3} {fmt_ts(p['entry_ts']):<19} {p['entry_price']:>10.4f} {p['entry_reason']:<25} {fmt_ts(p['exit_ts']):<19} {p['exit_price']:>10.4f} {p['exit_reason']:<25} {p['pnl']:>+10.2f}") print(" " + "─" * 130) wins = sum(1 for p in paired if p["pnl"] > 0) print(f" 合计 {len(paired)} 笔 | 盈利 {wins} 笔 | 总盈亏 {total_pnl:+,.2f} USDT") print() if __name__ == "__main__": asyncio.run(main())