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
20 changes: 8 additions & 12 deletions docs/market-regime-control-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,12 @@ allowlist:

- `notification_allowed`: the runner may generate and distribute notification
artifacts.
- `position_control_allowed`: a strategy runtime may automatically consume
position-control fields.
- `position_control_allowed`: legacy compatibility field; V2 outputs keep it
false and expose any old position-control-shaped data as shadow metadata.
- `evidence_status`: records whether the strategy/plugin pair is
`automation_approved`, `notification_only`, or `deprecated_compatibility`.
- When `position_control_allowed = true`, the runner output should also expose
a machine-readable `auditable_position_control` block with
`evidence_package_id`, `evidence_valid_until`, and `bounded_budget`.
- Historical `auditable_position_control` evidence remains readable for replay,
but does not grant allocation authority.
- `since_version`: records the runner schema version where the permission
became effective.

Expand All @@ -159,18 +158,15 @@ the human notification body:

- The plugin repository only writes artifacts and notifications. It does not
call broker APIs or directly mutate account allocation.
- Automated position impact happens only when the strategy side explicitly
consumes `position_control`, and only when `position_control_allowed = true`
and `evidence_status = automation_approved`.
- Automated position impact must be generated by an owning strategy candidate
and pass the central Risk Gate; this plugin artifact cannot cause it directly.
- New or expanded automated position consumers should clear a 25-30 year
long-history validation gate before promotion. For TQQQ/SOXL-style products
without enough live ETF history, the long replay must explicitly use a
synthetic daily-reset 3x proxy built from the underlying QQQ/SOXX series and
archive synthetic and real-ETF evidence separately.
- The current automation-approved strategy consumers are TQQQ growth/income
and SOXL/SOXX trend/income. Global ETF, Russell 1000, and Mega Cap rotation
remain notification/evidence artifacts for this plugin until their own
long-history promotion packages are archived. Their runner artifacts carry
- TQQQ growth/income and SOXL/SOXX trend/income keep their historical evidence,
but all strategy mounts are now notification/evidence artifacts. Their runner artifacts carry
`position_control_allowed = false`,
`strategy_runtime_metadata_allowed = false`, and
`capital_impact = notification_only`.
Expand Down
14 changes: 7 additions & 7 deletions docs/market-regime-control-plan.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,21 @@
策略插件 runner 使用显式消费权限 registry,而不是只维护松散 allowlist:

- `notification_allowed`:允许生成和分发通知 artifact。
- `position_control_allowed`:允许策略 runtime 自动消费仓位控制字段。
- `position_control_allowed`:旧兼容字段;V2 输出固定为 false,旧仓位控制形状的
数据只作为 shadow 元数据。
- `evidence_status`:记录该策略/插件组合是 `automation_approved`、`notification_only` 还是 `deprecated_compatibility`。
- 当 `position_control_allowed = true` 时,runner 输出还应暴露机器可读的
`auditable_position_control` 块,包含 `evidence_package_id`、
`evidence_valid_until` 和 `bounded_budget`。
- 历史 `auditable_position_control` 证据仍可用于回放,但不授予仓位权限。
- `since_version`:记录该消费权限从哪个 runner schema 开始生效。

权限边界写在文档和机器字段里,不重复写进人工通知正文:

