@riskmodels/mcp 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/INSTALL.md +130 -0
  2. package/README.md +196 -0
  3. package/data/capabilities.json +924 -0
  4. package/data/openapi.json +6760 -0
  5. package/data/schema-paths.json +16 -0
  6. package/data/schemas/error-v1.json +49 -0
  7. package/data/schemas/estimate-v1.json +49 -0
  8. package/data/schemas/factor-correlation-request-v1.json +55 -0
  9. package/data/schemas/factor-correlation-v1.json +31 -0
  10. package/data/schemas/health-v1.json +85 -0
  11. package/data/schemas/l3-decomposition-v1.json +125 -0
  12. package/data/schemas/macro-factors-series-v1.json +45 -0
  13. package/data/schemas/portfolio-risk-index-v1.json +48 -0
  14. package/data/schemas/portfolio-risk-snapshot-v1.json +134 -0
  15. package/data/schemas/risk-metadata-v1.json +35 -0
  16. package/data/schemas/telemetry-v1.json +122 -0
  17. package/data/schemas/ticker-returns-v2.json +114 -0
  18. package/data/schemas/tickers-list-v1.json +64 -0
  19. package/data/whitepaper/01-core-claim.md +10 -0
  20. package/data/whitepaper/02-aapl-vs-nvda.md +9 -0
  21. package/data/whitepaper/03-hedging.md +7 -0
  22. package/data/whitepaper/examples-aapl-nvda-crwd.md +7 -0
  23. package/data/whitepaper/one-position-four-bets.md +14 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/lib/mcp/tools/riskmodels-tools.d.ts +34 -0
  27. package/dist/lib/mcp/tools/riskmodels-tools.js +211 -0
  28. package/dist/mcp/src/index.d.ts +9 -0
  29. package/dist/mcp/src/index.js +19 -0
  30. package/dist/mcp/src/server.d.ts +25 -0
  31. package/dist/mcp/src/server.js +434 -0
  32. package/dist/server.d.ts +1 -0
  33. package/dist/server.js +1 -0
  34. package/package.json +39 -0
  35. package/scripts/write-dist-wrappers.mjs +29 -0
  36. package/src/index.ts +22 -0
  37. package/src/server.ts +555 -0
  38. package/tsconfig.json +15 -0
