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:
Rekey
2026-06-13 19:30:25 +08:00
parent b5cdb41993
commit edc50e8809
20 changed files with 484544 additions and 34 deletions
+122
View File
@@ -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())