{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/equity-rank-com/json-schema/equity-rank-com-app-routers-methodology-backtest-summary-response-schema.json",
  "title": "BacktestSummaryResponse",
  "description": "Response model for GET /api/methodology/backtest-summary.\n\nTopline hit-rate fields are permanently null (see _WITHHELD_DATA_SOURCE\nnote above) — kept in the model so existing consumers deserialize\ncleanly; every consumer must treat null as \"withheld\", not zero.",
  "x-generated": "2026-11-07",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/equity-rank-com-openapi.yml#/components/schemas/app__routers__methodology__BacktestSummaryResponse",
  "properties": {
    "combined_hit_rate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Combined Hit Rate"
    },
    "combined_r2": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Combined R2"
    },
    "save_hit_rate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Save Hit Rate"
    },
    "innovation_hit_rate": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Innovation Hit Rate"
    },
    "signal_correlation": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "title": "Signal Correlation"
    },
    "n_simulations": {
      "type": "integer",
      "title": "N Simulations",
      "default": 0
    },
    "data_source": {
      "type": "string",
      "title": "Data Source",
      "default": "withheld_uninformative_metric"
    },
    "real_data_since": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Real Data Since"
    },
    "real_n": {
      "type": "integer",
      "title": "Real N",
      "default": 0
    },
    "live_factor_ic": {
      "items": {
        "$ref": "#/$defs/LiveFactorIC"
      },
      "type": "array",
      "title": "Live Factor Ic",
      "default": []
    },
    "live_ic_as_of": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Live Ic As Of"
    },
    "ic_series": {
      "items": {
        "$ref": "#/$defs/FactorICSeries"
      },
      "type": "array",
      "title": "Ic Series",
      "default": []
    },
    "realized_outcomes": {
      "items": {
        "$ref": "#/$defs/RealizedOutcomeBucket"
      },
      "type": "array",
      "title": "Realized Outcomes",
      "default": []
    },
    "realized_outcomes_total_n": {
      "type": "integer",
      "title": "Realized Outcomes Total N",
      "default": 0
    },
    "realized_outcomes_as_of": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Realized Outcomes As Of"
    }
  },
  "type": "object",
  "$defs": {
    "FactorICSeries": {
      "properties": {
        "factor_name": {
          "type": "string",
          "title": "Factor Name"
        },
        "horizon_days": {
          "type": "integer",
          "title": "Horizon Days"
        },
        "points": {
          "items": {
            "$ref": "#/$defs/FactorICSeriesPoint"
          },
          "type": "array",
          "title": "Points"
        }
      },
      "type": "object",
      "required": [
        "factor_name",
        "horizon_days",
        "points"
      ],
      "title": "FactorICSeries",
      "description": "Time series of rolling-20d IC for one factor at one horizon (B5).\n\nPowers the per-factor sparklines on /methodology. Points ascend by date and\nare capped server-side (~90) — this is a trend glyph, not an export feed."
    },
    "FactorICSeriesPoint": {
      "properties": {
        "date": {
          "type": "string",
          "title": "Date"
        },
        "ic": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Ic"
        }
      },
      "type": "object",
      "required": [
        "date",
        "ic"
      ],
      "title": "FactorICSeriesPoint",
      "description": "One dated rolling-IC reading for the sparkline series (B5)."
    },
    "LiveFactorIC": {
      "properties": {
        "factor_name": {
          "type": "string",
          "title": "Factor Name"
        },
        "horizon_days": {
          "type": "integer",
          "title": "Horizon Days"
        },
        "ic_rolling_20d": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Ic Rolling 20D"
        },
        "as_of": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "As Of"
        },
        "mean_ic": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Mean Ic"
        },
        "t_hac": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "T Hac"
        },
        "eff_n": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "title": "Eff N"
        },
        "significant": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Significant"
        },
        "passes_rule": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Passes Rule"
        },
        "adequate_eff_n": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "title": "Adequate Eff N"
        }
      },
      "type": "object",
      "required": [
        "factor_name",
        "horizon_days",
        "ic_rolling_20d",
        "as_of"
      ],
      "title": "LiveFactorIC",
      "description": "One live information-coefficient cell from factor_ic_history.\n\n2026-08-03 (P1-8): carries the overlap-corrected significance verdict\nalongside the legacy rolling mean, so no consumer can over-read a raw\nrolling IC again. `passes_rule` is the Newey-West HAC + BH-FDR +\neffective-N decision from factor_ic_significance; `adequate_eff_n` is\nFalse when the cell's overlap-corrected sample is still too thin to\ndecide anything."
    },
    "RealizedOutcomeBucket": {
      "properties": {
        "signal_source": {
          "type": "string",
          "title": "Signal Source"
        },
        "horizon_days": {
          "type": "integer",
          "title": "Horizon Days"
        },
        "n": {
          "type": "integer",
          "title": "N"
        },
        "hits": {
          "type": "integer",
          "title": "Hits"
        },
        "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"
        },
        "ticker_count": {
          "type": "integer",
          "title": "Ticker Count",
          "default": 0
        },
        "sufficient": {
          "type": "boolean",
          "title": "Sufficient",
          "default": true
        },
        "first_prediction": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "First Prediction"
        },
        "last_evaluated": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Last Evaluated"
        }
      },
      "type": "object",
      "required": [
        "signal_source",
        "horizon_days",
        "n",
        "hits",
        "first_prediction",
        "last_evaluated"
      ],
      "title": "RealizedOutcomeBucket",
      "description": "Aggregated realized directional outcomes for one (signal, horizon) (B5).\n\nComputed at request time from signal_outcomes.hit_flag (the outcome\nrecorder's own direction-agreement flag), never from the stale\nmethodology_stats keys. Buckets publish only at n >= 50 by default;\npublished verbatim — including readings below coin-flip.\n\n`ticker_count` / `sufficient` (AWL-08, Phase 89-14): ticker_count is the\ndistinct-symbol count within the bucket (a bare hit-rate percentage over\na handful of tickers is exactly what the track-record surface must never\npresent bare). `sufficient` is False only when the caller opts in via\n`include_insufficient=True` (the track-record surface) — the default\n/backtest-summary caller never sees a sub-threshold bucket, unchanged.\n\n2026-08-03 (P0-4): `hit_rate` is now ALWAYS None. The verification pack\nproved it identical to the drift base rate at every horizon — zero\ninformation. Published instead: `base_rate` (share of positive realized\nmoves in the bucket — explicitly labeled market drift, so readers see WHY\nthe raw rate is uninformative) and `excess_hit_rate` (mean of\nsignal_outcomes.excess_hit_flag: direction agreement measured against the\nsame-horizon universe median move, the drift-corrected metric), which is\nNone until enough excess evaluations accumulate."
    }
  }
}