feat: 量化引擎加固 — 新增测试 + 数据/因子/回测层优化
- 新增 finance/tests/ 6 个测试套件(agents/backtest/dao_upsert/factors/features/fundamental_lookahead) - 数据层: data_manager / dao 优化,新增 upsert 逻辑 - 因子层: 基本面因子抽象定位 _mapping、ROE/PE/PB 重构 - 回测层: vectorbt/engine 大改动(251 行),report 增强 - ML 层: features/backtest_integration 特征工程与回测优化 - CLI: agent_cli 重构 - config/settings 扩充配置项
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
回测引擎回归测试: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
|
||||
Reference in New Issue
Block a user