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,122 @@
|
||||
"""
|
||||
牛熊自适应策略 — 日内级别全币种扫描 (15m / 30m / 1h)
|
||||
|
||||
用法:
|
||||
source .venv/bin/activate && python example/regime_intraday.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, 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 import BacktestConfig
|
||||
from engine.data import DataService
|
||||
from engine.example.regime_all import RegimeEmaStrategy, RegimeEmaConfig
|
||||
from engine.example.long_short import LongShortEngine
|
||||
|
||||
SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
|
||||
INTERVALS = ["15m", "30m", "1h"]
|
||||
|
||||
# 沿用 4h 级别优化参数(日内级别可能需单独调参)
|
||||
PARAMS = {
|
||||
"BTCUSDT": (10, 50),
|
||||
"ETHUSDT": (10, 75),
|
||||
"BNBUSDT": (20, 50),
|
||||
"SOLUSDT": (30, 50),
|
||||
}
|
||||
|
||||
|
||||
async def get_actual_range(ds: DataService, symbol: str, interval: str):
|
||||
"""获取币种指定周期的实际数据范围"""
|
||||
start, end = await ds.fetch_symbol_date_range(symbol, interval)
|
||||
return start, end
|
||||
|
||||
|
||||
async def main():
|
||||
ds = DataService(config.db)
|
||||
await ds.connect()
|
||||
|
||||
print()
|
||||
print("═" * 130)
|
||||
print(" 牛熊自适应策略 — 日内级别全币种扫描 | 牛市只多/熊市只空/震荡空仓")
|
||||
print("═" * 130)
|
||||
|
||||
total_start = time.time()
|
||||
results: list[dict] = []
|
||||
|
||||
for interval in INTERVALS:
|
||||
print(f"\n ■ {interval} 级别")
|
||||
print(f" {'币种':<10} {'数据范围':<22} {'总收益%':>8} {'年化%':>8} {'夏普':>7} {'回撤%':>8} {'交易':>6} {'多头P&L':>11} {'空头P&L':>11} {'耗时s':>7}")
|
||||
print(" " + "─" * 125)
|
||||
|
||||
for symbol in SYMBOLS:
|
||||
fast, slow = PARAMS[symbol]
|
||||
sc = RegimeEmaConfig(symbol=symbol, fast=fast, slow=slow)
|
||||
|
||||
try:
|
||||
act_start, act_end = await get_actual_range(ds, symbol, interval)
|
||||
range_str = f"{act_start.date()}~{act_end.date()}"
|
||||
except Exception:
|
||||
# 数据不存在,跳过
|
||||
print(f" {symbol:<10} {'无数据':<22}")
|
||||
continue
|
||||
|
||||
bt = BacktestConfig(
|
||||
symbol=symbol, interval=interval,
|
||||
start_time=act_start, end_time=act_end,
|
||||
initial_capital=10_000.0,
|
||||
)
|
||||
engine = LongShortEngine(bt, db_config=config.db)
|
||||
|
||||
t0 = time.time()
|
||||
try:
|
||||
r = await engine.run(RegimeEmaStrategy, sc)
|
||||
elapsed = time.time() - t0
|
||||
except Exception as ex:
|
||||
print(f" {symbol:<10} {range_str:<22} {'错误: ' + str(ex)[:30]}")
|
||||
continue
|
||||
|
||||
m = r.metrics
|
||||
long_t = [t for t in r.trades if t.pnl is not None and t.side == "SELL"]
|
||||
short_t = [t for t in r.trades if t.pnl is not None and t.side == "BUY"]
|
||||
long_pnl = sum(t.pnl for t in long_t) if long_t else 0
|
||||
short_pnl = sum(t.pnl for t in short_t) if short_t else 0
|
||||
|
||||
print(f" {symbol:<10} {range_str:<22} {m.total_return_pct:>7.1f}% {m.annual_return_pct:>7.1f}% {m.sharpe_ratio:>7.2f} {m.max_drawdown_pct:>7.1f}% {m.total_trades:>6} {long_pnl:>+10.0f} {short_pnl:>+10.0f} {elapsed:>6.1f}s")
|
||||
|
||||
results.append({
|
||||
"interval": interval, "symbol": symbol,
|
||||
"return": m.total_return_pct, "annual": m.annual_return_pct,
|
||||
"sharpe": m.sharpe_ratio, "dd": m.max_drawdown_pct,
|
||||
"trades": m.total_trades, "win_rate": m.win_rate,
|
||||
"profit_factor": m.profit_factor,
|
||||
"long_pnl": long_pnl, "short_pnl": short_pnl,
|
||||
"elapsed": elapsed,
|
||||
})
|
||||
|
||||
await ds.close()
|
||||
|
||||
# ── 汇总排名 ──
|
||||
total_elapsed = time.time() - total_start
|
||||
print(f"\n ■ 最佳组合 (按夏普排名)")
|
||||
print(f" {'排名':<5} {'级别':<6} {'币种':<10} {'总收益%':>8} {'夏普':>7} {'回撤%':>8} {'交易':>6} {'胜率%':>7} {'盈亏比':>7}")
|
||||
print(" " + "─" * 75)
|
||||
|
||||
sorted_results = sorted(results, key=lambda x: x["sharpe"], reverse=True)
|
||||
for i, r in enumerate(sorted_results):
|
||||
medal = ["🥇", "🥈", "🥉"][i] if i < 3 else f"{i+1:>2}."
|
||||
print(f" {medal:<5} {r['interval']:<6} {r['symbol']:<10} {r['return']:>7.1f}% {r['sharpe']:>7.2f} {r['dd']:>7.1f}% {r['trades']:>6} {r['win_rate']*100:>6.1f}% {r['profit_factor']:>7.2f}")
|
||||
|
||||
print(f"\n 总耗时: {total_elapsed:.1f}s")
|
||||
print("═" * 130)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user