{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/equity-rank-com/json-schema/equity-rank-com-track-record-response-schema.json",
  "title": "TrackRecordResponse",
  "description": "Response model for GET /api/backtest/track-record.\n\n`total_realized_outcomes` / `total_ticker_count` are the aggregate live\ncounts (across every evaluated signal_outcomes row, not just the\npublished buckets) — the exact numbers the UI-SPEC's mandatory\ninsufficient-data copy substitutes into \"{n} realized outcomes across\n{ticker_count} tickers\". Kept as explicit numeric fields (not just prose\ninside `limitations`) so the frontend never has to parse text to render\nthe mandated string.",
  "x-generated": "2026-11-10",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/equity-rank-com-backtest-api-openapi.yml#/components/schemas/TrackRecordResponse",
  "properties": {
    "live_tracking_started": {
      "type": "string",
      "title": "Live Tracking Started"
    },
    "hit_rates": {
      "items": {
        "$ref": "#/$defs/TrackRecordHitRate"
      },
      "type": "array",
      "title": "Hit Rates"
    },
    "backtest": {
      "$ref": "#/$defs/TrackRecordBacktestBlock"
    },
    "limitations": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Limitations"
    },
    "total_realized_outcomes": {
      "type": "integer",
      "title": "Total Realized Outcomes",
      "default": 0
    },
    "total_ticker_count": {
      "type": "integer",
      "title": "Total Ticker Count",
      "default": 0
    },
    "degraded": {
      "type": "boolean",
      "title": "Degraded",
      "default": false
    }
  },
  "type": "object",
  "required": [
    "live_tracking_started",
    "backtest"
  ],
  "$defs": {
    "AbstainedStep": {
      "properties": {
        "entry_date": {
          "type": "string",
          "title": "Entry Date"
        },
        "exit_date": {
          "type": "string",
          "title": "Exit Date"
        },
        "reason": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Reason"
        },
        "entry_universe": {
          "type": "integer",
          "title": "Entry Universe",
          "default": 0
        },
        "exit_universe": {
          "type": "integer",
          "title": "Exit Universe",
          "default": 0
        },
        "basket_size": {
          "type": "integer",
          "title": "Basket Size",
          "default": 0
        },
        "priced": {
          "type": "integer",
          "title": "Priced",
          "default": 0
        }
      },
      "type": "object",
      "required": [
        "entry_date",
        "exit_date"
      ],
      "title": "AbstainedStep",
      "description": "One rebalance step left out of every curve and figure (MDD-01a/b)."
    },
    "BacktestComplianceBlock": {
      "properties": {
        "framing": {
          "type": "string",
          "title": "Framing",
          "default": "Historical model performance under stated assumptions. Not a forecast, recommendation, or guarantee of future returns."
        },
        "look_ahead_controls": {
          "type": "string",
          "title": "Look Ahead Controls",
          "default": "At each rebalance date the strategy uses ONLY data already in valuation_history as of that date. No forward-looking model values used to make past selections."
        },
        "live_history_disclaimer": {
          "type": "string",
          "title": "Live History Disclaimer",
          "default": "Model live since March 2026. Track record covers a limited period. Historical output does not predict future returns."
        },
        "survivorship_bias_note": {
          "type": "string",
          "title": "Survivorship Bias Note",
          "default": "IMPORTANT — Survivorship bias: this backtest selects from stocks currently in the Equity Rank universe, which excludes companies that were delisted, acquired, or went bankrupt during the historical period. This inflates displayed results by an amount that varies with the window and cannot currently be stated precisely, but which can be large relative to the figures shown — prior internal point-in-time work on an earlier window estimated the effect in the tens of percentage points. A point-in-time universe reconstruction is planned to bound it for the current window. Past model performance is not indicative of future results; provided for informational purposes only."
        }
      },
      "type": "object",
      "title": "BacktestComplianceBlock",
      "description": "Compliance copy surfaced to the frontend (BACK-05).\n\nAll language locks to \"historical model performance\" framing and\nexplicitly negates \"forecast\" / \"recommendation\" / \"guarantee\"."
    },
    "BacktestMetricsBlock": {
      "properties": {
        "cagr": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Cagr"
        },
        "sharpe": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Sharpe"
        },
        "max_drawdown": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Max Drawdown"
        },
        "total_return": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Total Return"
        },
        "alpha_vs_spy": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Alpha Vs Spy"
        },
        "alpha_vs_qqq": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Alpha Vs Qqq"
        }
      },
      "type": "object",
      "title": "BacktestMetricsBlock",
      "description": "Headline metrics — None when sufficient_data=False.\n\nP0-3: cagr / sharpe / alpha_vs_spy are additionally None until\nn_rebalances >= 24 (annualizing a ~4-month window is statistically\nmeaningless — df=3). MDD-01 (2026-10-09): max_drawdown is gated the same\nway (a sample maximum is most biased on a short panel); the response's\n`max_drawdown_suppressed` says so. `alpha_vs_spy` is a CAGR DIFFERENCE, not\nrisk-adjusted alpha; surfaces must label it \"CAGR gap vs SPY\"."
    },
    "BenchmarkBlock": {
      "properties": {
        "total_return": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Total Return"
        },
        "max_drawdown": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Max Drawdown"
        }
      },
      "type": "object",
      "title": "BenchmarkBlock",
      "description": "Per-benchmark figures over the SAME measured steps as the strategy.\n\nMDD-01e (2026-10-09): `max_drawdown` is published exactly when the\nstrategy's is (n_rebalances >= 24 measured steps) so the strategy drawdown\nis never shown without its comparator; None otherwise, and None for SPY\nwhen the feed degraded."
    },
    "CoverageBlock": {
      "properties": {
        "min_snapshot_universe": {
          "type": "integer",
          "title": "Min Snapshot Universe",
          "default": 0
        },
        "min_priced_fraction": {
          "type": "number",
          "title": "Min Priced Fraction",
          "default": 0.0
        },
        "min_basket_names": {
          "type": "integer",
          "title": "Min Basket Names",
          "default": 0
        },
        "steps_attempted": {
          "type": "integer",
          "title": "Steps Attempted",
          "default": 0
        },
        "steps_measured": {
          "type": "integer",
          "title": "Steps Measured",
          "default": 0
        },
        "steps_abstained": {
          "type": "integer",
          "title": "Steps Abstained",
          "default": 0
        },
        "measured_steps_contiguous": {
          "type": "boolean",
          "title": "Measured Steps Contiguous",
          "default": true
        },
        "abstained": {
          "items": {
            "$ref": "#/$defs/AbstainedStep"
          },
          "type": "array",
          "title": "Abstained"
        }
      },
      "type": "object",
      "title": "CoverageBlock",
      "description": "MDD-01 (2026-10-09): per-step coverage floor and what it removed.\n\nA step is measured only when both of its monthly snapshots hold at least\n`min_snapshot_universe` distinct scored symbols AND at least\nmax(`min_basket_names`, `min_priced_fraction` x basket) of the basket is\npriced at both ends. Any other step abstains: it is absent from the curves\nand from every figure (never published as a flat 0.00% month) and is\nlisted in `abstained`. `measured_steps_contiguous` is False when an\nabstained step sits BETWEEN measured ones, i.e. the curve joins\nnon-adjacent periods."
    },
    "TrackRecordBacktestBlock": {
      "properties": {
        "sufficient_data": {
          "type": "boolean",
          "title": "Sufficient Data"
        },
        "real_data_since": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Real Data Since"
        },
        "period_start": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Period Start"
        },
        "period_end": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Period End"
        },
        "n_rebalances": {
          "type": "integer",
          "title": "N Rebalances",
          "default": 0
        },
        "max_drawdown_suppressed": {
          "type": "boolean",
          "title": "Max Drawdown Suppressed",
          "default": false
        },
        "coverage": {
          "$ref": "#/$defs/CoverageBlock"
        },
        "strategy": {
          "$ref": "#/$defs/BacktestMetricsBlock"
        },
        "spy": {
          "$ref": "#/$defs/BenchmarkBlock"
        },
        "qqq": {
          "$ref": "#/$defs/BenchmarkBlock"
        },
        "validation": {
          "$ref": "#/$defs/ValidationGateBlock"
        },
        "caveats": {
          "$ref": "#/$defs/BacktestComplianceBlock"
        }
      },
      "type": "object",
      "required": [
        "sufficient_data"
      ],
      "title": "TrackRecordBacktestBlock",
      "description": "The existing /backtest simulation, passed through with its\ncompliance caveats verbatim (byte-identical to what /summary serves)."
    },
    "TrackRecordHitRate": {
      "properties": {
        "signal": {
          "type": "string",
          "title": "Signal"
        },
        "horizon_days": {
          "type": "integer",
          "title": "Horizon Days"
        },
        "n": {
          "type": "integer",
          "title": "N"
        },
        "ticker_count": {
          "type": "integer",
          "title": "Ticker Count"
        },
        "window_start": {
          "type": "string",
          "title": "Window Start"
        },
        "window_end": {
          "type": "string",
          "title": "Window End"
        },
        "hit_rate": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Hit Rate"
        },
        "base_rate": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Base Rate"
        },
        "excess_hit_rate": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Excess Hit Rate"
        },
        "excess_n": {
          "type": "integer",
          "title": "Excess N",
          "default": 0
        },
        "excess_ci_low": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Excess Ci Low"
        },
        "excess_ci_high": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Excess Ci High"
        },
        "sufficient": {
          "type": "boolean",
          "title": "Sufficient"
        }
      },
      "type": "object",
      "required": [
        "signal",
        "horizon_days",
        "n",
        "ticker_count",
        "window_start",
        "window_end",
        "sufficient"
      ],
      "title": "TrackRecordHitRate",
      "description": "One realized-outcome bucket, always carrying its full sample context.\n\n`hit_rate` is None (never a synthetic or borrowed number) whenever\n`sufficient` is False — i.e. the bucket has fewer than the honest\nminimum (`_OUTCOME_BUCKET_MIN_N`) evaluated outcomes."
    },
    "ValidationGateBlock": {
      "properties": {
        "dsr": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Dsr"
        },
        "pbo": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Pbo"
        },
        "pbo_applicable": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Pbo Applicable"
        },
        "pbo_n_candidates": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Pbo N Candidates"
        },
        "passes_gates": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Passes Gates"
        },
        "validation_status": {
          "type": "string",
          "title": "Validation Status",
          "default": "not_evaluated"
        }
      },
      "type": "object",
      "title": "ValidationGateBlock",
      "description": "D3 / VALGATE hard gates (item 37, .planning/PRICE-PREDICTION-TIMING-PLAN-2026-09-03.md).\n\nDeflated Sharpe Ratio + Probability of Backtest Overfitting (Bailey &\nLópez de Prado 2014 — computed once in\napp/services/factor_ic_significance.py and never re-derived here) applied to\nthe persisted strategy result. `dsr`/`pbo`/`passes_gates` are all None and\n`validation_status` stays 'not_evaluated' until the annualized Sharpe itself\nis populated (fewer than MIN_REBALANCES_FOR_ANNUALIZED periods — nothing to\ndeflate yet). `passes_gates` is true only when dsr >= 0.95 AND, where PBO is\napplicable, pbo < 0.35; a False or None value here means this result must\nNOT be read as validated, regardless of how the headline metrics look.\n\n`pbo_applicable` (2026-10-08): PBO measures selection among >= 2 candidate\nconfigurations. This strategy is one fixed configuration, so `pbo` is null\nand `pbo_applicable` is false: no overfitting-of-selection test was run,\nand the gate rests on the DSR alone. It is never published as 0.0.\n`pbo_n_candidates` is the number of configurations compared.\n\n`dsr` IS A PROBABILITY IN [0, 1] AS OF 2026-10-07, NOT A RATIO. It estimates\nP(true Sharpe > the Sharpe the best of n_trials null configurations would be\nexpected to show by luck), so 0.95 is a one-sided 95% confidence level. The\nfield previously carried a ratio-valued statistic whose documented 1.5\nthreshold was unreachable at any trial count of 4 or more (DSR-01); it was\nnever non-null on this surface, because the live result has had fewer than\nMIN_REBALANCES_FOR_ANNUALIZED rebalances throughout, so no published value\nchanged. Both fields stay None rather than 0.0 so \"not measurable\" can never\nread as \"measured\"."
    }
  }
}