Skip to content

feat(Notebooks): Add risk-positioning use-case notebook - #39

Draft
szemyd wants to merge 4 commits into
mainfrom
claude/notebook-risk-positioning-ga30w4
Draft

feat(Notebooks): Add risk-positioning use-case notebook#39
szemyd wants to merge 4 commits into
mainfrom
claude/notebook-risk-positioning-ga30w4

Conversation

@szemyd

@szemyd szemyd commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds notebooks/risk-positioning.py, implementing the risk-positioning outline from #34 (use case: Read positioning & risk).

Combines funding, basis and open-interest change into a transparent crowding score (trailing rolling percentile ranks — the alpha-discovery transform), then runs a decile event study and an in-window threshold table.

  • Binance BTC perpetuals, 5-minute, May 2025 preview slice; runs end-to-end on DEMO-KEY.
  • Ranks use trailing data only (no look-ahead); forward outcomes cover exactly t+1 .. t+h; the composite keeps sign (>0.5 long-crowded, <0.5 short-crowded); derivative price is a cross-check on the OHLCV return source (corr 0.996), not a replacement.
  • Reports the negative in-window result straight: in May 2025 elevated crowding preceded above-baseline forward returns (+0.28% baseline → +0.67% at score ≥ 0.9) and unexceptional drawdowns — i.e. it behaved as momentum/carry-confirmation, not a fragility warning. Section 1 is framed as a hypothesis one month cannot confirm, and the Takeaways separate what was demonstrated (a clean, look-ahead-free construction) from what was not (unwind risk). This makes the "risk-state diagnostic, not a crash forecast" framing concrete.

.ipynb and HTML are generated by CI. Adds one README row and one preview-notebooks.json slug (sibling PRs touch these too — whichever merges last needs a trivial rebase).

Part of splitting #35 into one PR per notebook. Implements #34.

🤖 Generated with Claude Code

claude and others added 2 commits July 14, 2026 14:35
Percent-format .py notebook implementing the risk-positioning outline spec from #34.
Runs end-to-end on the DEMO-KEY May 2025 preview slice; .ipynb and HTML are
generated by CI. Registers the notebook in the README and preview-notebooks.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVxnJZYNRifP6pgJ5D13bz

@szemyd szemyd left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quant review of notebooks/risk-positioning.py

Verdict: REQUEST CHANGES — posted as a comment review because GitHub blocks formal change-requests from the PR author's own identity. Treat the blocking items as merge-blockers; written so a follow-up agent can implement directly.

Reviewed the .py source against the executed .ipynb outputs. This is the most conceptually ambitious of the four use-case notebooks and much of the plumbing is right: strictly trailing ranks (the alpha-discovery transform, correctly reused), sign-preserving composite, the mark-price cross-check on the return source (corr 0.996 — good practice, keep it), overlap-of-windows disclosure, and left-joins with forward-only fill. The blockers are that the notebook's story and its own executed numbers point in opposite directions without acknowledgment, and two outcome-measurement bugs. Details inline; summary below.

Blocking

  1. The narrative contradicts the data and nobody mentions it (inline at the threshold table): in-window, higher crowding → higher 1-day forward returns (+0.63% at score ≥ 0.8 vs ~+0.33%/day baseline) and no worse drawdowns. The "most exposed to a sharp unwind" framing is asserted in Section 1 and re-asserted in the Takeaways as if demonstrated. Add an unconditional baseline row, state the in-window finding plainly (crowding behaved as momentum-confirmation this month), and rewrite the first Takeaway. A straight negative result reads as rigor; the current juxtaposition reads as spin.
  2. forward_vol / forward_max_drawdown are off by one bar (inline, verified with synthetic data): both include the current bar (known at score time) and cover only h−1 future bars, inconsistent with forward_return's (t, t+h]. The vol leak is correlated with the score via vol clustering, flattering the "crowding → forward vol" panel. Two-line fix with rolling(h).…().shift(-h); re-run the event study after.

