docs: distinguish signal execution and holding times
CI / lite (pull_request) Successful in 4s

This commit is contained in:
ao gong
2026-08-21 21:49:07 +08:00
parent f9b7f2ab1a
commit 15b283bdf9
3 changed files with 24 additions and 15 deletions
+16 -7
View File
@@ -24,6 +24,7 @@
- `data_adapter` — 桥接 qtdb_pro 长表与新模块(rename / long-wide / 复权 / vwap 代理) - `data_adapter` — 桥接 qtdb_pro 长表与新模块(rename / long-wide / 复权 / vwap 代理)
- `backtest` — weight-based 多日仿真(rebalance_table / compute_nav / compare_to_benchmark) - `backtest` — weight-based 多日仿真(rebalance_table / compute_nav / compare_to_benchmark)
- `portfolio_construction` — 多期因子分数 → Top-K → 等权目标权重表 - `portfolio_construction` — 多期因子分数 → Top-K → 等权目标权重表
- `research_pipeline` — 因子日 → 下一真实交易日 → 显式执行价 → 执行审计(防前视编排)
- `metrics` — 绩效(年化收益 / 波动率 / Sharpe / 最大回撤 / Calmar) - `metrics` — 绩效(年化收益 / 波动率 / Sharpe / 最大回撤 / Calmar)
- `factor_library` — 通用方法(turnover / winsorize / IC / OLS / jb_test) - `factor_library` — 通用方法(turnover / winsorize / IC / OLS / jb_test)
- `portfolio_decomp` — 组合分解(risk_parity / mean_variance / 因子归因) - `portfolio_decomp` — 组合分解(risk_parity / mean_variance / 因子归因)
@@ -61,8 +62,8 @@ from quant_engine.alpha_factors import alpha_001, alpha_005, ALPHA158_REGISTRY
from quant_engine.execution import ( from quant_engine.execution import (
ExecutionConfig, simulate_multi_day_with_audit, simulate_with_daily_data, ExecutionConfig, simulate_multi_day_with_audit, simulate_with_daily_data,
) )
from quant_engine.research_pipeline import run_factor_execution_research
from quant_engine.backtest import run_weight_backtest from quant_engine.backtest import run_weight_backtest
from quant_engine.portfolio_construction import scores_to_weight_table
from quant_engine.indicators import macd, bollinger, kdj from quant_engine.indicators import macd, bollinger, kdj
from quant_engine.data_adapter import ( from quant_engine.data_adapter import (
long_to_wide, wide_to_long, rename_tushare_columns, long_to_wide, wide_to_long, rename_tushare_columns,
@@ -73,8 +74,9 @@ from quant_engine.data_adapter import (
# 端到端:qtdb_pro 长表 → 适配 → alpha158 → execution # 端到端:qtdb_pro 长表 → 适配 → alpha158 → execution
df = load_qtdb_daily(["000001.SZ"], "2024-01-01", with_adj=True) df = load_qtdb_daily(["000001.SZ"], "2024-01-01", with_adj=True)
prices, volumes = prepare_execution_inputs(df) close_prices, volumes = prepare_execution_inputs(df)
result = simulate_with_daily_data(prices, initial_cash=1_000_000.0) open_prices, _ = prepare_execution_inputs(df, price_col="open")
result = simulate_with_daily_data(close_prices, initial_cash=1_000_000.0)
# 已正确滞后的目标权重 → 现金约束执行 → 唯一来源的成交/拒绝/日末持仓/NAV # 已正确滞后的目标权重 → 现金约束执行 → 唯一来源的成交/拒绝/日末持仓/NAV
execution = simulate_multi_day_with_audit( execution = simulate_multi_day_with_audit(
@@ -92,18 +94,25 @@ execution = simulate_multi_day_with_audit(
print(execution.nav_series) print(execution.nav_series)
print(execution.daily_executions) print(execution.daily_executions)
# 多期因子分数 → Top-K 等权组合 → 稳定回测结果 # 多期因子分数(必须是 point-in-time 数据)→ Top-K → 下一交易日 open 执行
rebalance_weights = scores_to_weight_table( factor_execution = run_factor_execution_research(
factor_scores, factor_scores,
top_k=20, top_k=20,
gross_exposure=1.0, execution_prices=open_prices,
execution_price_field="open",
initial_cash=1_000_000.0,
) )
# run_weight_backtest 是低层算子:只接受收益区间开始前已经生效的持仓权重。
# 不要把 signal-date 的 factor_scores/decision_weights 直接传给它。
backtest = run_weight_backtest( backtest = run_weight_backtest(
weights=rebalance_weights, weights=effective_holding_weights,
stock_returns=daily_returns, stock_returns=daily_returns,
initial_capital=1_000_000.0, initial_capital=1_000_000.0,
benchmark_nav=benchmark_nav, benchmark_nav=benchmark_nav,
) )
print(factor_execution.schedule.signal_to_execution)
print(factor_execution.execution.daily_executions)
print(backtest.stats()) print(backtest.stats())
print(backtest.benchmark_report()) print(backtest.benchmark_report())
``` ```
+3 -2
View File
@@ -76,8 +76,9 @@ def compute_nav_from_weights(
) -> pd.Series: ) -> pd.Series:
"""从调仓表(日期 × 股票权重)+ 个股日收益 → 净值曲线。 """从调仓表(日期 × 股票权重)+ 个股日收益 → 净值曲线。
假设:在调仓日之间权重不变(**前向填充**)。 假设:输入是该收益测量区间开始前已经生效的持仓权重,并在调仓日之间
调仓日的权重 = `weights.loc[rebalance_date]`。 保持不变(**前向填充**)。本函数不会把信号日自动解释为执行日;因子分数
应先经交易日历调度和实际执行时点处理,避免把同一时点未知的收益计入。
Args: Args:
weights: 调仓日 × 股票代码 的权重 DataFrame(**0~1**,行和 ≤ 1) weights: 调仓日 × 股票代码 的权重 DataFrame(**0~1**,行和 ≤ 1)
+5 -6
View File
@@ -91,10 +91,10 @@ def test_scores_to_weight_table_constructs_each_rebalance_independently() -> Non
pd.testing.assert_series_equal(result.iloc[0], changed_result.iloc[0]) pd.testing.assert_series_equal(result.iloc[0], changed_result.iloc[0])
def test_factor_scores_flow_directly_into_weight_backtest() -> None: def test_effective_holding_weights_flow_into_weight_backtest() -> None:
dates = pd.date_range("2026-01-05", periods=3, freq="B") dates = pd.date_range("2026-01-05", periods=3, freq="B")
scores = pd.DataFrame( effective_weights = pd.DataFrame(
{"A": [2.0, 0.0], "B": [1.0, 3.0]}, {"A": [1.0, 0.0], "B": [0.0, 1.0]},
index=dates[[0, 2]], index=dates[[0, 2]],
) )
stock_returns = pd.DataFrame( stock_returns = pd.DataFrame(
@@ -102,11 +102,10 @@ def test_factor_scores_flow_directly_into_weight_backtest() -> None:
index=dates, index=dates,
) )
weights = scores_to_weight_table(scores, top_k=1) result = run_weight_backtest(effective_weights, stock_returns)
result = run_weight_backtest(weights, stock_returns)
pd.testing.assert_series_equal(result.nav, pd.Series([1.1, 1.1, 1.32], index=dates)) pd.testing.assert_series_equal(result.nav, pd.Series([1.1, 1.1, 1.32], index=dates))
pd.testing.assert_frame_equal(result.weights, weights) pd.testing.assert_frame_equal(result.weights, effective_weights)
@pytest.mark.parametrize("top_k", [0, -1]) @pytest.mark.parametrize("top_k", [0, -1])