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 代理)
- `backtest` — weight-based 多日仿真(rebalance_table / compute_nav / compare_to_benchmark)
- `portfolio_construction` — 多期因子分数 → Top-K → 等权目标权重表
- `research_pipeline` — 因子日 → 下一真实交易日 → 显式执行价 → 执行审计(防前视编排)
- `metrics` — 绩效(年化收益 / 波动率 / Sharpe / 最大回撤 / Calmar)
- `factor_library` — 通用方法(turnover / winsorize / IC / OLS / jb_test)
- `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 (
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.portfolio_construction import scores_to_weight_table
from quant_engine.indicators import macd, bollinger, kdj
from quant_engine.data_adapter import (
long_to_wide, wide_to_long, rename_tushare_columns,
@@ -73,8 +74,9 @@ from quant_engine.data_adapter import (
# 端到端:qtdb_pro 长表 → 适配 → alpha158 → execution
df = load_qtdb_daily(["000001.SZ"], "2024-01-01", with_adj=True)
prices, volumes = prepare_execution_inputs(df)
result = simulate_with_daily_data(prices, initial_cash=1_000_000.0)
close_prices, volumes = prepare_execution_inputs(df)
open_prices, _ = prepare_execution_inputs(df, price_col="open")
result = simulate_with_daily_data(close_prices, initial_cash=1_000_000.0)
# 已正确滞后的目标权重 → 现金约束执行 → 唯一来源的成交/拒绝/日末持仓/NAV
execution = simulate_multi_day_with_audit(
@@ -92,18 +94,25 @@ execution = simulate_multi_day_with_audit(
print(execution.nav_series)
print(execution.daily_executions)
# 多期因子分数 → Top-K 等权组合 → 稳定回测结果
rebalance_weights = scores_to_weight_table(
# 多期因子分数(必须是 point-in-time 数据)→ Top-K → 下一交易日 open 执行
factor_execution = run_factor_execution_research(
factor_scores,
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(
weights=rebalance_weights,
weights=effective_holding_weights,
stock_returns=daily_returns,
initial_capital=1_000_000.0,
benchmark_nav=benchmark_nav,
)
print(factor_execution.schedule.signal_to_execution)
print(factor_execution.execution.daily_executions)
print(backtest.stats())
print(backtest.benchmark_report())
```
+3 -2
View File
@@ -76,8 +76,9 @@ def compute_nav_from_weights(
) -> pd.Series:
"""从调仓表(日期 × 股票权重)+ 个股日收益 → 净值曲线。
假设:在调仓日之间权重不变(**前向填充**)。
调仓日的权重 = `weights.loc[rebalance_date]`。
假设:输入是该收益测量区间开始前已经生效的持仓权重,并在调仓日之间
保持不变(**前向填充**)。本函数不会把信号日自动解释为执行日;因子分数
应先经交易日历调度和实际执行时点处理,避免把同一时点未知的收益计入。
Args:
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])
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")
scores = pd.DataFrame(
{"A": [2.0, 0.0], "B": [1.0, 3.0]},
effective_weights = pd.DataFrame(
{"A": [1.0, 0.0], "B": [0.0, 1.0]},
index=dates[[0, 2]],
)
stock_returns = pd.DataFrame(
@@ -102,11 +102,10 @@ def test_factor_scores_flow_directly_into_weight_backtest() -> None:
index=dates,
)
weights = scores_to_weight_table(scores, top_k=1)
result = run_weight_backtest(weights, stock_returns)
result = run_weight_backtest(effective_weights, stock_returns)
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])