Important

  1. Chart 3's six "sharpest unwinds" are one 35-minute event cluster (inline), and the annotated prior scores (0.50–0.57, one 0.79) answer the chart's own question — "was the score elevated beforehand?" — with "no", which the text should say. De-cluster with a min-separation rule and state the answer.
  2. The OI component ranks raw per-5m OI change — effectively noise (inline): it doesn't measure the "leverage building" the text describes, and it mechanically compresses the composite's dispersion (only 2.7% of bars ≥ 0.9). Rank a 6h/1d OI change instead; also tighten min_periods so day-one "7-day ranks" on a handful of observations don't seed the deciles.

Minor

  • Funding and basis both price the same perp-vs-index premium, so the composite is effectively ⅔ premium + ⅓ OI; one sentence on component correlation (or a printed 3×3 rank-correlation) would preempt the "aren't two of these the same thing?" question.
  • Decile counts (892/decile) are computed on the score; tail rows with NaN 1-day outcomes still sit in those counts while dropping out of the means — trivial, but a dropna per horizon (or a footnote) would make the printed counts exact.
  • pd.qcut(..., labels=range(1,11), duplicates="drop") raises if bins are ever actually dropped — latent breakage on lower-variance symbols/windows.
  • Section numbering jumps ## 3 → ## 5 → ## 8 (template remnant).

Suggested acceptance criteria

  • The threshold table carries a baseline row and a plain-language statement of the in-window result; Takeaways no longer claim unwind-risk evidence the month didn't provide.
  • Forward-outcome helpers cover exactly t+1 .. t+h; event study re-run.
  • Chart 3 shows de-clustered events and states what the prior scores were.
  • OI component measures a multi-hour build; composite sections re-run and re-interpreted.
  • Re-executed .ipynb/HTML committed by CI so outputs match the revised source.

Generated by Claude Code

Comment thread notebooks/risk-positioning.py
Comment thread notebooks/risk-positioning.py Outdated
Comment thread notebooks/risk-positioning.py
Comment thread notebooks/risk-positioning.py
claude and others added 2 commits July 14, 2026 15:46
- Confront the data honestly: add an unconditional baseline row to the threshold
  table and a plain-language statement that in May 2025 elevated crowding preceded
  above-baseline returns and unexceptional drawdowns (momentum/carry-confirmation,
  not fragility). Reframe Section 1 as a hypothesis about conditional tails one
  month cannot confirm, and rewrite the first Takeaway to separate what was
  demonstrated (a clean, look-ahead-free construction) from what was not (unwind
  evidence).
- Fix forward_vol / forward_max_drawdown off-by-one: both now cover exactly
  t+1..t+h via rolling(h).shift(-h), excluding the contemporaneous bar (which was
  leaking into the "crowding -> forward vol" panel); clip drawdown at 0. The event
  study re-runs on the corrected windows.
- De-cluster Chart 3's "sharpest moves" (they were one 35-minute event as six
  overlapping windows) with a min-separation rule, and state that the prior scores
  were near-neutral — i.e. the score did not flag the drop in advance.
- Rank a 1-day OI change (leverage build) instead of the ~white-noise 5-minute
  change, and require a quarter-window of history for the trailing ranks.
- Print the 3x3 component rank correlation (funding and basis both price the
  premium); count valid observations per decile at the long horizon; guard the
  decile qcut; renumber sections.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVxnJZYNRifP6pgJ5D13bz

szemyd commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — all addressed in the latest commit (CI green on real data). Point by point:

Blocking

  • Narrative vs data. The threshold table now carries an unconditional baseline row, and a plain-language cell states the in-window finding straight: in May 2025 elevated crowding preceded above-baseline returns (real data: +0.28% baseline → +0.67% at score ≥ 0.9) and unexceptional drawdowns — momentum/carry-confirmation, not fragility. Section 1 is reframed as a hypothesis about conditional tails one month can't confirm, and the first Takeaway now separates what was demonstrated (a transparent, look-ahead-free construction) from what was not (unwind evidence).
  • Forward-outcome off-by-one. forward_vol and forward_max_drawdown now cover exactly t+1 .. t+h via rolling(h).shift(-h) (contemporaneous bar excluded; drawdown clipped at 0), so the vol panel no longer leaks ret[t]. Event study re-runs on the corrected windows.

