Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/qsl_long_horizon_risk_observation_v2.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ v2 复用已验证的 v1 计算器,**仅**在以下范围输出 `ADVISORY_RECO
| 多策略组合 | `PORTFOLIO_COMPOSER_REQUIRED` | 必须重算相关性、边际风险贡献和组合净收益 |
| DCA 或外部现金流 | `CASHFLOW_COMPOSER_REQUIRED` | 必须使用现金流匹配、时间一致的路径 |
| 混合、现金或绝对收益基准 | `BENCHMARK_POLICY_COMPOSER_REQUIRED` | 不能伪装为权益无杠杆基准 |
| 非净成本总收益基准口径 | `BENCHMARK_RETURN_BASIS_COMPOSER_REQUIRED` | 当前回撤比较不具可比性 |
| 非净成本总收益基准口径(包括 `SPLIT_ADJUSTED_PRICE_RETURN`) | `BENCHMARK_RETURN_BASIS_COMPOSER_REQUIRED` | 当前回撤比较不具可比性;拆分复权收盘价不得伪称总收益 |

`PARKED` 没有尺度、最大回撤或前沿;它不是失败后的默认继续运行,更不能触发实盘动作。

Expand Down
4 changes: 4 additions & 0 deletions python/scripts/long_horizon_risk_composer_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
_RETURN_BASES = {
"TOTAL_RETURN_NET_OF_COST",
"TIME_WEIGHTED_TOTAL_RETURN",
# Some verified research lanes intentionally retain only split-adjusted
# closes. They must be representable without being mislabeled as a total
# return series; the generic v1-compatible composer still parks them.
"SPLIT_ADJUSTED_PRICE_RETURN",
"CASHFLOW_MATCHED_RETURN",
}
_RISK_FACTORS = {
Expand Down
11 changes: 11 additions & 0 deletions python/tests/test_long_horizon_risk_observation_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@ def test_cashflow_matched_strategies_cannot_be_coerced_to_time_weighted_linear_m
with self.assertRaisesRegex(composer.LongHorizonRiskComposerError, "requires CASHFLOW_MATCHED_RETURN"):
composer_v2.validate_long_horizon_risk_observation_v2(invalid)

def test_split_adjusted_price_benchmark_is_represented_but_not_mislabeled_as_total_return(self):
observation = self._observation()
observation["benchmark_policy"]["return_basis"] = "SPLIT_ADJUSTED_PRICE_RETURN"
observation["observation_sha256"] = composer_v2.calculate_risk_observation_v2_sha256(observation)

recommendation = composer_v2.compose_long_horizon_risk_recommendation_v2(observation, self._profile())

self.assertEqual(recommendation["status"], "PARKED")
self.assertEqual(recommendation["reason_codes"], ["BENCHMARK_RETURN_BASIS_COMPOSER_REQUIRED"])
self.assertIsNone(recommendation["recommended_scale_bps"])


if __name__ == "__main__":
unittest.main()