Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions .claude/commit_acceptors/research-systemic-risk-kuramoto.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Diff-bound commit acceptor for the systemic-risk Kuramoto research module.
#
# Adds research/systemic_risk/ — a pre-registered falsification battery
# for the hypothesis that interbank phase-locking precedes banking-crisis
# events. The module composes existing core.kuramoto primitives; it does
# not introduce new physics. Layer 2 (Sustainer) in the maintenance
# hierarchy: emits a diagnostic score, never takes execution action.
#
# Decision rule (frozen pre-registration):
# HARD_FAIL : ∃ crisis with AUC ≤ 0.55
# HARD_PASS : ≥2 crises with AUC ≥ 0.70 AND BH-corrected p ≤ 0.01
# UNDECIDED : neither (collect more data, do not promote tier)
#
# Locally verified:
# * pytest tests/research/systemic_risk/: 57/57 passed
# * mypy --strict on every new file: clean
# * ruff + black: clean
# * Lower-rail dry-run (random phases): HARD_FAIL, AUC=0.455 (correct)
# * Upper-rail dry-run (injected +2σ pre-event signal): HARD_PASS,
# AUC=0.92, p_BH=5e-4 across 2 crises (correct)
# * No real interbank exposure data shipped; topology loader expects
# user-supplied parquet/CSV.

id: research-systemic-risk-kuramoto
status: ACTIVE
# claim_type=governance: this PR is primarily governance over a falsifiable
# research hypothesis — pre-registered decision rule, frozen thresholds,
# CLAIMS.md ledger entry, fail-closed verdict. The research code itself is
# composition over the existing core.kuramoto stack.
claim_type: governance
promise: >-
After this PR lands, research/systemic_risk/ ships a pre-registered
falsification battery for C-SYSRISK-PHASE (HYPOTHESIS tier in
CLAIMS.md). The battery composes core.kuramoto primitives, an event
ledger sourced from Laeven & Valencia 2018 plus post-LV2020 anchor
events, an exposure-matrix → adjacency adapter with a self-contained
Barabási-Albert null, and a Mann-Whitney AUC + permutation p +
Benjamini-Hochberg FDR pipeline. The decision rule (HARD_FAIL ≤ 0.55,
HARD_PASS ≥ 0.70 AND p_BH ≤ 0.01 on ≥2 crises) is encoded once and
frozen — any subsequent change to thresholds is a new pre-registration
on a fresh branch. End-to-end rails are verified: random phases →
HARD_FAIL (AUC≈0.45), injected pre-event signal → HARD_PASS (AUC≈0.92,
p_BH=5e-4). Tier promotion to MEASURED requires the battery to return
HARD_PASS on ≥2 independent crises with real user-supplied interbank
exposure data.
diff_scope:
changed_files:
- path: ".claude/commit_acceptors/research-systemic-risk-kuramoto.yaml"
- path: "CLAIMS.md"
- path: "research/systemic_risk/README.md"
- path: "research/systemic_risk/__init__.py"
- path: "research/systemic_risk/early_warning.py"
- path: "research/systemic_risk/event_ledger.py"
- path: "research/systemic_risk/falsification.py"
- path: "research/systemic_risk/phase_extraction.py"
- path: "research/systemic_risk/topology.py"
- path: "tests/research/systemic_risk/__init__.py"
- path: "tests/research/systemic_risk/test_early_warning.py"
- path: "tests/research/systemic_risk/test_event_ledger.py"
- path: "tests/research/systemic_risk/test_falsification.py"
- path: "tests/research/systemic_risk/test_topology.py"
forbidden_paths:
- "trading/"
- "execution/"
- "forecast/"
- "policy/"
- "core/physics/"
- "core/kuramoto/"
- "application/governance/claim_ledger.py"
- "application/governance/commit_acceptor.py"
- "scripts/ci/check_claims.py"
- "tools/commit_acceptor/validate_commit_acceptor.py"
required_python_symbols:
- "research/systemic_risk/event_ledger.py::BankingCrisisLedger"
- "research/systemic_risk/event_ledger.py::DEFAULT_LEDGER"
- "research/systemic_risk/topology.py::InterbankTopology"
- "research/systemic_risk/topology.py::barabasi_albert_null"
- "research/systemic_risk/early_warning.py::compute_early_warning"
- "research/systemic_risk/early_warning.py::kuramoto_order_parameter"
- "research/systemic_risk/falsification.py::run_falsification"
- "research/systemic_risk/falsification.py::auc_mann_whitney"
- "research/systemic_risk/falsification.py::benjamini_hochberg"
expected_signal: >-
Local: `python -m pytest tests/research/systemic_risk/ -q` reports
"57 passed"; `mypy --strict` is clean on every file under
research/systemic_risk/ and tests/research/systemic_risk/;
`ruff check` and `black --check` both pass on those paths;
the lower-rail dry-run on random phases returns
verdict=HARD_FAIL with AUC ≈ 0.45 against the USA filter, while
the upper-rail dry-run with an injected +2σ pre-event signal
returns verdict=HARD_PASS with AUC ≈ 0.92 and p_BH ≈ 5e-4 on
≥2 USA crises.
measurement_command: >-
bash -c 'mypy --strict
research/systemic_risk/event_ledger.py
research/systemic_risk/topology.py
research/systemic_risk/phase_extraction.py
research/systemic_risk/early_warning.py
research/systemic_risk/falsification.py
research/systemic_risk/__init__.py
tests/research/systemic_risk/
&& ruff check research/systemic_risk/ tests/research/systemic_risk/
&& black --check research/systemic_risk/ tests/research/systemic_risk/
&& python -m pytest tests/research/systemic_risk/ -q'
signal_artifact: "tmp/research_systemic_risk_kuramoto.log"
falsifier:
command: >-
bash -c 'python -m pytest tests/research/systemic_risk/test_falsification.py::TestRunFalsificationSanity::test_random_scores_do_not_pass
-q >/tmp/_sysrisk_null.log 2>&1
&& ! grep -q "1 passed" /tmp/_sysrisk_null.log'
description: >-
Probes the null-rail invariant of the falsification battery:
on i.i.d. random scores against the synthetic ledger,
`run_falsification` must never return HARD_PASS. The protected
test asserts this. The falsifier inverts: it succeeds (exit 0)
only when the protected null-rail test did NOT pass, which
would mean the battery has a leak that systematically inflates
AUC under the null.
rollback_command: >-
git checkout HEAD~1 --
CLAIMS.md
&& rm -rf research/systemic_risk/ tests/research/systemic_risk/
.claude/commit_acceptors/research-systemic-risk-kuramoto.yaml
rollback_verification_command: >-
bash -c 'test ! -d research/systemic_risk
&& test ! -d tests/research/systemic_risk
&& test ! -f .claude/commit_acceptors/research-systemic-risk-kuramoto.yaml'
memory_update_type: append
ledger_path: ".claude/commit_acceptors/research-systemic-risk-kuramoto.yaml"
report_path: "tmp/research_systemic_risk_kuramoto.log"
evidence: []
1 change: 1 addition & 0 deletions CLAIMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
| C-INV-COUNT | "87 invariants in `.claude/physics/INVARIANTS.yaml`" | `FACT` | `python scripts/count_invariants.py` | 2026-04-30 |
| C-PHYS-KERNEL | "Physics-inspired research platform with partially machine-checkable invariant layer" | `MEASURED` | `physics-kernel-gate.yml`, `BASELINE.md` | 2026-04-30 |
| C-TLA-PROOF | "Four-barrier admission gate model-checked in TLA⁺ with 3 invariants" | `FACT` | `formal/tla/AdmissionGate.tla`, `formal-verification.yml` | 2026-04-30 |
| C-SYSRISK-PHASE | "Interbank phase-locking precedes banking-crisis events" | `HYPOTHESIS` | `research/systemic_risk/falsification.py` (pre-registered AUC + permutation p + BH FDR battery, `HARD_PASS` requires AUC≥0.70 + p_BH≤0.01 on ≥2 crises); `research/systemic_risk/README.md` | 2026-05-08 |

