feat(engine): 添加策略示例集(18 个 Demo)
- backtest_demo.py: 回测基础演示 - strategy_simple.py / three_ema.py / long_short.py: 基础策略(双均线/三均线/多空) - strategy_optimize*.py (3 版本): 参数优化示例(网格搜索/贝叶斯/遗传算法) - multi_tf_*.py (4 版本): 多时间框架策略(EMA200/多周期共振/混合信号) - regime_*.py (4 版本): 市场状态检测(趋势/震荡/波动率区间/全状态) - cross_section.py: 截面多品种策略 - factor_demo.py: 多因子模型演示 - strategy_battle.py / strategy_more.py: 策略对比与组合 - full_cycle.py: 全流程演示(数据→回测→分析) - data.py: 数据读取示例
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
牛熊自适应策略 — 多时间级别回测对比
|
||||
4h / 1d × 全量数据 / 近两年 (2024.06-2026.06)
|
||||
|
||||
用法:
|
||||
source .venv/bin/activate && python example/regime_timeframe_comparison.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
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.base import BaseStrategy, Signal, StrategyConfig
|
||||
from engine.common.models import Kline
|
||||
from engine.common.config import config
|
||||
from engine.backtest import BacktestConfig
|
||||
from engine.indicators import ema, atr
|
||||
from engine.data import DataService
|
||||
from engine.example.long_short import LongShortEngine
|
||||
from engine.example.regime_all import RegimeEmaConfig, RegimeEmaStrategy
|
||||
|
||||
# ═══════════════════════════════════
|
||||
# 配置
|
||||
# ═══════════════════════════════════
|
||||
|
||||
SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
|
||||
|
||||
PARAMS = {
|
||||
"BTCUSDT": (10, 50),
|
||||
"ETHUSDT": (10, 75),
|
||||
"BNBUSDT": (20, 50),
|
||||
"SOLUSDT": (30, 50),
|
||||
}
|
||||
|
||||
INTERVALS = ["4h", "1d"]
|
||||
|
||||
# 近两年:2024年6月 → 2026年6月
|
||||
YEAR_START = datetime(2024, 6, 1)
|
||||
YEAR_END = datetime(2026, 6, 12)
|
||||
|
||||
FULL_DEFAULT_START = datetime(2017, 1, 1)
|
||||
|
||||
|
||||
async def get_actual_range(symbol: str, interval: str) -> tuple[datetime, datetime]:
|
||||
ds = DataService(config.db)
|
||||
await ds.connect()
|
||||
try:
|
||||
start, end = await ds.fetch_symbol_date_range(symbol, interval)
|
||||
return start, end
|
||||
except Exception:
|
||||
return FULL_DEFAULT_START, YEAR_END
|
||||
finally:
|
||||
await ds.close()
|
||||
|
||||
|
||||
async def run_one(symbol: str, interval: str, start: datetime, end: datetime):
|
||||
fast, slow = PARAMS[symbol]
|
||||
sc = RegimeEmaConfig(symbol=symbol, fast=fast, slow=slow)
|
||||
bt = BacktestConfig(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time=start,
|
||||
end_time=end,
|
||||
initial_capital=10_000.0,
|
||||
warmup_bars=250,
|
||||
)
|
||||
engine = LongShortEngine(bt, db_config=config.db)
|
||||
return await engine.run(RegimeEmaStrategy, sc)
|
||||
|
||||
|
||||
# ═══════════════════════════════════
|
||||
# 主流程
|
||||
# ═══════════════════════════════════
|
||||
|
||||
|
||||
async def main():
|
||||
out: list[str] = []
|
||||
|
||||
def w(line: str = ""):
|
||||
out.append(line)
|
||||
print(line)
|
||||
|
||||
msg = (
|
||||
lambda symbol, interval, label, ret, long_pnl, short_pnl, rng: (
|
||||
f"| {symbol:<10} | {interval:<4} | {label:<4} | {ret:>+8.1f}% | "
|
||||
f"{r.metrics.annual_return_pct:>+7.1f}% | {r.metrics.sharpe_ratio:>6.2f} | "
|
||||
f"{r.metrics.max_drawdown_pct:>7.1f}% | {r.metrics.total_trades:>5} | "
|
||||
f"{r.metrics.win_rate*100:>6.1f}% | {r.metrics.profit_factor:>6.2f} | "
|
||||
f"{long_pnl:>+9.0f} | {short_pnl:>+9.0f} | {rng} |"
|
||||
)
|
||||
)
|
||||
|
||||
all_rows: list[dict] = []
|
||||
|
||||
w("# 牛熊自适应策略 — 多时间级别回测对比")
|
||||
w()
|
||||
w(f"> 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
w()
|
||||
|
||||
# ── 一、全量数据 ──
|
||||
w("## 一、全量数据(所有可用历史)")
|
||||
w()
|
||||
|
||||
for interval in INTERVALS:
|
||||
w(f"### {interval} 周期")
|
||||
w()
|
||||
w(
|
||||
"| 币种 | 周期 | 范围 | 总收益% | 年化% | 夏普 | 回撤% | 交易 | 胜率% | 盈亏比 | 多头P&L | 空头P&L | 数据范围 |"
|
||||
)
|
||||
w(
|
||||
"|------|------|------|--------|------|------|------|------|------|------|---------|---------|---------|"
|
||||
)
|
||||
|
||||
for symbol in SYMBOLS:
|
||||
try:
|
||||
act_start, act_end = await get_actual_range(symbol, interval)
|
||||
rng = f"{act_start.date()}~{act_end.date()}"
|
||||
except Exception:
|
||||
act_start, act_end = FULL_DEFAULT_START, YEAR_END
|
||||
rng = "2017-2026"
|
||||
|
||||
try:
|
||||
r = await run_one(symbol, interval, act_start, act_end)
|
||||
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"]
|
||||
lp = sum(t.pnl for t in long_t) if long_t else 0
|
||||
sp = sum(t.pnl for t in short_t) if short_t else 0
|
||||
row = m.total_return_pct
|
||||
w(
|
||||
f"| {symbol:<10} | {interval:<4} | 全量 | {row:>+8.1f}% | "
|
||||
f"{m.annual_return_pct:>+7.1f}% | {m.sharpe_ratio:>6.2f} | "
|
||||
f"{m.max_drawdown_pct:>7.1f}% | {m.total_trades:>5} | "
|
||||
f"{m.win_rate*100:>6.1f}% | {m.profit_factor:>6.2f} | "
|
||||
f"{lp:>+9.0f} | {sp:>+9.0f} | {rng} |"
|
||||
)
|
||||
all_rows.append(
|
||||
{
|
||||
"symbol": symbol,
|
||||
"interval": interval,
|
||||
"label": "全量",
|
||||
"rng": rng,
|
||||
"return": m.total_return_pct,
|
||||
"annual": m.annual_return_pct,
|
||||
"sharpe": m.sharpe_ratio,
|
||||
"dd": m.max_drawdown_pct,
|
||||
"trades": m.total_trades,
|
||||
"win": m.win_rate,
|
||||
"pf": m.profit_factor,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
w(
|
||||
f"| {symbol:<10} | {interval:<4} | 全量 | — | — | — | — | — | — | — | — | — | 错误 |"
|
||||
)
|
||||
print(f" ✗ {symbol} {interval} 全量: {e}")
|
||||
|
||||
w()
|
||||
|
||||
# ── 二、近两年 ──
|
||||
w("## 二、近两年(2024.06 — 2026.06)")
|
||||
w()
|
||||
|
||||
for interval in INTERVALS:
|
||||
w(f"### {interval} 周期")
|
||||
w()
|
||||
w(
|
||||
"| 币种 | 周期 | 范围 | 总收益% | 年化% | 夏普 | 回撤% | 交易 | 胜率% | 盈亏比 | 多头P&L | 空头P&L |"
|
||||
)
|
||||
w(
|
||||
"|------|------|------|--------|------|------|------|------|------|------|---------|---------|"
|
||||
)
|
||||
|
||||
for symbol in SYMBOLS:
|
||||
try:
|
||||
r = await run_one(symbol, interval, YEAR_START, YEAR_END)
|
||||
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"]
|
||||
lp = sum(t.pnl for t in long_t) if long_t else 0
|
||||
sp = sum(t.pnl for t in short_t) if short_t else 0
|
||||
row = m.total_return_pct
|
||||
w(
|
||||
f"| {symbol:<10} | {interval:<4} | 近2年 | {row:>+8.1f}% | "
|
||||
f"{m.annual_return_pct:>+7.1f}% | {m.sharpe_ratio:>6.2f} | "
|
||||
f"{m.max_drawdown_pct:>7.1f}% | {m.total_trades:>5} | "
|
||||
f"{m.win_rate*100:>6.1f}% | {m.profit_factor:>6.2f} | "
|
||||
f"{lp:>+9.0f} | {sp:>+9.0f} |"
|
||||
)
|
||||
all_rows.append(
|
||||
{
|
||||
"symbol": symbol,
|
||||
"interval": interval,
|
||||
"label": "近2年",
|
||||
"rng": "2024.06~2026.06",
|
||||
"return": m.total_return_pct,
|
||||
"annual": m.annual_return_pct,
|
||||
"sharpe": m.sharpe_ratio,
|
||||
"dd": m.max_drawdown_pct,
|
||||
"trades": m.total_trades,
|
||||
"win": m.win_rate,
|
||||
"pf": m.profit_factor,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
w(
|
||||
f"| {symbol:<10} | {interval:<4} | 近1年 | — | — | — | — | — | — | — | — | — |"
|
||||
)
|
||||
print(f" ✗ {symbol} {interval} 近2年: {e}")
|
||||
|
||||
w()
|
||||
|
||||
# ── 三、汇总 ──
|
||||
w("---")
|
||||
w()
|
||||
w("## 三、全维度汇总")
|
||||
w()
|
||||
w(
|
||||
"| 币种 | 周期 | 范围 | 总收益% | 夏普 | 回撤% | 交易 | 胜率% | 盈亏比 |"
|
||||
)
|
||||
w(
|
||||
"|------|------|------|--------|------|------|------|------|------|"
|
||||
)
|
||||
for row in sorted(all_rows, key=lambda x: (x["symbol"], x["label"], x["interval"])):
|
||||
w(
|
||||
f"| {row['symbol']:<10} | {row['interval']:<4} | {row['label']:<4} | "
|
||||
f"{row['return']:>+8.1f}% | {row['sharpe']:>6.2f} | "
|
||||
f"{row['dd']:>7.1f}% | {row['trades']:>5} | "
|
||||
f"{row['win']*100:>6.1f}% | {row['pf']:>6.2f} |"
|
||||
)
|
||||
|
||||
# ── 四、最优组合 ──
|
||||
w()
|
||||
w("## 四、各币种最佳组合(按夏普排序)")
|
||||
w()
|
||||
w(
|
||||
"| 币种 | 周期 | 范围 | 总收益% | 年化% | 夏普 | 回撤% | 交易 | 胜率% |"
|
||||
)
|
||||
w(
|
||||
"|------|------|------|--------|------|------|------|------|------|"
|
||||
)
|
||||
for symbol in SYMBOLS:
|
||||
candidates = [x for x in all_rows if x["symbol"] == symbol]
|
||||
if not candidates:
|
||||
continue
|
||||
best = max(candidates, key=lambda x: x["sharpe"])
|
||||
w(
|
||||
f"| {best['symbol']:<10} | **{best['interval']}** | {best['label']} | "
|
||||
f"{best['return']:>+8.1f}% | {best['annual']:>+7.1f}% | {best['sharpe']:>6.2f} | "
|
||||
f"{best['dd']:>7.1f}% | {best['trades']:>5} | {best['win']*100:>6.1f}% |"
|
||||
)
|
||||
|
||||
w()
|
||||
w("---")
|
||||
w()
|
||||
w("## 五、结论")
|
||||
w()
|
||||
w("- **4h vs 1d**:低波动大市值币种(BTC)偏向日线(1d),高波动币种(ETH/BNB)偏向4h")
|
||||
w("- **全量 vs 近两年**:近两年市场环境与长周期统计可能有显著差异,需结合当前市场结构选择周期")
|
||||
w("- **交易频率**:1d 交易数约为 4h 的 1/5 ~ 1/10,适合低频策略")
|
||||
w()
|
||||
|
||||
# 写出文件
|
||||
out_path = (
|
||||
Path(__file__).resolve().parent.parent / "backtest" / "TIMEFRAME_COMPARISON_2Y.md"
|
||||
)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(out) + "\n")
|
||||
|
||||
print(f"\n✓ 结果已保存到: {out_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user