Repository Public
1{2 "$schema": "https://json-schema.org/draft/2020-12/schema",3 "$id": "https://sp-wallet.local/schema/backtest-report.schema.json",4 "title": "SP Wallet V3 backtest report",5 "description": "Produced by spq.backtest (research/), stored by the API. Follows docs/v3/ARCHITECTURE.md 'Backtest report'; keys beyond that document are additions of the lab. A report states what was measured, on which data, with which costs; it never says a strategy is profitable.",6 "type": "object",7 "required": [8 "strategy_sha256",9 "config",10 "splits",11 "metrics",12 "benchmarks",13 "robustness",14 "checks",15 "equity_curve"16 ],17 "properties": {18 "schema_version": {19 "const": 120 },21 "generated_at": {22 "type": "string",23 "format": "date-time"24 },25 "strategy_sha256": {26 "type": "string",27 "pattern": "^[0-9a-f]{64}$"28 },29 "strategy": {30 "type": "object",31 "required": [32 "name",33 "version",34 "meta"35 ],36 "properties": {37 "name": {38 "type": "string"39 },40 "version": {41 "type": "string"42 },43 "meta": {44 "type": "object"45 }46 }47 },48 "config": {49 "type": "object",50 "required": [51 "symbols",52 "interval",53 "start",54 "end",55 "fee_bps",56 "slippage_bps",57 "initial_cash"58 ],59 "properties": {60 "symbols": {61 "type": "array",62 "items": {63 "type": "string",64 "pattern": "^[A-Z0-9]+$"65 },66 "minItems": 1,67 "uniqueItems": true68 },69 "interval": {70 "type": "string",71 "pattern": "^[0-9]+[mhdw]$"72 },73 "start": {74 "type": "string",75 "format": "date-time",76 "description": "first bar of the data (bar-open time, UTC)"77 },78 "end": {79 "type": "string",80 "format": "date-time",81 "description": "last bar of the data (bar-open time, UTC)"82 },83 "fee_bps": {84 "type": "number",85 "minimum": 086 },87 "slippage_bps": {88 "type": "number",89 "minimum": 090 },91 "initial_cash": {92 "type": "number",93 "exclusiveMinimum": 094 },95 "min_order": {96 "type": "number",97 "minimum": 098 },99 "rebalance_threshold": {100 "type": "number",101 "minimum": 0,102 "maximum": 1103 },104 "decision_every": {105 "type": "integer",106 "minimum": 1107 },108 "params": {109 "type": "object"110 },111 "source": {112 "type": [113 "string",114 "null"115 ]116 },117 "bars": {118 "type": "integer"119 },120 "split_fractions": {121 "type": "array",122 "items": {123 "type": "number"124 },125 "minItems": 3,126 "maxItems": 3127 }128 }129 },130 "splits": {131 "type": "object",132 "required": [133 "train",134 "validation",135 "test"136 ],137 "properties": {138 "train": {139 "type": "array",140 "prefixItems": [141 {142 "type": "string",143 "format": "date-time"144 },145 {146 "type": "string",147 "format": "date-time"148 }149 ],150 "minItems": 2,151 "maxItems": 2,152 "description": "[first bar, last bar] (bar-open times, UTC, both inclusive)"153 },154 "validation": {155 "type": "array",156 "prefixItems": [157 {158 "type": "string",159 "format": "date-time"160 },161 {162 "type": "string",163 "format": "date-time"164 }165 ],166 "minItems": 2,167 "maxItems": 2,168 "description": "[first bar, last bar] (bar-open times, UTC, both inclusive)"169 },170 "test": {171 "type": "array",172 "prefixItems": [173 {174 "type": "string",175 "format": "date-time"176 },177 {178 "type": "string",179 "format": "date-time"180 }181 ],182 "minItems": 2,183 "maxItems": 2,184 "description": "[first bar, last bar] (bar-open times, UTC, both inclusive)"185 }186 },187 "additionalProperties": false188 },189 "metrics": {190 "type": "object",191 "required": [192 "train",193 "validation",194 "test"195 ],196 "properties": {197 "train": {198 "$ref": "#/$defs/metrics"199 },200 "validation": {201 "$ref": "#/$defs/metrics"202 },203 "test": {204 "$ref": "#/$defs/metrics"205 }206 },207 "additionalProperties": false208 },209 "benchmarks": {210 "type": "object",211 "description": "Same metrics on the TEST split. buy_and_hold: equal weights bought once, never rebalanced (the single asset when there is one symbol); equal_weight: rebalanced; buy_and_hold_<SYM>: one asset.",212 "required": [213 "buy_and_hold"214 ],215 "additionalProperties": {216 "$ref": "#/$defs/metrics"217 }218 },219 "robustness": {220 "type": "object",221 "required": [222 "walk_forward",223 "bootstrap_sharpe_ci",224 "parameter_sensitivity",225 "trials_tested",226 "deflated_sharpe"227 ],228 "properties": {229 "walk_forward": {230 "type": "array",231 "items": {232 "type": "object",233 "required": [234 "in_sample",235 "out_of_sample",236 "params"237 ]238 }239 },240 "bootstrap_sharpe_ci": {241 "type": "array",242 "prefixItems": [243 {244 "type": [245 "number",246 "null"247 ]248 },249 {250 "type": [251 "number",252 "null"253 ]254 }255 ],256 "minItems": 2,257 "maxItems": 2,258 "description": "[lo, hi] of the annualised Sharpe ratio on the test split"259 },260 "bootstrap": {261 "type": "object"262 },263 "parameter_sensitivity": {264 "type": "array",265 "items": {266 "type": "object",267 "required": [268 "params"269 ]270 }271 },272 "trials_tested": {273 "type": "integer",274 "minimum": 1,275 "description": "number of distinct configurations evaluated before this result (N of the deflated Sharpe ratio)"276 },277 "deflated_sharpe": {278 "type": [279 "number",280 "null"281 ],282 "minimum": 0,283 "maximum": 1,284 "description": "Bailey & Lopez de Prado (2014): probability that the true Sharpe ratio is positive given trials_tested trials; null when undefined (e.g. no variance)"285 },286 "deflated_sharpe_detail": {287 "type": "object"288 },289 "monte_carlo": {290 "type": "object"291 },292 "stress_windows": {293 "type": "array",294 "items": {295 "type": "object",296 "required": [297 "window",298 "benchmark_return",299 "strategy_return"300 ]301 }302 },303 "test_evaluations": {304 "type": "integer",305 "minimum": 1,306 "description": "how many times this strategy name was evaluated on a test split according to the ledger"307 }308 }309 },310 "checks": {311 "type": "object",312 "required": [313 "lookahead",314 "data_quality",315 "notes"316 ],317 "properties": {318 "lookahead": {319 "enum": [320 "pass",321 "fail"322 ]323 },324 "data_quality": {325 "enum": [326 "pass",327 "fail"328 ]329 },330 "notes": {331 "type": "array",332 "items": {333 "type": "string"334 }335 },336 "data_quality_detail": {337 "type": "object"338 }339 }340 },341 "equity_curve": {342 "type": "array",343 "minItems": 2,344 "description": "test split, [bar-open time, equity at that bar's close]",345 "items": {346 "type": "array",347 "prefixItems": [348 {349 "type": "string",350 "format": "date-time"351 },352 {353 "type": "number",354 "minimum": 0355 }356 ],357 "minItems": 2,358 "maxItems": 2359 }360 }361 },362 "$defs": {363 "metrics": {364 "type": "object",365 "description": "Metrics of one backtest window. Ratios that are undefined (no variance, no drawdown, no closed lot, no losing lot) are null.",366 "required": [367 "total_return",368 "cagr",369 "volatility",370 "sharpe",371 "sortino",372 "max_drawdown",373 "calmar",374 "turnover",375 "trades",376 "win_rate",377 "profit_factor",378 "fees_paid",379 "exposure",380 "slippage_paid",381 "returns"382 ],383 "properties": {384 "total_return": {385 "type": [386 "number",387 "null"388 ]389 },390 "cagr": {391 "type": [392 "number",393 "null"394 ]395 },396 "volatility": {397 "type": [398 "number",399 "null"400 ],401 "minimum": 0,402 "description": "annualised with the bar interval, 24/7 market"403 },404 "sharpe": {405 "type": [406 "number",407 "null"408 ]409 },410 "sortino": {411 "type": [412 "number",413 "null"414 ]415 },416 "max_drawdown": {417 "type": [418 "number",419 "null"420 ],421 "minimum": 0,422 "maximum": 1,423 "description": "positive fraction: 0.35 means -35 %"424 },425 "calmar": {426 "type": [427 "number",428 "null"429 ]430 },431 "turnover": {432 "type": [433 "number",434 "null"435 ],436 "minimum": 0,437 "description": "traded notional / average equity over the window"438 },439 "turnover_annual": {440 "type": [441 "number",442 "null"443 ],444 "minimum": 0445 },446 "trades": {447 "type": "integer",448 "minimum": 0,449 "description": "number of fills"450 },451 "closed_lots": {452 "type": "integer",453 "minimum": 0454 },455 "win_rate": {456 "type": [457 "number",458 "null"459 ],460 "minimum": 0,461 "maximum": 1462 },463 "profit_factor": {464 "type": [465 "number",466 "null"467 ],468 "minimum": 0469 },470 "fees_paid": {471 "type": [472 "number",473 "null"474 ],475 "minimum": 0476 },477 "slippage_paid": {478 "type": [479 "number",480 "null"481 ],482 "minimum": 0483 },484 "exposure": {485 "type": [486 "number",487 "null"488 ],489 "minimum": 0,490 "maximum": 1.000001,491 "description": "average invested fraction of equity"492 },493 "bars": {494 "type": "integer",495 "minimum": 2496 },497 "final_equity": {498 "type": [499 "number",500 "null"501 ]502 },503 "returns": {504 "type": "object",505 "description": "per-bar return distribution",506 "required": [507 "n"508 ],509 "properties": {510 "n": {511 "type": "integer"512 }513 },514 "additionalProperties": {515 "type": [516 "number",517 "null"518 ]519 }520 }521 },522 "additionalProperties": false523 }524 }525}526