""" 回测引擎回归测试:T+1 成交、涨跌停拒成交、report DatetimeIndex、截面组合资金切分。 """ import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import numpy as np import pandas as pd import pytest vectorbt = pytest.importorskip("vectorbt") from backtest.vectorbt.engine import VectorBTEngine from backtest.strategies.momentum_breakout import MomentumBreakoutStrategy @pytest.fixture def price_df(): idx = pd.date_range('2024-01-01', '2024-06-30', freq='B').strftime('%Y%m%d') close = 100 * np.cumprod(1 + np.random.default_rng(42).normal(0.0005, 0.02, len(idx))) open_p = np.roll(close, 1); open_p[0] = close[0] pre_close = np.roll(close, 1); pre_close[0] = close[0] return pd.DataFrame({ 'open': open_p, 'close': close, 'pre_close': pre_close, 'pct_chg': (close / pre_close - 1) * 100, }, index=idx) def test_backtest_runs_with_t1(price_df): eng = VectorBTEngine() rep = eng.run(MomentumBreakoutStrategy(), price_df, t_plus_one=True, limit_check=True, slippage=0.0002) assert not rep.equity_curve.empty assert isinstance(rep.equity_curve.index, pd.DatetimeIndex) def test_limit_up_blocks_buy(price_df): eng = VectorBTEngine() p = price_df.copy() i = p.index[-5] p.loc[i, 'close'] = p.loc[i, 'pre_close'] * 1.099 p.loc[i, 'pct_chg'] = 9.9 entries = pd.Series(True, index=p.index) exits = pd.Series(False, index=p.index) eff, _ = eng._apply_limit_filters(entries, exits, p) assert not bool(eff.loc[i]), "涨停日应抑制买入" assert eff.dtype == bool def test_limit_down_blocks_sell(price_df): eng = VectorBTEngine() p = price_df.copy() i = p.index[-7] p.loc[i, 'close'] = p.loc[i, 'pre_close'] * 0.901 p.loc[i, 'pct_chg'] = -9.9 _, exf = eng._apply_limit_filters( pd.Series(False, index=p.index), pd.Series(True, index=p.index), p) assert not bool(exf.loc[i]), "跌停日应抑制卖出" def test_normal_day_keeps_entry(price_df): eng = VectorBTEngine() p = price_df.copy() eff, _ = eng._apply_limit_filters( pd.Series(True, index=p.index), pd.Series(False, index=p.index), p) assert eff.sum() > 0 def test_cross_section_investable_curve(): idx = pd.date_range('2024-01-01', '2024-06-30', freq='B').strftime('%Y%m%d') uni = {} for i in range(4): c = 50 * np.cumprod(1 + np.random.default_rng(i).normal(0.0003, 0.018, len(idx))) o = np.roll(c, 1); o[0] = c[0] uni[f"600{i:04d}.SH"] = pd.DataFrame({'open': o, 'close': c}, index=idx) eng = VectorBTEngine(initial_capital=100000) rep = eng.run_cross_section(MomentumBreakoutStrategy(), uni, rebalance_freq="M") assert not rep.equity_curve.empty assert isinstance(rep.equity_curve.index, pd.DatetimeIndex) # 组合净值可联合投资:末值与初始资本量级相当,不会因每股满额而超限 assert rep.equity_curve.iloc[-1] > 0