feat: 新增2h/6h时间框架支持,策略重构为增量指标计算
- 数据层: 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 等分析脚本
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""获取近一年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())
|
||||
Reference in New Issue
Block a user