## Retired claims (pending re-validation under tier rules)

Expand Down
98 changes: 98 additions & 0 deletions research/systemic_risk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Systemic Risk as Phase Transition — Kuramoto on Interbank Networks

> **Tier (per `CLAIMS.md`):** `HYPOTHESIS` until the falsification battery
> in `falsification.py` returns `HARD_PASS` on ≥ 2 independent crises.

## Hypothesis (pre-registered)

The interbank market is a network of phase oscillators coupled through
exposure-weighted lending. Approach to the Kuramoto bifurcation
:math:`K_c` manifests as elevated rolling order parameter `R(t)`,
positive slope of `R(t)`, and growing variance of `R(t)` (canonical
critical-slowing-down diagnostics, Scheffer et al. 2009, *Nature*
**461**: 53). The composite early-warning score

```
score(t) = R̄(t) · |slope(R)(t)| · √var(R)(t)
```

is *elevated* in pre-event windows preceding banking-crisis dates
relative to null windows drawn from the same series at safe distance
from any event.

## Modules

| File | Role |
|------|------|
| `event_ledger.py` | `BankingCrisisLedger` from Laeven & Valencia 2018 + 2023 anchor events |
| `topology.py` | Empirical exposure → adjacency adapter + Barabási-Albert null |
| `phase_extraction.py` | Interbank-rate spread → :math:`\theta_i(t)` via Hilbert (wraps `core.kuramoto.phase_extractor`) |
| `early_warning.py` | Rolling-window CSD predictor (level + slope + variance composite) |
| `falsification.py` | Pre-registered AUC + permutation p + Benjamini-Hochberg battery |

