Swing Deck Deep Guide
The power-user reference. About a 45-minute read top-to-bottom; mostly used as a lookup once you've been around the dashboard a few weeks.
If you haven't read Swing Deck Guide Reference yet, start there. This doc assumes you know what a card looks like, what a trap chip does, and that "the math cleared" is different from "the AI told you to."
Part 1 — How it ticks
1. The five-layer architecture
Swing Deck is a stack. Each layer answers a different question. They run in order; outputs feed downstream.
| Layer | What it answers | Output |
|---|---|---|
| L1 — 11-point technical | Is the tape worth trading? | Composite score 0–100, grade A–F |
| L2 — 13 risk pillars (E1–E13) | Does the math allow this trade? | Pass / refuse, per pillar |
| L3 — 12-point war calibration | When VIX > 25, what changes? | Tighter caps, defensive overlays |
| L4 — 13-point options | If options are open, is this strategy worth it? | Long-call vs spread vs CSP, strike, DTE |
| AI surfaces (10 coaches) | What does this mean? | Triggered narration, structured output |
The framework refuses. The dashboard narrates. AI explains. You decide. That order is the brand.
2. The audit cycle, every 5 minutes
The daemon (audit_framework.py --watch) runs the cycle on a 5-min interval. One cycle, ~30–90 s for 7–10 tickers:
- Macro fetch — VIX, oil ($USD), brent ($USD), ^IRX. Determines war mode (VIX > 25), oil shock (Oil > $100), armor yield warn (^IRX < 4%).
- Regime classification —
regime_engineresolves the active regime label (OIL_SHOCK,KINETIC,WAR,NORMAL) plus per-sleeve cap overrides. - Per-ticker pass — for each symbol in
portfolio.txt+candidates.txt: - Skip if cash armor (USFR/SGOV). - Fetchdf_d(daily) +df_w(weekly) from yfinance/Polygon. -compute_daily(df_d)→ indicators (EMA stack, RSI, ADX, MACD, ATR, BB, OBV, sweep_clean). -compute_weekly(df_w)→ weekly trend + EMA alignment. -detect_catalyst(t)→ earnings calendar, headline news, Fed events. -compute_structure(df_d)→ primary_S, primary_R, levels_above, levels_below from 10 S/R sources collapsed via 0.15 × ATR confluence merging. -score_11pt(...)→ composite, grade, bias, sl_val, tp1/tp2/tp3, rr. -score_12pt_war(...)if war mode. -score_13pt_options(...)if options engine enabled. -compute_composite(...)→ final score with regime modifiers. - Pillar gate checks — sleeve caps, sector caps, hard-cash floor, drawdown. - Trap contradiction detection — see below. - Enrichment fetches: insider Form 4, float/short, RS-rank, gamma walls, comparable setups. - System-level rebalance — if portfolio drawdown > 7% (E3) or hard-cash floor breached (E5) or armor yield drops (E12),
portfolio_rebalancerwrites rotation orders. - Atomic JSON write to
audit_output.json. Dashboard polls every 5 s. - Alerts engine detects threshold transitions and emails on state changes (entry-eligible, near-stop, exit-review, etc.).
Heartbeat is written at every step (_write_audit_state) so the dashboard's audit signal can show progress and detect a stalled cycle.
3. Trap contradiction detection — the safety net
After scoring, the framework runs _detect_trap_contradictions(ind_d, ind_w, r11). Trap flags fire when two server-computed signals disagree in ways the methodology calls bagholder traps. Pre-v6.6.9 the framework had ALL the data to surface these but didn't — the user had to mentally reconcile contradictory chips. Trap surfacing is the methodology's "math floor not motivation" job.
| Trap flag | Fires when | Translation |
|---|---|---|
⚠ Trap: chase-the-top |
Strong weekly trend (ADX-W ≥ 30) AND overbought weekly RSI (≥ 75) | Move already happened; you'd pay the top |
⚠ Trap: bull-stack distribution |
Bullish EMA stack but Falling OBV with high vol | Price up, smart money out |
⚠ Trap: weak rally |
HH/HL but vol_ratio < 0.7 | Rally on no volume — usually fails |
Trap chips override the score. If you see one, the framework is refusing the trade even when the gate technically opened. Don't override.
Part 2 — The math
4. Layer 1 — The 11-point technical score
Each cell scores 0–10. Sum = 0–100 (Point 9 is the composite, not a separate scoring cell). Grades: A ≥ 90 · B 80–89 · C 70–79 · D 60–69 · F < 60.
| # | Cell | What it reads |
|---|---|---|
| 1 | Trend Confirmation | HH/HL on daily, EMA stack alignment, weekly bonus |
| 2 | Momentum & Strength | RSI band (40–65 sweet spot), ADX (≥ 30 strong), MACD bullish + hist rising |
| 3 | Volatility Context | BB width, ATR%, EMA21/price proximity, loose-trend bonus |
| 4 | Volume & Flow | vol_ratio (today vs 20-day avg), OBV trend, sweep_clean |
| 5 | Whale Sentiment | MarketAux news + AI confirmation coach output |
| 6 | Manipulation Detection | Sweeps + OBV + vol confirmation; book-aware penalty (v6.7.43) |
| 7 | Trade Execution | Price vs EMA21, structure type, entry quality near support |
| 8 | Position Sizing | Vol-adjusted (0–5) + stop-tightness (0–5); cap-breach floor at conc=2 |
| 10 | Macro Correlation | VIX leg (0–3) + sector trend leg (0–3) + per-ticker RS leg (0–4) |
| 11 | Hidden Tape | Call/put gamma ratio + put sweep + max-pain magnet + squeeze potential |
Modifiers apply after the base sum:
- SL breach: −20 (price below EMA21 stop in LH/LL structure)
- Energy-Tech inversion: −10 (oil > $100 + high-energy ticker)
- Diplomatic decay: −10 (within 48 h of named truce; Sports-Cars only)
Composite = max(0, min(100, base + modifiers)).
The cells are not equally weighted — they're equally scored. The 11-pt model trusts that a clean signal across cells matters more than any one cell's strength. That's why a single dead cell (e.g. macro at 5) doesn't tank the score, but five mediocre cells will.
5. Layer 2 — The 13 risk pillars (E1–E13)
Pillars 1–7 are baseline (always active). Pillars 8–13 are war-time / regime overlays.
Detailed mechanics per pillar:
E1 — Armor Cap (15% per asset)
Refuses: any single position > 15% of portfolio. Cash positions exempt. Structural — overrides "I'm so sure about this one."
E2 — Stop Discipline (Trade-the-Structure, v6.7.47)
Refuses: holding without a real broker stop. The stop sits at max(primary_S × 0.999, entry − 2×ATR) — tighter of structural support or 2×ATR floor. The 2×ATR floor preserves Pillar 7's spirit; structural support snaps closer when the framework can see real geometry. Mental stops negotiate. Broker stops don't.
E3 — Red-Line (7% portfolio drawdown)
Refuses: holding through a 7% drop from cost-basis high-water mark. Daemon writes 50%-cash pivot orders automatically. Multi-window thresholds added v5.6: today vs prior close ≤ −4%, week peak ≤ −7%, 30-day rolling ≤ −10%.
E4 — Score Gate (composite ≥ 80)
Refuses: new entries on tickers below B-grade (composite < 80). Gate is hard. Most setups don't clear it. By design.
E5 — Pre-Market Firewall (S&P futures −1.5%)
Refuses: new long entries when ES futures down > 1.5% at 7:25 AM MST. Exits and stop-raises still allowed.
E6 — Sector Cap (40% per theme)
Refuses: > 40% in any single sleeve (semis, AI infra, defense, pharma). Engine tags tickers at audit time. Stops the NVDA + AVGO + AMD + SMCI + MRVL "pretend diversification."
E7 — Weekly Confirmation
Refuses: new entries when weekly EMAs aren't aligned. Half the structural support is missing. Daily trend without weekly = lower-conviction setup.
E8 — Hard-Cash Floor (10%)
Refuses: going below 10% in USFR/SGOV/cash. The sizer refuses to allocate the last 10%. "If you're 100% invested, you can't add."
E9 — Earnings Proximity (≤ 3 sessions)
Refuses: new entries within 3 trading sessions of earnings. Holds and exits unaffected.
E10 — Defensive Oil Cap (15%, war regime)
Refuses: energy > 15% in war regimes (VIX > 25).
E11 — Hardware War Cap (30%)
Refuses: semis + compute infra > 30% in inversion regimes (natgas/oil/uranium term-structure inverted).
E12 — Velocity Exception (35%)
Refuses: sleeve > 35% even on momentum names. Velocity Track tickers can exceed the 25% Sports-Cars cap up to 35%, only while Velocity flag is active AND Armor Cash > 15%.
E13 — Diplomatic Decay (48h post-truce)
Refuses: full-conviction sizing within 48 h of a named truce/summit (Ukraine, Gaza, Taiwan). Sports-Cars score penalized −10. Geopolitics matters to your portfolio whether you like it or not.
The pillars are gates, not rules. They don't generate trades. They filter out trades your technical scanner wants to take but shouldn't.
6. Layer 3 — War mode (VIX > 25)
Activates automatically. What flips:
- 12-point defensive scoring runs alongside L1 (Combat Readiness, Armor Sizing, Gap-Limit Rule, etc.)
- Position caps tighten: WAR_EXPOSURE = 15% (vs 25% default per-asset)
- Pillars E10 + E11 activate (defensive oil cap, hardware war cap)
- Sports-Cars sleeve gets regime penalty in
regime_engine - Energy-Tech inversion modifier (−10) applies to high-energy tickers when oil > $100
What you do differently:
- Don't add to Sports-Cars (NVDA / AVGO / AMD).
- Anchors stay in scope but at half-size.
- Defensive sleeve (LMT / RTX / NOC / GD) becomes the active hunting ground.
- Cash floor effectively rises (the framework refuses sizing that would breach the lower cap).
Back to baseline triggers when VIX < 25 for 5 consecutive cycles, or when the regime engine clears the WAR label via macro re-classification.
7. Layer 4 — 13-point options (Level 2 only)
Activates when OPTIONS_ENGINE_ENABLED = True in config and the ticker has a Tradier chain available.
The 13 cells (each 0–10):
- Structure & Trend — same logic as L1 Point 1 with options-aware loose-trend bucket
- Volatility regime — VIX-driven; tighter when VIX > 25
- IV Rank — ≥ 30 floor, ≤ 80 cap; deep-ITM filter when in VIX trap
- Theta Budget — portfolio theta ≥ MIN_PORTFOLIO_THETA before short premium
- Delta Target — −0.30 to 0 for short puts; matches strategy
- DTE Preference — 14-day default; 7–60 acceptable
- Liquidity — bid-ask < 10%, OI > threshold
- Strategy Fit — long call vs vertical spread vs CSP, picked by IV regime
- Sentiment Alignment — MarketAux / AI thesis must match strategy direction
- Macro Correlation — same VIX/sector/RS structure as L1
- Hidden Tape — gamma exposure ratio, dealer GEX, max pain
- Manipulation — options-specific sweep + book skew detection
- Energy Penalty — applies when oil shock + high-energy ticker
Strategy picker logic:
- IV ≤ 30 → long calls (vol cheap, directional bet)
- IV ≥ 70 → short premium (CSPs, bull put spreads — sell rich vol)
- IV 30–70 → vertical spreads (defined risk)
When to ignore the recommendation:
- L1 composite < 80 (the 13-pt doesn't override L1's gate)
- Trap flag fires on the underlying
- Stale chain data (cache > 15 min)
- Earnings within 3 sessions
- Liquidity below threshold
The options layer is opt-in. If you trade equities only, you can leave it disabled — the dashboard hides the panel. If you trade options, the layer is your structured second opinion. It doesn't pick the trade. It scores it.
Part 3 — The UI, deep
8. Card anatomy — every signal, threshold, edge case
A ticker card has six zones. Top to bottom:
Header
- Ticker + sector tag (e.g.
NVDA · AI Infra) - Live price + change % (color-coded)
- Sparkline (20-bar close trend)
Score row
- Composite score (0–100) + grade (A/B/C/D/F)
- Bias chip (ACCUMULATE / HOLD / DE-RISK 50% / REDUCE / HARD EXIT)
- Audit-age chip (turns amber > 5 min, red > 15 min)
Whale toolkit (3-col)
- Structure chip (HH/HL · Ranging · LH/LL) with magnitude gate
- 🐋 RSI Exhaustion (≥ 80 amber, ≥ 90 red)
- 🧭 MACD Compass (bullish / bearish / cross-imminent)
Live-data chips (when present)
- 🚨 INSIDER SELL ($XXM) — Form 4 last 90d aggregated dollar volume; AMBER if 10b5-1 dominates (SCHEDULED), RED if EXTREME (discretionary)
- 📊 FLOAT/SHORT — short-interest %, float size band
- 🔄 RS-RANK — relative strength composite (1m/3m/6m/1y vs SPY + sector ETF), leader/laggard tag
- 🧱 GAMMA — call-wall + put-wall + zero-gamma flip level
- 🔁 COMPARABLE — count of prior matching setups (when ≥ 1)
Execution levels
- Entry · SL · TP1 · TP2 · TP3 · R:R
- SL label reads "STOP" — tooltip discloses whether it snapped to structural or sat at 2×ATR
- TP1 label same — caps at primary_R × 0.999 when tighter than 3×ATR
- R:R color: green ≥ 2 · amber 1.5–2 · red < 1.5
Position sizer
- Rec. shares (1% account risk via stop distance)
- % of portfolio at recommended size
- Cap status (within 25% per-asset cap, or "CAPPED" when 1%-risk sizing exceeds the cap)
Pillar chips (bottom row)
Small chips, one per active pillar. Green pass · Red violation · Amber warning. Hover for the specific gate state.
Flags
- Color-coded by severity: red for traps, amber for warnings, neutral for informational
- Order matters — first three are most important
⚠ Trap:flags trump everything — they're the framework's veto
9. The modal — every tab
Click any card. Modal opens. Header has:
- Refresh chip (
↻ Refreshed @ HH:MM:SS) — visible receipt that the data was pulled fresh - Audit-age chip — same as the card, but in modal context
- Score breakdown — 11 cells with their values
Tabs:
- Execution — entry/SL/TP1–3, R:R, position sizer, broker order builder
- Trap & Structure — auto-classified state (TRAP / FADE / CHASE / CLEAN / NEUTRAL) + AI override (Trap & Structure Coach)
- Whale Sentiment — news headlines, dark pool flow, AI Whale Confirmation Coach
- Hidden Tape — gamma data, max-pain magnet, squeeze potential, dealer GEX
- Position History — your trades on this ticker, P&L, score-drift over time
- Catalyst — earnings calendar, Fed events, news with AI Catalyst Interpreter
- Comparable Setups — prior tape matches with outcome stats (when ≥ 1)
Critical UX rule (v6.7.17): the modal is sacred during a session. Any code path that mounts a coach panel must respect _isModalOpen() or the LLM-loaded coach content gets destroyed. If you're hacking on the dashboard and a coach disappears mid-session, that's the rule you broke.
10. The 10 AI coaches — when, why, what
| Coach | Trigger | Payload it gets | What it produces |
|---|---|---|---|
| Pillar Coach | Pillar violation/warn | Violated pillar + position state + caps | "You're at X% in Y; pillar Z says reduce to W" |
| Exit Coach | Score < 70 OR SL approached | Position state + new score + signals | Sell vs reduce vs wait, with reasoning |
| Entry Coach | ARMED state | Full entry context, pillars, R:R, weekly | Thesis vs risk in one read |
| Devil's Advocate | Conviction high (≥ 90 + ARMED) | Bull thesis + position context | Bear case, adversarial — that's the point |
| Thesis Drift | 14d-old AI thesis vs today | Both theses + signal diff | Where the read changed and why |
| Position History Audit | Held position with score drop | Trade history + audit timeline | Why this position is no longer scoring |
| Comparable Setups | Today's tape matches prior | Comparables + outcome stats | What happened last time |
| Catalyst Interpreter | News / earnings / Fed detected | Catalyst details + ticker context | What it means for this ticker |
| Whale Confirmation | Flow contradicts price | Whale data + price action | Smart money is in or out |
| Trap & Structure | Trap detector fires | Structure state + contradiction details | Walks the contradiction in detail |
Coach payload contract: each coach receives structured inputs (no free-text). Each produces structured output that the dashboard renders into a fixed panel. No chat box. Ever. The coaches are bound to framework events, summoned by triggers, never by free-form questions.
Threshold-citation guard (v6.6.40+): all coaches inject _THRESHOLD_CITATION_GUARD into their prompts so they cite payload thresholds (entry-score gate, R:R floor, sleeve cap, sector cap, stop %) rather than hardcoding them. This is why a coach saying "the 80/100 entry gate" vs "a 80/100 entry gate" matters: the first is canonical, the second is the AI hallucinating a threshold the framework didn't pass it.
Cache TTL: 5 minutes per coach per ticker. Stale-coach badge fires on the panel when audit data refreshes without a re-classify; user clicks ↻ Regenerate to refresh.
Cost tracking: every coach call logs to ai_thesis_log.jsonl with token counts + model + cost. Cost-meter incident (2026-04-28) caught the bug where the cost calc was missing web_search_requests fees — that's now part of the canonical accounting ($10/1000 web_search calls).
Part 4 — Execution
11. Broker integration — preflight, brackets, ladder
The 8-check preflight chain
Every BUY order runs broker_guard.preflight_new_order() before E*TRADE sees it. Strictly ordered, fail-fast:
| # | Guard | Refuses |
|---|---|---|
| 0 | guard_writes_enabled |
Master kill switch (set BROKER_WRITES_ENABLED=false to disable all writes) |
| 1 | guard_circuit |
Recent failures > threshold; circuit breaker open |
| 2 | guard_rate_limit |
E*TRADE rate-limit (50/min default) |
| 3 | guard_audit_freshness |
Audit > 15 min old (G6 stale_audit) |
| 4 | G1 trade_dollar_cap |
Notional > MAX_TRADE_USD |
| — preview returns here — | ||
| 5 | G2 daily_order_budget |
Rolling 24h order count > MAX_DAILY_ORDERS |
| 6 | G4 symbol_cooldown |
Re-order on same symbol within cooldown |
| 7 | G3 new_order_token |
HMAC token from preceding preview, binds (cid, account, symbol, qty, action, price_type, prices) |
G5 — R:R minimum runs inside G1 (v6.5; v6.6.4 limit-price-aware). Refuses BUY when audit R:R < BROKER_RR_MIN (default 2.0). For LIMIT orders, recomputes R:R from planned fill price. Set BROKER_RR_MIN=0 to disable.
Stages:
- stage="preview" → skips G2 + G3 (no order placed)
- stage="place" → full chain
- stage="cancel" → only 0–2 (kill switch never blocks a cancel)
Bracket order flow
Dashboard sends a preview first to get a preview_id. Then place_bracket_order submits the BRACKETED body with two legs:
- Entry leg — STOP_LIMIT or LIMIT, GOOD_FOR_DAY
- SL leg — STOP or STOP_LIMIT, GOOD_UNTIL_CANCEL (stays after entry fills)
The SL leg auto-opposites the entry action (BUY → SELL, SELL_SHORT → BUY_TO_COVER).
E*TRADE returns order IDs for both legs. Reconciler tracks status.
TP ladder reconciler
broker_tp_ladder.py manages the 40/40/20 scale-out. State file: broker_ladder_state.json. Per ticker:
- Snapshot
original_qtyso 40/40/20 is computed against the starting position even if partials fill. - TP1 placed immediately at the recommended price.
- TP2 + TP3 queued internally (NOT placed at broker yet).
- Reconcile cycle: query broker for current leg status:
-
OPEN→ keep waiting -FILLED→ mark done, advance + place next leg -CANCELLED→ user cancelled at broker; markpaused(don't auto-advance) - Source of truth = broker. Local state file is a CACHE reconciled every cycle. Orphan orders we don't recognize are ignored.
Recovery from broker failures
- Token expired → topbar broker chip turns red; re-authorize via Settings menu.
- Rate-limited → exponential backoff (180/360/720 s).
- Code 100 →
<message>service unavailable</message>. First check is COID length (must be < 20 chars; v6.7.40 lesson). Generators usef'sd{secrets.token_hex(8)}'(18 chars) to stay under the cap by construction. - Connection refused → fresh connection retry with backoff.
- Singleton wedged → auto-reset on circuit-open transition.
12. Calibration & customization — what to touch, what not to
settings.json (canonical)
| Key | Default | When to change |
|---|---|---|
BROKER_RR_MIN |
2.0 | Rare. Set 0 to disable G5. |
BROKER_MODE |
live |
Switch to dryrun for testing without firing real orders. |
BROKER_WRITES_ENABLED |
true |
Set false for read-only analyst mode. |
LICENSE_KEY |
required | Set once, don't touch. |
OPTIONS_ENGINE_ENABLED |
false |
Enable for L4 options scoring. Requires Tradier chain access. |
settings.local.json overrides
Per-user / per-machine tweaks that don't ship to the repo. Same shape as settings.json. Wins over settings.json for any key that's set.
Env vars (for Docker / cloud)
ANTHROPIC_API_KEY,OPENAI_API_KEY— AI coach providersE_TRADE_KEY,E_TRADE_SECRET— brokerTRADIER_TOKEN— options chain dataPOLYGON_API_KEY— equity data fallbackDATABENTO_API_KEY— premium tapeMARKETAUX_API_KEY— sentiment
Things you should NOT touch
- ATR multipliers (
ATR_SL_MULT=2.0,ATR_TP1_MULT=3.0, etc.) — calibrated as v6.6 canon. - Pillar caps (
MAX_EXPOSURE=0.25,SECTOR_CAP=0.40,HARD_ASSET_FLOOR=0.10) — calibrated against years of position-sizing memory. - Score thresholds (80 entry gate, 70 de-risk, 60 exit) — calibrated against 8,741+ historical audits. Don't move them.
The framework has been calibrated. If you find yourself wanting to lower the gate to "let more setups through," that's the urge the framework is built to refuse. Tighten your watchlist instead.
Part 5 — Operating reality
13. Edge cases & recovery
| Symptom | Most likely cause | Fix |
|---|---|---|
| Audit signal red, > 15 min stale | Provider timeout, framework wedged | Check topbar; hit refresh; restart daemon if persistent |
| Topbar broker chip red | E*TRADE token expired | Settings → re-authorize |
| All cards grey | yfinance rate-limited or down | Wait one cycle; check audit_dashboard.log |
| Coach panel says "loading" forever | LLM provider timeout | Click ↻ Regenerate; check API key in env |
| Alerts not firing | alerts_engine daemon stopped |
ps aux | grep alerts; restart |
| Bracket order rejected (code 100) | COID > 20 chars OR genuine service outage | Check client_order_id in log; if < 20, retry after backoff |
| Score moved 5+ points cycle-to-cycle | Live data refresh or sentiment swing | Look at the cell breakdown; if no clear cause, restart for clean state |
| TP ladder paused | User cancelled at broker | Modal → review legs → resume or reset |
| Modal coach disappeared mid-session | Code path didn't respect modal-open guard | Bug; reload the page |
| Version banner says "force upgrade" | App version below MIN_VERSION |
git pull && restart |
The "everything turned grey" moment: usually one of three things — provider degradation, license check failing, or the framework process died. Check audit_dashboard.log first. The log will tell you which one.
14. Case studies library
These are the calibration moments that shaped the framework. Each is ~½ page. Read them once; the patterns recur.
VRT TRAP→NEUTRAL (the inverted swept-low rule)
What fired: Original swept-low rule classified VRT as TRAP whenever today swept the prior-day low and close > primary support. VRT had been chronically misclassified TRAP on routine pullbacks.
Root cause: A swept-low that closes above support is a bullish reversal, not a trap. The rule was inverted from the bullish-swept-high TRAP logic without flipping the inequality.
Fix: Geometry classifier now reads swept-low + close > S as NEUTRAL (bullish potential reversal, not failure).
Lesson: When a rule fires on every routine pullback, re-read the rule. It's probably inverted.
Companion case: VRT also produced a published blog post about a separate moment — score 60/D / NO ENTRY — TRAP on VRT despite "trend continuation" being structurally true. R:R was 1.5 (below 2.0 floor); ADX 22; volume 0.52 × normal; RSI 69 daily / 81 weekly; 5 wick sweeps. Framework's read: "Someone is painting this chart sideways-up while distributing — don't be the bagholder who buys $328 on 0.5 × volume after a 30% run." User didn't pull the trigger. Hindsight: would have been right as an investment, not a trade. Trade-quality and structure are separate questions.
AAPL EXTREME→SCHEDULED (10b5-1 distinction)
What fired: insider_transactions._classify_signal() originally flagged any portfolio with ≥ 5 sells, 0 buys, ≥ $5M as EXTREME_SELLING. AAPL fired EXTREME with $25M of sells.
Root cause: ≥ 80% of that volume was 10b5-1 pre-planned — insiders committed months ago while not in possession of MNPI. Today's volume reflects a calendar, not a decision.
Fix (v6.5.0a-4): SCHEDULED_SELLING short-circuit before discretionary classifiers:
if is_predominantly_10b5_1 and sell_count > buy_count:
return "SCHEDULED_SELLING"
The same false-positive is now an amber/neutral chip when ≥ 80% of dollar volume carries the 10b5-1 flag.
Lesson: Insider data needs context. The number alone is misleading; the type of sale carries the signal.
NVDA TRAP→CHASE (auto-rule override)
What fired: structure_classifier.auto_classify_state() returned TRAP on NVDA from a swept-low pattern.
Root cause: The level wasn't a structural support; price was extended > 1.5 × ATR from S/R; momentum-only setup. That's CHASE, not TRAP.
AI synthesizer override: Flipped the verdict from TRAP to CHASE based on geometry the rules didn't capture.
Lesson: "The auto-rule is the prior, not the verdict — the AI then has a chance to override based on geometry the rules don't capture." Rules give the prior; AI gives the verdict. Both ship to the user's panel with rule reasoning AND AI override note.
PLTR insider sell ($435M Thiel)
What fired: PLTR's insider chip read 🚨 INSIDER SELL ($435M).
Detection: 227 sales vs 0 buys in 6 months. Peter Thiel sold 4M shares in 90 days. Director Moore sold 16k on April 15.
Pattern caught on 3 of 6 calibration tickers: PLTR (Thiel), GOOGL (Pichai 2.53M), NVDA (953,976 shares + 15:0 ratio).
Lesson: Insider-selling detection landed on 3 of 6 calibration tickers — the #1 signal AI consistently catches that the 11-pt framework misses. Drove the v6.5 priority bump making Form 4 ingestion canonical.
MU 2026-05-05 (the trap chip in action)
Setup:
- Score: 80/B · HOLD — Bullish · 80
- R:R: 2.45 (clears 2:1 floor)
- Entry $640.20 · SL $599.75 (struct, ~6% drawdown) · TP1 $739.42 (3×ATR — no structural cap)
- Trap flag: ⚠ Trap: chase-the-top
- Weekly: ADX 41.5 (extreme trend) · RSI 80.3 (extreme overbought)
Temptation: every momentum signal is screaming yes. Score is a clean 80. R:R is fine. Daily HH/HL. Bullish news. Call-dominant gamma.
Framework's read: Trap detector caught what the score gate missed. Strong weekly trend on overbought RSI = bagholder pattern. The move has happened. compute_structure couldn't find a primary_R for MU — so the framework can't see where price would actually pause. TP1 sits at $739 because there's no closer ceiling, but that's a 15.5% straight-line run. With weekly RSI at 80, improbable.
Decision: Wait. Watchlist alert at $610–620 — that's where MU pulls back to a tight-structural setup with a real edge.
Lesson: This is the VRT pattern restated. Clean structure, wrong moment. The first override of a ⚠ Trap: flag is the most expensive trade you'll ever make.
The cost-meter incident (meta-case)
What happened: An hour after the v6.4.0 launch, a 12¢ Anthropic charge didn't reconcile with the dashboard's recorded cost.
Two bugs compounding:
1. Whale retry-burn — the Whale Confirmation Coach panel's Retry button fired a fresh paid Anthropic call instead of using the 5-min cache. Stale-cache fallback was missing on the retry path.
2. Missing web_search tool fee — cost calc counted Anthropic input/output/cache tokens but not the $10/1000 server_tool_use.web_search_requests line. Each call invoked web_search 6–8× → recorded cost was ~50% under reality.
Fix: Cost calc reads usage.server_tool_use.web_search_requests from the API response; falls back to scanning content for server_tool_use blocks; adds web_search_requests × $0.01. Whale Retry now uses the 5-min cache; 90s timeout. Per-coach cost capture across all 7 v5.7 AI coaches.
Lesson: "You found a real bug an hour after launch by being annoyed at a 12¢ charge. That's the kind of pressure-testing that catches things." Cost discrepancies are signals.
15. Bug class catalog (the recurring patterns)
These are the bug classes the framework has hit more than once. Each one was caught and shipped a fix; each one tends to recur in new code unless you grep for the pattern.
| Pattern | Where it shows up | Fix |
|---|---|---|
| Timezone anchoring | time.time() vs getmtime(), daily reset boundaries |
Use ZoneInfo("America/New_York"); never bare datetime |
| Atomic writes | JSON state files | Always temp + os.replace; never write directly |
| Cross-surface fix-leakage | Same scoring logic copy-pasted (11pt vs 13pt) | Run diagnostic recipe against BOTH surfaces |
| Baseline-migration leak | Hardcoded constants downstream of new baseline var | Grep for the hardcoded value before shipping |
| Same field, different formula | closed filter, win definition, profit factor |
Rename or unify |
| Modal-render guard staleness | render() blocks during modal-open, data freezes | Add targeted in-place rebuild path |
| DOM-bound observer survival | innerHTML rewrite destroys observed subtree | Disconnect-and-null at every site that replaces target |
| Brand-contract sad-path leaks | Banners are easy; empty states / toasts / chip tooltips are slow drips | Run brand-contract grep end-to-end, not just on banners |
| DOMContentLoaded race vs render() | Component depends on data not yet loaded | Cache + v5states:ready re-fire pattern |
| Hardcoded sticky offsets | top: 44px breaks when sticky parents wrap |
Use var(--Xheight) driven by ResizeObserver |
| Stacking-context trap | Descendant of finite-z-index sticky parent | Bump parent's z-index above all floating descendants |
| Disambiguation tooltips encoding the bug they prevent | "NOT to be confused with Pillar N" | Scan disambiguation text when fixing labels |
| Undocumented vendor caps | E*TRADE COID 20-char limit | Probe boundaries (1, 5, 10, 15, 20, 21, 25) |
| Symptom-text describing failure category, not cause | Generic error text masks specific cause | Diff request bytes between working and broken paths |
| Gate-vs-element-class divergence | Class predicate gate (.contains('show')) misses elements |
Audit every element supposed to flow through the gate |
The pattern that's hit most often: same field name carrying different formulas across surfaces. Always rename or unify. Don't trust two surfaces to "do the same thing" because they share a name.
Where to file bugs
If something looks off — a chip that shouldn't be there, a coach that won't load, a score that doesn't match the cell breakdown, a broker order that errors out — write it down before you investigate. A 30-second note ("MU showed Hidden Tape 5 in modal but 6 in compact") becomes an hour-saver six weeks later when you're debugging the same thing.
Three places to write it:
audit_dashboard.log— the framework's own log. Most issues leave a fingerprint here.tail -50 audit_dashboard.logis the first move.broker_orders.log— broker-specific issues (preflight refusals, place errors, reconciler diffs).- The bug-class catalog above — if it's a pattern that's recurred, add a row.
Swing Deck is a trading framework. Not financial advice.
See Swing Deck Guide Reference for the everyday user manual. See FRAMEWORK_AND_RISK_PILLARS.md for the internal calibration reference (per-cell scoring rubrics, historical case calibration, implementation notes).