Repository Public
1{2 "$schema": "https://json-schema.org/draft/2020-12/schema",3 "$id": "https://sp-wallet.local/schema/backtest-report-v2.schema.json",4 "title": "SP Wallet V4 backtest report, version 2 (perpetual futures)",5 "description": "Produced by spq.perp_backtest / spq.perp_pipeline (research/). Every key of version 1 (backtest-report.schema.json) is kept with the same meaning; version 2 adds the perpetual-futures metrics, the benchmark perp_long_1x, the exact ledger, the liquidation log, the funding summary and the checks `ledger` and `funding_alignment`. Model and reading guide: docs/v4/quant-perps.md. 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 "schema_version",17 "kind",18 "ledger",19 "liquidations",20 "funding"21 ],22 "properties": {23 "schema_version": {24 "const": 225 },26 "generated_at": {27 "type": "string",28 "format": "date-time"29 },30 "strategy_sha256": {31 "type": "string",32 "pattern": "^[0-9a-f]{64}$"33 },34 "strategy": {35 "type": "object",36 "required": [37 "name",38 "version",39 "meta"40 ],41 "properties": {42 "name": {43 "type": "string"44 },45 "version": {46 "type": "string"47 },48 "meta": {49 "type": "object"50 }51 }52 },53 "config": {54 "type": "object",55 "required": [56 "symbols",57 "interval",58 "start",59 "end",60 "fee_bps",61 "slippage_bps",62 "initial_cash",63 "taker_fee_bps",64 "leverage",65 "max_leverage",66 "max_gross_exposure",67 "maintenance_fraction",68 "liquidation_penalty_bps"69 ],70 "properties": {71 "symbols": {72 "type": "array",73 "items": {74 "type": "string",75 "pattern": "^[A-Za-z0-9]+(\\.SPOT)?$"76 },77 "minItems": 1,78 "uniqueItems": true,79 "description": "perpetuals, and spot legs written <SYMBOL>.SPOT"80 },81 "interval": {82 "type": "string",83 "pattern": "^[0-9]+[mhdw]$"84 },85 "start": {86 "type": "string",87 "format": "date-time",88 "description": "first bar of the data (bar-open time, UTC)"89 },90 "end": {91 "type": "string",92 "format": "date-time",93 "description": "last bar of the data (bar-open time, UTC)"94 },95 "fee_bps": {96 "type": "number",97 "minimum": 098 },99 "slippage_bps": {100 "type": "number",101 "minimum": 0102 },103 "initial_cash": {104 "type": "number",105 "exclusiveMinimum": 0106 },107 "min_order": {108 "type": "number",109 "minimum": 0110 },111 "rebalance_threshold": {112 "type": "number",113 "minimum": 0,114 "maximum": 1115 },116 "decision_every": {117 "type": "integer",118 "minimum": 1119 },120 "params": {121 "type": "object"122 },123 "source": {124 "type": [125 "string",126 "null"127 ]128 },129 "bars": {130 "type": "integer"131 },132 "split_fractions": {133 "type": "array",134 "items": {135 "type": "number"136 },137 "minItems": 3,138 "maxItems": 3139 },140 "venue": {141 "type": [142 "string",143 "null"144 ]145 },146 "taker_fee_bps": {147 "type": "number",148 "minimum": 0149 },150 "maker_fee_bps": {151 "type": "number"152 },153 "liquidity": {154 "enum": [155 "taker",156 "maker"157 ],158 "description": "which fee the fills paid; fee_bps is that fee"159 },160 "leverage": {161 "type": "number",162 "exclusiveMinimum": 0,163 "description": "account leverage setting used for the initial margin (capped per symbol by venue_max_leverage)"164 },165 "max_leverage": {166 "type": "number",167 "exclusiveMinimum": 0,168 "description": "META['max_leverage']: bound of one weight"169 },170 "max_gross_exposure": {171 "type": "number",172 "exclusiveMinimum": 0,173 "description": "META['max_gross_exposure']: bound of the sum of absolute weights"174 },175 "venue_max_leverage": {176 "type": "object",177 "additionalProperties": {178 "type": "number"179 }180 },181 "maintenance_fraction": {182 "type": "object",183 "additionalProperties": {184 "type": "number",185 "minimum": 0,186 "exclusiveMaximum": 1187 }188 },189 "liquidation_penalty_bps": {190 "type": "number",191 "minimum": 0192 },193 "size_decimals": {194 "type": "object",195 "additionalProperties": {196 "type": "integer",197 "minimum": 0198 }199 },200 "spot_fee_bps": {201 "type": "number",202 "minimum": 0203 },204 "spot_slippage_bps": {205 "type": "number",206 "minimum": 0207 },208 "funding_reference_price": {209 "type": "string"210 }211 }212 },213 "splits": {214 "type": "object",215 "required": [216 "train",217 "validation",218 "test"219 ],220 "properties": {221 "train": {222 "type": "array",223 "prefixItems": [224 {225 "type": "string",226 "format": "date-time"227 },228 {229 "type": "string",230 "format": "date-time"231 }232 ],233 "minItems": 2,234 "maxItems": 2,235 "description": "[first bar, last bar] (bar-open times, UTC, both inclusive)"236 },237 "validation": {238 "type": "array",239 "prefixItems": [240 {241 "type": "string",242 "format": "date-time"243 },244 {245 "type": "string",246 "format": "date-time"247 }248 ],249 "minItems": 2,250 "maxItems": 2,251 "description": "[first bar, last bar] (bar-open times, UTC, both inclusive)"252 },253 "test": {254 "type": "array",255 "prefixItems": [256 {257 "type": "string",258 "format": "date-time"259 },260 {261 "type": "string",262 "format": "date-time"263 }264 ],265 "minItems": 2,266 "maxItems": 2,267 "description": "[first bar, last bar] (bar-open times, UTC, both inclusive)"268 }269 },270 "additionalProperties": false271 },272 "metrics": {273 "type": "object",274 "required": [275 "train",276 "validation",277 "test"278 ],279 "properties": {280 "train": {281 "$ref": "#/$defs/perp_metrics"282 },283 "validation": {284 "$ref": "#/$defs/perp_metrics"285 },286 "test": {287 "$ref": "#/$defs/perp_metrics"288 }289 },290 "additionalProperties": false291 },292 "benchmarks": {293 "type": "object",294 "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.",295 "required": [296 "buy_and_hold",297 "perp_long_1x"298 ],299 "additionalProperties": {300 "anyOf": [301 {302 "$ref": "#/$defs/perp_metrics"303 },304 {305 "$ref": "#/$defs/metrics"306 }307 ]308 },309 "properties": {310 "buy_and_hold": {311 "anyOf": [312 {313 "$ref": "#/$defs/perp_metrics"314 },315 {316 "$ref": "#/$defs/metrics"317 }318 ],319 "description": "the same assets bought once on the spot market and held (spot engine and costs); the perpetual price without funding when no spot data were supplied (see checks.notes)"320 },321 "perp_long_1x": {322 "$ref": "#/$defs/perp_metrics",323 "description": "perpetuals bought once at 1x, equal weights, held: price PnL and funding, same costs"324 }325 }326 },327 "robustness": {328 "type": "object",329 "required": [330 "walk_forward",331 "bootstrap_sharpe_ci",332 "parameter_sensitivity",333 "trials_tested",334 "deflated_sharpe"335 ],336 "properties": {337 "walk_forward": {338 "type": "array",339 "items": {340 "type": "object",341 "required": [342 "in_sample",343 "out_of_sample",344 "params"345 ]346 }347 },348 "bootstrap_sharpe_ci": {349 "type": "array",350 "prefixItems": [351 {352 "type": [353 "number",354 "null"355 ]356 },357 {358 "type": [359 "number",360 "null"361 ]362 }363 ],364 "minItems": 2,365 "maxItems": 2,366 "description": "[lo, hi] of the annualised Sharpe ratio on the test split"367 },368 "bootstrap": {369 "type": "object"370 },371 "parameter_sensitivity": {372 "type": "array",373 "items": {374 "type": "object",375 "required": [376 "params"377 ]378 }379 },380 "trials_tested": {381 "type": "integer",382 "minimum": 1,383 "description": "number of distinct configurations evaluated before this result (N of the deflated Sharpe ratio)"384 },385 "deflated_sharpe": {386 "type": [387 "number",388 "null"389 ],390 "minimum": 0,391 "maximum": 1,392 "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)"393 },394 "deflated_sharpe_detail": {395 "type": "object"396 },397 "monte_carlo": {398 "type": "object"399 },400 "stress_windows": {401 "type": "array",402 "items": {403 "type": "object",404 "required": [405 "window",406 "benchmark_return",407 "strategy_return"408 ]409 }410 },411 "test_evaluations": {412 "type": "integer",413 "minimum": 1,414 "description": "how many times this strategy name was evaluated on a test split according to the ledger"415 },416 "funding_stress_windows": {417 "type": "array",418 "description": "strategy vs perp_long_1x in the week-long windows of the test split with the highest and the lowest mean funding",419 "items": {420 "type": "object",421 "required": [422 "regime",423 "window",424 "strategy_return"425 ]426 }427 },428 "benchmarks_by_split": {429 "type": "object",430 "description": "total_return / sharpe / max_drawdown of the benchmarks on train and validation"431 }432 }433 },434 "checks": {435 "type": "object",436 "required": [437 "lookahead",438 "data_quality",439 "notes",440 "ledger",441 "funding_alignment"442 ],443 "properties": {444 "lookahead": {445 "enum": [446 "pass",447 "fail"448 ]449 },450 "data_quality": {451 "enum": [452 "pass",453 "fail"454 ]455 },456 "notes": {457 "type": "array",458 "items": {459 "type": "string"460 }461 },462 "data_quality_detail": {463 "type": "object"464 },465 "ledger": {466 "enum": [467 "pass",468 "fail"469 ],470 "description": "pass when, for every run of the report, final equity = initial + realised PnL + funding - fees - slippage + unrealised PnL + debt written off, exactly, both from the running balance and from the trade and funding logs"471 },472 "funding_alignment": {473 "enum": [474 "pass",475 "fail"476 ],477 "description": "pass when the funding column of every perpetual equals the settlement series re-attached by time (settlement at the bar's open hour, none from the future) and settlement instants are on the hour grid"478 },479 "funding_quality_detail": {480 "type": "object"481 },482 "funding_alignment_detail": {}483 }484 },485 "equity_curve": {486 "type": "array",487 "minItems": 2,488 "description": "test split, [bar-open time, equity at that bar's close]",489 "items": {490 "type": "array",491 "prefixItems": [492 {493 "type": "string",494 "format": "date-time"495 },496 {497 "type": "number",498 "minimum": 0499 }500 ],501 "minItems": 2,502 "maxItems": 2503 }504 },505 "kind": {506 "const": "perp"507 },508 "ledger": {509 "type": "object",510 "required": [511 "train",512 "validation",513 "test"514 ],515 "additionalProperties": {516 "type": "object",517 "description": "exact decimal strings (USDC)",518 "required": [519 "initial",520 "realised_pnl",521 "funding",522 "fees",523 "slippage",524 "unrealised_pnl",525 "bad_debt_written_off",526 "final_equity",527 "reconciles"528 ],529 "properties": {530 "initial": {531 "type": "string",532 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"533 },534 "realised_pnl": {535 "type": "string",536 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"537 },538 "funding": {539 "type": "string",540 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"541 },542 "fees": {543 "type": "string",544 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"545 },546 "liquidation_penalties": {547 "type": "string",548 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"549 },550 "slippage": {551 "type": "string",552 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"553 },554 "unrealised_pnl": {555 "type": "string",556 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"557 },558 "bad_debt_written_off": {559 "type": "string",560 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"561 },562 "collateral": {563 "type": "string",564 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"565 },566 "final_equity": {567 "type": "string",568 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"569 },570 "from_logs": {571 "type": "string",572 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"573 },574 "reconciles": {575 "type": "boolean"576 }577 }578 }579 },580 "liquidations": {581 "type": "object",582 "required": [583 "train",584 "validation",585 "test"586 ],587 "additionalProperties": {588 "type": "array",589 "items": {590 "type": "object",591 "required": [592 "ts",593 "reason",594 "positions"595 ]596 }597 }598 },599 "funding": {600 "type": "object",601 "description": "per split and perpetual: funding rates seen by the run",602 "required": [603 "train",604 "validation",605 "test"606 ]607 }608 },609 "$defs": {610 "metrics": {611 "type": "object",612 "description": "Metrics of one backtest window. Ratios that are undefined (no variance, no drawdown, no closed lot, no losing lot) are null.",613 "required": [614 "total_return",615 "cagr",616 "volatility",617 "sharpe",618 "sortino",619 "max_drawdown",620 "calmar",621 "turnover",622 "trades",623 "win_rate",624 "profit_factor",625 "fees_paid",626 "exposure",627 "slippage_paid",628 "returns"629 ],630 "properties": {631 "total_return": {632 "type": [633 "number",634 "null"635 ]636 },637 "cagr": {638 "type": [639 "number",640 "null"641 ]642 },643 "volatility": {644 "type": [645 "number",646 "null"647 ],648 "minimum": 0,649 "description": "annualised with the bar interval, 24/7 market"650 },651 "sharpe": {652 "type": [653 "number",654 "null"655 ]656 },657 "sortino": {658 "type": [659 "number",660 "null"661 ]662 },663 "max_drawdown": {664 "type": [665 "number",666 "null"667 ],668 "minimum": 0,669 "maximum": 1,670 "description": "positive fraction: 0.35 means -35 %"671 },672 "calmar": {673 "type": [674 "number",675 "null"676 ]677 },678 "turnover": {679 "type": [680 "number",681 "null"682 ],683 "minimum": 0,684 "description": "traded notional / average equity over the window"685 },686 "turnover_annual": {687 "type": [688 "number",689 "null"690 ],691 "minimum": 0692 },693 "trades": {694 "type": "integer",695 "minimum": 0,696 "description": "number of fills"697 },698 "closed_lots": {699 "type": "integer",700 "minimum": 0701 },702 "win_rate": {703 "type": [704 "number",705 "null"706 ],707 "minimum": 0,708 "maximum": 1709 },710 "profit_factor": {711 "type": [712 "number",713 "null"714 ],715 "minimum": 0716 },717 "fees_paid": {718 "type": [719 "number",720 "null"721 ],722 "minimum": 0723 },724 "slippage_paid": {725 "type": [726 "number",727 "null"728 ],729 "minimum": 0730 },731 "exposure": {732 "type": [733 "number",734 "null"735 ],736 "minimum": 0,737 "maximum": 1.000001,738 "description": "average invested fraction of equity"739 },740 "bars": {741 "type": "integer",742 "minimum": 2743 },744 "final_equity": {745 "type": [746 "number",747 "null"748 ]749 },750 "returns": {751 "type": "object",752 "description": "per-bar return distribution",753 "required": [754 "n"755 ],756 "properties": {757 "n": {758 "type": "integer"759 }760 },761 "additionalProperties": {762 "type": [763 "number",764 "null"765 ]766 }767 }768 },769 "additionalProperties": false770 },771 "perp_metrics": {772 "type": "object",773 "description": "Metrics of one backtest window. Ratios that are undefined (no variance, no drawdown, no closed lot, no losing lot) are null.",774 "required": [775 "total_return",776 "cagr",777 "volatility",778 "sharpe",779 "sortino",780 "max_drawdown",781 "calmar",782 "turnover",783 "trades",784 "win_rate",785 "profit_factor",786 "fees_paid",787 "exposure",788 "slippage_paid",789 "returns",790 "realised_pnl",791 "funding_pnl",792 "liquidation_penalties",793 "long_exposure",794 "short_exposure",795 "gross_leverage_mean",796 "gross_leverage_max",797 "net_leverage_mean",798 "net_leverage_max",799 "net_leverage_min",800 "time_in_market",801 "avg_holding_bars",802 "avg_holding_hours",803 "liquidations",804 "wiped_out",805 "margin_utilisation_mean",806 "margin_utilisation_max",807 "worst_intrabar_drawdown",808 "orders_cut",809 "orders_refused",810 "funding_payments",811 "funding_missing"812 ],813 "properties": {814 "total_return": {815 "type": [816 "number",817 "null"818 ]819 },820 "cagr": {821 "type": [822 "number",823 "null"824 ]825 },826 "volatility": {827 "type": [828 "number",829 "null"830 ],831 "minimum": 0,832 "description": "annualised with the bar interval, 24/7 market"833 },834 "sharpe": {835 "type": [836 "number",837 "null"838 ]839 },840 "sortino": {841 "type": [842 "number",843 "null"844 ]845 },846 "max_drawdown": {847 "type": [848 "number",849 "null"850 ],851 "minimum": 0,852 "maximum": 1,853 "description": "positive fraction: 0.35 means -35 %"854 },855 "calmar": {856 "type": [857 "number",858 "null"859 ]860 },861 "turnover": {862 "type": [863 "number",864 "null"865 ],866 "minimum": 0,867 "description": "traded notional / average equity over the window"868 },869 "turnover_annual": {870 "type": [871 "number",872 "null"873 ],874 "minimum": 0875 },876 "trades": {877 "type": "integer",878 "minimum": 0,879 "description": "number of fills"880 },881 "closed_lots": {882 "type": "integer",883 "minimum": 0884 },885 "win_rate": {886 "type": [887 "number",888 "null"889 ],890 "minimum": 0,891 "maximum": 1892 },893 "profit_factor": {894 "type": [895 "number",896 "null"897 ],898 "minimum": 0899 },900 "fees_paid": {901 "type": [902 "number",903 "null"904 ],905 "minimum": 0,906 "description": "USD, trading fees + liquidation penalties"907 },908 "slippage_paid": {909 "type": [910 "number",911 "null"912 ],913 "minimum": 0914 },915 "exposure": {916 "type": [917 "number",918 "null"919 ],920 "minimum": 0,921 "description": "mean gross exposure / equity (for a long-only spot run: the invested fraction)"922 },923 "bars": {924 "type": "integer",925 "minimum": 2926 },927 "final_equity": {928 "type": [929 "number",930 "null"931 ]932 },933 "returns": {934 "type": "object",935 "description": "per-bar return distribution",936 "required": [937 "n"938 ],939 "properties": {940 "n": {941 "type": "integer"942 }943 },944 "additionalProperties": {945 "type": [946 "number",947 "null"948 ]949 }950 },951 "realised_pnl": {952 "type": [953 "number",954 "null"955 ],956 "description": "USD, sum of the realised PnL of closing fills (fees excluded)"957 },958 "funding_pnl": {959 "type": [960 "number",961 "null"962 ],963 "description": "USD, sum of funding payments; negative = paid"964 },965 "liquidation_penalties": {966 "type": [967 "number",968 "null"969 ],970 "description": "USD, part of fees_paid"971 },972 "long_exposure": {973 "type": [974 "number",975 "null"976 ],977 "description": "mean long notional / equity"978 },979 "short_exposure": {980 "type": [981 "number",982 "null"983 ],984 "description": "mean short notional / equity (positive)"985 },986 "gross_leverage_mean": {987 "type": [988 "number",989 "null"990 ],991 "description": "mean (long + short notional) / equity"992 },993 "gross_leverage_max": {994 "type": [995 "number",996 "null"997 ],998 "description": "maximum of the same at bar closes"999 },1000 "net_leverage_mean": {1001 "type": [1002 "number",1003 "null"1004 ],1005 "description": "mean (long - short notional) / equity"1006 },1007 "net_leverage_max": {1008 "type": [1009 "number",1010 "null"1011 ],1012 "description": "largest net long"1013 },1014 "net_leverage_min": {1015 "type": [1016 "number",1017 "null"1018 ],1019 "description": "largest net short (negative)"1020 },1021 "time_in_market": {1022 "type": [1023 "number",1024 "null"1025 ],1026 "description": "share of bars ending with a position"1027 },1028 "avg_holding_bars": {1029 "type": [1030 "number",1031 "null"1032 ],1033 "description": "mean run of bars with a same-sign position in one symbol"1034 },1035 "avg_holding_hours": {1036 "type": [1037 "number",1038 "null"1039 ],1040 "description": "same in hours"1041 },1042 "liquidations": {1043 "type": "integer",1044 "minimum": 0,1045 "description": "account liquidations (each closes every perpetual position)"1046 },1047 "wiped_out": {1048 "type": "boolean",1049 "description": "equity reached zero; the run stopped there"1050 },1051 "margin_utilisation_mean": {1052 "type": [1053 "number",1054 "null"1055 ],1056 "description": "initial margin in use / margin equity, bars with a perpetual position"1057 },1058 "margin_utilisation_max": {1059 "type": [1060 "number",1061 "null"1062 ],1063 "description": "maximum of the same"1064 },1065 "worst_intrabar_drawdown": {1066 "type": [1067 "number",1068 "null"1069 ],1070 "description": "positive fraction; uses each bar's adverse extreme against the previous closing peak"1071 },1072 "orders_cut": {1073 "type": "integer",1074 "minimum": 01075 },1076 "orders_refused": {1077 "type": "integer",1078 "minimum": 01079 },1080 "funding_payments": {1081 "type": "integer",1082 "minimum": 01083 },1084 "funding_missing": {1085 "type": "integer",1086 "minimum": 01087 }1088 },1089 "additionalProperties": false1090 }1091 }1092}1093