@@ -0,0 +1,16 @@
1
+ [
2
+ "/schemas/ticker-returns-v2.json",
3
+ "/schemas/macro-factors-series-v1.json",
4
+ "/schemas/factor-correlation-v1.json",
5
+ "/schemas/factor-correlation-request-v1.json",
6
+ "/schemas/l3-decomposition-v1.json",
7
+ "/schemas/tickers-list-v1.json",
8
+ "/schemas/error-v1.json",
9
+ "/schemas/health-v1.json",
10
+ "/schemas/telemetry-v1.json",
11
+ "/schemas/estimate-v1.json",
12
+ "/schemas/decompose-v1.json",
13
+ "/schemas/risk-metadata-v1.json",
14
+ "/schemas/portfolio-risk-snapshot-v1.json",
15
+ "/schemas/portfolio-risk-index-v1.json"
16
+ ]
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Error Response",
4
+ "type": "object",
5
+ "required": [
6
+ "error"
7
+ ],
8
+ "properties": {
9
+ "error": {
10
+ "type": "string",
11
+ "description": "Error message"
12
+ },
13
+ "error_code": {
14
+ "type": "string",
15
+ "description": "Machine-readable error code"
16
+ },
17
+ "message": {
18
+ "type": "string",
19
+ "description": "Detailed error message"
20
+ },
21
+ "details": {
22
+ "type": "object",
23
+ "description": "Additional error details (dev only)"
24
+ },
25
+ "_agent": {
26
+ "type": "object",
27
+ "properties": {
28
+ "action": {
29
+ "type": "string",
30
+ "enum": [
31
+ "retry",
32
+ "top_up",
33
+ "upgrade",
34
+ "contact_support"
35
+ ]
36
+ },
37
+ "retry_after_seconds": {
38
+ "type": "integer"
39
+ },
40
+ "top_up_url": {
41
+ "type": "string"
42
+ },
43
+ "upgrade_url": {
44
+ "type": "string"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Cost Estimate Response",
4
+ "type": "object",
5
+ "required": [
6
+ "estimated_cost_usd",
7
+ "capability",
8
+ "pricing_model",
9
+ "note"
10
+ ],
11
+ "properties": {
12
+ "estimated_cost_usd": {
13
+ "type": "number",
14
+ "description": "Predicted cost in USD"
15
+ },
16
+ "estimated_tokens": {
17
+ "type": "integer",
18
+ "description": "Equivalent token count at $0.00002/token"
19
+ },
20
+ "estimated_rows": {
21
+ "type": "integer",
22
+ "description": "Estimated row count for time-series endpoints"
23
+ },
24
+ "estimated_bytes": {
25
+ "type": "integer",
26
+ "description": "Estimated response size in bytes"
27
+ },
28
+ "capability": {
29
+ "type": "string",
30
+ "description": "Capability ID (e.g. ticker-returns, batch-analysis)"
31
+ },
32
+ "pricing_model": {
33
+ "type": "string",
34
+ "enum": ["per_request", "per_position", "per_token", "subscription"]
35
+ },
36
+ "unit_cost_usd": {
37
+ "type": "number",
38
+ "description": "Per-request or per-position unit cost"
39
+ },
40
+ "min_charge": {
41
+ "type": "number",
42
+ "description": "Minimum charge for batch endpoints"
43
+ },
44
+ "note": {
45
+ "type": "string",
46
+ "description": "Disclaimer about actual cost variance"
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://riskmodels.app/schemas/factor-correlation-request-v1.json",
4
+ "title": "FactorCorrelationRequest",
5
+ "description": "JSON body for POST /api/correlation. Matches OpenAPI components.schemas.FactorCorrelationRequest and FactorCorrelationRequestSchema in lib/api/schemas.ts. Use integer types for window_days; the API may coerce numeric strings at runtime.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["ticker"],
9
+ "properties": {
10
+ "ticker": {
11
+ "description": "Single US equity ticker, or batch: 1–50 tickers (200 response returns a results array).",
12
+ "oneOf": [
13
+ {
14
+ "type": "string",
15
+ "minLength": 1,
16
+ "maxLength": 12
17
+ },
18
+ {
19
+ "type": "array",
20
+ "minItems": 1,
21
+ "maxItems": 50,
22
+ "items": {
23
+ "type": "string",
24
+ "minLength": 1,
25
+ "maxLength": 12
26
+ }
27
+ }
28
+ ]
29
+ },
30
+ "factors": {
31
+ "type": "array",
32
+ "description": "Macro factor keys (lowercase canonical: bitcoin, gold, oil, dxy, vix, ust10y2y). Server normalizes aliases (e.g. btc → bitcoin). Omit to use all six.",
33
+ "items": {
34
+ "type": "string",
35
+ "minLength": 1
36
+ }
37
+ },
38
+ "return_type": {
39
+ "type": "string",
40
+ "enum": ["gross", "l1", "l2", "l3_residual"],
41
+ "default": "l3_residual"
42
+ },
43
+ "window_days": {
44
+ "type": "integer",
45
+ "minimum": 20,
46
+ "maximum": 2000,
47
+ "default": 252
48
+ },
49
+ "method": {
50
+ "type": "string",
51
+ "enum": ["pearson", "spearman"],
52
+ "default": "pearson"
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://riskmodels.app/schemas/factor-correlation-v1.json",
4
+ "title": "FactorCorrelationResponse",
5
+ "description": "Successful single-ticker 200 body for POST /api/correlation (and GET /api/metrics/{ticker}/correlation). Batch POST responses wrap per-ticker items in results (see OpenAPI). POST request body: factor-correlation-request-v1.json.",
6
+ "type": "object",
7
+ "required": ["ticker", "return_type", "window_days", "method", "correlations", "overlap_days", "warnings"],
8
+ "properties": {
9
+ "ticker": { "type": "string" },
10
+ "return_type": {
11
+ "type": "string",
12
+ "enum": ["gross", "l1", "l2", "l3_residual"]
13
+ },
14
+ "window_days": { "type": "integer", "minimum": 20, "maximum": 2000 },
15
+ "method": { "type": "string", "enum": ["pearson", "spearman"] },
16
+ "correlations": {
17
+ "type": "object",
18
+ "additionalProperties": { "type": ["number", "null"] }
19
+ },
20
+ "overlap_days": { "type": "integer" },
21
+ "warnings": { "type": "array", "items": { "type": "string" } },
22
+ "_metadata": { "type": "object" },
23
+ "_agent": {
24
+ "type": "object",
25
+ "properties": {
26
+ "latency_ms": { "type": "integer" },
27
+ "request_id": { "type": "string" }
28
+ }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Health Status Response",
4
+ "type": "object",
5
+ "required": ["status", "timestamp", "version"],
6
+ "properties": {
7
+ "status": {
8
+ "type": "string",
9
+ "enum": ["healthy", "degraded", "down"]
10
+ },
11
+ "timestamp": {
12
+ "type": "string",
13
+ "format": "date-time"
14
+ },
15
+ "version": {
16
+ "type": "string"
17
+ },
18
+ "services": {
19
+ "type": "object",
20
+ "additionalProperties": {
21
+ "type": "object",
22
+ "properties": {
23
+ "status": {
24
+ "type": "string",
25
+ "enum": ["healthy", "degraded", "down"]
26
+ },
27
+ "latency_ms": {
28
+ "type": "integer"
29
+ },
30
+ "last_update": {
31
+ "type": "string",
32
+ "format": "date-time"
33
+ }
34
+ }
35
+ }
36
+ },
37
+ "capabilities": {
38
+ "type": "object",
39
+ "additionalProperties": {
40
+ "type": "object",
41
+ "properties": {
42
+ "status": {
43
+ "type": "string",
44
+ "enum": ["available", "degraded", "unavailable"]
45
+ },
46
+ "avg_latency_ms_24h": {
47
+ "type": "integer"
48
+ },
49
+ "success_rate_24h": {
50
+ "type": "number"
51
+ },
52
+ "current_load": {
53
+ "type": "number"
54
+ }
55
+ }
56
+ }
57
+ },
58
+ "macro_factors": {
59
+ "type": "object",
60
+ "description": "Macro factors data health for correlation and GET /macro-factors series",
61
+ "properties": {
62
+ "status": {
63
+ "type": "string",
64
+ "enum": ["healthy", "stale", "unavailable"]
65
+ },
66
+ "latest_teos": {
67
+ "type": "object",
68
+ "additionalProperties": { "type": "string" }
69
+ },
70
+ "row_count_last_7d": {
71
+ "type": "integer"
72
+ },
73
+ "newest_teo": {
74
+ "type": ["string", "null"]
75
+ },
76
+ "oldest_teo": {
77
+ "type": ["string", "null"]
78
+ },
79
+ "stale": {
80
+ "type": "boolean"
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,125 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "L3 Risk Decomposition Response",
4
+ "description": "Daily EOD hierarchical orthogonal decomposition of a ticker's returns into market, sector, subsector, and residual components. Returns time-aligned parallel arrays (dates[i] pairs with every other array's [i]). Historical data streams from GCP zarr.",
5
+ "type": "object",
6
+ "required": [
7
+ "ticker",
8
+ "market_factor_etf",
9
+ "universe",
10
+ "dates",
11
+ "l3_market_hr",
12
+ "l3_sector_hr",
13
+ "l3_subsector_hr",
14
+ "l3_market_er",
15
+ "l3_sector_er",
16
+ "l3_subsector_er",
17
+ "l3_residual_er"
18
+ ],
19
+ "properties": {
20
+ "ticker": {
21
+ "type": "string",
22
+ "description": "Uppercased ticker symbol the decomposition was computed for (e.g. NVDA)."
23
+ },
24
+ "market_factor_etf": {
25
+ "type": "string",
26
+ "description": "ETF used as the market factor at Level 1. Defaults to SPY."
27
+ },
28
+ "universe": {
29
+ "type": "string",
30
+ "description": "Risk universe label (e.g. erm3_us_equity). Documents which peer set drove sector/subsector ETF selection."
31
+ },
32
+ "data_source": {
33
+ "type": "string",
34
+ "description": "Provenance tag for the underlying data bars. Typical values: 'zarr' (historical), 'supabase_latest' (latest snapshot)."
35
+ },
36
+ "dates": {
37
+ "type": "array",
38
+ "description": "Trading-day dates, one per observation, in ascending order. Aligned index-by-index with the other arrays in this response.",
39
+ "items": {
40
+ "type": "string",
41
+ "format": "date",
42
+ "description": "Trading day in YYYY-MM-DD format (US markets)."
43
+ }
44
+ },
45
+ "l3_market_hr": {
46
+ "type": "array",
47
+ "description": "Per-day hedge ratio vs. the Level 1 market factor ETF (units of market ETF per unit of ticker) after all three levels of orthogonalization.",
48
+ "items": { "type": ["number", "null"] }
49
+ },
50
+ "l3_sector_hr": {
51
+ "type": "array",
52
+ "description": "Per-day hedge ratio vs. the sector ETF after removing market beta (Level 2 residual regressed on sector). Negative values are valid after orthogonalization.",
53
+ "items": { "type": ["number", "null"] }
54
+ },
55
+ "l3_subsector_hr": {
56
+ "type": "array",
57
+ "description": "Per-day hedge ratio vs. the subsector ETF after removing both market and sector beta (Level 3 residual regressed on subsector). Negative values are valid after orthogonalization.",
58
+ "items": { "type": ["number", "null"] }
59
+ },
60
+ "l3_market_er": {
61
+ "type": "array",
62
+ "description": "Per-day fraction of total variance explained by the market factor (0–1). Summing (l3_market_er + l3_sector_er + l3_subsector_er + l3_residual_er) at any index yields ~1.0.",
63
+ "items": { "type": ["number", "null"] }
64
+ },
65
+ "l3_sector_er": {
66
+ "type": "array",
67
+ "description": "Per-day fraction of variance explained by the sector factor after removing market. Sums with the other *_er arrays to ~1.0.",
68
+ "items": { "type": ["number", "null"] }
69
+ },
70
+ "l3_subsector_er": {
71
+ "type": "array",
72
+ "description": "Per-day fraction of variance explained by the subsector factor after removing market and sector. Sums with the other *_er arrays to ~1.0.",
73
+ "items": { "type": ["number", "null"] }
74
+ },
75
+ "l3_residual_er": {
76
+ "type": "array",
77
+ "description": "Per-day fraction of variance that is idiosyncratic (stock-specific). Cannot be hedged by the L1/L2/L3 ETFs — this is the 'true alpha' contribution. Sums with the other *_er arrays to ~1.0.",
78
+ "items": { "type": ["number", "null"] }
79
+ },
80
+ "_metadata": {
81
+ "type": "object",
82
+ "description": "Provenance and freshness metadata attached by the API.",
83
+ "properties": {
84
+ "data_as_of": {
85
+ "type": "string",
86
+ "format": "date",
87
+ "description": "Latest trading day in the response (YYYY-MM-DD). Also emitted as the X-Data-As-Of response header."
88
+ },
89
+ "data_source": {
90
+ "type": "string",
91
+ "description": "Source of the time series (e.g. 'zarr')."
92
+ },
93
+ "range": {
94
+ "type": "array",
95
+ "description": "Inclusive [start_date, end_date] covered by the returned series.",
96
+ "items": { "type": "string", "format": "date" },
97
+ "minItems": 2,
98
+ "maxItems": 2
99
+ },
100
+ "billing_code": {
101
+ "type": "string",
102
+ "description": "Internal billing SKU recorded for this request."
103
+ }
104
+ }
105
+ },
106
+ "_agent": {
107
+ "type": "object",
108
+ "description": "Per-call agent envelope: cost, latency, and request correlation for telemetry.",
109
+ "properties": {
110
+ "cost_usd": {
111
+ "type": "number",
112
+ "description": "USD cost billed for this request against the key's daily spend budget."
113
+ },
114
+ "latency_ms": {
115
+ "type": "integer",
116
+ "description": "Server-side data fetch latency in milliseconds."
117
+ },
118
+ "request_id": {
119
+ "type": "string",
120
+ "description": "Unique request identifier for log correlation and support tickets."
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://riskmodels.app/schemas/macro-factors-series-v1.json",
4
+ "title": "MacroFactorsSeriesResponse",
5
+ "description": "Successful 200 body for GET /api/macro-factors (daily macro factor returns, long format).",
6
+ "type": "object",
7
+ "required": ["factors_requested", "start", "end", "row_count", "series", "warnings"],
8
+ "properties": {
9
+ "factors_requested": {
10
+ "type": "array",
11
+ "items": { "type": "string" }
12
+ },
13
+ "start": { "type": "string", "format": "date" },
14
+ "end": { "type": "string", "format": "date" },
15
+ "row_count": { "type": "integer", "minimum": 0 },
16
+ "series": {
17
+ "type": "array",
18
+ "items": {
19
+ "type": "object",
20
+ "required": ["factor_key", "teo"],
21
+ "properties": {
22
+ "factor_key": { "type": "string" },
23
+ "teo": { "type": "string", "format": "date" },
24
+ "return_gross": { "type": ["number", "null"] },
25
+ "metadata": { "type": "object", "additionalProperties": true }
26
+ },
27
+ "additionalProperties": true
28
+ }
29
+ },
30
+ "warnings": {
31
+ "type": "array",
32
+ "items": { "type": "string" }
33
+ },
34
+ "_metadata": { "type": "object", "additionalProperties": true },
35
+ "_agent": {
36
+ "type": "object",
37
+ "properties": {
38
+ "latency_ms": { "type": "integer" },
39
+ "request_id": { "type": "string" }
40
+ },
41
+ "additionalProperties": true
42
+ }
43
+ },
44
+ "additionalProperties": true
45
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "PortfolioRiskIndex",
4
+ "description": "JSON response for POST /api/portfolio/risk-index. Returns L3 variance decomposition for a weighted portfolio.",
5
+ "type": "object",
6
+ "required": ["portfolio_risk_index"],
7
+ "properties": {
8
+ "portfolio_risk_index": {
9
+ "type": "object",
10
+ "required": ["variance_decomposition", "portfolio_volatility_23d", "position_count"],
11
+ "properties": {
12
+ "variance_decomposition": {
13
+ "type": "object",
14
+ "required": ["market", "sector", "subsector", "residual"],
15
+ "properties": {
16
+ "market": { "type": "number" },
17
+ "sector": { "type": "number" },
18
+ "subsector": { "type": "number" },
19
+ "residual": { "type": "number" }
20
+ }
21
+ },
22
+ "portfolio_volatility_23d": {
23
+ "type": ["number", "null"]
24
+ },
25
+ "position_count": {
26
+ "type": "integer"
27
+ }
28
+ }
29
+ },
30
+ "time_series": {
31
+ "type": "array",
32
+ "description": "Only present when timeSeries=true",
33
+ "items": {
34
+ "type": "object",
35
+ "properties": {
36
+ "date": { "type": "string", "format": "date" },
37
+ "market": { "type": "number" },
38
+ "sector": { "type": "number" },
39
+ "subsector": { "type": "number" },
40
+ "residual": { "type": "number" }
41
+ }
42
+ }
43
+ },
44
+ "cost_usd": { "type": "number" },
45
+ "positions_resolved": { "type": "integer" },
46
+ "positions_errored": { "type": "integer" }
47
+ }
48
+ }
@@ -0,0 +1,134 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "PortfolioRiskSnapshot",
4
+ "description": "JSON response for POST /api/portfolio/risk-snapshot (format=json). When format=pdf, the response is binary application/pdf.",
5
+ "type": "object",
6
+ "required": ["title", "as_of", "portfolio_risk_index", "per_ticker", "_metadata"],
7
+ "properties": {
8
+ "title": {
9
+ "type": "string",
10
+ "description": "Report title (user-supplied or auto-generated)"
11
+ },
12
+ "as_of": {
13
+ "type": "string",
14
+ "format": "date",
15
+ "description": "Report label date (YYYY-MM-DD)"
16
+ },
17
+ "portfolio_risk_index": {
18
+ "type": "object",
19
+ "required": ["variance_decomposition", "portfolio_volatility_23d", "position_count"],
20
+ "properties": {
21
+ "variance_decomposition": {
22
+ "type": "object",
23
+ "required": ["market", "sector", "subsector", "residual", "systematic"],
24
+ "properties": {
25
+ "market": { "type": "number", "description": "Fraction of portfolio variance from market factor" },
26
+ "sector": { "type": "number", "description": "Fraction from sector factors" },
27
+ "subsector": { "type": "number", "description": "Fraction from subsector factors" },
28
+ "residual": { "type": "number", "description": "Fraction from idiosyncratic / stock-specific risk" },
29
+ "systematic": { "type": "number", "description": "market + sector + subsector" }
30
+ }
31
+ },
32
+ "portfolio_volatility_23d": {
33
+ "type": ["number", "null"],
34
+ "description": "23-day weighted annualized portfolio volatility"
35
+ },
36
+ "position_count": {
37
+ "type": "integer",
38
+ "description": "Number of resolved positions"
39
+ },
40
+ "diversification": {
41
+ "type": "object",
42
+ "description": "Optional correlation-adjusted diversification metrics. Present only when include_diversification=true on the request.",
43
+ "properties": {
44
+ "window_days": { "type": "integer", "description": "Rolling window used to estimate ETF correlations (trading days)." },
45
+ "method": { "type": "string", "description": "Correlation method used (e.g. 'pearson')." },
46
+ "naive_pws": {
47
+ "type": "object",
48
+ "description": "Position-weighted sum of per-ticker ERs, ignoring cross-position correlations (upper bound on systematic risk).",
49
+ "properties": {
50
+ "market_er": { "type": "number", "description": "Position-weighted sum of market ER." },
51
+ "sector_er": { "type": "number", "description": "Position-weighted sum of sector ER." },
52
+ "subsector_er": { "type": "number", "description": "Position-weighted sum of subsector ER." },
53
+ "residual_er": { "type": "number", "description": "Position-weighted sum of residual ER." },
54
+ "total": { "type": "number", "description": "Sum of the four layers; naive upper bound for portfolio risk share." }
55
+ }
56
+ },
57
+ "correlation_adjusted": {
58
+ "type": "object",
59
+ "description": "Same layers as naive_pws, adjusted for the correlation matrix of the sector/subsector ETFs (actual variance contribution).",
60
+ "properties": {
61
+ "market_er": { "type": "number", "description": "Correlation-adjusted market ER." },
62
+ "sector_er": { "type": "number", "description": "Correlation-adjusted sector ER (reflects cross-sector correlations)." },
63
+ "subsector_er": { "type": "number", "description": "Correlation-adjusted subsector ER (reflects cross-subsector correlations)." },
64
+ "residual_er": { "type": "number", "description": "Residual ER after correlation adjustment." },
65
+ "total": { "type": "number", "description": "Sum of the adjusted layers." }
66
+ }
67
+ },
68
+ "diversification_credit": {
69
+ "type": "object",
70
+ "description": "Per-layer 'credit' from correlation adjustment: naive_pws[layer] − correlation_adjusted[layer]. Higher = more diversification benefit.",
71
+ "properties": {
72
+ "market": { "type": "number", "description": "Diversification credit at the market layer." },
73
+ "sector": { "type": "number", "description": "Diversification credit at the sector layer." },
74
+ "subsector": { "type": "number", "description": "Diversification credit at the subsector layer." },
75
+ "residual": { "type": "number", "description": "Diversification credit at the residual (idiosyncratic) layer." },
76
+ "total": { "type": "number", "description": "Sum of per-layer diversification credits." }
77
+ }
78
+ },
79
+ "layers": {
80
+ "type": "array",
81
+ "description": "Per-layer detail aligned with the decomposition hierarchy. Useful for ranking which layer is carrying the most (and least-diversified) risk.",
82
+ "items": {
83
+ "type": "object",
84
+ "required": ["layer", "naive_er", "adjusted_er", "adjustment_er"],
85
+ "properties": {
86
+ "layer": { "type": "string", "enum": ["market", "sector", "subsector", "residual"], "description": "Which layer of the decomposition this row describes." },
87
+ "naive_er": { "type": "number", "description": "Position-weighted sum of ER at this layer (no correlation adjustment)." },
88
+ "adjusted_er": { "type": "number", "description": "Correlation-adjusted ER at this layer." },
89
+ "adjustment_er": { "type": "number", "description": "naive_er − adjusted_er. Positive means diversification reduced risk at this layer." },
90
+ "multiplier": { "type": ["number", "null"], "description": "Ratio adjusted_er / naive_er (null if naive_er is 0)." },
91
+ "unique_etfs": { "type": "integer", "description": "Count of distinct sector/subsector ETFs contributing to this layer for the portfolio." }
92
+ }
93
+ }
94
+ },
95
+ "warnings": { "type": "array", "items": { "type": "string" }, "description": "Non-fatal warnings about data coverage or assumption violations." },
96
+ "_explanation": { "type": "string", "description": "Plain-English summary suitable for showing to an end user." }
97
+ }
98
+ }
99
+ }
100
+ },
101
+ "per_ticker": {
102
+ "type": "array",
103
+ "description": "One row per position, in the order supplied in the request. Null values indicate the metric was unavailable for that ticker on the snapshot date.",
104
+ "items": {
105
+ "type": "object",
106
+ "required": ["ticker", "weight"],
107
+ "properties": {
108
+ "ticker": { "type": "string", "description": "Uppercased ticker symbol." },
109
+ "weight": { "type": "number", "description": "Supplied portfolio weight (not necessarily normalized to 1)." },
110
+ "l3_mkt_er": { "type": ["number", "null"], "description": "Fraction of this position's variance explained by the market factor (0–1)." },
111
+ "l3_sec_er": { "type": ["number", "null"], "description": "Fraction explained by the sector factor after removing market (0–1)." },
112
+ "l3_sub_er": { "type": ["number", "null"], "description": "Fraction explained by the subsector factor after removing market and sector (0–1)." },
113
+ "l3_res_er": { "type": ["number", "null"], "description": "Fraction of idiosyncratic / stock-specific variance. Cannot be ETF-hedged." },
114
+ "l3_mkt_hr": { "type": ["number", "null"], "description": "Hedge ratio vs. market factor ETF (SPY by default)." },
115
+ "l3_sec_hr": { "type": ["number", "null"], "description": "Hedge ratio vs. sector ETF after orthogonalizing against market. Negative values are valid." },
116
+ "l3_sub_hr": { "type": ["number", "null"], "description": "Hedge ratio vs. subsector ETF after orthogonalizing against market and sector. Negative values are valid." },
117
+ "vol_23d": { "type": ["number", "null"], "description": "23-day annualized volatility." },
118
+ "price_close": { "type": ["number", "null"], "description": "Latest close price in USD." }
119
+ }
120
+ }
121
+ },
122
+ "_metadata": {
123
+ "type": "object",
124
+ "description": "Response provenance and billing metadata. Also surfaced as response headers (X-Data-As-Of, X-Cost-Usd).",
125
+ "required": ["generated_at", "lineage", "billing_code"],
126
+ "properties": {
127
+ "generated_at": { "type": "string", "format": "date-time", "description": "Server-side timestamp when this snapshot was computed (ISO 8601)." },
128
+ "lineage": { "type": "string", "description": "Lineage identifier linking this response to the risk engine version and data cut." },
129
+ "billing_code": { "type": "string", "description": "Internal billing SKU recorded for this request." },
130
+ "data_as_of": { "type": "string", "format": "date", "description": "Trading day the underlying metrics correspond to (YYYY-MM-DD)." }
131
+ }
132
+ }
133
+ }
134
+ }