Important

  • Sharpest moves. De-clustered with a min-separation rule (they were one 35-minute event as six overlapping windows), and the text now states the prior scores were near-neutral (mean ~0.45) — i.e. the score did not flag the drop.
  • OI component. Now ranks a 1-day OI change (leverage build) instead of the ~white-noise 5-minute change, and the trailing ranks require a quarter-window of history.

Minor: printed the 3×3 component rank correlation (funding–basis ≈ 0.42 on real data — they price the same premium); per-horizon valid counts; guarded the decile qcut; sections renumbered.


Generated by Claude Code

@szemyd szemyd left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quant re-review (round 2) of notebooks/risk-positioning.py @ 4791054

Verdict: clean — no remaining blockers from the quant-review standpoint. Posted as a comment review (GitHub blocks formal states from the PR author's identity). All round-1 items are fixed; each verified against the re-executed .ipynb:

  1. Narrative vs data — resolved properly, not papered over. Section 1 is reframed as an explicit hypothesis about conditional tails that one month cannot confirm or refute; the threshold table now carries an unconditional baseline row (all bars +0.283%/1d vs +0.559% at ≥0.6 and +0.648% at ≥0.8, drawdowns unexceptional, vol up modestly); a "What the table says — read straight" block states that the score behaved as momentum/carry-confirmation this month; and the Takeaways are split into "what was demonstrated" (the construction) vs "what was not demonstrated" (unwind risk). This is the strongest version of this notebook: the negative result is the content, not a footnote.
  2. Forward-outcome off-by-one — fixed exactly as recommended: ret.rolling(h).std().shift(-h) and close.rolling(h).min().shift(-h) / close − 1 clipped at 0, with docstrings explaining the [t+1, t+h] window and why the clip now carries the ≤ 0 semantics. All three forward outcomes are consistent, and the event study was re-run on the corrected definitions.
  3. Event de-clustering — fixed with the greedy min-separation rule; the six sharpest 1-day drops now fall on six distinct dates (May 4, 11, 23, 27, 29, 30) instead of one 35-minute cluster, and the printed summary states the honest answer to the chart's question: prior scores ranged 0.22–0.77, mean 0.45 — near-neutral, the score did not flag the unwinds in advance, consistent with the threshold table.
  4. OI component — now ranks the trailing 1-day OI change with the rationale documented; min_periods = max(10, window//4) keeps day-one ranks off tiny samples; and the printed component rank-correlation matrix (funding–basis 0.42) preempts the "two of these are the same premium" objection.

Minors also done: decile counts now count valid observations at the 1-day horizon (~800/decile instead of the nominal 892), the qcut decile binning is robust to dropped bins (labels=False + 1), and section numbering is sequential.

Optional polish (non-blocking)

  • The PR description could add one line noting the notebook now explicitly reports the negative in-window result (high crowding preceded above-baseline returns in May 2025) — it's the most interesting thing in the notebook and makes the "risk-state diagnostic, not a crash forecast" framing concrete.
  • As with the sibling PRs: the README/preview-notebooks.json rows collide across #36#39, so whichever merges last needs the trivial rebase.

Generated by Claude Code

szemyd commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. The PR description now notes the negative in-window result (elevated crowding preceded above-baseline returns and unexceptional drawdowns — momentum/carry-confirmation, not fragility), which makes the "risk-state diagnostic, not a crash forecast" framing concrete.

On the README/preview-notebooks.json collision across #36#39: I can drop those two shared edits from the PRs so they merge conflict-free (registering the notebooks in a single follow-up instead), or leave them and rebase whichever merges last — happy to do either; let me know your preference.


Generated by Claude Code

@szemyd
szemyd marked this pull request as draft July 28, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants