edc50e8809
- 数据层: build_aggregates_sql 新增 2h/6h 聚合视图,默认起始时间调整为 2017-05 - 模型层: KlineInterval 类型扩展 2h/6h,DataService 新增对应表名和毫秒映射 - 指标层: 新增 incremental.py 增量指标模块 (EmaInc/AtrInc/RsiInc/BbInc),O(1) per bar - 策略重构: long_short.py 和 regime_all.py 从批量 ema/atr 迁移至增量指标,避免每 bar 重复全量计算 - regime 探测器: RegimeDetector3 改为增量 EMA200,detect() 接口简化 - 回测扩展: regime_timeframe_comparison 从 4h/1d 扩展至 2h/4h/6h/1d - 新增示例: multi_strategy_report, vol_break_compare/periods, intraday_explore, top3_trades 等分析脚本
97 lines
4.1 KiB
Python
97 lines
4.1 KiB
Python
"""获取近一年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())
|