diff --git a/README.md b/README.md index 644bbf0..33a2dbc 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ Investing-signal platform for US equities. It runs one strategy, and it is a bor **Philosophy:** don't predict price — rank it. The edge is *relative* strength across the universe, and the discipline is in the exit: cut losers fast, let winners run until the trail catches them. -**What is NOT the edge — read this before trusting a number on screen.** The composite score, the 5 dimensions, sentiment, fundamentals, and the support/resistance engine are **display and screening context**. None has a measured edge. In particular: +**What is NOT the edge — read this before trusting a number on screen.** The composite score, the 5 dimensions, sentiment, fundamentals, and Structural S/R are **display context**, not validated predictors. The Gate Target Ladder is screening machinery that preserves the production setup population; it is not a claim about true market structure. In particular: -- **The S/R "target" is not an exit.** It exists only to compute the R:R and touch-odds that admit a setup through the activation gate. The live exit never reads it. Across 320 backtested production trades the exit reasons were **144 initial stop, 98 trailing stop, 78 max hold — and 0 targets.** Honoring the target as a take-profit was tested and *halves CAGR* ([research](docs/research/sr-levels-and-exits.md)). +- **The headline "target" is not an exit.** It comes from the internal **Gate Target Ladder** and exists only to compute the R:R and reach-probability used by the activation gate. Human-facing chart S/R is a separate model. The live exit reads neither. Across 320 backtested production trades the exit reasons were **144 initial stop, 98 trailing stop, 78 max hold — and 0 targets.** Honoring the target as a take-profit was tested and *halves CAGR* ([research](docs/research/sr-levels-and-exits.md)). - **The composite score does not select trades.** Residual momentum does. Full experiment log — everything tested, kept, and rejected: **[docs/research/](docs/research/README.md)**. @@ -21,10 +21,10 @@ flowchart TD M --> R["Rank cross-sectionally
into percentiles"] R --> G1{"Top 20%?
percentile ≥ 80"} G1 -->|no| SKIP["Not traded
(still scored — the control group)"] - G1 -->|yes| S["Build the setup
entry = last close
stop = entry − 1.5 × ATR
level = nearest S/R above"] + G1 -->|yes| S["Build the setup
entry = last close
stop = entry − 1.5 × ATR
primary = Gate Target Ladder proposal"] S --> G2{"Activation gate"} - G2 --> G2a["R:R ≥ 2.0 (to the S/R level)"] + G2 --> G2a["R:R ≥ 2.0 (to the primary gate target)"] G2 --> G2b["touch odds ≥ 20%"] G2 --> G2c["action not NEUTRAL
and matches direction"] G2a & G2b & G2c --> Q{"qualified?"} @@ -39,7 +39,7 @@ flowchart TD EXIT --> E1["Initial stop hit
entry − 1.5 × ATR → −1R
45% of trades"] EXIT --> E2["Trailing stop hit
highest close − 3 × ATR
only binds once price is ~1R up
31% of trades"] EXIT --> E3["Max hold reached
30 trading days
24% of trades"] - EXIT -.->|"NEVER"| E4["S/R target
0% of trades"] + EXIT -.->|"NEVER"| E4["Gate Target Ladder target
0% of trades"] style M fill:#1e3a5f,color:#fff style OPEN fill:#1e4d2b,color:#fff @@ -54,13 +54,59 @@ flowchart TD Scheduled pipelines turn raw prices into a ranked, gated list of tradeable setups. Everything downstream of OHLCV is recomputed from stored data, so each refresh is cheap and idempotent. Job timing is cron-based and configurable in **Admin → Jobs** (default timezone Europe/Berlin). +### Price-level architecture: two different jobs + +The platform deliberately has two price-level components. Calling both of them +"S/R" hid an important distinction, so the internal screening component is now +named the **Gate Target Ladder (GTL)**. + +| Component | Purpose | Lifetime | Consumed by | +|---|---|---|---| +| **Structural S/R** | A small set of meaningful support/resistance zones for humans | Persisted as `SRLevel` | Charts and alerts | +| **Gate Target Ladder** | A broad set of price proposals that preserves the validated setup screen | Built transiently per scan; never persisted as S/R | Target table, headline R:R and activation gate | + +```mermaid +flowchart TD + O["Ticker OHLCV history"] --> SR["Structural S/R detector
volume peaks + prominent pivots + round numbers
rejection and recency strength"] + SR --> DB[("Persisted SRLevel rows")] + DB --> UI["Charts and alerts"] + + O --> GTL["Gate Target Ladder
20 price-range centers + 5-bar pivots
no volume calculation"] + GTL --> TRAFFIC["Score historical price traffic
merge nearby proposals and tag side"] + TRAFFIC --> HAS{"Any directional proposal
with R:R ≥ 1.5?"} + HAS -->|no| NONE["No setup for that direction"] + HAS -->|yes| TARGETS["Build up to 5 target candidates
estimate reach-probability"] + TARGETS --> PRIMARY["Headline target
most likely candidate clearing
R:R ≥ 1.5 and probability ≥ 20%"] + PRIMARY --> GATE{"Live activation gate
headline R:R ≥ 2.0
probability ≥ 20%
momentum and direction pass?"} + GATE -->|no| OBS["Keep as unqualified observation"] + GATE -->|yes| BOOK["Eligible for production ranking/book"] + BOOK --> EXIT["Exit only by ATR stop/trail
or max hold — never by target"] +``` + +The Gate Target Ladder works step by step: + +1. Build 20 evenly spaced centers over the ticker's observed high/low range and add unfiltered five-bar swing pivots. Volume is not used. +2. Count how often historical bars pass through each proposal, convert that traffic to strength, merge proposals within 0.5%, and label them above/below spot. +3. For each direction, require at least one proposal with scanner R:R ≥ 1.5 against the 1.5× ATR initial stop. +4. Collapse nearby proposals into target zones, discard unsuitable ATR distances, and retain up to five candidates spanning near to far. +5. Estimate each candidate's probability of reaching the target before the stop. The headline target is the most likely candidate with R:R ≥ 1.5 and probability ≥ 20%; if none clears both, the most likely candidate remains headline so a distant lottery target cannot game the gate. +6. Apply the separate live activation floor to that headline target: production requires R:R ≥ 2.0 and probability ≥ 20%, plus the momentum/direction rules. +7. If traded, ignore the target for exits. The initial ATR stop, 3× ATR trail and maximum hold remain authoritative. + +The ladder is intentionally broad and mechanical. It is not presented as +market structure, and its transient negative level IDs must never be stored as +chart S/R. The full-period parity run reproduced all 202,765 backtest setup +candidates, all 1,086 qualified setups, and the production book exactly +(Sharpe 2.03, CAGR 50.0%, max drawdown 21.4%, 321 trades). See the +[S/R and Gate Target Ladder research](docs/research/sr-levels-and-exits.md#explicit-gate-target-ladder). + ### Daily Load — the full refresh Once a day (default 07:00). Steps run **in dependency order**, each consuming the previous step's fresh output: 1. **OHLCV** — fetch the latest daily bars for every tracked ticker (Alpaca); new tickers backfill ~5 years. 2. **Sentiment** — fetch sentiment for the names that matter and are stale (> 5 days): top-pick feeders (residual-momentum leaders with a tradeable long setup), the watchlist, and open paper trades, plus a top-N-by-composite discovery net. Runs *before* the scan so the scan sees fresh sentiment. -3. **R:R Scan** — recompute S/R zones, the 5-dimension scores and long/short setups (ATR stops, S/R targets) for every ticker, and attach each ticker's residual 12‑1 momentum activation percentile plus the promoted 80/20 production rank. +3. **R:R Scan** — persist clean Structural S/R for charts/alerts, recompute the 5-dimension scores, and build long/short setups from a transient Gate Target Ladder (ATR stops and nominal gate targets) for every ticker. Attach each ticker's residual 12‑1 momentum activation percentile plus the promoted 80/20 production rank. 4. **Outcome Eval** — resolve setups that hit target/stop or expired (default 30 trading days) and auto-close paper trades per the exit policy (default: 3x ATR trail with a 30-trading-day max hold). 5. **Market Regime** — recompute the regime index (breadth/trend). 6. **Regime Monitor** — observational early-warning snapshot (VIX, credit spreads via FRED); feeds nothing else. @@ -78,11 +124,11 @@ Fundamentals (weekly, early Monday) · Alerts (hourly, Telegram) · Backtest (we ### From score to "top pick" 1. **Composite score** — technical, S/R-quality, sentiment, fundamental and momentum sub-scores (0–100) combine into a weighted composite (weights configurable; missing dimensions re-normalize). **Display and ranking only — it does not select trades.** -2. **Setups** — the scanner builds long/short setups with a 1.5× ATR stop and picks the nearest S/R level as a nominal target, then adds a confidence score, conflict flags and a per-level touch-probability. -3. **Activation gate** — a setup *qualifies* only if it ranks in the top residual-momentum percentile of the universe (**the actual selection**, long-only), clears the R:R floor, **and** its primary level carries at least a 20% touch-probability. The confidence floor was ablated to zero effect and defaults off. +2. **Setups** — the scanner builds long/short setups with a 1.5× ATR stop, generates up to five candidates from the transient Gate Target Ladder, and makes the most likely worthwhile candidate the headline target. It then adds confidence and conflict context plus a per-target reach-probability. +3. **Activation gate** — a setup *qualifies* only if it ranks in the top residual-momentum percentile of the universe (**the actual selection**, long-only), its headline target clears the live R:R floor, **and** that target carries at least a 20% reach-probability. The confidence floor was ablated to zero effect and defaults off. 4. **Top pick** — qualified setups are ordered by the production rank: 80% residual momentum percentile + 20% 6-month realized-volatility percentile. The #1 is highlighted on the Dashboard and labelled on the ticker page. -**What the R:R and touch-probability in step 3 actually are.** They are *gate inputs*, computed from an S/R level the trade will never exit at — they exist to filter setups, not to forecast the trade you're about to take. A setup with "R:R 2.4:1, 34% touch odds" is not a claim that you'll make 2.4R with 34% probability; it's a claim that this setup cleared the screen. What actually happens to a trade is in the exit box of the diagram above, and on the "what usually happens" panel in the UI. Conflating the two is the single easiest way to misread this app. +**What the R:R and reach-probability in step 3 actually are.** They are *gate inputs*, computed from a Gate Target Ladder proposal the trade will never exit at — they exist to filter setups, not to forecast the trade you're about to take. A setup with "R:R 2.4:1, 34% reach probability" is not a claim that you'll make 2.4R with 34% probability; it's a claim that this setup cleared the screen. What actually happens to a trade is in the exit box of the diagram above, and on the "what usually happens" panel in the UI. Conflating the two is the single easiest way to misread this app. ## Strategy Status — What's Validated and What Isn't @@ -94,13 +140,14 @@ Fundamentals (weekly, early Monday) · Alerts (hourly, Telegram) · Backtest (we |---|---|---| | **Residual 12-1 cross-sectional momentum** (the activation gate, long-only) | **Production gate — in-sample edge** | Promoted July 2026 after the portfolio variant beat raw 80 on CAGR, Sharpe and drawdown. Raw 12-1 remains a fallback only when benchmark data is unavailable | | **3× ATR trailing exit** (+ 1.5× ATR initial stop, 30-day max hold) | **Production exit — best Sharpe of every exit tested** | Beat hold / SMA50 / 20-day-low / technical-40 and both take-profit variants (July 2026) | -| S/R setup engine (ATR stops, S/R levels, touch-probability) | **Gate input only — NOT an exit, and not an edge** | The exit never reads the target (0 of 320 trades). Honoring it as a take-profit drops Sharpe 2.04 → 1.47. The R:R floor does real work *as a filter*; the detector itself is methodologically weak. [Full write-up](docs/research/sr-levels-and-exits.md) | +| **Structural S/R** | **Human-facing context only — not a gate and not an exit** | Clean, capped zones are persisted for charts and alerts. The scanner deliberately does not read them. | +| **Gate Target Ladder** | **Gate input only — not market structure and not an exit** | Volume-free range grid + pivots preserves the useful legacy screening behavior exactly: 1,086/1,086 qualified setups retained and identical Sharpe 2.03 / CAGR 50.0% / DD 21.4% / 321 trades. The exit never reads its target. [Full write-up](docs/research/sr-levels-and-exits.md#explicit-gate-target-ladder) | | Composite score + 5 dimensions | **Display/ranking only** | Sub-scores are hand-built heuristics; none has a measured IC. Note: the "momentum" *dimension* is 5/20-day ROC — NOT the validated 12-1 factor (that lives in `momentum_service`) | | LLM sentiment | Display + a bounded composite adjustment (± weight × 100 pts around neutral 50) | Deliberately kept out of the setup engine; no point-in-time history to validate against yet | | Fundamentals | Feeds composite + confidence only | Latest values only, no history — same limitation | | Short setups | **Excluded while the momentum gate is active** | Backtest showed shorts fight the trend and drag expectancy | | Expected-value gate (removed June 2026) | Degenerate — do not resurrect | Structurally favored distant lottery targets; selected *worse*-than-random setups. Orphaned settings dropped in migration 020 | -| S/R target as a take-profit (tested July 2026) | **Rejected** | Sharpe 2.04 → 1.47, CAGR halved. Win rate *rose* — it truncates the right tail where the edge lives | +| Gate target as a take-profit (tested July 2026) | **Rejected** | Sharpe 2.04 → 1.47, CAGR halved. Win rate *rose* — it truncates the right tail where the edge lives | | "Clear-air" gate relaxation (tested July 2026) | **Rejected — failed out-of-sample** | Strictly better in-sample (Sharpe 2.07 / CAGR 62.3% / DD 20.1%), then lost on a real train/test split (Sharpe 2.78 → 2.45). A cautionary tale: nested lookbacks are not OOS | Caveats on the momentum result: in-sample, roughly one market regime, costs/slippage approximated at 0.1% per side, and residual momentum still needs SPY benchmark history to compute. The **out-of-sample proof is the forward paper-trade record**: Signals → Track Record compares live qualified expectancy against the backtest. @@ -112,7 +159,7 @@ Use this as a regression guardrail for future strategy changes, not as a return | Item | Current baseline | |---|---| | Strategy version | `residual_highvol_80_20_atr_trail3_v1` | -| Production gate | Long-only, residual 12-1 momentum percentile >= 80, R:R >= 2.0 (live `activation_min_rr`; the code default is 1.2), primary-level touch-probability >= 20%, NEUTRAL excluded, confidence floor off (0) | +| Production gate | Long-only, residual 12-1 momentum percentile >= 80, headline gate-target R:R >= 2.0 (live `activation_min_rr`; the code default is 1.2), primary-target reach-probability >= 20%, NEUTRAL excluded, confidence floor off (0) | | Production rank | 80% residual momentum percentile + 20% 6-month realized-volatility percentile | | Exit | Initial ATR stop plus 3x ATR trailing stop, max 30 trading days | | Portfolio CAGR | +50.4% | @@ -183,7 +230,7 @@ Corollaries: never let an unvalidated score gate setups; the outcome evaluator m ## Key Use Cases -- **Find today's best long setup.** On the **Dashboard**, the *Top Setups* table lists residual-gated qualified setups ranked by the production 80/20 residual/high-vol score, with the #1 flagged "Top pick". Each row opens the ticker page for the chart, scores, S/R targets and entry/stop. +- **Find today's best long setup.** On the **Dashboard**, the *Top Setups* table lists residual-gated qualified setups ranked by the production 80/20 residual/high-vol score, with the #1 flagged "Top pick". Each row opens the ticker page for its chart, Structural S/R, Gate Target Ladder targets and entry/stop. - **Track a trade you took.** Mark a setup as a **paper trade**: it's marked-to-market against the latest close, auto-closed by the active exit policy (default: 3x ATR trail with a 30-trading-day max hold), and its sentiment stays fresh while open. *Signals → Track Record* shows the realized edge. ## Stack @@ -208,12 +255,13 @@ Corollaries: never let an unvalidated score gate setups; the outcome evaluator m - Universe bootstrap for `sp500`, `nasdaq100`, `nasdaq_all` via admin endpoint - OHLCV price storage with upsert and validation - Technical indicators: ADX, EMA, RSI, ATR, Volume Profile, Pivot Points, EMA Cross -- Support/Resistance detection with strength scoring and merge-within-tolerance +- Structural Support/Resistance detection with rejection/recency strength, ATR-adaptive merging and a hard cap; persisted for charts and alerts +- Transient Gate Target Ladder — volume-free range grid plus pivots, used only for nominal targets, reach-probability and gate R:R - Sentiment analysis with time-decay weighted scoring - Fundamental data tracking (P/E, revenue growth, earnings surprise, market cap) - 5-dimension scoring engine (technical, S/R quality, sentiment, fundamental, momentum) with configurable weights -- Risk:Reward scanner — long and short setups, 1.5x ATR stops, S/R-based nominal targets, configurable scan R:R threshold (default 1.5:1 — distinct from the activation floor below) -- Activation gate — qualifies setups on a residual-momentum percentile floor (the actual selection), an R:R floor (prod: 2.0) and a 20% primary-level touch-probability floor (validated long-only edge) +- Risk:Reward scanner — long and short setups, 1.5x ATR stops, Gate Target Ladder nominal targets, configurable scan R:R threshold (default 1.5:1 — distinct from the activation floor below) +- Activation gate — qualifies setups on a residual-momentum percentile floor (the actual selection), a headline gate-target R:R floor (prod: 2.0) and a 20% primary-target reach-probability floor (validated long-only edge) - Recommendation layer — directional confidence, conflict detection, per-target reach-probability - Paper trading — take a setup, mark-to-market vs. latest close, auto-close per the exit policy (default: 3x ATR trail with a 30-trading-day max hold; time / percent-trailing / target-stop selectable), realized track record + outcome evaluation - Market-regime index + FRED early-warning monitor (VIX, credit spreads); weekly backtest + manual event study @@ -620,9 +668,9 @@ Context for whoever — human or AI — continues this work. The owner pushes st ### Invariants — do not break these - **`app/services/qualification.py` is mirrored in `frontend/src/lib/qualification.ts`.** Any gate change must land in both, or the UI's "qualified" flags silently disagree with the server. -- **Live scan and backtest share the same pure functions.** The backtest replays production logic through DB-free functions (`compute_technical_from_arrays`, `compute_momentum_from_closes`, `detect_sr_levels`, the recommendation helpers). New strategy logic must stay in pure functions consumed by both paths, or the backtest stops measuring what production actually does. -- **One S/R model app-wide:** `sr_service.detect_sr_levels` + `cluster_sr_zones` (2% tolerance) feed the chart, alerts, and target generation identically. -- **The S/R target is a gate input, never an exit.** `_atr_trailing_close()` does not take it as a parameter, and it must stay that way — take-profit exits were tested and halve CAGR. Any UI or alert that implies the trade exits at the target is a bug ([research](docs/research/sr-levels-and-exits.md)). +- **Live scan and backtest share the same pure functions.** The backtest replays production logic through DB-free functions (`compute_technical_from_arrays`, `compute_momentum_from_closes`, `detect_sr_levels`, `detect_gate_target_ladder`, the recommendation helpers). New strategy logic must stay in pure functions consumed by both paths, or the backtest stops measuring what production actually does. +- **Keep the two price-level models separate.** `detect_sr_levels` produces persisted Structural S/R for charts and alerts. `detect_gate_target_ladder` produces transient screening proposals and must never be persisted or presented as market structure. The scanner must not read `SRLevel` rows for target generation. +- **The Gate Target Ladder target is a gate input, never an exit.** `_atr_trailing_close()` does not take it as a parameter, and it must stay that way — take-profit exits were tested and halve CAGR. Any UI or alert that implies the trade exits at the target is a bug ([research](docs/research/sr-levels-and-exits.md#explicit-gate-target-ladder)). - **The outcome evaluator evaluates ALL setups**, not just qualified ones — unqualified setups are the control group that makes the Track Record meaningful. - **`SystemSetting` access goes through `app/services/settings_store.py`** — don't query the model directly. - **Time-series data gets a real table** (see `benchmark_prices`, `regime_snapshots`); `SystemSetting` JSON is only for config and cached reports. @@ -635,7 +683,7 @@ Context for whoever — human or AI — continues this work. The owner pushes st |---|---| | Composite + 5 dimension scores, weights | `app/services/scoring_service.py` | | Residual 12-1 momentum ranking (the validated activation factor) | `app/services/momentum_service.py` | -| Setup construction (ATR stop, S/R targets) | `app/services/rr_scanner_service.py` | +| Setup construction (ATR stop, Gate Target Ladder targets) | `app/services/rr_scanner_service.py` | | Confidence, targets, reach-probability, action | `app/services/recommendation_service.py` | | Activation gate predicate (mirrored in TS) | `app/services/qualification.py` | | Gate defaults / admin config | `app/services/admin_service.py` (`ACTIVATION_DEFAULTS`) | @@ -643,7 +691,7 @@ Context for whoever — human or AI — continues this work. The owner pushes st | Outcome resolution (target/stop/expired/ambiguous) | `app/services/outcome_service.py` | | Paper trades + time/trailing/target auto-exit | `app/services/paper_trade_service.py` | | Point-in-time setup context snapshots | `app/models/signal_context_snapshot.py` + `app/services/rr_scanner_service.py` | -| S/R detection & zone clustering | `app/services/sr_service.py` | +| Structural S/R detection, Gate Target Ladder & zone clustering | `app/services/sr_service.py` | | **Research log — what's been tested and rejected** | **`docs/research/`** | | SPY benchmark for residual momentum + paper-trade alpha | `app/services/benchmark_service.py` | | Pipelines & job registration | `app/scheduler.py` | diff --git a/docs/research/README.md b/docs/research/README.md index 338c2f0..668923c 100644 --- a/docs/research/README.md +++ b/docs/research/README.md @@ -9,7 +9,8 @@ was run and the data said no.** Detail lives in the linked docs and in cross-sectional momentum book** — buy the top quintile by beta-adjusted 12-1 momentum, tilt toward higher volatility, hold ≤ 10 names, cut at 1.5× ATR, then trail at 3× ATR for up to 30 trading days. Everything else in the app (composite -score, S/R levels, sentiment, fundamentals) is **display or screening**, not edge. +score, Structural S/R, the Gate Target Ladder, sentiment, fundamentals) is +**display or screening**, not edge. --- @@ -22,6 +23,8 @@ score, S/R levels, sentiment, fundamentals) is **display or screening**, not edg | 1.5× ATR initial stop | Real exit | Cuts losers fast | | 3× ATR trailing stop, 30-day max hold | Real exit | Best Sharpe of every exit tested | | Max 10 concurrent positions, 1% risk per trade | Sizing | Cap never binds in practice | +| Structural S/R | Human-facing product context | Clean, capped zones for charts and alerts; not read by the scanner | +| Gate Target Ladder | Screening machinery | Volume-free transient proposals preserve the production candidate set exactly; never an exit | --- @@ -29,7 +32,7 @@ score, S/R levels, sentiment, fundamentals) is **display or screening**, not edg | # | Experiment | Result | Decision | Evidence | |---|---|---|---|---| -| 1 | **S/R target as a take-profit** (exit at the target, with or without the trail) | Sharpe **2.04 → 1.47**, CAGR halved (50.4% → 28.9%). Win rate *rose* (37.5% → 40.0%) — the tell: it truncates the right tail | **Rejected.** The target must never become an exit | [sr-levels-and-exits.md](sr-levels-and-exits.md) · `backtest-20260712-sr-target-exit.json` | +| 1 | **Gate target as a take-profit** (exit at the target, with or without the trail) | Sharpe **2.04 → 1.47**, CAGR halved (50.4% → 28.9%). Win rate *rose* (37.5% → 40.0%) — the tell: it truncates the right tail | **Rejected.** The target must never become an exit | [sr-levels-and-exits.md](sr-levels-and-exits.md) · `backtest-20260712-sr-target-exit.json` | | 2 | **Clear-air fallback** — synthesize a 3× ATR target so 52-week-high breakouts stop being vetoed by "no resistance above" | Looked *strictly better* in-sample (Sharpe 2.07, CAGR 62.3%, DD 20.1%) but **failed a real out-of-sample holdout**: Sharpe 2.78 → 2.45, higher drawdown | **Rejected.** Gate stays as-is | [sr-levels-and-exits.md](sr-levels-and-exits.md) · `backtest-20260712-holdout-*.json` | | 3 | **Blanket S/R fallback** (any missing target, not just clear air) | Sharpe 1.82, per-setup expectancy 0.583 → 0.280 R | **Rejected.** 65% of what it admitted were ATR/R:R filter misses, which are actively bad | [sr-levels-and-exits.md](sr-levels-and-exits.md) | | 4 | **Expected-value gate** (`min_expected_value` replacing the R:R + probability pair) | Structurally favoured distant lottery targets; selected *worse*-than-random setups | **Removed June 2026.** Settings dropped in migration 020 | migration 009, 020 | @@ -57,6 +60,7 @@ invites overfitting. | Selection cutoff {70…90} × book size {10, 15, 20} | **Keep 80 × 10** — monotonically worse in both directions | | Position sizing (equal-weight, inverse-vol, risk-% sweep) | **Keep 1% fixed-fractional** | | Primary-target probability floor | **Keep 20%** — pruned lottery targets, 1,428 → 1,089 qualified, lifted Sharpe | +| Primary-target R:R selector | **Keep 1.5** — target choice is intentionally independent of the later 2.0 activation floor | | Exit policy (hold / SMA50 / 20-day low / technical-40 / ATR trail) | **Keep 3× ATR trail** — best Sharpe (2.04) | | **Activation R:R floor `min_rr`** (swept 2026-07-12) | **Keep 2.0** — best in-sample *and* out-of-sample. But it is a **spike, not a plateau** — see below | @@ -105,7 +109,6 @@ and it would also sever the last dependency the *gate* has on the weak S/R detec | **Broader universe** (`nasdaq_all`) | Strengthens every week's cross-section and the IC t-stat | Also where `fip_id` could become tradeable | | **Forward paper-trade record** | The only true out-of-sample evidence the snapshot cannot give | Time | | **Better target model for clear-air names** | The return is demonstrably there (#2 wins on raw CAGR in *both* train and test); it's the *flat* 3× ATR target that makes it too expensive in risk | Needs a per-name model, not a constant k×ATR | -| **S/R detector quality** | POC/VAH/VAL computed then discarded; HVN = "any above-mean bin"; volume double-counted 1.48×; "touch" counts pass-throughs; no round numbers | Worth fixing for the levels users *see* — but it does **not** reach P&L, so don't justify it on returns | --- @@ -133,11 +136,12 @@ out-of-sample, or turned out to be measuring something other than what it claime What's left is a boring, well-documented result: **cross-sectional momentum works; the machinery around it mostly doesn't.** -The S/R engine, the composite score, the sentiment and fundamentals dimensions are -all still in the product — they make the app legible and are useful context for a -human — but none of them has a measured edge, and the platform is honest about -that in the UI (see the exit plan and base-rate panels on every setup card). The -one component that *does* have an edge is the momentum gate, and every knob on it -has been swept and confirmed. +Structural S/R, the composite score, sentiment and fundamentals remain useful +human context but have no measured edge. The Gate Target Ladder is different: +it is internal screening machinery whose broad historical-price-traffic behavior +was preserved explicitly and volume-free, with exact full-period parity. It is +still neither market structure nor an exit. The one component that *does* have +measured predictive edge is the momentum gate, and every knob on it has been +swept and confirmed. The next real evidence is **forward**, not backward: the live paper-trade record. diff --git a/docs/research/sr-levels-and-exits.md b/docs/research/sr-levels-and-exits.md index b85727e..ff8a6d9 100644 --- a/docs/research/sr-levels-and-exits.md +++ b/docs/research/sr-levels-and-exits.md @@ -4,10 +4,14 @@ **Question that started it:** are our support/resistance levels built the way best practice says they should be, and do we actually use them that way? -Short answer: the detector is weak against best practice, but its reach into P&L -runs entirely through the **entry gate** — not the exit. Honoring the target as a -take-profit was tested and is decisively worse. Whether the S/R-derived gate is -net-positive is the open question, tracked below. +Final answer: one level model should not serve two different jobs. The clean +**Structural S/R** detector now supplies persisted chart and alert structure. +The transient **Gate Target Ladder** preserves the broad historical-price +traffic proposals that the setup screen depends on. Its headline target affects +entry qualification only; honoring it as a take-profit is decisively worse. +The final volume-free implementation reproduced the production candidate set +and portfolio exactly. The sections below retain the investigation that led to +that split. --- @@ -690,6 +694,58 @@ and LVN bins, whose union is the complete 20-bin price-range grid, then adds unfiltered pivots and touch strength. For the gate this behaves as a broad target-proposal ladder, not human-facing support/resistance. +**Component name: Gate Target Ladder (GTL).** "Structural S/R" names the +separate, persisted human-facing model. "Gate Target Ladder" names this +transient screening component and avoids implying that its dense proposals are +real support/resistance. + +#### Runtime decision flow + +```mermaid +flowchart TD + O["Ticker OHLCV history"] --> STRUCT["Structural S/R
clean detector"] + STRUCT --> STORE[("Persist SRLevel")] + STORE --> HUMAN["Charts and alerts"] + + O --> LADDER["Gate Target Ladder
20 range centers + 5-bar pivots"] + LADDER --> SCORE["Count historical price traffic
strength + 0.5% merge + side tag"] + SCORE --> SCAN{"Directional proposal
with R:R ≥ 1.5?"} + SCAN -->|no| NOSETUP["No setup for that direction"] + SCAN -->|yes| ZONES["Cluster 2% target zones
use reachable near edge"] + ZONES --> FILTER["ATR-distance filter
retain up to 5 near-to-far candidates"] + FILTER --> PROB["Estimate target-before-stop
reach probability"] + PROB --> PRIMARY["Headline = most likely candidate
clearing R:R ≥ 1.5 and probability ≥ 20%"] + PRIMARY --> ACTIVATE{"Activation gate
headline R:R ≥ 2.0
probability ≥ 20%
momentum/direction pass?"} + ACTIVATE -->|no| OBS["Store as unqualified observation"] + ACTIVATE -->|yes| QUAL["Eligible for production book"] + QUAL --> EXIT["ATR stop/trail or max hold
target is never an exit"] +``` + +Step by step: + +1. `scan_ticker` loads the ticker's OHLCV history and computes the 1.5× ATR + initial stop. It does not query persisted `SRLevel` rows for targets. +2. `detect_gate_target_ladder` creates 20 evenly spaced centers over the + observed low/high range and adds unfiltered five-bar swing highs/lows. The + implementation performs no volume calculation. +3. Each proposal is scored by the share of historical bars whose range crosses + it. Proposals within 0.5% are merged, their traffic strengths combine, and + they are tagged support/resistance relative to the latest close. The scanner + materializes them with negative transient IDs; they are never persisted. +4. A direction exists only when at least one proposal clears the scanner's 1.5 + R:R floor. This is setup construction, not the later live activation gate. +5. The recommendation layer clusters proposals into 2% target zones, uses each + zone's reachable near edge, removes unsuitable ATR distances, and keeps up + to five candidates spanning near, moderate and far distances. +6. Each retained candidate gets a target-before-stop reach probability based on + distance, R:R, traffic strength and signal alignment. +7. The headline target is the most likely candidate clearing both R:R ≥ 1.5 + and probability ≥ 20%. If none does, the most likely target overall remains + headline so a distant high-R:R lottery target cannot game qualification. +8. The separate live gate then requires headline R:R ≥ 2.0 and probability ≥ + 20%, plus the residual-momentum and direction rules. A traded setup still + exits only through the ATR stop/trail or maximum hold. + The `explicit_target_ladder` arm therefore replaces only the irrelevant volume pass with the complete range grid. It retains pivots, touch strength, merge geometry, primary selection, qualification, ranking, and exit behavior. Grid diff --git a/reports/README.md b/reports/README.md index 03774e3..655170c 100644 --- a/reports/README.md +++ b/reports/README.md @@ -47,7 +47,8 @@ The follow-up breadth-preserving ranking experiment writes pre-registered 5% clean-structure overlay; exploratory weight sweeps should not be committed. -The explicit gate-ladder parity run writes: +The Gate Target Ladder (the `explicit_target_ladder` research arm) parity run +writes: - `backtest-sr-full-explicit_target_ladder.json` - `sr-explicit-target-ladder-comparison.json`