- 插件仓库只生成 artifact 和通知,不调用券商接口,也不直接改账户配置。
- 自动仓位影响只发生在策略侧显式消费 `position_control` 时,并且必须同时满足
`position_control_allowed = true` 和 `evidence_status = automation_approved`
- 自动仓位影响必须由归属策略候选生成并通过中央 Risk Gate;插件 artifact
不能直接造成仓位变化
- 新增或扩大自动仓位消费范围前,应先通过 25-30 年长周期验证。TQQQ/SOXL 这类实盘产品历史不足的策略,长周期验证必须明确使用
QQQ/SOXX 等底层资产合成的 3x 日重置代理,并把 synthetic 口径和真实 ETF 口径分开归档。
- 当前已批准自动消费的策略级消费者只有 TQQQ 增长收益和 SOXL/SOXX 趋势收益。Global ETF、Russell 1000 和 Mega Cap 轮动在各自长周期推广包归档前,只保留通知和证据 artifact;runner artifact 会写入
- TQQQ 增长收益和 SOXL/SOXX 趋势收益保留历史验证证据,但所有策略挂载现在
都只保留通知和证据 artifact;runner artifact 会写入
`position_control_allowed = false`、`strategy_runtime_metadata_allowed = false`
和 `capital_impact = notification_only`。
- 同一个自动化证据可以有偏进攻和偏保守两种策略侧消费偏好;插件只输出确定性字段和证据,具体选用哪个偏好由策略配置决定。
Expand Down
33 changes: 13 additions & 20 deletions docs/plugin_lifecycle_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,19 @@ permission by accident.
| `notification_only` | Can notify humans, but cannot control position | none |
| `shadow_observer` | Can be attached to runtime metadata and audit trails | none |
| `automation_candidate` | Has enough evidence to be considered for automation | gated |
| `automation_approved` | Strategy-side automation is allowed when the platform gate also passes | yes |
| `automation_approved` | Legacy evidence label retained for replay; not direct allocation authority | none |
| `deprecated_compatibility` | Kept for replay or staged migration only | none |

## Three-Gate Rule
## V2 Authority Rule

For any plugin-driven capital impact, all three gates must pass:
Plugin artifacts never carry direct capital authority. They may provide a
signal, observation, or bounded risk suggestion to an owning strategy
candidate. That strategy must validate the behavior through its lifecycle and
submit any target through the central Risk Gate.

1. **Plugin schema gate**
- The artifact must match a supported schema version and remain in `shadow`
mode for the shared contract.
2. **Plugin evidence gate**
- The plugin must be marked `automation_approved` and
`position_control_allowed = true`.
- If the platform enables automated position control, the runner should also
carry a machine-readable `auditable_position_control` block with
`evidence_package_id`, `evidence_valid_until`, and `bounded_budget`.
3. **Strategy/platform gate**
- The consuming strategy must explicitly opt in to the plugin and remain
allowed by the platform catalog.

If any gate fails, the plugin should stay notification-only or compatibility-only.
Legacy v1 `position_control_allowed` and `automation_approved` fields are kept
readable for historical artifacts, but the runner emits them as
notification/shadow-only and cannot use them to mutate allocations.

## Current Policy Shape

Expand All @@ -59,7 +51,8 @@ If any gate fails, the plugin should stay notification-only or compatibility-onl
## Recommended Operating Rules

- Keep `notification_allowed` broad for research visibility.
- Keep `position_control_allowed` narrow and explicit.
- Keep `position_control_allowed = false`; strategy-owned adapters consume
validated signals through the central Risk Gate.
- Prefer a single shared policy registry instead of spreading allowlists across
runners.
- When a strategy consumes a plugin for live capital impact, keep the platform
Expand All @@ -69,7 +62,7 @@ If any gate fails, the plugin should stay notification-only or compatibility-onl

- If the artifact is only for human review, use `notification_only`.
- If the artifact is still a sidecar evidence layer, use `shadow_observer`.
- If the plugin is being prepared for automation, keep it in
`automation_candidate` until the strategy gate also passes.
- If plugin behavior is being prepared for automation, promote a new owning
strategy candidate; do not promote the plugin into allocation authority.
- If the plugin is no longer the preferred path, mark it
`deprecated_compatibility` and keep it out of new runtime defaults.
26 changes: 10 additions & 16 deletions docs/plugin_lifecycle_policy.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,17 @@
| `notification_only` | 可以通知人工,但不能控制仓位 | 无 |
| `shadow_observer` | 可以挂到 runtime metadata 和审计轨迹 | 无 |
| `automation_candidate` | 证据足够,进入自动化候选 | 受门槛控制 |
| `automation_approved` | 当平台门槛也通过时,策略侧可自动消费 | |
| `automation_approved` | 为历史回放保留的旧证据标签,不代表直接仓位权限 | |
| `deprecated_compatibility` | 仅用于回放或迁移兼容 | 无 |

## 三道门槛
## V2 权限规则

任何由插件驱动的资金影响,都必须同时通过三道门槛:
插件 artifact 永远不携带直接资金权限。它只能向归属策略候选提供信号、观察或
受限风险建议;策略必须通过自己的生命周期验证,并把仓位目标提交中央 Risk Gate。

1. **插件 schema 门槛**
- artifact 必须匹配支持的 schema version,并在共享契约中保持 `shadow` 模式。
2. **插件证据门槛**
- 插件必须标记为 `automation_approved`,且 `position_control_allowed = true`。
- 如果平台启用自动仓位控制,runner 还应携带机器可读的
`auditable_position_control` 块,包含 `evidence_package_id`、
`evidence_valid_until` 和 `bounded_budget`。
3. **策略 / 平台门槛**
- 消费策略必须显式 opt-in,并且仍然被平台 catalog 允许。

任意一项失败,插件就应该停留在 notification-only 或兼容模式。
旧 v1 的 `position_control_allowed` 和 `automation_approved` 字段继续可读,
方便历史 artifact 回放;但 runner 统一输出为 notification/shadow-only,不能
借这些字段修改仓位。

## 当前策略形态

Expand All @@ -53,13 +46,14 @@
## 推荐运行规则

- `notification_allowed` 可以保持宽松,方便研究可见。
- `position_control_allowed` 必须保持窄而明确。
- `position_control_allowed` 固定为 `false`;策略侧适配器只通过中央 Risk Gate
消费已经验证的信号。
- 尽量使用统一的 policy registry,不要把 allowlist 分散到多个 runner。
- 当策略消费插件并产生 live 资金影响时,平台通知路径应与策略执行路径分离。

## 实际解释