## Falsification protocol (pre-registered, frozen)

For each crisis *c* in the ledger with at least one valid pre-event window:

1. Extract pre-event score values from the `pre_event_window_days`-long
window ending one day before `c.start`.
2. Sample `null_window_count` non-overlapping null windows of the same
length, each ≥ `min_distance_from_event_days` from any event.
3. Compute :math:`AUC_c` (Mann-Whitney U) and one-sided permutation p
with `n_permutations` resamples.
4. Apply Benjamini-Hochberg FDR correction across all crises that
yielded valid windows.

### Decision rule

| Verdict | Condition |
|---------|-----------|
| `HARD_FAIL` | ∃ c with :math:`AUC_c \le` `fail_auc` (default 0.55) |
| `HARD_PASS` | ≥ 2 crises with :math:`AUC_c \ge` `pass_auc` AND :math:`p^{BH}_c \le` `pass_alpha` (defaults 0.70, 0.01) |
| `UNDECIDED` | neither — collect more data, do not promote tier |

The thresholds are written *once* and never edited after seeing data.
Any change to thresholds is a new pre-registration on a fresh branch.

## Dataset manifest

| Source | Status | Notes |
|--------|--------|-------|
| Laeven-Valencia 2018 (IMF WP/18/206) | inline | systemic banking crisis dates |
| post-LV2020 designations (USA-2023, CHE-2023) | inline | flagged `source="post_LV2020"` |
| e-MID Italian interbank (2009-2015) | external — caller-supplied | feed `from_exposure_matrix` |
| BIS Locational Banking Statistics | external — caller-supplied | quarterly aggregate, sensitivity-only |

The package ships *no* real interbank exposure data; the topology
loader expects user-supplied parquet/CSV. The Barabási-Albert null
exists exclusively as a falsification baseline (Boss et al. 2004).

## Physics anchors (from `CLAUDE.md`)

* INV-K1 — :math:`0 \le R(t) \le 1` (universal, P0); enforced in
`EarlyWarningResult.__post_init__`.
* INV-K5 — :math:`\langle R \rangle \sim O(1/\sqrt{N})` for incoherent
phases (statistical, P1); test
`test_early_warning::test_inv_k5_incoherent_finite_size`.
* INV-EVT1, INV-EVT2 — event-ledger date and country invariants
(this module).
* INV-TOP1..3 — topology adjacency / weights / labels invariants
(this module).
* INV-EW1, INV-EW2 — early-warning config invariants (this module).

## Maintenance-hierarchy role

Sustainer (Layer 2). The module emits a diagnostic score; it never
takes execution action. A `HARD_PASS` outcome would only motivate
promotion to a Protector role downstream — it does not itself protect
any gradient.

## Status

Initial commit:

* All public APIs typed (`mypy --strict` clean).
* 57 tests covering invariants + statistical sanity (BH FWER,
null-AUC ≈ 0.5).
* Hypothesis remains `HYPOTHESIS` tier; first real-data run pending
on user-supplied e-MID dump.
65 changes: 65 additions & 0 deletions research/systemic_risk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright (c) 2023-2026 Yaroslav Vasylenko (neuron7xLab)
# SPDX-License-Identifier: MIT
"""Systemic-risk-as-phase-transition research module.

A pre-registered falsification of the hypothesis that interbank
phase-locking precedes banking-crisis events. All claims under
``CLAIMS.md`` are ``HYPOTHESIS`` tier until the battery returns
``HARD_PASS`` on >= 2 independent crises.

Layer in the maintenance hierarchy: this module is a *Sustainer*
diagnostic — it reports approach to the Kuramoto bifurcation Φ → 0
without taking any execution action.
"""

from __future__ import annotations

from .early_warning import (
EarlyWarningConfig,
EarlyWarningResult,
compute_early_warning,
kuramoto_order_parameter,
)
from .event_ledger import (
DEFAULT_LEDGER,
BankingCrisisEvent,
BankingCrisisLedger,
)
from .falsification import (
CrisisOutcome,
FalsificationConfig,
FalsificationReport,
auc_mann_whitney,
benjamini_hochberg,
run_falsification,
)
from .phase_extraction import (
INTERBANK_DEFAULT_BAND,
interbank_phase_extract,
)
from .topology import (
InterbankTopology,
barabasi_albert_null,
from_exposure_matrix,
)

__all__ = [
"BankingCrisisEvent",
"BankingCrisisLedger",
"CrisisOutcome",
"DEFAULT_LEDGER",
"EarlyWarningConfig",
"EarlyWarningResult",
"FalsificationConfig",
"FalsificationReport",
"INTERBANK_DEFAULT_BAND",
"InterbankTopology",
"auc_mann_whitney",
"barabasi_albert_null",
"benjamini_hochberg",
"compute_early_warning",
"from_exposure_matrix",
"interbank_phase_extract",
"kuramoto_order_parameter",
"run_falsification",
]
Loading
Loading