Status: Approved 2026-05-25 Repo: FlashAlpha-lab/flashalpha-examples (expanded in place; no new repo) Author: solo Goal version: v1.0 cookbook = 30 jupytext-paired recipes + 5-layer test pyramid + dash.flashalpha.com lead-magnet
A code-cookbook converts API-evaluating quants better than a docs site because:
- Direct conversion — quants run a recipe, hit an Alpha-gated cell, upgrade.
- LLM-ingestion moat — committed
.ipynboutputs get crawled into training data; six months out, "how do I compute dealer gamma in Python?" surfaces our recipe. - Star/clone-velocity flywheel — quants share repos; awesome-lists and newsletters pick them up. Docs pages don't get that treatment.
This spec defines the structure, format, and test discipline. Conversion attribution (UTM → signup → trial → paid) ships as a separate concern joined into the existing 11_attribution.csv pipeline.
In:
- Expansion of flashalpha-examples/ from 11
.pyscripts to 30 jupytext-paired (.py+.ipynb) recipes across seven tiers. - 5-layer test pyramid that doubles as a downstream integration test suite for the FlashAlpha API + every released SDK.
- Conversion-funnel CTAs (top, bottom, mid-gate) enforced by structural test.
dash.flashalpha.comStreamlit dashboard (Tier B recipe #11 hosted live) as the lead magnet.- A static
endpoint_tiers.yamlmirrored from the API'sEndpointAccessMiddlewareplus a sync script.
Out:
- New repo. Cookbook lives inside
flashalpha-examples. - MCP examples /
.cursorrules. That stays inflashalpha-mcp. - Theory deep-dives. They stay in
gex-explained,0dte-options-analytics,volatility-surface-python; cookbook cross-links to them. - Empirical per-tier probing (Layer 4 v2). One Alpha CI key for now; revisit in v1.1.
- Hosted notebooks server (Binder/JupyterHub). Colab badges only.
flashalpha-examples/
├── README.md # rewritten as cookbook front-door
├── COOKBOOK.md # 30-recipe catalog with Colab badges
├── CLAUDE.md # keep
├── LICENSE # keep (MIT)
├── pyproject.toml # bump version 0.1 → 1.0; add [project.optional-dependencies].dev
├── requirements.txt # runtime deps
├── requirements-dev.txt # NEW: jupytext, papermill, nbmake, vcrpy, nbqa, ruff, pydantic
├── endpoint_tiers.yaml # NEW: tier map mirrored from EndpointAccessMiddleware.cs
├── notebooks/
│ ├── tier-a-hooks/ # 7 recipes
│ ├── tier-b-dealer-flow/ # 7
│ ├── tier-c-vol-surface/ # 6
│ ├── tier-d-0dte/ # 3
│ ├── tier-e-flow/ # 3
│ ├── tier-f-backtest/ # 4
│ └── tier-g-engineering/ # 3
│ # each recipe: <slug>.py + <slug>.ipynb (jupytext-paired, both committed)
├── tests/
│ ├── __init__.py
│ ├── conftest.py # papermill runner, vcrpy config, shared fixtures
│ ├── test_layer0_secrets.py # secret + auth-header sweep beyond gitleaks
│ ├── test_layer1_structural.py # frontmatter, CTAs, links, slug ↔ file, AST scan
│ ├── test_layer2_execution.py # papermill + vcrpy cassettes
│ ├── test_layer3_golden.py # backtest DataFrame snapshots + image hashes
│ ├── test_layer4_tier_static.py # endpoints_used ⊆ allowed-by-tier static check
│ └── cassettes/<slug>/ # vcrpy recordings, one dir per recipe
├── snapshots/<slug>/ # pytest-approval golden DataFrames + chart hashes
├── scripts/
│ ├── new_recipe.py # scaffold paired notebook from template + frontmatter
│ ├── scrub_outputs.py # strip Authorization/X-Api-Key/Cookie headers from .ipynb output cells
│ ├── record_cassettes.py # live-API recorder (one-shot per recipe)
│ ├── sync_tier_map.py # regenerate endpoint_tiers.yaml from EndpointAccessMiddleware.cs
│ └── render_dashboard.py # build/deploy the dash.flashalpha.com Streamlit app
├── docs/
│ ├── superpowers/specs/ # this spec lives here
│ ├── authoring-guide.md # how to add a recipe
│ └── frontmatter-schema.md # pydantic model documented
├── dashboard/ # NEW: Streamlit app for #11 + Dockerfile + deploy config
│ ├── app.py
│ ├── Dockerfile
│ └── docker-compose.yml
├── .github/workflows/
│ ├── pr.yml # Layers 0,1,2-cassette,4 (~3 min target)
│ ├── nightly.yml # Layer 2-live, Layer 3, drift-PR opener (~15 min)
│ ├── weekly.yml # Layer 5 funnel + full lychee link check
│ ├── on-sdk-release.yml # repository_dispatch from each SDK repo on tag
│ └── on-api-release.yml # repository_dispatch from MC deploy
├── .pre-commit-config.yaml # gitleaks, scrub_outputs, jupytext sync, nbqa, ruff, frontmatter
└── .gitignore # add .cache/, .papermill/, .pytest_cache, dashboard/.streamlit/
Existing tests/test_notebooks_syntax.py and tests/test_examples.py are absorbed by the new layered suite in Phase 1.
.pyin percent format — diff target, edited by humans, source of truth for code..ipynb— source of truth for executed outputs (charts, DataFrames), regenerated by author viapapermill, committed.- Pre-commit
jupytext --synckeeps them aligned by content (not outputs). - CI
jupytext --sync --checkfails if drift.
Trade-off: every recipe is two files. Accepted because PR diffs become reviewable and grep across recipes stays fast, while still shipping committed outputs for the LLM/SEO crawl.
Every recipe's top cell is a tagged code cell containing a YAML triple-dashed block, parsed by pydantic in test_layer1_structural.py.
---
slug: 01-gex-dashboard
title: Build a GEX Dashboard in 30 Lines
tier: free # free | basic | growth | alpha
runtime_budget_seconds: 60
max_api_calls: 8
endpoints_used:
- /v1/exposure/gex/{symbol}
- /v1/exposure/levels/{symbol}
tier_gated_cells: [] # cell indices that intentionally call above `tier`; empty = none
sdk_version_min: "1.0.1"
utm_campaign: 01-gex-dashboard
expected_artifacts: # used by Layer 3 (backtest recipes only)
dataframes: []
charts: [gex_chart.png]
last_validated_live: 2026-05-25
---Enforced invariants:
slug == Path(file).stemutm_campaign == slugendpoints_used ⊆ endpoint_tiers.yamland every endpoint's required tier ≤tier(or its index is intier_gated_cells)last_validated_liveis updated by the nightly job whenever Layer 2-live passes
Three fixed blocks. scripts/new_recipe.py renders them from frontmatter; pre-commit re-renders idempotently.
Top markdown cell (always first, after frontmatter):
# {title}
> 🔑 Get a free FlashAlpha API key (5 req/day, no card):
> https://flashalpha.com/signup?utm_source=github-cookbook&utm_medium=notebook&utm_campaign={slug}
>
> Tier required: **{tier_display}** · [](https://colab.research.google.com/github/FlashAlpha-lab/flashalpha-examples/blob/main/notebooks/{tier_dir}/{slug}.ipynb)Bottom markdown cell (always last):
## What to try next
- 🔁 Backtest this with historical replay (Alpha) → https://flashalpha.com/pricing?utm_source=github-cookbook&utm_campaign={slug}
- 💬 Discord: https://flashalpha.com/discord
- 📚 More recipes: https://github.com/FlashAlpha-lab/flashalpha-examples
- 🤖 Use with Claude/Cursor via MCP: https://flashalpha.com/docs/mcpMid-gate cell (markdown, immediately before each cell whose endpoint exceeds the recipe's declared tier):
> 🔒 The next call uses `{endpoint}` which requires **{required_tier}+**.
> Free/{lower_tier} users will get a 403 here.
> Upgrade: https://flashalpha.com/pricing?utm_source=github-cookbook&utm_campaign={slug}Layer 1 enforces presence + exact UTM. Authors don't hand-type these; the scaffold script writes them and pre-commit keeps them in sync with frontmatter.
- gitleaks with custom rules for
fa_*,flashalpha_*, JWT/PEM shapes — scans code AND output cells. - scrub_outputs.py pre-commit hook strips
Authorization,X-Api-Key,Cookieheaders from.ipynboutput cells. Keeps bodies, status codes, latencies (preserves LLM training value). - jupytext --sync --check — fails on
.py/.ipynbcontent drift. - nbqa ruff check + nbqa black --check.
- Output-size caps: 1 MB per cell, 5 MB per notebook (catches accidentally-committed screenshots).
Parameterized over every .ipynb. Per recipe:
- valid nbformat JSON; round-trips through
nbformat.read/writeunchanged - frontmatter validates against pydantic schema
slug == file.stemandslug == utm_campaign- top + bottom CTA blocks present with exact UTM string match
- every cell whose endpoint exceeds
tieris preceded by a mid-gate cell (matched againsttier_gated_cells) - every import resolves from
requirements.txt - no hardcoded API key shape anywhere (regex sweep covers code, markdown, output)
- no
%%captureand no broadtry/exceptthat silently swallows API errors (AST scan) - outbound URLs in markdown return 200 — cached for 7 days in
.cache/links.json; full re-check in weekly.yml via lychee
PR path:
- papermill executes each notebook
- vcrpy intercepts the SDK's
httpxclient; cassettes attests/cassettes/<slug>/ - enforce per-recipe
runtime_budget_secondsandmax_api_calls - fail if any cell raises
Nightly path:
- same papermill run with
record_mode=new_episodesagainst live API using the single Alpha CI key - structural diff: if cassette JSON differs at field/shape level (not values), auto-open a PR via
peter-evans/create-pull-requestcontaining the new cassette + a comment naming the changed endpoint and fields - this is the API-drift early-warning system
Applies to recipes #16, #27–30. They use historical API with locked at=YYYY-MM-DDTHH:mm:ssZ for determinism.
- pytest-approval snapshots for declared
dataframes(full CSV) - imagehash perceptual hash for declared
charts; Hamming-distance tolerance ≥ 4 bits (catches blank / sign-flipped charts; tolerates matplotlib font-rendering changes) - snapshots live at
snapshots/<slug>/; diff requires human re-approval
Because the canonical tier map lives in C# code at EndpointAccessMiddleware.cs:60 and we only have one Alpha CI key (no per-tier probing), the cookbook ships endpoint_tiers.yaml mirroring that map.
Per recipe:
- for each endpoint in
endpoints_used, look up its required tier inendpoint_tiers.yaml - assert
required_tier ≤ frontmatter.tierOR the cell index appears intier_gated_cells
A separate test verifies the YAML's structural integrity (all endpoints map to a known tier, no orphans).
Sample endpoint_tiers.yaml (excerpt mirroring EndpointAccessMiddleware.cs:60):
# Generated 2026-05-25 by scripts/sync_tier_map.py from EndpointAccessMiddleware.cs
# DO NOT EDIT BY HAND — run the sync script.
tiers:
- free
- basic
- growth
- alpha
rules:
# ordered most-specific first; first match wins (mirrors middleware semantics)
- prefix: /v1/vrp/ ; required: alpha
- prefix: /v1/flow/levels/ ; required: growth
- prefix: /v1/flow/pin-risk/ ; required: growth
- prefix: /v1/flow/dealer-risk/ ; required: growth
- prefix: /v1/flow/ ; required: alpha
- prefix: /v1/earnings/vrp/ ; required: alpha
- prefix: /v1/earnings/dealer-positioning/ ; required: alpha
- prefix: /v1/earnings/ ; required: growth
- prefix: /v1/screener ; required: growth
- prefix: /v1/exposure/summary/ ; required: growth
- prefix: /v1/exposure/zero-dte/ ; required: growth
- prefix: /v1/volatility/ ; required: growth
- prefix: /v1/exposure/dex/ ; required: basic
- prefix: /v1/exposure/vex/ ; required: basic
- prefix: /v1/exposure/chex/ ; required: basic
- prefix: /v1/maxpain/ ; required: basic
- prefix: /v1/exposure/gex/ ; required: free
- prefix: /v1/exposure/levels/ ; required: free
- prefix: /stockquote/ ; required: free
# Index/ETF symbol gating + 0DTE grandfather rules live in `symbol_gates` and
# `endpoint_gates` blocks (omitted here; see full file). Layer 4 asserts the
# notebook's tier covers the highest-required endpoint it uses.Drift management:
scripts/sync_tier_map.pyparses EndpointAccessMiddleware.cs when run locally and regenerates the YAMLon-api-release.ymltriggers a workflow that runs the sync script and opens a drift PR if the YAML changes- v1.1 will add empirical per-tier probing when CI provisions four dedicated tier keys
- Headless-Chrome render of each notebook's signup URL; assert
utm_*params land in the rendered analytics tag - Full uncached lychee link sweep
- Stars/clones/referrers report posted to Slack
| Workflow | Trigger | Layers | Target time |
|---|---|---|---|
| pr.yml | PR; push to non-main | 0, 1, 2-cassette, 4 | < 3 min |
| nightly.yml | schedule 03:00 UTC; workflow_dispatch | 2-live, 3, drift-PR opener | ~15 min |
| weekly.yml | schedule Mon 09:00 UTC | 5 | ~10 min |
| on-sdk-release.yml | repository_dispatch from flashalpha-{python,js,go,dotnet,java} on tag push |
bump SDK in requirements.txt; rerun nightly suite; open issue on regression | ~15 min |
| on-api-release.yml | repository_dispatch from MC deploy pipeline |
rerun nightly suite live; run sync_tier_map; PR on tier-map drift | ~15 min |
Python matrix: 3.10, 3.11, 3.12, 3.13 (matches existing CI). Ubuntu only.
Secrets required:
FLASHALPHA_API_KEY— dedicated low-quota Alpha CI accountCOOKBOOK_DISPATCH_PAT— installed on the 5 SDK repos and MC deploy pipeline to sendrepository_dispatchintoflashalpha-examplesSLACK_FUNNEL_WEBHOOK— for weekly stats post
| Existing file | New location | Action |
|---|---|---|
01_quick_start.py |
tier-a-hooks/05-bsm-greeks-one-call |
port + minimal-ize to "15 greeks in one call" |
02_gex_dashboard.py |
tier-a-hooks/01-gex-dashboard |
port + Colab badge + screenshot — canonical Phase-0 template |
03_iv_rank_scanner.py |
tier-e-flow/utility-iv-rank-scanner |
port — keep as non-numbered utility (doesn't fit "unusual flow" theme) |
04_vol_surface_3d.py |
tier-a-hooks/03-3d-vol-surface-svi |
port + SVI overlay |
05_dealer_positioning.py |
tier-b-dealer-flow/08-positioning-regime-classifier |
port + classifier output |
06_kelly_sizing.py |
tier-b-dealer-flow/utility-kelly-sizing |
keep as supporting utility |
07_zero_dte_analytics.py |
tier-d-0dte/21-pin-risk-scanner |
port |
08_advanced_volatility.py |
tier-c-vol-surface/15-svi-time-series-spx |
port + time series |
09_volatility_analysis.py |
tier-c-vol-surface/18-skew-dynamics-fomc |
port + event overlay |
10_live_options_screener.py |
tier-e-flow/24-uoa-scanner-150-lines |
port + align with the UOA article |
11_max_pain_analysis.py |
tier-a-hooks/06-why-spx-pins-max-pain |
port |
Counting:
- 33 numbered slots in the plan (A:7 + B:7 + C:6 + D:3 + E:3 + F:4 + G:3)
- 9 of the 11 existing scripts fill numbered slots (1 in A:01,03,05,06; B:08; C:15,18; D:21; E:24)
- 2 existing scripts (
06_kelly_sizing,03_iv_rank_scanner) become non-numbered utilities - 24 net-new numbered recipes to author
- v1.0 ships 30 numbered (hold 3 in reserve) + 2 utilities = 32 recipes total
- Tier A (3 net-new):
02-gamma-flip-cross-index,04-call-wall-put-wall-explorer,07-daily-gamma-flip-slack-bot - Tier B (6 net-new):
09-intraday-dealer-hedging-flow,10-pin-risk-monitor-0dte,11-spotgamma-killer-dashboard(Streamlit, deployed to dash.flashalpha.com),12-cross-symbol-dealer-leaderboard,13-charm-decay-map,14-vanna-trap-detector - Tier C (4 net-new):
16-vrp-harvest-leak-free-backtest(Alpha conversion notebook),17-variance-swap-pricing,19-realtime-butterfly-arb-detector,20-term-structure-zscore - Tier D (2 net-new):
22-expected-move-shrinkage-tracker,23-0dte-gamma-acceleration-alert - Tier E (2 net-new):
25-sweep-vs-block-classifier,26-opening-bias-position-building - Tier F (4 net-new):
27-gex-flip-reversal-backtest,28-event-study-opex,29-replay-covid-march-2020,30-walkforward-0dte-optimization - Tier G (3 net-new):
31-fastapi-proxy-for-team,32-postgres-daily-ingest,33-discord-bot-daily-levels
| Phase | Days | Deliverable |
|---|---|---|
| 0 — Foundation | 3 | jupytext, pre-commit, Layer 0+1 tests, frontmatter pydantic schema, CTA renderer, scripts/new_recipe.py, endpoint_tiers.yaml v1 (manually extracted from middleware), CI pr.yml. Port 02_gex_dashboard.py → tier-a-hooks/01-gex-dashboard end-to-end as canonical template. CI green. |
| 1 — Migrate existing 11 | 5 | Port all 11 existing scripts to paired form in tier subdirs. Layer 0/1/2-cassette green per recipe. |
| 2 — Tier A complete + launch v0.1 | 4 | 3 net-new Tier A. Layer 4 wired up. Coordinated launch: X thread, awesome-quant + awesome-finance + awesome-python PRs, HN Show, Discord. |
| 3 — Tier B + dash.flashalpha.com | 11 | 6 net-new Tier B. Streamlit dashboard for #11 deployed to dash.flashalpha.com (subdomain reservation + container deploy adds ~2 days vs HF Spaces). |
| 4 — Tier C vol-surface | 7 | 4 net-new. #16 is the Alpha-conversion notebook. Layer 4 stress-tested. |
| 5 — Tier D + E | 5 | 4 net-new combined. |
| 6 — Tier F backtests | 9 | 4 net-new. Layer 3 golden snapshots wired up. Every backtest produces a publishable equity-curve screenshot. |
| 7 — Tier G engineering | 4 | 3 net-new. |
| 8 — Polish + v1.0 launch | 3 | HN Show #2, newsletter outreach (5 quant newsletters), llms.txt update, cross-link sweep from MC concept pages. |
Total: 51 working days solo (~10–11 weeks at 5 days/week).
| Risk | Mitigation |
|---|---|
| API key leaks in committed cell outputs | Layer 0 gitleaks + scrub_outputs.py + Layer 1 regex; CI key is a dedicated low-quota Alpha account so blast radius is bounded |
| API drift breaks cookbook silently | Nightly Layer 2-live + auto-opened drift PRs |
| SDK breaking change orphans recipes | on-sdk-release.yml fires on every SDK tag |
| Tier map drift (endpoint moves Free → Alpha undetected) | on-api-release.yml runs sync_tier_map.py and opens drift PR; v1.1 adds empirical per-tier probing |
| Runaway CI quota burn | per-recipe max_api_calls budget; dedicated CI key with hard rate limit |
| Notebook rot (stale "as-of" charts) | last_validated_live updated nightly; recipes >30 days stale flagged in weekly report |
imagehash flapping on matplotlib bumps |
Pin matplotlib to minor version; explicit bump + snapshot re-approval |
.ipynb JSON merge conflicts |
jupytext makes .py the authoritative diff target; .ipynb rarely conflicts because authors don't edit it directly |
| Author-time blowout (51 days) | Layer 0+1 + scripts/new_recipe.py keep each net-new recipe at ~2–3 h once template is solid |
| One-Alpha-key limitation hides tier bugs | Static Layer 4 + sync_tier_map.py is best-effort; v1.1 four-key empirical probing closes the gap |
Phase 0 ships when all of the following are true:
pre-commit run --all-filespasses with the new hookspytest tests/test_layer0_secrets.py tests/test_layer1_structural.pypasses (one recipe present)pytest tests/test_layer2_execution.pypasses against the cassette for01-gex-dashboardpytest tests/test_layer4_tier_static.pypasses (one recipe +endpoint_tiers.yaml)- CI workflow
pr.ymlgreen on a clean PR scripts/new_recipe.py --slug demo-99 --tier freeproduces a valid pair that passes Layers 0/1notebooks/tier-a-hooks/01-gex-dashboard.{py,ipynb}is the canonical template — every later recipe is a copy-and-modify of this
Phase 0 is the only phase that requires green-fielding test infra. Phase 1+ is repetitive mechanical authoring.
- v1.1 per-tier probing: provision 4 CI accounts (Free / Basic / Growth / Alpha); add empirical Layer 4 matrix.
- Binder support: skipped for v1.0 (Colab covers the need). Reconsider if Binder discovery proves to drive traffic.
- JS/Go/.NET/Java cookbook ports: out of scope for v1.0. The five other SDKs can ship their own cookbook repos later, gated on Python cookbook conversion data.
- MCP integration recipes: stay scoped to
flashalpha-mcp; cookbook links to MCP docs from the bottom CTA only.