- 只给人工复核看的 artifact,用 `notification_only`。
- 仍然只是 sidecar 证据层的 artifact,用 `shadow_observer`。
- 正在准备自动化的插件,用 `automation_candidate`,直到策略门槛也通过
- 如果准备自动消费某个插件行为,应晋级新的归属策略候选,而不是给插件仓位权限
- 不再是新默认路径的插件,用 `deprecated_compatibility`,并避免出现在新的 runtime 默认值里。
6 changes: 5 additions & 1 deletion src/quant_strategy_plugins/plugin_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .plugin_lineage import PLUGIN_LINEAGE_REGISTRY
from .plugin_policies import (
PLUGIN_COMPATIBLE_STRATEGIES,
PLUGIN_DIRECT_POSITION_CONTROL_ALLOWED,
PLUGIN_LIFECYCLE_POLICY_REGISTRY,
PLUGIN_CONSUMPTION_POLICY_REGISTRY,
)
Expand All @@ -34,7 +35,10 @@ def build_plugin_catalog() -> dict[str, Any]:
for (policy_plugin, _), policy in PLUGIN_CONSUMPTION_POLICY_REGISTRY.items()
if policy_plugin == plugin_id
]
position_allowed = any(policy.position_control_allowed for policy in policies)
position_allowed = bool(
PLUGIN_DIRECT_POSITION_CONTROL_ALLOWED
and any(policy.position_control_allowed for policy in policies)
)
entries.append(
{
"plugin_id": plugin_id,
Expand Down
20 changes: 12 additions & 8 deletions src/quant_strategy_plugins/plugin_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
PLUGIN_LIFECYCLE_AUTOMATION_APPROVED = "automation_approved"
PLUGIN_LIFECYCLE_NOTIFICATION_ONLY = "notification_only"
PLUGIN_LIFECYCLE_DEPRECATED_COMPATIBILITY = "deprecated_compatibility"
# V1 artifacts retain position-control-shaped fields for deterministic replay,
# but the V2 sidecar boundary never grants direct allocation authority.
PLUGIN_DIRECT_POSITION_CONTROL_ALLOWED = False

GENERAL_MARKET_REGIME_NOTIFICATION_TARGET = "market_regime_notification"

Expand Down Expand Up @@ -79,11 +82,11 @@ class PluginLifecyclePolicy:
PLUGIN_LIFECYCLE_POLICIES: tuple[PluginLifecyclePolicy, ...] = (
PluginLifecyclePolicy(
plugin=PLUGIN_MARKET_REGIME_CONTROL,
lifecycle_stage=PLUGIN_LIFECYCLE_AUTOMATION_APPROVED,
lifecycle_stage=PLUGIN_LIFECYCLE_NOTIFICATION_ONLY,
schema_versions=PLUGIN_SCHEMA_VERSIONS[PLUGIN_MARKET_REGIME_CONTROL],
new_mount_allowed=True,
replay_only=False,
description="Unified market-regime sidecar; automated consumption still requires per-strategy policy approval.",
description="Unified market-regime sidecar; outputs are notification/shadow metadata for strategy-owned evaluation.",
),
PluginLifecyclePolicy(
plugin=PLUGIN_PANIC_REVERSAL_SHADOW,
Expand Down Expand Up @@ -132,10 +135,10 @@ class PluginLifecyclePolicy:
plugin=PLUGIN_MARKET_REGIME_CONTROL,
strategy="tqqq_growth_income",
notification_allowed=True,
position_control_allowed=True,
evidence_status=EVIDENCE_AUTOMATION_APPROVED,
position_control_allowed=False,
evidence_status=EVIDENCE_NOTIFICATION_ONLY,
since_version="strategy_plugins.v1",
description="Backtested automatic macro/crisis risk controls for the TQQQ growth-income strategy.",
description="Backtested macro/crisis observation for strategy-owned evaluation; no direct position control.",
manual_review_notification_target=GENERAL_MARKET_REGIME_NOTIFICATION_TARGET,
),
PluginConsumptionPolicy(
Expand Down Expand Up @@ -172,10 +175,10 @@ class PluginLifecyclePolicy:
plugin=PLUGIN_MARKET_REGIME_CONTROL,
strategy="soxl_soxx_trend_income",
notification_allowed=True,
position_control_allowed=True,
evidence_status=EVIDENCE_AUTOMATION_APPROVED,
position_control_allowed=False,
evidence_status=EVIDENCE_NOTIFICATION_ONLY,
since_version="strategy_plugins.v1",
description="Backtested automatic macro/crisis risk controls for the SOXL/SOXX trend-income strategy.",
description="Backtested macro/crisis observation for strategy-owned evaluation; no direct position control.",
manual_review_notification_target=GENERAL_MARKET_REGIME_NOTIFICATION_TARGET,
),
PluginConsumptionPolicy(
Expand Down Expand Up @@ -296,6 +299,7 @@ def extract_auditable_position_control_context(raw: Mapping[str, Any] | None) ->
"PLUGIN_CONSUMPTION_POLICY_REGISTRY",
"PLUGIN_CRISIS_RESPONSE_SHADOW",
"PLUGIN_DEPRECATED_SUCCESSORS",
"PLUGIN_DIRECT_POSITION_CONTROL_ALLOWED",
"PLUGIN_LIFECYCLE_AUTOMATION_APPROVED",
"PLUGIN_LIFECYCLE_DEPRECATED_COMPATIBILITY",
"PLUGIN_LIFECYCLE_NOTIFICATION_ONLY",
Expand Down
9 changes: 7 additions & 2 deletions src/quant_strategy_plugins/strategy_plugin_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
PLUGIN_CONSUMPTION_POLICY_REGISTRY,
PLUGIN_CRISIS_RESPONSE_SHADOW,
PLUGIN_DEPRECATED_SUCCESSORS,
PLUGIN_DIRECT_POSITION_CONTROL_ALLOWED,
PLUGIN_LIFECYCLE_POLICIES,
PLUGIN_LIFECYCLE_POLICY_REGISTRY,
PLUGIN_MACRO_RISK_GOVERNOR,
Expand Down Expand Up @@ -1357,7 +1358,11 @@ def _apply_plugin_contract(
execution_controls["repository_allocation_mutation_allowed"] = False
if consumption_policy is not None:
execution_controls["notification_allowed"] = bool(consumption_policy.notification_allowed)
execution_controls["position_control_allowed"] = bool(consumption_policy.position_control_allowed)
position_control_allowed = bool(
PLUGIN_DIRECT_POSITION_CONTROL_ALLOWED
and consumption_policy.position_control_allowed
)
execution_controls["position_control_allowed"] = position_control_allowed
execution_controls["consumption_evidence_status"] = consumption_policy.evidence_status
if consumption_policy.manual_review_notification_target:
delegated_notification_target = consumption_policy.manual_review_notification_target
Expand All @@ -1367,7 +1372,7 @@ def _apply_plugin_contract(
f"notification_target:{delegated_notification_target}"
)
execution_controls["manual_review_notification_authority"] = "plugin_notification_target"
if consumption_policy.position_control_allowed:
if position_control_allowed:
execution_controls["capital_impact"] = "strategy_opt_in"
execution_controls["strategy_runtime_metadata_allowed"] = True
execution_controls["position_control_shadow_only"] = False
Expand Down
8 changes: 4 additions & 4 deletions tests/test_plugin_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def test_catalog_is_complete_inventory_and_never_grants_capital_authority() -> N
assert entry["broker_order_allowed"] is False


def test_catalog_keeps_approved_policy_distinct_from_runtime_authority() -> None:
def test_catalog_closes_legacy_policy_position_authority() -> None:
entries = {entry["plugin_id"]: entry for entry in build_plugin_catalog()["entries"]}

# A policy may permit a future strategy-side use, but the inventory itself
# never promotes a plugin or supplies a run-specific evidence package.
assert entries["market_regime_control"]["policy_position_control_allowed"] is True
# V1 policy fields remain readable, but V2 never grants direct position
# authority to a sidecar.
assert entries["market_regime_control"]["policy_position_control_allowed"] is False
assert entries["market_regime_control"]["status"] == "DEFERRED"
8 changes: 4 additions & 4 deletions tests/test_plugin_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import quant_strategy_plugins.strategy_plugin_runner as strategy_plugin_runner
from quant_strategy_plugins.plugin_policies import (
AUDITABLE_POSITION_CONTROL_FIELDS,
EVIDENCE_AUTOMATION_APPROVED,
EVIDENCE_NOTIFICATION_ONLY,
GENERAL_MARKET_REGIME_NOTIFICATION_TARGET,
PLUGIN_COMPATIBLE_STRATEGIES,
PLUGIN_CONSUMPTION_POLICY_REGISTRY,
Expand All @@ -16,12 +16,12 @@
)


def test_market_regime_control_tqqq_policy_is_automation_approved() -> None:
def test_market_regime_control_tqqq_policy_is_notification_only() -> None:
policy = PLUGIN_CONSUMPTION_POLICY_REGISTRY[(PLUGIN_MARKET_REGIME_CONTROL, "tqqq_growth_income")]

assert policy.evidence_status == EVIDENCE_AUTOMATION_APPROVED
assert policy.evidence_status == EVIDENCE_NOTIFICATION_ONLY
assert policy.notification_allowed is True
assert policy.position_control_allowed is True
assert policy.position_control_allowed is False
assert policy.manual_review_notification_target == GENERAL_MARKET_REGIME_NOTIFICATION_TARGET


Expand Down
Loading