Skip to content

Commit 9bb8f31

Browse files
authored
Merge pull request #231 from QuantStrategyLab/codex/quant-strategy-spec-v1
feat: add research and optimization spec contracts
2 parents 7f0367c + e4fa557 commit 9bb8f31

11 files changed

Lines changed: 1386 additions & 0 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Quant Strategy Specification Contracts
2+
3+
This document defines the first versioned artifacts introduced by the Quant
4+
Strategy Review and Optimization Standard. They are research evidence inputs;
5+
they do not change existing lifecycle stages, evidence-package fields, or
6+
runtime interfaces.
7+
8+
## Artifacts
9+
10+
| Artifact | Schema version | Purpose |
11+
| --- | --- | --- |
12+
| `ResearchSpec` | `research_spec.v1` | Freeze a falsifiable hypothesis, PIT data revision, four-layer benchmarks, net cost model, OOS plan, and complete trial ledger before evaluation. |
13+
| `OptimizationSpec` | `optimization_spec.v1` | Freeze optimization inputs, permitted parameter ranges, constrained objective, nested WFA, locked holdout, multiple-testing control, cost stress, stop rules, and human-only promotion. |
14+
15+
Schemas are stored as package data under `quant_platform_kit/schemas/`, so they
16+
remain available in installed wheels. Python validation is deliberately
17+
dependency free so an evidence gate can consume the same artifact without
18+
installing a JSON Schema runtime.
19+
20+
The schemas use QPK extensions for invariants that standard JSON Schema cannot
21+
express: sibling date ordering (`x-qpk-date-order` and
22+
`x-qpk-exclusive-date-order`) and parameter-name uniqueness
23+
(`x-qpk-unique-by`). QPK's validator and CLI enforce these extensions; a generic
24+
JSON Schema-only consumer must not be used as a promotion gate.
25+
26+
```bash
27+
quant-strategy-spec path/to/spec.json
28+
```
29+
30+
The command is silent on success, prints field-level contract violations to
31+
stderr on failure, and returns a non-zero exit code. Code integrations may
32+
use `validate_research_spec`, `validate_optimization_spec`, or
33+
`validate_strategy_spec_file` from the lightweight
34+
`quant_platform_kit.strategy_spec` package.
35+
Source checkouts may use `python scripts/validate_strategy_spec.py` as an
36+
equivalent compatibility wrapper.
37+
38+
## v1 safety rules
39+
40+
- Research evaluation requires non-overlapping in-sample/OOS ranges, a locked
41+
OOS interval, at least three walk-forward folds, PIT and survivorship checks,
42+
net-of-cost accounting, all-trial recording, and capital/passive/risk-matched/
43+
simple-rule benchmarks.
44+
- Optimization can only vary declared parameters. It requires frozen data,
45+
universe, benchmark, cost-model, and code references; nested walk-forward;
46+
a non-reused locked holdout; complete trial recording; and 1x/2x/3x cost
47+
stress.
48+
- v1 rejects full Kelly and automatic risk increases. Kelly is retained only
49+
as a bounded fractional-cap input and human approval remains mandatory.
50+
51+
`StrategyReviewReport` and `PositionBudgetReport` are intentionally deferred.
52+
They will consume these immutable inputs rather than duplicate them.

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ dependencies = []
1616

1717
[project.scripts]
1818
quant-lifecycle = "quant_platform_kit.strategy_lifecycle.cli:main"
19+
quant-strategy-spec = "quant_platform_kit.strategy_spec.cli:main"
1920

2021
[tool.setuptools]
2122
package-dir = { "" = "src" }
2223

2324
[tool.setuptools.packages.find]
2425
where = ["src"]
2526

27+
[tool.setuptools.package-data]
28+
quant_platform_kit = ["schemas/*.json"]
29+
2630
[tool.ruff]
2731
target-version = "py310"
2832

scripts/validate_strategy_spec.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python3
2+
"""Validate a versioned ResearchSpec or OptimizationSpec JSON artifact."""
3+
4+
from __future__ import annotations
5+
6+
import sys
7+
from pathlib import Path
8+
9+
10+
ROOT = Path(__file__).resolve().parents[1]
11+
sys.path.insert(0, str(ROOT / "src"))
12+
13+
from quant_platform_kit.strategy_spec.cli import main
14+
15+
16+
if __name__ == "__main__":
17+
raise SystemExit(main())

src/quant_platform_kit.egg-info/SOURCES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ src/quant_platform_kit/risk/contracts.py
104104
src/quant_platform_kit/risk/engine.py
105105
src/quant_platform_kit/risk/gate.py
106106
src/quant_platform_kit/risk/portfolio_diagnostics.py
107+
src/quant_platform_kit/schemas/optimization-spec.v1.schema.json
108+
src/quant_platform_kit/schemas/research-spec.v1.schema.json
107109
src/quant_platform_kit/schwab/__init__.py
108110
src/quant_platform_kit/schwab/auth.py
109111
src/quant_platform_kit/schwab/execution.py
@@ -140,6 +142,9 @@ src/quant_platform_kit/strategy_lifecycle/shadow_validator.py
140142
src/quant_platform_kit/strategy_lifecycle/strategy_health_score.py
141143
src/quant_platform_kit/strategy_lifecycle/update_orchestrator.py
142144
src/quant_platform_kit/strategy_lifecycle/update_policy.py
145+
src/quant_platform_kit/strategy_spec/__init__.py
146+
src/quant_platform_kit/strategy_spec/cli.py
147+
src/quant_platform_kit/strategy_spec/validation.py
143148
tests/test_backtest_orchestrator.py
144149
tests/test_binance_account.py
145150
tests/test_binance_client.py
@@ -216,6 +221,7 @@ tests/test_strategy_plugin_push_notifications.py
216221
tests/test_strategy_plugin_sms_notifications.py
217222
tests/test_strategy_plugin_telegram_notifications.py
218223
tests/test_strategy_plugins.py
224+
tests/test_strategy_spec_validation.py
219225
tests/test_telegram.py
220226
tests/test_update_orchestrator.py
221227
tests/test_validate_strategy_evidence_package.py
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://quantplatformkit.local/schemas/optimization-spec.v1.schema.json",
4+
"title": "OptimizationSpec v1",
5+
"type": "object",
6+
"required": [
7+
"schema_version",
8+
"spec_id",
9+
"research_spec_id",
10+
"strategy_profile",
11+
"created_at",
12+
"frozen_inputs",
13+
"allowed_parameters",
14+
"objective",
15+
"search",
16+
"validation",
17+
"stop_rules",
18+
"promotion"
19+
],
20+
"properties": {
21+
"schema_version": { "const": "optimization_spec.v1" },
22+
"spec_id": { "type": "string", "minLength": 1 },
23+
"research_spec_id": { "type": "string", "minLength": 1 },
24+
"strategy_profile": { "type": "string", "minLength": 1 },
25+
"created_at": {
26+
"type": "string",
27+
"format": "date-time",
28+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$"
29+
},
30+
"frozen_inputs": { "$ref": "#/$defs/frozen_inputs" },
31+
"allowed_parameters": {
32+
"type": "array",
33+
"minItems": 1,
34+
"x-qpk-unique-by": "name",
35+
"items": { "$ref": "#/$defs/parameter" }
36+
},
37+
"objective": { "$ref": "#/$defs/objective" },
38+
"search": { "$ref": "#/$defs/search" },
39+
"validation": { "$ref": "#/$defs/validation" },
40+
"stop_rules": {
41+
"type": "array",
42+
"minItems": 1,
43+
"items": { "type": "string", "minLength": 1 }
44+
},
45+
"promotion": { "$ref": "#/$defs/promotion" }
46+
},
47+
"$defs": {
48+
"frozen_inputs": {
49+
"type": "object",
50+
"required": ["data_manifest_id", "universe_id", "benchmark_ids", "cost_model_id", "code_revision"],
51+
"properties": {
52+
"data_manifest_id": { "type": "string", "minLength": 1 },
53+
"universe_id": { "type": "string", "minLength": 1 },
54+
"benchmark_ids": {
55+
"type": "array",
56+
"minItems": 1,
57+
"items": { "type": "string", "minLength": 1 }
58+
},
59+
"cost_model_id": { "type": "string", "minLength": 1 },
60+
"code_revision": { "type": "string", "minLength": 1 }
61+
},
62+
"additionalProperties": true
63+
},
64+
"parameter": {
65+
"type": "object",
66+
"required": ["name", "kind"],
67+
"properties": {
68+
"name": { "type": "string", "minLength": 1 },
69+
"kind": { "enum": ["integer", "number", "choice", "boolean"] },
70+
"bounds": {
71+
"type": "array",
72+
"minItems": 2,
73+
"maxItems": 2,
74+
"items": { "type": "number" }
75+
},
76+
"choices": {
77+
"type": "array",
78+
"minItems": 1,
79+
"items": { "type": ["string", "number", "boolean"] }
80+
},
81+
"step": { "type": "number", "exclusiveMinimum": 0 }
82+
},
83+
"allOf": [
84+
{
85+
"if": {
86+
"required": ["kind"],
87+
"properties": { "kind": { "const": "integer" } }
88+
},
89+
"then": {
90+
"required": ["bounds"],
91+
"properties": {
92+
"bounds": { "items": { "type": "integer" } },
93+
"step": { "type": "integer", "exclusiveMinimum": 0 }
94+
}
95+
}
96+
},
97+
{
98+
"if": {
99+
"required": ["kind"],
100+
"properties": { "kind": { "const": "number" } }
101+
},
102+
"then": { "required": ["bounds"] }
103+
},
104+
{
105+
"if": {
106+
"required": ["kind"],
107+
"properties": { "kind": { "const": "choice" } }
108+
},
109+
"then": { "required": ["choices"] }
110+
}
111+
],
112+
"additionalProperties": true
113+
},
114+
"objective": {
115+
"type": "object",
116+
"required": ["primary_metric", "hard_constraints"],
117+
"properties": {
118+
"primary_metric": { "type": "string", "minLength": 1 },
119+
"secondary_metrics": {
120+
"type": "array",
121+
"items": { "type": "string", "minLength": 1 }
122+
},
123+
"hard_constraints": {
124+
"type": "array",
125+
"minItems": 1,
126+
"items": { "type": "string", "minLength": 1 }
127+
}
128+
},
129+
"additionalProperties": true
130+
},
131+
"search": {
132+
"type": "object",
133+
"required": ["method", "max_trials", "random_seed"],
134+
"properties": {
135+
"method": { "enum": ["grid", "random", "bayesian"] },
136+
"max_trials": { "type": "integer", "minimum": 1 },
137+
"random_seed": { "type": "integer" }
138+
},
139+
"additionalProperties": true
140+
},
141+
"validation": {
142+
"type": "object",
143+
"required": ["nested_walk_forward", "locked_holdout", "multiple_testing", "cost_stress"],
144+
"properties": {
145+
"nested_walk_forward": {
146+
"type": "object",
147+
"required": ["enabled", "fold_count", "selection_scope"],
148+
"properties": {
149+
"enabled": { "const": true },
150+
"fold_count": { "type": "integer", "minimum": 3 },
151+
"selection_scope": { "const": "train_validation_only" }
152+
},
153+
"additionalProperties": true
154+
},
155+
"locked_holdout": {
156+
"type": "object",
157+
"required": ["enabled", "reused_for_selection"],
158+
"properties": {
159+
"enabled": { "const": true },
160+
"reused_for_selection": { "const": false }
161+
},
162+
"additionalProperties": true
163+
},
164+
"multiple_testing": {
165+
"type": "object",
166+
"required": ["method", "trial_ledger_id", "record_all_trials"],
167+
"properties": {
168+
"method": { "enum": ["dsr", "pbo", "spa", "reality_check", "fdr", "other_equivalent"] },
169+
"trial_ledger_id": { "type": "string", "minLength": 1 },
170+
"record_all_trials": { "const": true }
171+
},
172+
"additionalProperties": true
173+
},
174+
"cost_stress": {
175+
"type": "object",
176+
"required": ["multipliers", "required_pass"],
177+
"properties": {
178+
"multipliers": {
179+
"type": "array",
180+
"minItems": 3,
181+
"items": { "type": "number", "minimum": 1 },
182+
"allOf": [
183+
{ "contains": { "const": 1 } },
184+
{ "contains": { "const": 2 } },
185+
{ "contains": { "const": 3 } }
186+
]
187+
},
188+
"required_pass": { "const": true }
189+
},
190+
"additionalProperties": true
191+
}
192+
},
193+
"additionalProperties": true
194+
},
195+
"promotion": {
196+
"type": "object",
197+
"required": [
198+
"requires_human_approval",
199+
"automatic_risk_increase_allowed",
200+
"full_kelly_allowed",
201+
"max_fractional_kelly"
202+
],
203+
"properties": {
204+
"requires_human_approval": { "const": true },
205+
"automatic_risk_increase_allowed": { "const": false },
206+
"full_kelly_allowed": { "const": false },
207+
"max_fractional_kelly": { "type": "number", "exclusiveMinimum": 0, "exclusiveMaximum": 1 }
208+
},
209+
"additionalProperties": true
210+
}
211+
},
212+
"additionalProperties": true
213+
}

0 commit comments

Comments
 (0)