Merge branch 'codex/sr-v2-research-harness'
Deploy / lint (push) Successful in 11s
Deploy / test (push) Successful in 1m16s
Deploy / deploy (push) Successful in 40s

- Finalize GTL and retire S/R research harness
- Cleanup retired research scaffolding (remove dead filters, mark diagnostic code, document env vars)
This commit is contained in:
2026-07-13 18:52:31 +02:00
55 changed files with 10323 additions and 278 deletions
+4
View File
@@ -17,9 +17,13 @@ build/
# IDE
.vscode/
.idea/
.claude/settings.local.json
*.swp
*.swo
# Local AI tool metadata
mcps/
# OS
.DS_Store
Thumbs.db
+102 -21
View File
@@ -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<br/>into percentiles"]
R --> G1{"Top 20%?<br/>percentile ≥ 80"}
G1 -->|no| SKIP["Not traded<br/><i>(still scored — the control group)</i>"]
G1 -->|yes| S["Build the setup<br/>entry = last close<br/><b>stop = entry 1.5 × ATR</b><br/>level = nearest S/R above"]
G1 -->|yes| S["Build the setup<br/>entry = last close<br/><b>stop = entry 1.5 × ATR</b><br/>primary = Gate Target Ladder proposal"]
S --> G2{"Activation gate"}
G2 --> G2a["R:R ≥ 2.0 <i>(to the S/R level)</i>"]
G2 --> G2a["R:R ≥ 2.0 <i>(to the primary gate target)</i>"]
G2 --> G2b["touch odds ≥ 20%"]
G2 --> G2c["action not NEUTRAL<br/>and matches direction"]
G2a & G2b & G2c --> Q{"qualified?"}
@@ -39,7 +39,7 @@ flowchart TD
EXIT --> E1["Initial stop hit<br/>entry 1.5 × ATR → 1R<br/><b>45% of trades</b>"]
EXIT --> E2["Trailing stop hit<br/>highest close 3 × ATR<br/><i>only binds once price is ~1R up</i><br/><b>31% of trades</b>"]
EXIT --> E3["Max hold reached<br/>30 trading days<br/><b>24% of trades</b>"]
EXIT -.->|"NEVER"| E4["S/R target<br/><b>0% of trades</b>"]
EXIT -.->|"NEVER"| E4["Gate Target Ladder target<br/><b>0% of trades</b>"]
style M fill:#1e3a5f,color:#fff
style OPEN fill:#1e4d2b,color:#fff
@@ -54,13 +54,74 @@ 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<br/>volume peaks + prominent pivots + round numbers<br/>rejection and recency strength"]
SR --> DB[("Persisted SRLevel rows")]
DB --> UI["Charts and alerts"]
O --> GTL["Gate Target Ladder<br/>20 price-range centers + 5-bar pivots<br/>no volume calculation"]
GTL --> TRAFFIC["Score historical price traffic<br/>merge nearby proposals and tag side"]
TRAFFIC --> HAS{"Any directional proposal<br/>with R:R ≥ 1.5?"}
HAS -->|no| NONE["No setup for that direction"]
HAS -->|yes| TARGETS["Build up to 5 target candidates<br/>estimate reach-probability"]
TARGETS --> PRIMARY["Headline target<br/>most likely candidate clearing<br/>R:R ≥ 1.5 and probability ≥ 20%"]
PRIMARY --> GATE{"Live activation gate<br/>headline R:R ≥ 2.0<br/>probability ≥ 20%<br/>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<br/>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).
**Ticker-chart diagnostic.** The optional **GTL traffic** toggle draws a
right-edge horizontal profile aligned to the price axis. It borrows the visual
grammar of a volume profile, but not its meaning: bar width is relative
historical OHLCV-bar crossings at each GTL proposal, not traded volume at that
price. Hover a bar to inspect its price, crossing count, strength and source.
The violet profile is deliberately distinct from the Structural S/R lines and
is off by default; it is a research aid, not another trade overlay.
Below the chart, the **Production rank** strip makes the current 80/20 ordering
snapshot explicit: a blue residual-momentum contribution and amber realized-
volatility contribution add to the stored strategy rank, while separate
percentile rails show each input. Only momentum carries the live activation-
gate marker. These are cross-sectional scan percentiles, not historical chart
indicators.
### 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 121 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 121 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 +139,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 (0100) 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 +155,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 +174,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 +245,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 +270,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
@@ -227,6 +290,8 @@ Corollaries: never let an unvalidated score gate setups; the outcome evaluator m
- Glassmorphism UI with frosted glass panels, gradient text, ambient glow effects, mesh gradient background
- Interactive candlestick chart (Canvas 2D) with hover tooltips showing OHLCV values
- Support/Resistance level overlays on chart (top 6 by strength, dashed lines with labels)
- Optional GTL price-traffic profile on the ticker chart (right-edge diagnostic; explicitly not volume)
- Production-rank strip below the ticker chart (80/20 contribution ledger plus separate momentum and volatility percentiles)
- Data freshness bar showing availability and recency of each data source
- Watchlist with composite scores, R:R ratios, and S/R summaries
- Ticker detail page: chart, scores, sentiment breakdown, fundamentals, technical indicators, S/R table
@@ -265,6 +330,7 @@ All under `/api/v1/`. Interactive docs at `/docs` (Swagger) and `/redoc`.
| Ingestion | `POST /ingestion/fetch/{symbol}` |
| Indicators | `GET /indicators/{symbol}/{type}`, `GET /indicators/{symbol}/ema-cross` |
| S/R Levels | `GET /sr-levels/{symbol}` |
| Gate Target Ladder | `GET /gate-target-ladder/{symbol}` |
| Sentiment | `GET /sentiment/{symbol}` |
| Fundamentals | `GET /fundamentals/{symbol}` |
| Scores | `GET /scores/{symbol}`, `GET /rankings`, `PUT /scores/weights` |
@@ -393,6 +459,21 @@ metrics. Keep the SSH tunnel open only while creating the snapshot; the backtest
run itself is local/offline. `backtest_snapshots/` and generated backtest reports
are git-ignored.
The local runner, scheduled job, and Admin UI all default to
`production_gtl`, matching the live scanner's target path. For a deliberate
comparison, select **Structural S/R (comparison)** in the UI or pass
`--target-model structural_sr` locally. Every report records the selected model
and whether it is the production path.
### Archived GTL tuning decision
The completed replacement, cohort-composition, and strength-sensitivity
matrices found no stable improvement over the frozen Gate Target Ladder. The
temporary matrix runners and tuning hooks have been retired; their three compact
consolidated report pairs remain in `reports/` as the decision audit. Keep the
GTL unchanged and evaluate any future challenger only on new forward data. See
the [full research record](docs/research/sr-levels-and-exits.md#gtl-tuning-matrix).
### Reading a local backtest report
The deployed **Signals → Track Record** page is deliberately trimmed to validation
@@ -616,9 +697,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.
@@ -631,7 +712,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`) |
@@ -639,7 +720,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` |
+8 -2
View File
@@ -13,6 +13,7 @@ from app.schemas.admin import (
AlertConfigUpdate,
CreateUserRequest,
DataCleanupRequest,
JobTriggerRequest,
JobToggle,
RecommendationConfigUpdate,
ScheduleConfigUpdate,
@@ -376,11 +377,16 @@ async def get_pipeline_readiness(
@router.post("/admin/jobs/{job_name}/trigger", response_model=APIEnvelope)
async def trigger_job(
job_name: str,
body: JobTriggerRequest | None = None,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Trigger a manual job run (placeholder)."""
result = await admin_service.trigger_job(db, job_name)
"""Trigger a manual job run, optionally with one-run parameters."""
result = await admin_service.trigger_job(
db,
job_name,
target_model=body.target_model if body is not None else None,
)
return APIEnvelope(status="success", data=result)
+63 -3
View File
@@ -5,17 +5,77 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db, require_access
from app.schemas.common import APIEnvelope
from app.schemas.sr_level import SRLevelResponse, SRLevelResult, SRZoneResult
from app.schemas.sr_level import (
GateTargetLadderResponse,
GateTargetLevelResult,
SRLevelResponse,
SRLevelResult,
SRZoneResult,
)
from app.services.price_service import query_ohlcv
from app.services.sr_service import cluster_sr_zones, get_sr_levels
from app.services.sr_service import (
cluster_sr_zones,
detect_gate_target_ladder,
get_sr_levels,
)
router = APIRouter(tags=["sr-levels"])
@router.get("/gate-target-ladder/{symbol}", response_model=APIEnvelope)
async def read_gate_target_ladder(
symbol: str,
_user=Depends(require_access),
db: AsyncSession = Depends(get_db),
) -> APIEnvelope:
"""Return the transient, volume-free GTL for chart diagnostics.
These proposals are not persisted ``SRLevel`` rows and must not be
presented as structural support/resistance.
"""
records = await query_ohlcv(db, symbol)
if not records:
data = GateTargetLadderResponse(
symbol=symbol.upper(),
levels=[],
count=0,
lookback_bars=0,
)
return APIEnvelope(status="success", data=data.model_dump())
highs = [float(record.high) for record in records]
lows = [float(record.low) for record in records]
closes = [float(record.close) for record in records]
detected = detect_gate_target_ladder(highs, lows, closes)
levels = [
GateTargetLevelResult(
price_level=float(level["price_level"]),
type=level["type"],
strength=int(level["strength"]),
detection_method=str(level.get("detection_method", "unknown")),
sources=list(level.get("sources") or []),
traffic_count=int(level.get("rejection_count", 0) or 0),
)
for level in sorted(detected, key=lambda row: float(row["price_level"]))
]
data = GateTargetLadderResponse(
symbol=symbol.upper(),
levels=levels,
count=len(levels),
lookback_bars=len(records),
)
return APIEnvelope(status="success", data=data.model_dump())
@router.get("/sr-levels/{symbol}", response_model=APIEnvelope)
async def read_sr_levels(
symbol: str,
tolerance: float = Query(0.005, ge=0, le=0.1, description="Merge tolerance (default 0.5%)"),
tolerance: float | None = Query(
None,
ge=0,
le=0.1,
description="Merge tolerance as fraction of price; omit for ATR-adaptive default",
),
max_zones: int = Query(6, ge=0, description="Max S/R zones to return (default 6)"),
_user=Depends(require_access),
db: AsyncSession = Depends(get_db),
+44 -4
View File
@@ -35,7 +35,12 @@ from app.providers.fundamentals_chain import build_fundamental_provider_chain
from app.providers.protocol import SentimentData
from app.services import fundamental_service, ingestion_service, sentiment_service, settings_store
from app.services.alert_service import dispatch_alerts
from app.services.backtest_service import run_and_store as run_backtest_and_store
from app.services.backtest_service import (
BACKTEST_TARGET_MODELS,
PRODUCTION_GTL_TARGET_MODEL,
run_and_store as run_backtest_and_store,
validate_backtest_target_model,
)
from app.services.benchmark_service import refresh_benchmark_prices
from app.services.market_regime_service import update_market_regime
from app.services.regime_monitor_service import update_regime_monitor
@@ -106,6 +111,7 @@ def _idle_runtime() -> dict[str, object]:
_job_runtime: dict[str, dict[str, object]] = {name: _idle_runtime() for name in _JOB_NAMES}
_next_backtest_target_model = PRODUCTION_GTL_TARGET_MODEL
# ---------------------------------------------------------------------------
@@ -113,6 +119,26 @@ _job_runtime: dict[str, dict[str, object]] = {name: _idle_runtime() for name in
# ---------------------------------------------------------------------------
def queue_backtest_target_model(target_model: str | None) -> str:
"""Select the model for the next manual backtest run only.
Scheduled runs and subsequent manual runs return to the production GTL.
"""
global _next_backtest_target_model
selected = validate_backtest_target_model(
target_model or PRODUCTION_GTL_TARGET_MODEL
)
_next_backtest_target_model = selected
return selected
def _consume_backtest_target_model() -> str:
global _next_backtest_target_model
selected = _next_backtest_target_model
_next_backtest_target_model = PRODUCTION_GTL_TARGET_MODEL
return selected
def _log_event(level: int, event: str, **fields: object) -> None:
"""Emit a structured JSON log line: {"event": ..., **fields}."""
logger.log(level, json.dumps({"event": event, **fields}))
@@ -939,7 +965,13 @@ async def compute_regime_monitor() -> None:
async def run_backtest_job() -> None:
"""Replay the price-derived engine over history and cache the report."""
job_name = "backtest"
_log_event(logging.INFO, "job_start", job=job_name)
target_model = _consume_backtest_target_model()
_log_event(
logging.INFO,
"job_start",
job=job_name,
target_model=target_model,
)
_runtime_start(job_name)
def _on_progress(done: int, count: int, symbol: str) -> None:
@@ -952,12 +984,20 @@ async def run_backtest_job() -> None:
_runtime_finish(job_name, "skipped", processed=0, total=0, message="Disabled")
return
report = await run_backtest_and_store(db, _on_progress)
report = await run_backtest_and_store(
db,
_on_progress,
target_model=target_model,
)
_runtime_finish(
job_name, "completed",
processed=report.get("tickers", 0), total=report.get("tickers", 0),
message=f"{report.get('candidates', 0)} setups, {report.get('qualified', 0)} qualified",
message=(
f"{BACKTEST_TARGET_MODELS[target_model]}: "
f"{report.get('candidates', 0)} setups, "
f"{report.get('qualified', 0)} qualified"
),
)
_log_event(logging.INFO, "job_complete", job=job_name, candidates=report.get("candidates"))
except Exception as exc:
+5
View File
@@ -43,6 +43,11 @@ class JobToggle(BaseModel):
enabled: bool
class JobTriggerRequest(BaseModel):
"""Optional parameters for a one-time manual job run."""
target_model: Literal["production_gtl", "structural_sr"] | None = None
class RecommendationConfigUpdate(BaseModel):
high_confidence_threshold: float | None = Field(default=None, ge=0, le=100)
moderate_confidence_threshold: float | None = Field(default=None, ge=0, le=100)
+23 -1
View File
@@ -15,7 +15,9 @@ class SRLevelResult(BaseModel):
price_level: float
type: Literal["support", "resistance"]
strength: int = Field(ge=0, le=100)
detection_method: Literal["volume_profile", "pivot_point", "merged"]
detection_method: Literal[
"volume_profile", "pivot_point", "merged", "round_number"
]
created_at: datetime
@@ -38,3 +40,23 @@ class SRLevelResponse(BaseModel):
zones: list[SRZoneResult] = []
visible_levels: list[SRLevelResult] = []
count: int
class GateTargetLevelResult(BaseModel):
"""A transient Gate Target Ladder proposal for diagnostic display."""
price_level: float
type: Literal["support", "resistance"]
strength: int = Field(ge=0, le=100)
detection_method: str
sources: list[str] = Field(default_factory=list)
traffic_count: int = Field(ge=0)
class GateTargetLadderResponse(BaseModel):
"""Volume-free Gate Target Ladder computed from current OHLCV history."""
symbol: str
levels: list[GateTargetLevelResult]
count: int
lookback_bars: int
+17 -2
View File
@@ -602,13 +602,20 @@ async def list_jobs(db: AsyncSession) -> list[dict]:
return jobs_out
async def trigger_job(db: AsyncSession, job_name: str) -> dict[str, str]:
async def trigger_job(
db: AsyncSession,
job_name: str,
*,
target_model: str | None = None,
) -> dict[str, str]:
"""Trigger a manual job run via the scheduler.
Runs the job immediately (in addition to its regular schedule).
"""
if job_name not in VALID_JOB_NAMES:
raise ValidationError(f"Unknown job: {job_name}. Valid jobs: {', '.join(sorted(VALID_JOB_NAMES))}")
if target_model is not None and job_name != "backtest":
raise ValidationError("target_model is supported only for the backtest job")
from app.scheduler import get_job_runtime_snapshot, scheduler
@@ -635,11 +642,19 @@ async def trigger_job(db: AsyncSession, job_name: str) -> dict[str, str]:
if job is None:
return {"job": job_name, "status": "not_found", "message": f"Job '{job_name}' is not registered in the scheduler"}
if job_name == "backtest":
from app.scheduler import queue_backtest_target_model
target_model = queue_backtest_target_model(target_model)
job.modify(next_run_time=None) # Reset, then trigger immediately
from datetime import datetime, timezone
job.modify(next_run_time=datetime.now(timezone.utc))
return {"job": job_name, "status": "triggered", "message": f"Job '{job_name}' triggered for immediate execution"}
result = {"job": job_name, "status": "triggered", "message": f"Job '{job_name}' triggered for immediate execution"}
if target_model is not None:
result["target_model"] = target_model
return result
async def toggle_job(db: AsyncSession, job_name: str, enabled: bool) -> SystemSetting:
+185 -36
View File
@@ -18,6 +18,18 @@ after D to record the realized outcome. The report contains:
Limitation: sentiment and fundamentals have no point-in-time history, so they're
held neutral here — this calibrates the price/S-R machinery only.
Environment variables (see also run_backtest_snapshot.py):
Production / general use:
BACKTEST_HOLDOUT_SPLIT=YYYY-MM-DD # disjoint train/test split
BACKTEST_SNAPSHOT_OFFLINE=1
BACKTEST_ALLOW_SPAWN=1 # for Windows multiprocessing
Research / diagnostic only (retired experiments — do not use for live decisions):
BACKTEST_ATR_TARGET_FALLBACK=3
BACKTEST_FALLBACK_CLEAR_AIR_ONLY=1
BACKTEST_RESEARCH_EXITS=1
BACKTEST_MIN_RR_SWEEP=1
"""
from __future__ import annotations
@@ -81,7 +93,7 @@ from app.services.scoring_service import (
compute_momentum_from_closes,
compute_technical_from_arrays,
)
from app.services.sr_service import detect_sr_levels
from app.services.sr_service import detect_gate_target_ladder, detect_sr_levels
logger = logging.getLogger(__name__)
@@ -91,6 +103,12 @@ STEP_DAYS = 5 # weekly cadence (≈ 5 trading days)
MIN_LOOKBACK = 60 # bars needed before D for indicators (EMA cross needs 51)
HORIZON = 30 # trading days to resolve an outcome (matches the evaluator)
ATR_MULTIPLIER = 1.5
PRODUCTION_GTL_TARGET_MODEL = "production_gtl"
STRUCTURAL_SR_TARGET_MODEL = "structural_sr"
BACKTEST_TARGET_MODELS = {
PRODUCTION_GTL_TARGET_MODEL: "Live GTL (production)",
STRUCTURAL_SR_TARGET_MODEL: "Structural S/R (comparison)",
}
# Cross-sectional signal evaluation (factor IC). Each candidate signal is a
# point-in-time number computed from closes alone (sentiment/fundamentals have no
@@ -120,18 +138,38 @@ def _wrap_levels(level_dicts: list[dict]) -> list[Any]:
price_level=float(d["price_level"]),
type=d["type"],
strength=int(d["strength"]),
detection_method=d.get("detection_method", "unknown"),
sources=list(d.get("sources") or [d.get("detection_method", "unknown")]),
rejection_count=int(d.get("rejection_count", 0) or 0),
last_rejection_age=d.get("last_rejection_age"),
)
for i, d in enumerate(level_dicts)
]
def validate_backtest_target_model(value: str) -> str:
"""Validate the small, user-facing set of supported backtest target models."""
normalized = value.strip().lower()
if normalized not in BACKTEST_TARGET_MODELS:
allowed = ", ".join(BACKTEST_TARGET_MODELS)
raise ValueError(f"Unknown backtest target model {value!r}; expected one of {allowed}")
return normalized
# ---------------------------------------------------------------------------
# RESEARCH / DIAGNOSTIC FALLBACKS (retired experiments)
#
# These implement behavior from experiments that were rejected for production
# (clear-air synthetic targets, blanket ATR fallbacks). They are OFF by default
# and exist only to reproduce historical research results or run future ablations.
# See docs/research/sr-levels-and-exits.md.
# Do NOT enable for production decision making.
# ---------------------------------------------------------------------------
def _atr_target_fallback_k() -> float | None:
"""Research ablation: k for a synthetic k*ATR target when a direction has no
S/R level to aim at. Off (None) by default, which is production behavior
no resistance above means no long setup at all. That veto lands hardest on
names at 52-week highs (clear air above), i.e. exactly what the momentum gate
selects, so this flag exists to measure what the veto costs. Set
BACKTEST_ATR_TARGET_FALLBACK=3 to enable. See docs/research/sr-levels-and-exits.md."""
"""RESEARCH DIAGNOSTIC: k for a synthetic k*ATR target when no S/R level.
Off (None) by default (production behavior). Set BACKTEST_ATR_TARGET_FALLBACK=3
to enable. See docs/research/sr-levels-and-exits.md."""
raw = os.getenv("BACKTEST_ATR_TARGET_FALLBACK", "").strip()
if not raw:
return None
@@ -143,13 +181,8 @@ def _atr_target_fallback_k() -> float | None:
def _fallback_clear_air_only() -> bool:
"""Restrict the fallback to setups with genuinely NO structure ahead.
Without this, the fallback also fires when levels DO exist ahead but
``TargetGenerator``'s distance filters rejected them (nearer than 1 ATR, or
past ``max_atr_multiple``). Measured on the snapshot, that's 65% of what the
fallback admits — a different population from the clear-air breakouts, which
confounds the famine test. Set BACKTEST_FALLBACK_CLEAR_AIR_ONLY=1."""
"""RESEARCH DIAGNOSTIC: restrict fallback to genuine clear-air cases only.
Set BACKTEST_FALLBACK_CLEAR_AIR_ONLY=1."""
return os.getenv("BACKTEST_FALLBACK_CLEAR_AIR_ONLY", "").strip().lower() in {
"1", "true", "yes", "on",
}
@@ -170,11 +203,7 @@ def _has_structure_ahead(direction: str, entry: float, sr_levels: list[Any]) ->
def _atr_fallback_target(
direction: str, entry: float, stop: float, atr: float, k: float
) -> dict:
"""A synthetic target k*ATR from entry, shaped like a TargetGenerator row.
``sr_strength`` is 50 (neutral) so the probability model's strength magnet
contributes nothing — the target stands on distance alone.
"""
"""RESEARCH DIAGNOSTIC: synthetic target k*ATR (neutral strength)."""
price = entry + k * atr if direction == "long" else entry - k * atr
distance = abs(price - entry)
risk = abs(entry - stop)
@@ -193,6 +222,8 @@ def _window_setups(
window_records: list,
config: dict,
activation: dict,
*,
target_model: str = PRODUCTION_GTL_TARGET_MODEL,
) -> list[dict]:
"""Rebuild the setup(s) at the last bar of ``window_records`` (the as-of date),
using only those bars. Returns one dict per tradeable direction."""
@@ -219,10 +250,21 @@ def _window_setups(
if atr <= 0:
return []
sr_levels = _wrap_levels(detect_sr_levels(highs, lows, closes, volumes))
target_model = validate_backtest_target_model(target_model)
if target_model == PRODUCTION_GTL_TARGET_MODEL:
detected_levels = detect_gate_target_ladder(
highs,
lows,
closes,
)
else:
detected_levels = detect_sr_levels(highs, lows, closes, volumes)
sr_levels = _wrap_levels(detected_levels)
if not sr_levels:
return []
gate_levels = list(sr_levels)
technical = (compute_technical_from_arrays(highs, lows, closes, volumes)[0]) or 50.0
momentum = (compute_momentum_from_closes(closes)[0]) or 50.0
dim_scores = {"technical": technical, "momentum": momentum}
@@ -237,14 +279,19 @@ def _window_setups(
per_dir: dict[str, dict] = {}
for direction in ("long", "short"):
stop = entry - atr * ATR_MULTIPLIER if direction == "long" else entry + atr * ATR_MULTIPLIER
zone_levels = _zone_representative_levels(sr_levels, entry)
zone_levels = _zone_representative_levels(
gate_levels,
entry,
strength_mode="sum",
)
targets = target_generator.generate_targets(direction, entry, stop, zone_levels, atr)
if not targets:
fallback_k = _atr_target_fallback_k()
if fallback_k is None:
continue
# RESEARCH DIAGNOSTIC only (see _atr_target_fallback_k etc.)
if _fallback_clear_air_only() and _has_structure_ahead(direction, entry, sr_levels):
continue # structure exists ahead; the distance filters rejected it, not the famine
continue
targets = [_atr_fallback_target(direction, entry, stop, atr, fallback_k)]
for t in targets:
t["probability"] = probability_estimator.estimate_probability(
@@ -254,7 +301,10 @@ def _window_setups(
# Collapse duplicate floor-pinned lottery targets (parity with
# enhance_trade_setup).
targets = _prune_floor_pinned_targets(targets)
primary = _select_primary_target(targets)
primary = _select_primary_target(
targets,
min_rr=1.5,
)
if primary is None:
continue
# Flag the primary so qualification's EV uses the primary target's
@@ -309,6 +359,18 @@ def _window_setups(
"meets_core": meets_core,
"action": action,
"risk_level": risk_level,
"target_model": target_model,
"primary_sources": list(primary.get("sr_sources") or []),
"primary_strength": float(primary.get("sr_strength", 0.0)),
"primary_rejection_count": int(
primary.get("sr_rejection_count", 0) or 0
),
"primary_last_rejection_age": primary.get("sr_last_rejection_age"),
"primary_distance_atr": float(
primary.get("distance_atr_multiple", 0.0)
),
"raw_level_count": len(sr_levels),
"gate_level_count": len(gate_levels),
})
return out
@@ -392,6 +454,7 @@ def _replay_ticker(
config: dict,
activation: dict,
benchmark_closes: dict[date, float] | None = None,
target_model: str = PRODUCTION_GTL_TARGET_MODEL,
) -> list[dict]:
"""Walk one ticker's history weekly, building setups and their realized outcomes."""
candidates: list[dict] = []
@@ -410,7 +473,13 @@ def _replay_ticker(
)
vol_6m = _realized_vol_6m(closes, len(window) - 1)
for s in _window_setups(window, config, activation):
setups = _window_setups(
window,
config,
activation,
target_model=target_model,
)
for s in setups:
outcome, outcome_date = evaluate_setup_against_bars(
s["direction"], s["stop"], s["target"], forward_bars, HORIZON
)
@@ -458,6 +527,14 @@ def _replay_ticker(
# every candidate looks NEUTRAL and the ablation rows collapse.
"action": s["action"],
"risk_level": s["risk_level"],
"target_model": s["target_model"],
"primary_sources": s["primary_sources"],
"primary_strength": s["primary_strength"],
"primary_rejection_count": s["primary_rejection_count"],
"primary_last_rejection_age": s["primary_last_rejection_age"],
"primary_distance_atr": s["primary_distance_atr"],
"raw_level_count": s["raw_level_count"],
"gate_level_count": s["gate_level_count"],
"outcome": outcome,
"target_hit": target_hit,
"realized_r": realized_r,
@@ -518,6 +595,45 @@ def _robustness_stats(net_rs: list[float]) -> dict:
}
def _target_model_diagnostics(candidates: list[dict], target_model: str) -> dict:
"""Compact target-source diagnostics for the selected supported model."""
source_counts: dict[str, int] = defaultdict(int)
round_only = 0
strengths: list[float] = []
distances: list[float] = []
rejections: list[int] = []
raw_counts: list[int] = []
gate_counts: list[int] = []
for cand in candidates:
sources = list(cand.get("primary_sources") or [])
for source in sources:
source_counts[str(source)] += 1
if set(sources) == {"round_number"}:
round_only += 1
strengths.append(float(cand.get("primary_strength", 0.0)))
distances.append(float(cand.get("primary_distance_atr", 0.0)))
rejections.append(int(cand.get("primary_rejection_count", 0) or 0))
raw_counts.append(int(cand.get("raw_level_count", 0) or 0))
gate_counts.append(int(cand.get("gate_level_count", 0) or 0))
def avg(values: list[float] | list[int]) -> float | None:
return round(sum(values) / len(values), 3) if values else None
return {
"target_model": target_model,
"target_model_label": BACKTEST_TARGET_MODELS[target_model],
"candidate_count": len(candidates),
"primary_source_counts": dict(sorted(source_counts.items())),
"primary_round_only": round_only,
"primary_strength_100": sum(1 for value in strengths if value >= 100.0),
"avg_primary_strength": avg(strengths),
"avg_primary_distance_atr": avg(distances),
"avg_primary_rejection_count": avg(rejections),
"avg_raw_level_count": avg(raw_counts),
"avg_gate_level_count": avg(gate_counts),
}
# The fixed take-profit and trailing-stop sweeps were retired 2026-07: swept
# TPs never found an interior optimum (momentum's edge lives in the right tail)
# and wide trails converged to the hold-to-horizon exit, so the time-exit sweep
@@ -850,6 +966,7 @@ def _replay_and_signals(
config: dict,
activation: dict,
benchmark_closes: dict[date, float] | None = None,
target_model: str = PRODUCTION_GTL_TARGET_MODEL,
) -> tuple[list[dict], dict]:
"""The CPU-bound per-ticker work, as a top-level (picklable) function so it can
run in a worker process. Takes primitive column arrays (cheap to pickle),
@@ -862,7 +979,14 @@ def _replay_and_signals(
for o, op, hi, lo, cl, vo in zip(date_ords, opens, highs, lows, closes, volumes)
]
return (
_replay_ticker(symbol, bars, config, activation, benchmark_closes),
_replay_ticker(
symbol,
bars,
config,
activation,
benchmark_closes,
target_model,
),
_signal_series(bars, benchmark_closes),
)
@@ -1195,6 +1319,7 @@ def _simulate_portfolio(
entries_by_ord: dict[int, list[dict]] = defaultdict(list)
start_ord = start_date.toordinal() if start_date is not None else None
# Explicit simulator/holdout end dates are exclusive split boundaries.
end_ord = end_date.toordinal() if end_date is not None else None
for c in candidates:
if not qualified_fn(c) or c.get("direction") != "long":
@@ -1910,6 +2035,10 @@ PORTFOLIO_MONITOR_STRATEGIES: tuple[dict, ...] = (
)
def _portfolio_monitor_strategies() -> tuple[dict, ...]:
return PORTFOLIO_MONITOR_STRATEGIES
def _entry_variant_config(variant: str) -> dict | None:
return next((cfg for cfg in STRATEGY_VARIANTS if cfg["variant"] == variant), None)
@@ -2196,16 +2325,19 @@ def _portfolio_monitor(
) -> dict:
latest_ord = max((max(cols[0]) for cols in prices.values() if cols[0]), default=None)
rows: list[dict] = []
for strategy in PORTFOLIO_MONITOR_STRATEGIES:
strategies = _portfolio_monitor_strategies()
for strategy in strategies:
entry_cfg = _entry_variant_config(str(strategy["entry_variant"]))
if entry_cfg is None:
continue
ranking_key = str(entry_cfg.get("ranking_key") or entry_cfg["percentile_key"])
# The production row must replay the LIVE configuration: the runtime
# qualification flag (Admin activation settings) instead of the frozen
# research-variant gate, and the Admin exit policy instead of the
# hardcoded 3x-trail/30d defaults. Research rows stay frozen so they
# remain comparable across runs.
ranking_key = str(
strategy.get("ranking_key")
or entry_cfg.get("ranking_key")
or entry_cfg["percentile_key"]
)
# Live-config rows replay the runtime qualification flag and Admin exit
# policy. The overlay opts into this deliberately so only ordering
# changes relative to the production row.
use_live = bool(strategy.get("use_live_config"))
exit_policy = str(strategy["exit_policy"])
row_hold_days = hold_days
@@ -2246,6 +2378,7 @@ def _portfolio_monitor(
"description": strategy["description"],
"is_production": bool(strategy.get("is_production")),
"entry_variant": strategy["entry_variant"],
"ranking_key": ranking_key,
"exit_policy": exit_policy,
"live_exit_mode": live_exit_mode,
"lookback": lookback["lookback"],
@@ -2261,7 +2394,7 @@ def _portfolio_monitor(
"description": s["description"],
"is_production": bool(s.get("is_production")),
}
for s in PORTFOLIO_MONITOR_STRATEGIES
for s in strategies
],
"lookbacks": [
{"lookback": lb["lookback"], "label": lb["label"]}
@@ -2270,7 +2403,9 @@ def _portfolio_monitor(
"runs": rows,
"note": (
"Portfolio monitor runs supported named strategies across cached lookbacks. "
"Local snapshot backtests remain the research surface for broad variant sweeps."
"The structural overlay appears only in its explicit research arm and changes "
"ordering, not production qualification. Local snapshot backtests remain the "
"research surface for broad variant sweeps."
),
}
@@ -2327,7 +2462,7 @@ def _sharpe_key(row: dict) -> float:
def _build_research_recommendation(report: dict) -> dict:
"""Advisory rules for the remaining research variants after residual promotion."""
"""Build advisory notes from any strategy variants present in the report."""
variants = {
v.get("variant"): v
for v in (report.get("strategy_variants") or {}).get("variants", [])
@@ -2650,8 +2785,11 @@ def _build_recommendation(report: dict) -> dict:
async def run_backtest(
db: AsyncSession,
progress_cb: Callable[[int, int, str], None] | None = None,
*,
target_model: str = PRODUCTION_GTL_TARGET_MODEL,
) -> dict:
"""Replay every ticker and aggregate the Phase-1 reports for the current config."""
target_model = validate_backtest_target_model(target_model)
config = await get_recommendation_config(db)
activation = await get_activation_config(db)
@@ -2716,6 +2854,7 @@ async def run_backtest(
futures.append(loop.run_in_executor(
pool, _replay_and_signals, ticker.symbol, columns, config, activation,
benchmark_closes,
target_model,
))
for result in await asyncio.gather(*futures, return_exceptions=True):
if isinstance(result, Exception):
@@ -2737,6 +2876,7 @@ async def run_backtest(
_merge(await asyncio.to_thread(
_replay_and_signals, ticker.symbol, columns, config, activation,
benchmark_closes,
target_model,
))
except Exception:
logger.exception("Backtest replay failed for %s", ticker.symbol)
@@ -2851,6 +2991,9 @@ async def run_backtest(
"horizon_days": HORIZON,
"min_lookback": MIN_LOOKBACK,
"cost_per_side_pct": round(COST_PER_SIDE * 100, 3),
"target_model": target_model,
"target_model_label": BACKTEST_TARGET_MODELS[target_model],
"is_production_target_model": target_model == PRODUCTION_GTL_TARGET_MODEL,
},
"activation": activation,
"overall_qualified": _bucket_stats(qualified),
@@ -2914,6 +3057,10 @@ async def run_backtest(
"portfolio_monitor": portfolio_monitor_report,
"holdout": holdout_report,
"min_rr_sweep": min_rr_sweep_report,
"target_model_diagnostics": _target_model_diagnostics(
candidates,
target_model,
),
"signal_eval": _signal_evaluation(collected),
"signal_eval_note": (
"Cross-sectional rank-IC of price-only signals vs the forward "
@@ -2941,9 +3088,11 @@ async def run_backtest(
async def run_and_store(
db: AsyncSession,
progress_cb: Callable[[int, int, str], None] | None = None,
*,
target_model: str = PRODUCTION_GTL_TARGET_MODEL,
) -> dict:
"""Run the backtest and cache the report in a SystemSetting. Job entrypoint."""
report = await run_backtest(db, progress_cb)
report = await run_backtest(db, progress_cb, target_model=target_model)
await update_setting(db, KEY_REPORT, json.dumps(report))
return report
+47 -14
View File
@@ -256,6 +256,12 @@ def compute_volume_profile(
) -> dict[str, Any]:
"""Compute Volume Profile: POC, Value Area, HVN, LVN.
Volume is assigned to the bin containing each bar's **close** (no
double-counting across the highlow span).
HVN = local peaks in the volume histogram (not every bin above mean).
LVN = local valleys in the histogram.
Score: proximity of latest close to POC (closer = higher).
"""
n = len(closes)
@@ -275,14 +281,18 @@ def compute_volume_profile(
price_min + (i + 0.5) * bin_width for i in range(num_bins)
]
# Assign each bar's full volume to the close's bin only.
for i in range(n):
# Distribute volume across bins the bar spans
bar_low, bar_high = lows[i], highs[i]
for b in range(num_bins):
bl = price_min + b * bin_width
bh = bl + bin_width
if bar_high >= bl and bar_low <= bh:
bins[b] += volumes[i]
c = closes[i]
if c <= price_min:
b = 0
elif c >= price_max:
b = num_bins - 1
else:
b = int((c - price_min) / bin_width)
if b >= num_bins:
b = num_bins - 1
bins[b] += volumes[i]
total_vol = sum(bins)
if total_vol == 0:
@@ -304,10 +314,17 @@ def compute_volume_profile(
va_low = round(price_min + min(va_indices) * bin_width, 4)
va_high = round(price_min + (max(va_indices) + 1) * bin_width, 4)
# HVN / LVN: bins above/below average volume
# HVN / LVN: local peaks / valleys (require above/below mean to skip noise)
avg_vol = total_vol / num_bins
hvn = [round(bin_prices[i], 4) for i in range(num_bins) if bins[i] > avg_vol]
lvn = [round(bin_prices[i], 4) for i in range(num_bins) if bins[i] < avg_vol]
hvn: list[float] = []
lvn: list[float] = []
for i in range(num_bins):
left = bins[i - 1] if i > 0 else bins[i]
right = bins[i + 1] if i < num_bins - 1 else bins[i]
if bins[i] > left and bins[i] > right and bins[i] > avg_vol:
hvn.append(round(bin_prices[i], 4))
elif bins[i] < left and bins[i] < right and bins[i] < avg_vol:
lvn.append(round(bin_prices[i], 4))
# Score: proximity of latest close to POC
latest = closes[-1]
@@ -333,10 +350,14 @@ def compute_pivot_points(
lows: list[float],
closes: list[float],
window: int = 2,
min_prominence: float | None = None,
) -> dict[str, Any]:
"""Detect swing highs/lows as pivot points.
A swing high at index *i* means highs[i] >= all highs in [i-window, i+window].
When *min_prominence* is set, only keep swings whose window range
(max high min low) is at least that amount — filters tiny noise fractals.
Score: based on number of pivots near current price.
"""
n = len(closes)
@@ -349,12 +370,24 @@ def compute_pivot_points(
swing_lows: list[float] = []
for i in range(window, n - window):
lo = i - window
hi = i + window + 1
# Swing high
if all(highs[i] >= highs[j] for j in range(i - window, i + window + 1)):
swing_highs.append(round(highs[i], 4))
if all(highs[i] >= highs[j] for j in range(lo, hi)):
if min_prominence is None or min_prominence <= 0:
swing_highs.append(round(highs[i], 4))
else:
depth = highs[i] - min(lows[j] for j in range(lo, hi))
if depth >= min_prominence:
swing_highs.append(round(highs[i], 4))
# Swing low
if all(lows[i] <= lows[j] for j in range(i - window, i + window + 1)):
swing_lows.append(round(lows[i], 4))
if all(lows[i] <= lows[j] for j in range(lo, hi)):
if min_prominence is None or min_prominence <= 0:
swing_lows.append(round(lows[i], 4))
else:
depth = max(highs[j] for j in range(lo, hi)) - lows[i]
if depth >= min_prominence:
swing_lows.append(round(lows[i], 4))
all_pivots = swing_highs + swing_lows
latest = closes[-1]
+41 -9
View File
@@ -56,7 +56,12 @@ def _clamp(value: float, low: float, high: float) -> float:
return max(low, min(high, value))
def _zone_representative_levels(sr_levels: list[SRLevel], entry_price: float) -> list[Any]:
def _zone_representative_levels(
sr_levels: list[SRLevel],
entry_price: float,
*,
strength_mode: str = "sum",
) -> list[Any]:
"""Collapse near-duplicate S/R levels into one representative per zone.
Targets are generated from these representatives, so a clustered wall (e.g.
@@ -71,11 +76,25 @@ def _zone_representative_levels(sr_levels: list[SRLevel], entry_price: float) ->
if not sr_levels or entry_price <= 0:
return list(sr_levels)
level_dicts = [
{"price_level": float(lv.price_level), "strength": int(lv.strength), "type": lv.type}
for lv in sr_levels
]
zones = cluster_sr_zones(level_dicts, entry_price, tolerance=_SR_ZONE_TOLERANCE)
level_dicts = []
for lv in sr_levels:
level_dicts.append({
"price_level": float(lv.price_level),
"strength": int(lv.strength),
"type": lv.type,
"detection_method": getattr(lv, "detection_method", "unknown"),
"sources": list(getattr(lv, "sources", None) or [
getattr(lv, "detection_method", "unknown")
]),
"rejection_count": int(getattr(lv, "rejection_count", 0) or 0),
"last_rejection_age": getattr(lv, "last_rejection_age", None),
})
zones = cluster_sr_zones(
level_dicts,
entry_price,
tolerance=_SR_ZONE_TOLERANCE,
strength_mode=strength_mode,
)
reps: list[Any] = []
for zone in zones:
@@ -94,6 +113,10 @@ def _zone_representative_levels(sr_levels: list[SRLevel], entry_price: float) ->
price_level=float(near_edge),
type=zone["type"],
strength=int(zone["strength"]),
detection_method=getattr(strongest, "detection_method", "unknown"),
sources=list(zone.get("sources") or []),
rejection_count=int(zone.get("rejection_count", 0)),
last_rejection_age=zone.get("last_rejection_age"),
)
)
return reps
@@ -312,6 +335,15 @@ class TargetGenerator:
"classification": "Moderate",
"sr_level_id": int(level.id),
"sr_strength": float(level.strength),
"sr_sources": list(getattr(level, "sources", None) or [
getattr(level, "detection_method", "unknown")
]),
"sr_rejection_count": int(
getattr(level, "rejection_count", 0) or 0
),
"sr_last_rejection_age": getattr(
level, "last_rejection_age", None
),
"quality": float(quality),
}
)
@@ -581,7 +613,6 @@ def build_recommendation_snapshot(
}
PRIMARY_TARGET_MIN_RR = 1.5
# Below this the target is a lottery ticket. Shared with the activation gate
# (qualification.MIN_TARGET_PROBABILITY) so the primary selection and the gate
# agree on what counts as a probability-backed target.
@@ -610,7 +641,7 @@ def _prune_floor_pinned_targets(targets: list[dict]) -> list[dict]:
def _select_primary_target(
targets: list[dict],
min_rr: float = PRIMARY_TARGET_MIN_RR,
min_rr: float,
min_probability: float = PRIMARY_TARGET_MIN_PROBABILITY,
) -> dict | None:
"""Primary = the most LIKELY target that still offers real asymmetry.
@@ -651,6 +682,7 @@ async def enhance_trade_setup(
sr_levels: list[SRLevel],
sentiment_classification: str | None,
atr_value: float,
primary_min_rr: float,
available_directions: set[str] | None = None,
) -> TradeSetup:
config = await get_recommendation_config(db)
@@ -698,7 +730,7 @@ async def enhance_trade_setup(
# _select_primary_target), not the old quality-score pick that ignored
# probability. Sync the setup's headline target/rr_ratio so the chart, gate
# and outcome eval all agree with the table's starred row.
primary = _select_primary_target(targets)
primary = _select_primary_target(targets, min_rr=primary_min_rr)
if primary is not None:
for target in targets:
target["is_primary"] = target is primary
+54 -15
View File
@@ -1,9 +1,8 @@
"""R:R Scanner service.
"""R:R scanner service.
Scans tracked tickers for asymmetric risk-reward trade setups.
Long: target = nearest SR above, stop = entry - ATR × multiplier.
Short: target = nearest SR below, stop = entry + ATR × multiplier.
Filters by configurable R:R threshold (default 1.5).
Scans tracked tickers for asymmetric risk-reward trade setups. Candidate
targets come from a transient, volume-free proposal ladder; persisted S/R is
reserved for human-facing charts and alerts. Stops remain ATR-based.
"""
from __future__ import annotations
@@ -12,6 +11,8 @@ import json
import logging
from collections.abc import Callable
from datetime import date, datetime, timedelta, timezone
from types import SimpleNamespace
from typing import Any
from sqlalchemy import and_, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
@@ -23,11 +24,11 @@ from app.models.paper_trade import PaperTrade
from app.models.score import CompositeScore, DimensionScore
from app.models.sentiment import SentimentScore
from app.models.signal_context_snapshot import SignalContextSnapshot
from app.models.sr_level import SRLevel
from app.models.ticker import Ticker
from app.models.trade_setup import TradeSetup
from app.services.indicator_service import _extract_ohlcv, compute_atr
from app.services.price_service import query_ohlcv
from app.services.sr_service import detect_gate_target_ladder
from app.services.recommendation_service import (
_risk_level_from_conflicts,
build_recommendation_snapshot,
@@ -38,6 +39,7 @@ from app.services.recommendation_service import (
logger = logging.getLogger(__name__)
STRATEGY_VERSION = "residual_highvol_80_20_atr_trail3_v1"
PRIMARY_TARGET_MIN_RR = 1.5
# A setup counts as live only while the daily scan keeps re-emitting it. The
# scan runs every day (07:00 UTC cron), so anything older than this was NOT
@@ -49,6 +51,28 @@ STRATEGY_VERSION = "residual_highvol_80_20_atr_trail3_v1"
LIVE_SETUP_MAX_AGE_DAYS = 3
def _materialize_gate_target_levels(
highs: list[float],
lows: list[float],
closes: list[float],
) -> list[Any]:
"""Create transient level objects for target generation, never persistence."""
detected = detect_gate_target_ladder(highs, lows, closes)
return [
SimpleNamespace(
id=-(index + 1),
price_level=float(level["price_level"]),
type=str(level["type"]),
strength=int(level["strength"]),
detection_method=str(level.get("detection_method", "range_grid")),
sources=list(level.get("sources") or ["range_grid"]),
rejection_count=int(level.get("rejection_count", 0) or 0),
last_rejection_age=level.get("last_rejection_age"),
)
for index, level in enumerate(detected)
]
async def _get_ticker(db: AsyncSession, symbol: str) -> Ticker:
normalised = symbol.strip().upper()
result = await db.execute(select(Ticker).where(Ticker.symbol == normalised))
@@ -412,15 +436,27 @@ async def scan_ticker(
momentum_percentile: float | None = None,
strategy_rank: float | None = None,
volatility_percentile: float | None = None,
primary_min_rr: float | None = None,
gate_levels_override: list[Any] | None = None,
) -> list[TradeSetup]:
"""Scan a single ticker for trade setups meeting the R:R threshold.
``momentum_percentile`` is the ticker's residual 12-1 momentum activation
rank across the universe (computed by the caller), stored on each setup so
the activation gate can select the top slice. ``strategy_rank`` is the
production ordering score used for top-pick ranking."""
production ordering score used for top-pick ranking.
``primary_min_rr`` controls target selection only. Its 1.5 default is
intentionally independent of the later activation floor (2.0 in the live
Admin configuration). ``gate_levels_override`` is dependency injection for
deterministic scanner tests; production builds the transient ladder from
the ticker's OHLCV window.
"""
ticker = await _get_ticker(db, symbol)
if primary_min_rr is None:
primary_min_rr = PRIMARY_TARGET_MIN_RR
records = await query_ohlcv(db, symbol)
if not records or len(records) < 15:
logger.info(
@@ -443,21 +479,22 @@ async def scan_ticker(
logger.info("Skipping %s: ATR is zero or negative", symbol)
return []
sr_result = await db.execute(
select(SRLevel).where(SRLevel.ticker_id == ticker.id)
gate_levels = (
list(gate_levels_override)
if gate_levels_override is not None
else _materialize_gate_target_levels(highs, lows, closes)
)
sr_levels = list(sr_result.scalars().all())
if not sr_levels:
logger.info("Skipping %s: no SR levels available", symbol)
if not gate_levels:
logger.info("Skipping %s: no gate target levels available", symbol)
return []
levels_above = sorted(
[lv for lv in sr_levels if lv.price_level > entry_price],
[lv for lv in gate_levels if lv.price_level > entry_price],
key=lambda lv: lv.price_level,
)
levels_below = sorted(
[lv for lv in sr_levels if lv.price_level < entry_price],
[lv for lv in gate_levels if lv.price_level < entry_price],
key=lambda lv: lv.price_level,
reverse=True,
)
@@ -555,9 +592,10 @@ async def scan_ticker(
ticker=ticker,
setup=setup,
dimension_scores=dimension_scores,
sr_levels=sr_levels,
sr_levels=gate_levels,
sentiment_classification=sentiment_classification,
atr_value=atr_value,
primary_min_rr=primary_min_rr,
available_directions=available_directions,
)
enhanced_setups.append(enhanced)
@@ -641,6 +679,7 @@ async def scan_all_tickers(
momentum_percentile=(ranks.get(symbol) or {}).get("momentum_percentile"),
strategy_rank=(ranks.get(symbol) or {}).get("strategy_rank"),
volatility_percentile=(ranks.get(symbol) or {}).get("volatility_percentile"),
primary_min_rr=PRIMARY_TARGET_MIN_RR,
)
all_setups.extend(setups)
except Exception:
+549 -74
View File
@@ -1,12 +1,15 @@
"""S/R Detector service.
Detects support/resistance levels from Volume Profile (HVN/LVN) and
Pivot Points (swing highs/lows), assigns strength scores, merges nearby
levels, tags as support/resistance, and persists to DB.
Detects support/resistance levels from Volume Profile (POC/VA/HVN peaks)
and Pivot Points (prominent swing highs/lows), plus light psychological
round numbers. Scores by rejection-weighted recent touches, merges nearby
levels with ATR-adaptive tolerance, tags support/resistance, caps count,
and persists to DB.
"""
from __future__ import annotations
import math
from datetime import datetime
from sqlalchemy import delete, select
@@ -17,12 +20,43 @@ from app.models.sr_level import SRLevel
from app.models.ticker import Ticker
from app.services.indicator_service import (
_extract_ohlcv,
compute_atr,
compute_pivot_points,
compute_volume_profile,
)
from app.services.price_service import query_ohlcv
DEFAULT_TOLERANCE = 0.005 # 0.5%
# ---------------------------------------------------------------------------
# Tunable constants (keep detection pure / deterministic)
# ---------------------------------------------------------------------------
DEFAULT_TOLERANCE = 0.005 # fallback when ATR unavailable; also API legacy default
VP_LOOKBACK = 252
TOUCH_LOOKBACK = 252
PIVOT_LOOKBACK = 504
PIVOT_PROMINENCE_ATR = 0.75
PIVOT_PROMINENCE_PCT = 0.006
MERGE_TOL_ATR_MULT = 0.35
MERGE_TOL_MIN = 0.004 # 0.4%
MERGE_TOL_MAX = 0.015 # 1.5%
MAX_LEVELS = 16
STRENGTH_HALF_LIFE = 60 # bars
# Raw respect score is soft-mapped to 0100 (see _raw_to_strength).
STRENGTH_SCALE = 8.0
STRENGTH_SOFT_K = 35.0 # higher → slower approach to 100
ROUND_NUMBER_RANGE = 0.15 # ±15% of spot
ROUND_NUMBER_MAX = 8
# Base strength seed before touch scoring (method priors)
_METHOD_BASE_STRENGTH = {
"volume_profile": 12,
"pivot_point": 8,
"round_number": 4,
}
async def _get_ticker(db: AsyncSession, symbol: str) -> Ticker:
@@ -35,36 +69,235 @@ async def _get_ticker(db: AsyncSession, symbol: str) -> Ticker:
return ticker
def _count_price_touches(
def _slice_tail(
highs: list[float],
lows: list[float],
closes: list[float],
volumes: list[int],
lookback: int,
) -> tuple[list[float], list[float], list[float], list[int]]:
"""Return the last *lookback* bars (or all if shorter)."""
n = len(closes)
if lookback <= 0 or n <= lookback:
return highs, lows, closes, volumes
start = n - lookback
return highs[start:], lows[start:], closes[start:], volumes[start:]
def _atr_pct(
highs: list[float],
lows: list[float],
closes: list[float],
) -> float | None:
"""ATR as a fraction of last close, or None if insufficient data."""
try:
result = compute_atr(highs, lows, closes)
except ValidationError:
return None
atr = result["atr"]
last = closes[-1]
if last == 0:
return None
return atr / last
def _merge_tolerance(
highs: list[float],
lows: list[float],
closes: list[float],
tolerance: float | None,
) -> float:
"""Resolve merge tolerance: explicit value or ATR-adaptive clamp."""
if tolerance is not None:
return tolerance
atr_frac = _atr_pct(highs, lows, closes)
if atr_frac is None:
return DEFAULT_TOLERANCE
return max(MERGE_TOL_MIN, min(MERGE_TOL_MAX, MERGE_TOL_ATR_MULT * atr_frac))
def _bar_respect_weight(
price_level: float,
high: float,
low: float,
close: float,
prev_close: float | None,
tolerance: float,
) -> float:
"""Weight for how much a bar *respects* a level (not mere occupancy).
Only bars whose high/low **probes near the level** and closes away from
that extreme count as rejections. Full-range pass-throughs score near zero.
"""
tol = price_level * tolerance if price_level != 0 else tolerance
if tol <= 0:
tol = abs(price_level) * DEFAULT_TOLERANCE if price_level else DEFAULT_TOLERANCE
# Tight probe band: ~0.4% of price (capped), not 2× merge tolerance
band = min(max(abs(price_level) * 0.004, tol * 0.35), abs(price_level) * 0.008)
if band <= 0:
band = abs(price_level) * 0.004 if price_level else 0.01
if high + band < price_level or low - band > price_level:
return 0.0
bar_range = high - low
# Support test: low probes near level, close recovers above
support_test = abs(low - price_level) <= band and close > price_level
if support_test and bar_range > 0:
support_test = (close - low) >= 0.25 * bar_range
# Resistance test: high probes near level, close rejects below
resist_test = abs(high - price_level) <= band and close < price_level
if resist_test and bar_range > 0:
resist_test = (high - close) >= 0.25 * bar_range
if support_test or resist_test:
return 1.0
# Clear directional pass-through — barely counts
if (
prev_close is not None
and (prev_close - price_level) * (close - price_level) < 0
and low < price_level - tol
and high > price_level + tol
):
return 0.1
return 0.0
def _raw_to_strength(raw: float) -> int:
"""Map unbounded raw score to 0100 with soft saturation (no hard pin)."""
if raw <= 0:
return 0
# 1 - e^(-raw/k): raw=k → ~63, 2k → ~86, 3k → ~95
return max(0, min(100, int(round(100.0 * (1.0 - math.exp(-raw / STRENGTH_SOFT_K))))))
def _respect_evidence(
price_level: float,
highs: list[float],
lows: list[float],
closes: list[float],
tolerance: float = DEFAULT_TOLERANCE,
) -> int:
"""Count how many bars touched/respected a price level within tolerance."""
count = 0
tol = price_level * tolerance if price_level != 0 else tolerance
for i in range(len(closes)):
# A bar "touches" the level if the level is within the bar's range
# (within tolerance)
if lows[i] - tol <= price_level <= highs[i] + tol:
count += 1
return count
base: int = 0,
half_life: float = STRENGTH_HALF_LIFE,
lookback: int = TOUCH_LOOKBACK,
cooldown: int = 3,
) -> dict[str, float | int | None]:
"""Return rejection evidence and its soft-mapped strength.
def _strength_from_touches(touches: int, total_bars: int) -> int:
"""Convert touch count to a 0-100 strength score.
More touches relative to total bars = higher strength.
Cap at 100.
*cooldown* bars after a full rejection are ignored so multi-day chop at a
level counts as one test cluster, not N identical rejections.
"""
if total_bars == 0:
return 0
# Scale: each touch contributes proportionally, with a multiplier
# so that a level touched ~20% of bars gets score ~100
raw = (touches / total_bars) * 500.0
return max(0, min(100, int(round(raw))))
n = len(closes)
if n == 0:
return {
"strength": _raw_to_strength(float(base)),
"rejection_count": 0,
"last_rejection_age": None,
"weighted_respects": 0.0,
}
start = max(0, n - lookback) if lookback > 0 else 0
weighted = 0.0
rejection_count = 0
last_rejection_age: int | None = None
next_ok = start
for i in range(start, n):
age = n - 1 - i
decay = 0.5 ** (age / half_life) if half_life > 0 else 1.0
prev = closes[i - 1] if i > 0 else None
w = _bar_respect_weight(
price_level, highs[i], lows[i], closes[i], prev, tolerance
)
if w >= 0.9:
if i < next_ok:
continue
weighted += decay * w
rejection_count += 1
if last_rejection_age is None or age < last_rejection_age:
last_rejection_age = age
next_ok = i + max(cooldown, 1)
elif w > 0:
weighted += decay * w
raw = float(base) + weighted * STRENGTH_SCALE
return {
"strength": _raw_to_strength(raw),
"rejection_count": rejection_count,
"last_rejection_age": last_rejection_age,
"weighted_respects": round(weighted, 6),
}
def _strength_from_respects(
price_level: float,
highs: list[float],
lows: list[float],
closes: list[float],
tolerance: float = DEFAULT_TOLERANCE,
base: int = 0,
half_life: float = STRENGTH_HALF_LIFE,
lookback: int = TOUCH_LOOKBACK,
cooldown: int = 3,
) -> int:
"""Compatibility wrapper returning only the evidence-derived strength."""
return int(_respect_evidence(
price_level,
highs,
lows,
closes,
tolerance,
base,
half_life,
lookback,
cooldown,
)["strength"])
def _round_number_candidates(
current_price: float,
range_pct: float = ROUND_NUMBER_RANGE,
max_count: int = ROUND_NUMBER_MAX,
) -> list[float]:
"""Psychological round levels near spot (cheap order-magnet candidates)."""
if current_price <= 0:
return []
if current_price < 5:
steps = [0.5, 1.0]
elif current_price < 20:
steps = [1.0, 5.0]
elif current_price < 100:
steps = [5.0, 10.0, 25.0]
elif current_price < 500:
steps = [10.0, 25.0, 50.0, 100.0]
else:
steps = [25.0, 50.0, 100.0, 250.0]
lo = current_price * (1.0 - range_pct)
hi = current_price * (1.0 + range_pct)
found: set[float] = set()
for step in steps:
if step <= 0:
continue
# Start at first multiple at or below lo
k = math.floor(lo / step)
while True:
level = round(k * step, 4)
if level > hi + step:
break
if lo <= level <= hi and level > 0:
# Skip levels that are essentially current price
if abs(level - current_price) / current_price > 0.001:
found.add(level)
k += 1
if k > 1_000_000: # safety
break
ordered = sorted(found, key=lambda p: abs(p - current_price))
return ordered[:max_count]
def _extract_candidate_levels(
@@ -73,55 +306,187 @@ def _extract_candidate_levels(
closes: list[float],
volumes: list[int],
) -> list[tuple[float, str]]:
"""Extract candidate S/R levels from Volume Profile and Pivot Points.
"""Extract candidate S/R levels from VP nodes, prominent pivots, rounds.
Returns list of (price_level, detection_method) tuples.
"""
candidates: list[tuple[float, str]] = []
if not closes:
return candidates
# Volume Profile: HVN and LVN as candidate levels
current_price = closes[-1]
# --- Volume profile on recent window ---
vp_h, vp_l, vp_c, vp_v = _slice_tail(
highs, lows, closes, volumes, VP_LOOKBACK
)
try:
vp = compute_volume_profile(highs, lows, closes, volumes)
vp = compute_volume_profile(vp_h, vp_l, vp_c, vp_v)
# Structural VP levels: POC, value-area edges, local HVN peaks.
# LVN intentionally omitted (rejection voids ≠ support/resistance lines).
for key in ("poc", "value_area_low", "value_area_high"):
price = vp.get(key)
if price is not None and price > 0:
candidates.append((float(price), "volume_profile"))
for price in vp.get("hvn", []):
candidates.append((price, "volume_profile"))
for price in vp.get("lvn", []):
candidates.append((price, "volume_profile"))
candidates.append((float(price), "volume_profile"))
except ValidationError:
pass # Not enough data for volume profile
pass
# --- Prominent pivots on pivot lookback ---
p_h, p_l, p_c, _ = _slice_tail(highs, lows, closes, volumes, PIVOT_LOOKBACK)
atr_frac = _atr_pct(p_h, p_l, p_c)
last = p_c[-1] if p_c else current_price
if atr_frac is not None and last > 0:
prominence = max(PIVOT_PROMINENCE_ATR * atr_frac * last, PIVOT_PROMINENCE_PCT * last)
else:
prominence = PIVOT_PROMINENCE_PCT * last if last > 0 else None
# Pivot Points: swing highs and lows
try:
pp = compute_pivot_points(highs, lows, closes)
pp = compute_pivot_points(p_h, p_l, p_c, min_prominence=prominence)
for price in pp.get("swing_highs", []):
candidates.append((price, "pivot_point"))
candidates.append((float(price), "pivot_point"))
for price in pp.get("swing_lows", []):
candidates.append((price, "pivot_point"))
candidates.append((float(price), "pivot_point"))
except ValidationError:
pass # Not enough data for pivot points
pass
# --- Psychological round numbers near spot ---
for price in _round_number_candidates(current_price):
candidates.append((price, "round_number"))
return candidates
def _gate_target_range_centers(
highs: list[float],
lows: list[float],
closes: list[float],
num_bins: int = 20,
) -> list[float]:
"""Return the evenly spaced price proposals used by the production GTL."""
if len(closes) < 20:
raise ValidationError(
f"Range grid requires at least 20 bars, got {len(closes)}"
)
price_min = min(lows)
price_max = max(highs)
if price_max == price_min:
price_max = price_min + 1.0
bin_width = (price_max - price_min) / num_bins
return [
round(price_min + (i + 0.5) * bin_width, 4)
for i in range(num_bins)
]
def detect_gate_target_ladder(
highs: list[float],
lows: list[float],
closes: list[float],
tolerance: float = DEFAULT_TOLERANCE,
) -> list[dict]:
"""Build the scanner's internal, volume-free target proposal ladder.
This is intentionally not human-facing support/resistance. It builds the
production gate's broad 20-bin range grid, adds unfiltered pivots, scores
historical price traffic, and merges nearby proposals. The returned levels
are transient and must not be persisted as chart S/R.
"""
if not closes:
return []
candidates: list[tuple[float, str]] = []
try:
candidates.extend(
(float(price), "range_grid")
for price in _gate_target_range_centers(highs, lows, closes)
)
except ValidationError:
pass
try:
pivots = compute_pivot_points(highs, lows, closes)
candidates.extend(
(float(price), "pivot_point")
for price in pivots.get("swing_highs", []) + pivots.get("swing_lows", [])
)
except ValidationError:
pass
if not candidates:
return []
total_bars = len(closes)
raw: list[dict] = []
for price, method in candidates:
tol = price * tolerance if price != 0 else tolerance
touches = sum(
1 for low, high in zip(lows, highs, strict=False)
if low - tol <= price <= high + tol
)
strength = max(0, min(100, int(round((touches / total_bars) * 500.0))))
raw.append({
"price_level": price,
"strength": strength,
"detection_method": method,
"type": "",
"sources": [method],
"rejection_count": touches,
"last_rejection_age": None,
"weighted_respects": float(touches),
})
merged: list[dict] = []
for level in sorted(raw, key=lambda row: row["price_level"]):
if not merged:
merged.append(dict(level))
continue
last = merged[-1]
ref = last["price_level"]
tol = ref * tolerance if ref != 0 else tolerance
if abs(level["price_level"] - ref) > tol:
merged.append(dict(level))
continue
last["price_level"] = round(
(last["price_level"] + level["price_level"]) / 2.0, 4
)
last["strength"] = min(100, last["strength"] + level["strength"])
sources = set(last.get("sources") or [last["detection_method"]])
sources |= set(level.get("sources") or [level["detection_method"]])
last["sources"] = sorted(sources)
last["detection_method"] = (
next(iter(sources)) if len(sources) == 1 else "merged"
)
last["rejection_count"] = max(
int(last.get("rejection_count", 0)),
int(level.get("rejection_count", 0)),
)
_tag_levels(merged, closes[-1])
merged.sort(key=lambda row: row["strength"], reverse=True)
return merged
def _merge_levels(
levels: list[dict],
tolerance: float = DEFAULT_TOLERANCE,
) -> list[dict]:
"""Merge levels within tolerance into consolidated levels.
Levels from different methods within tolerance are merged.
Merged levels combine strength scores (capped at 100) and get
detection_method = "merged".
Strength combines via max + partial min (avoids instant saturation) with
a confluence bonus when detection methods differ. Price is strength-weighted.
"""
if not levels:
return []
# Sort by price
sorted_levels = sorted(levels, key=lambda x: x["price_level"])
merged: list[dict] = []
for level in sorted_levels:
if not merged:
merged.append(dict(level))
entry = dict(level)
sources = level.get("sources") or [level["detection_method"]]
entry["sources"] = sorted(set(sources))
merged.append(entry)
continue
last = merged[-1]
@@ -129,19 +494,52 @@ def _merge_levels(
tol = ref_price * tolerance if ref_price != 0 else tolerance
if abs(level["price_level"] - ref_price) <= tol:
# Merge: average price, combine strength, mark as merged
combined_strength = min(100, last["strength"] + level["strength"])
avg_price = (last["price_level"] + level["price_level"]) / 2.0
method = (
"merged"
if last["detection_method"] != level["detection_method"]
else last["detection_method"]
)
s1 = last["strength"]
s2 = level["strength"]
# Soft combine — avoid merge math pinning everything at 100
combined = int(round(0.85 * max(s1, s2) + 0.15 * min(s1, s2)))
sources = set(last.get("sources") or [last["detection_method"]])
sources |= set(level.get("sources") or [level["detection_method"]])
if len(sources) > 1:
combined = min(100, combined + 5)
else:
combined = min(100, combined)
w1, w2 = max(s1, 1), max(s2, 1)
avg_price = (last["price_level"] * w1 + level["price_level"] * w2) / (w1 + w2)
if len(sources) == 1:
method = next(iter(sources))
else:
method = "merged"
last["price_level"] = round(avg_price, 4)
last["strength"] = combined_strength
last["strength"] = combined
last["detection_method"] = method
last["sources"] = sorted(sources)
# Nearby candidates often describe the same price reaction, so do
# not add their rejection counts and double-count one market event.
last["rejection_count"] = max(
int(last.get("rejection_count", 0)),
int(level.get("rejection_count", 0)),
)
ages = [
age for age in (
last.get("last_rejection_age"),
level.get("last_rejection_age"),
)
if age is not None
]
last["last_rejection_age"] = min(ages) if ages else None
last["weighted_respects"] = max(
float(last.get("weighted_respects", 0.0)),
float(level.get("weighted_respects", 0.0)),
)
else:
merged.append(dict(level))
entry = dict(level)
sources = level.get("sources") or [level["detection_method"]]
entry["sources"] = sorted(set(sources))
merged.append(entry)
return merged
@@ -159,14 +557,66 @@ def _tag_levels(
return levels
def _cap_levels(
levels: list[dict],
max_levels: int = MAX_LEVELS,
) -> list[dict]:
"""Keep up to *max_levels* levels, interleaving support/resistance by strength."""
if max_levels <= 0 or len(levels) <= max_levels:
return levels
support = sorted(
[lvl for lvl in levels if lvl.get("type") == "support"],
key=lambda x: x["strength"],
reverse=True,
)
resistance = sorted(
[lvl for lvl in levels if lvl.get("type") != "support"],
key=lambda x: x["strength"],
reverse=True,
)
selected: list[dict] = []
si, ri = 0, 0
pick_support = True
while len(selected) < max_levels and (si < len(support) or ri < len(resistance)):
if pick_support:
if si < len(support):
selected.append(support[si])
si += 1
elif ri < len(resistance):
selected.append(resistance[ri])
ri += 1
else:
if ri < len(resistance):
selected.append(resistance[ri])
ri += 1
elif si < len(support):
selected.append(support[si])
si += 1
pick_support = not pick_support
selected.sort(key=lambda x: x["strength"], reverse=True)
return selected
def detect_sr_levels(
highs: list[float],
lows: list[float],
closes: list[float],
volumes: list[int],
tolerance: float = DEFAULT_TOLERANCE,
tolerance: float | None = None,
max_levels: int = MAX_LEVELS,
) -> list[dict]:
"""Detect, score, merge, and tag S/R levels from OHLCV data.
"""Detect, score, merge, tag, and cap S/R levels from OHLCV data.
Parameters
----------
tolerance:
Relative merge tolerance. ``None`` (default) uses ATR-adaptive
tolerance clamped to [0.4%, 1.5%]. Pass an explicit fraction to override.
max_levels:
Hard cap after merge (balanced support/resistance). 0 = no cap.
Returns list of dicts with keys: price_level, type, strength,
detection_method sorted by strength descending.
@@ -178,37 +628,42 @@ def detect_sr_levels(
if not candidates:
return []
total_bars = len(closes)
current_price = closes[-1]
merge_tol = _merge_tolerance(highs, lows, closes, tolerance)
# Touch tolerance for strength: use merge tol (same price scale)
touch_tol = merge_tol
# Build level dicts with strength scores
# Score each candidate on recent rejection-weighted touches
raw_levels: list[dict] = []
for price, method in candidates:
touches = _count_price_touches(price, highs, lows, closes, tolerance)
strength = _strength_from_touches(touches, total_bars)
base = _METHOD_BASE_STRENGTH.get(method, 0)
evidence = _respect_evidence(
price, highs, lows, closes, touch_tol, base=base
)
raw_levels.append({
"price_level": price,
"strength": strength,
"strength": int(evidence["strength"]),
"detection_method": method,
"type": "", # will be tagged after merge
"type": "",
"sources": [method],
"rejection_count": int(evidence["rejection_count"]),
"last_rejection_age": evidence["last_rejection_age"],
"weighted_respects": float(evidence["weighted_respects"]),
})
# Merge nearby levels
merged = _merge_levels(raw_levels, tolerance)
# Tag as support/resistance
merged = _merge_levels(raw_levels, merge_tol)
tagged = _tag_levels(merged, current_price)
capped = _cap_levels(tagged, max_levels=max_levels)
capped.sort(key=lambda x: x["strength"], reverse=True)
return capped
# Sort by strength descending
tagged.sort(key=lambda x: x["strength"], reverse=True)
return tagged
def cluster_sr_zones(
levels: list[dict],
current_price: float,
tolerance: float = 0.02,
max_zones: int | None = None,
strength_mode: str = "sum",
) -> list[dict]:
"""Cluster nearby S/R levels into zones.
@@ -263,8 +718,26 @@ def cluster_sr_zones(
low = min(prices)
high = max(prices)
midpoint = (low + high) / 2.0
strength = min(100, sum(lvl["strength"] for lvl in cluster))
if strength_mode == "soft":
strongest = max(int(lvl["strength"]) for lvl in cluster)
all_sources = {
source
for lvl in cluster
for source in (lvl.get("sources") or [lvl.get("detection_method", "unknown")])
}
strength = min(100, strongest + (5 if len(all_sources) > 1 else 0))
elif strength_mode == "sum":
strength = min(100, sum(int(lvl["strength"]) for lvl in cluster))
all_sources = {
source
for lvl in cluster
for source in (lvl.get("sources") or [lvl.get("detection_method", "unknown")])
}
else:
raise ValueError(f"Unsupported S/R zone strength mode: {strength_mode}")
level_count = len(cluster)
rejection_count = max(int(lvl.get("rejection_count", 0)) for lvl in cluster)
ages = [lvl.get("last_rejection_age") for lvl in cluster if lvl.get("last_rejection_age") is not None]
# 4. Tag zone type
zone_type = "support" if midpoint < current_price else "resistance"
@@ -276,6 +749,9 @@ def cluster_sr_zones(
"strength": strength,
"type": zone_type,
"level_count": level_count,
"sources": sorted(all_sources),
"rejection_count": rejection_count,
"last_rejection_age": min(ages) if ages else None,
})
# 5. Split into support and resistance pools, each sorted by strength desc
@@ -319,11 +795,10 @@ def cluster_sr_zones(
return selected
async def recalculate_sr_levels(
db: AsyncSession,
symbol: str,
tolerance: float = DEFAULT_TOLERANCE,
tolerance: float | None = None,
) -> list[SRLevel]:
"""Recalculate S/R levels for a ticker and persist to DB.
@@ -380,7 +855,7 @@ async def recalculate_sr_levels(
async def get_sr_levels(
db: AsyncSession,
symbol: str,
tolerance: float = DEFAULT_TOLERANCE,
tolerance: float | None = None,
) -> list[SRLevel]:
"""Get S/R levels for a ticker, recalculating on every request (MVP).
+13 -9
View File
@@ -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.
+493 -12
View File
@@ -4,23 +4,37 @@
**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.
---
## 1. How the levels are built today
`app/services/sr_service.py::detect_sr_levels`, over **all stored history**
(`query_ohlcv` with no date range — 5 years / ~1260 daily bars per ticker):
> **Update (2026-07-12 detector rewrite):** several gaps below were addressed in
> `sr_service` / `indicator_service` — close-bin VP, local-peak HVN, POC/VAH/VAL
> as candidates, LVN dropped from S/R, pivot prominence + lookbacks, rejection-
> weighted recency strength, ATR-adaptive merge, hard cap, round numbers. The
> table documents the *pre-rewrite* failure modes measured on the snapshot; keep
> it for historical context. Re-measure density on prod after deploy if gate
> rates shift.
1. Candidates = volume-profile **HVN and LVN** bins + **pivot** swing highs/lows.
2. Strength = share of bars that "touched" the level, scaled so ~20% of bars → 100.
3. Nearby levels merged within 0.5%; tagged `support` if below spot, else `resistance`.
`app/services/sr_service.py::detect_sr_levels` (post-rewrite):
### Where that departs from best practice
1. Candidates = VP **POC / VAH / VAL / local HVN peaks** (lookback 252) +
**prominent** swing pivots (lookback 504) + nearby **round numbers**.
2. Strength = rejection-weighted touches on last 252 bars with recency decay
(pass-throughs down-weighted); method base + confluence on merge.
3. Nearby levels merged with **ATR-adaptive** tolerance (clamped ~0.41.5%);
capped (~16, interleaved S/R); tagged `support` if below spot, else `resistance`.
### Where the pre-rewrite detector departed from best practice
Measured on `backtest_snapshots/prod.sqlite` (AAPL, 1261 bars, spot $308.63):
@@ -35,8 +49,8 @@ Measured on `backtest_snapshots/prod.sqlite` (AAPL, 1261 bars, spot $308.63):
| **Round-number levels absent** — the mechanism with the best empirical support ([Osler 2000](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=888805)). | not implemented |
Not a defect: the pivot `window=2` is the standard 5-bar Williams fractal. What it
lacks is a **prominence filter** — AAPL yields 338 pivots over 1261 bars, one every
~3.7 bars.
lacked pre-rewrite is a **prominence filter** — AAPL yielded 338 pivots over 1261
bars, one every ~3.7 bars.
### The structural problem: resistance famine
@@ -383,6 +397,473 @@ Note `--allow-spawn` is required on Windows: `_mp_context()` has no `fork`/
large, consistent across five nested windows — and still didn't survive a holdout.
Nested lookbacks are not out-of-sample. Split by entry date before believing anything.
## 7. Archived S/R v2 investigation (2026-07-12/13)
The detector rewrite was decomposed into the causal arms below. They are names
in the historical experiment record, not supported runtime configuration:
| arm | behavior |
|---|---|
| `production_control` | deployed detector plus legacy 1.5 primary selection |
| `rr_aligned_control` | deployed detector; primary selection uses activation `min_rr` |
| `rewrite` | rewritten detector with activation-aligned primary selection |
| `soft_zones` | rewrite plus max-strength/confluence zone aggregation |
| `confirmed_rounds` | soft zones; standalone rounds need two rejection clusters |
| `gate_v2` | confirmed rounds plus uncapped gate evidence |
The matrix runner, comparator, environment switch, and candidate-level audit
were removed when the investigation closed. The compact comparison JSONs,
cohort CSVs, and this narrative retain the decisions; Git history retains the
raw implementation and reports for forensic reconstruction.
> Validation result: `confirmed_rounds` is rejected and is no longer a lockable
> arm. It remains in the corrected training matrix only to preserve the causal
> experiment record. The first training reports used an entry end bound without
> forwarding it to the portfolio calendar, leaving each book in flat cash through
> the test period. The simulator now treats `BACKTEST_ENTRY_END` as an inclusive
> entry bound and truncates the calendar after the final position can resolve.
The isolated `rr_aligned_control` validation also failed (Sharpe 2.78 to 1.32,
CAGR 73.3% to 33.1%, drawdown 11.7% to 18.4%). The live 1.5 primary-selection
behavior is therefore frozen: although it predates the 2.0 activation gate, it
acts as a useful selectivity mechanism. The final detector matrix holds that
behavior constant and varies only detection/zone policy:
- `rewrite_legacy_primary`
- `soft_zones_legacy_primary`
- `confirmed_rounds_legacy_primary`
- `gate_v2_legacy_primary`
The post-2024 interval has informed earlier research, so this is validation rather
than a pristine holdout; do not sweep variants on it. No deployment follows
automatically. A lower validation Sharpe or higher drawdown remains a no-ship
result even when CAGR rises.
## 8. Final detector-only result: no rewritten gate arm advances
The last matrix froze production's effective gate (`primary min_rr=1.5`,
activation `min_rr=2.0`) and varied only level detection/zone policy on entries
through 2024-06-30. Corrected portfolio calendars end after the last position can
resolve; there is no flat-cash tail.
| arm | Sharpe | CAGR | MaxDD | qualified | net avg R | ex-top-5% |
|---|---:|---:|---:|---:|---:|---:|
| production control | **1.28** | **28.8%** | 21.4% | 676 | **0.230** | **0.066** |
| rewrite + legacy primary | 0.96 | 21.0% | 22.2% | 1,200 | 0.037 | -0.102 |
| soft zones + legacy primary | 1.08 | 25.1% | **17.8%** | 1,161 | 0.045 | -0.096 |
| confirmed rounds + legacy primary | 1.14 | 22.1% | 20.2% | 570 | 0.189 | 0.045 |
| gate v2 + legacy primary | 0.87 | 16.7% | 20.7% | 604 | 0.187 | 0.041 |
No arm advances to validation. The raw rewrite retains only 249 of 676 production
setups, removes 427 good setups, and adds 951 setups with negative expectancy.
Round confirmation repairs the added cohort but still removes 430 production
setups whose 30-day average (+0.680R) exceeds the additions (+0.511R). Uncapping
recovers only 16 of those missing setups. The old detector averages 43.3 gate
levels versus 15.0 rewritten and 24.0 rewritten-uncapped levels.
**Standing no-ship decision:** keep both the deployed detector and the legacy 1.5
primary-selection behavior in the trading path. The rewritten structure may only
proceed as a separately computed display model. Do not merge this research branch
into production as-is.
### Hidden-feature isolation
The deployed detector accidentally measures long-memory historical price traffic
rather than genuine S/R. A dedicated matrix holds the complete gate fixed and
changes one legacy component at a time:
- `legacy_geometry_neutral`: old locations, every merged strength fixed at 50;
- `legacy_pivots_only`: unfiltered full-history pivots, no VP grid;
- `legacy_traffic_grid_only`: deployed HVN+LVN grid, no pivots.
The traffic matrix first established whether geometry, pivots, or the
range-occupancy grid reproduced production on pre-2024 training data.
Corrected training result:
| arm | qualified | Sharpe | CAGR | MaxDD | net avg R | ex-top-5% |
|---|---:|---:|---:|---:|---:|---:|
| production control | 676 | 1.28 | 28.8% | 21.4% | 0.230 | 0.066 |
| corrected neutral geometry | 505 | 1.48 | 33.5% | 18.2% | 0.230 | 0.087 |
| pivots only | 717 | 1.38 | 32.9% | 25.2% | 0.236 | 0.076 |
| traffic grid only | 504 | **1.72** | **41.8%** | **16.8%** | **0.271** | **0.136** |
The traffic grid is the first research arm to beat control simultaneously on
Sharpe, CAGR, drawdown, and robust expectancy. It retains 264 production setups
at +0.214R ex-top-5%, adds 240 at +0.051R, and removes 412 at only +0.010R.
Against corrected neutral geometry, only 239 qualified setups overlap; the 265
traffic-only setups return +0.140R ex-top-5% versus +0.073R for the 266
neutral-only setups. This is different selection, not merely a lower trade count.
The old `volume_profile` name is misleading. Its helper returns both HVN and LVN
bins, so their union retains almost every one of the 20 evenly spaced centers over
the expanding historical high-low range (19.987 levels on average in the audit).
The later strength calculation does not use volume; it counts bars whose ranges
cross each center. The candidate feature is therefore:
1. a normalized, expanding 20-bin price-range grid;
2. range-touch occupancy strength;
3. no full-history pivot ladder or pivot/grid strength saturation.
Two final training arms isolate the first two items explicitly:
- `legacy_range_grid_touch`: all 20 range centers, no volume calculation, legacy
touch strength;
- `legacy_range_grid_neutral`: identical centers, strength fixed at 50 after
clustering.
The touch arm reproduced `legacy_traffic_grid_only` exactly: all 121,464
candidates, 504 qualified setups, cohort membership, expectancy, and portfolio
metrics match. Volume contributes nothing. Neutral strength won the training
portfolio comparison (Sharpe 1.98 versus 1.72), but failed the locked validation:
| validation arm | Sharpe | CAGR | MaxDD | net avg R | ex-top-5% |
|---|---:|---:|---:|---:|---:|
| production control | **2.78** | **73.3%** | **11.7%** | 0.174 | 0.022 |
| neutral range grid | 1.85 | 43.2% | 15.2% | **0.178** | **0.039** |
The neutral grid is a no-ship. The validation failure prompted a causal audit of
the control rather than another detector sweep. One relationship survives both
periods: dense legacy ladders are a proxy for a wide multiplicative price range.
| control cohort | training ex-top-5% | validation ex-top-5% |
|---|---:|---:|
| at least 70 legacy levels | +0.165R | +0.185R |
| fewer than 70 levels | +0.004R | -0.379R |
Level count is not independently useful after controlling for the last 504
trading days' range. For `log(max(high) / min(low)) >= 1.0315` (about a 2.8x
high/low ratio), the overlap cohort returns +0.292R training and +0.322R
validation ex-top-5%. High density without high range returns -0.096R and
+0.029R. Correlation between the explicit range and legacy level count is 0.864
training and 0.822 validation.
This isolates the hidden feature as a two-year realized price-excursion factor,
accidentally encoded by how many full-history pivots survive a 0.5% merge. It is
not evidence that the arbitrary lines are structural. A rounded threshold of
`log range >= 1.0` remains positive across a 0.9/1.0/1.1 sensitivity plateau.
Two diagnostic-only arms now test whether the explicit scalar replaces the side
effect:
- `production_range504`: deployed targets plus the explicit range gate;
- `rewrite_range504_legacy_primary`: clean targets, frozen primary selection,
plus the identical range gate.
Result:
| training arm | qualified | Sharpe | CAGR | MaxDD | net avg R | ex-top-5% |
|---|---:|---:|---:|---:|---:|---:|
| production control | 676 | 1.28 | 28.8% | 21.4% | 0.230 | 0.066 |
| production + range504 | 180 | **1.65** | 31.4% | **15.9%** | **0.476** | **0.250** |
| clean rewrite | 1,200 | 0.96 | 21.0% | 22.2% | 0.037 | -0.102 |
| clean rewrite + range504 | 291 | 1.46 | **31.5%** | 18.4% | 0.292 | 0.158 |
The scalar recovers most of the detector rewrite's regression. The clean arm now
beats the original production baseline on Sharpe, CAGR, drawdown, and robust
expectancy. Old target geometry retains a smaller edge over the equally filtered
clean arm.
The residual cohort is target selection, not another range feature. The clean arm
retains 63 exceptionally strong common setups (+0.665R ex-top-5%), adds 228 weak
setups (+0.018R), and misses 117 strong old-geometry setups (+0.175R). Of the
clean-only additions, 156 have a standalone round-number primary; all 162
round-only qualified setups have fewer than two observed rejections and return
-0.024R ex-top-5%. Structural-only and round-confluent primaries return +0.394R
and +0.369R.
For 113 of the 117 missed setups, the clean detector does produce a target, but
its selected primary averages 1.56R; 77 land in the 1.5-2R veto band. A final
two-arm residual matrix therefore holds the clean detector and range factor fixed:
- `rewrite_range504_structural_legacy_primary`: exclude standalone round targets,
retain the deployed 1.5 primary floor;
- `rewrite_range504_structural_primary2`: identical, but select the primary from
targets clearing 2.0R.
### Full-period production comparison
The frozen `rewrite_range504_structural_legacy_primary` candidate was run beside
a fresh `production_control` over the complete snapshot with identical
portfolio and exit settings. The retained decision files are
`reports/sr-full-production-vs-candidate-comparison.json` and its cohort CSV;
the redundant full candidate-row reports were removed after consolidation.
This is an apples-to-apples full-history diagnostic against the current live
production path. It is not a new untouched holdout because the post-2024 data
was already inspected while isolating the range factor.
Full-period results reject the clean range-gated candidate as a replacement:
it improves robust setup expectancy and drawdown, but cuts the qualified set
from 1,086 to 290 and the live-path book from 321 to 170 trades. Its 73 unique
qualified symbols are also concentrated (36.2% of setups in the top ten names),
so overlapping setup expectancy does not translate into independent portfolio
opportunity.
### Structural confirmation as a ranking overlay
The next experiment preserves the production detector, setup geometry,
qualified universe, activation gate, and live exit. For each production setup,
the clean detector is evaluated point-in-time only to attach a binary feature:
whether `rewrite_range504_structural_legacy_primary` also clears its core gate.
That confirmation receives a single pre-registered 5% weight:
```text
overlay_rank = 95% * production_80_20_rank + 5% * structural_confirmation
```
There is deliberately no weight sweep and no union with clean-only setups. The
report must first reproduce the production qualified count and production book;
otherwise the comparison is invalid. The candidate advances only if
the overlay improves full-period Sharpe, does not worsen drawdown, and retains
at least 90% of production CAGR. The 1-year and 6-month rows must not both
deteriorate. This remains contaminated full-history research, not promotion
validation.
Result: **reject the overlay**. Production parity is exact, but the overlay
reduces full-period Sharpe from 2.03 to 2.00 and CAGR from 50.0% to 48.4%; max
drawdown improves from 21.4% to 20.0%. Confirmation has real standalone quality
(+0.263R ex-top-5% versus +0.024R), but little marginal ranking value: confirmed
setups already average production rank 92.3 and 46.1% come from ten symbols. The
5% boost changes ordering on only 45 active dates and overweights the same
concentrated names.
### Explicit gate target ladder
The legacy detector's volume-profile label is misleading. It returns both HVN
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<br/>clean detector"]
STRUCT --> STORE[("Persist SRLevel")]
STORE --> HUMAN["Charts and alerts"]
O --> LADDER["Gate Target Ladder<br/>20 range centers + 5-bar pivots"]
LADDER --> SCORE["Count historical price traffic<br/>strength + 0.5% merge + side tag"]
SCORE --> SCAN{"Directional proposal<br/>with R:R ≥ 1.5?"}
SCAN -->|no| NOSETUP["No setup for that direction"]
SCAN -->|yes| ZONES["Cluster 2% target zones<br/>use reachable near edge"]
ZONES --> FILTER["ATR-distance filter<br/>retain up to 5 near-to-far candidates"]
FILTER --> PROB["Estimate target-before-stop<br/>reach probability"]
PROB --> PRIMARY["Headline = most likely candidate<br/>clearing R:R ≥ 1.5 and probability ≥ 20%"]
PRIMARY --> ACTIVATE{"Activation gate<br/>headline R:R ≥ 2.0<br/>probability ≥ 20%<br/>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<br/>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.
#### Ticker-chart diagnostic
The optional **GTL traffic** overlay uses a volume-profile-like layout because
price-axis bars make the ladder's density easy to read. The comparison stops at
the layout:
| Profile | Bar width measures | Valid interpretation |
|---|---|---|
| Volume profile | Traded volume assigned to a price bin | Where trading activity was accepted |
| GTL price traffic | Relative count of historical OHLCV bars crossing a GTL proposal | How strongly the legacy gate geometry revisited that price |
The chart renders GTL traffic as violet bars extending left from the current
price axis. It includes only proposals inside the displayed price range, so old
far-away ladder levels do not compress the candles. Hover reveals price,
crossings, capped strength, side and source. The overlay is off by default,
loaded only on demand from `GET /gate-target-ladder/{symbol}`, and must remain
visually distinct from persisted Structural S/R.
The production GTL 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 levels are labelled
`range_grid`, making the internal purpose explicit.
The arm advances only on exact parity with the full-period production control:
no added or removed qualified setups and identical production-book Sharpe,
CAGR, drawdown, and trade count. Passing parity supports a dual-purpose design:
clean structure for charts and alerts, explicit target ladder for the gate.
Failing parity means the supposedly irrelevant volume pass still affects an
edge case and must be located before any architectural change.
Result: **exact parity**. Both arms produce 1,086 qualified setups from 202,765
candidates, with 1,086 retained, zero added, and zero removed. Both production
books have Sharpe 2.03, CAGR 50.0%, max drawdown 21.4%, and 321 trades. The
retained cohort is +0.2086R net average and +0.0492R after removing the top 5%.
This proves that neither volume nor the `volume_profile` interpretation is part
of the deployed edge.
Implementation decision: keep the clean `detect_sr_levels` output persisted as
human-facing S/R for charts and alerts. The scanner instead builds
`detect_gate_target_ladder` directly from its OHLCV window, materializes it only
for the current scan, and never writes those proposal levels to `SRLevel`. The
primary-target selector retains its independently researched 1.5 floor; the
later live activation gate remains 2.0, and the ATR-trailing exit is unchanged.
The `production_gtl` backtest model calls the same pure helper as the live
scanner, so the final full-period rerun is an implementation-parity check rather
than another detector experiment.
Final implementation-parity result after commit `8161c35`: **pass**. The
regenerated full-period report differs from the pre-integration parity report
only in `generated_at`; all 202,765 candidates, 1,086 qualified setups, cohort
statistics, and portfolio results are unchanged. This closes the local
backtest gate for the dual-purpose implementation. It does not itself authorize
or perform a production deployment.
#### GTL tuning matrix
Exact parity established a safe, explicit control but did not prove that the
inherited GTL constants were optimal. A temporary offline harness exposed those
constants without changing the live scanner. That harness has now been retired;
the compact consolidated reports remain as the reproducible decision record.
The single-command matrix contains 20 full-period arms. Each non-control arm
changes exactly one input:
| Knob | Frozen control | Alternatives | Question isolated |
|---|---:|---|---|
| History | All available bars | 252 / 504 / 756 bars | Is recent or long-cycle range geometry useful? |
| Candidate cap | 5 | 8 / unlimited | Does early pruning discard the useful headline? |
| Max target distance | Existing volatility-dependent rule | 5.5 / 8 ATR universally | Is the medium-volatility unlimited branch the hidden edge? |
| Traffic touch padding | 0.5% | 0 / 0.25% | Does padded price traffic carry information? |
| Proposal merge | 0.5% | 0.25% / 1% | Is proposal density or consolidation important? |
| Target zones | 2% | 1% / 3% | Does the reachable near edge manufacture the gate geometry? |
| Range centers | 20 | 12 / 32 | Is coarse ladder density the useful feature? |
| Pivots | Five-bar swings | None / eleven-bar swings | Do pivots add anything beyond the range ladder? |
| Traffic strength scale | 500 | 250 / 1000 | Does strength saturation affect probability/selection? |
Arms execute sequentially so multiprocessing pools never compete. Each arm
produces full-period production metrics, train/test books split at 2024-07-01,
robust expectancy after removing the top 5% of setups, and retained/added/
removed cohorts against control. The consolidated JSON and Markdown table are
checkpointed after every arm; successful runs delete temporary per-arm reports
unless `--keep-arm-reports` is set.
The pre-registered screen requires all of the following versus control:
full/train/test Sharpe not worse, full-period drawdown not worse, at least 80%
of production trades retained, and positive qualified expectancy after removing
the top 5%. Passing identifies a candidate for forward paper validation, not an
automatic deployment. The post-2024 interval has already influenced this
research, so the split is a robustness check rather than a pristine holdout.
Result on the 2026-07-13 snapshot: **20/20 arms completed; no replacement arm
passed all six checks.** The frozen control remained best on full-period Sharpe
(2.03), CAGR (50.0%), and post-2024 Sharpe (2.78), with 321 production trades
and 21.4% drawdown. The closest replacement, 0.25% touch padding, still fell to
Sharpe 1.94 / CAGR 47.9%. This rejects direct constant replacement; the inherited
behavior is not explained by one obvious GTL knob.
The paired cohorts do expose a narrower mechanism worth testing:
| Variant | Retained control setups | Added by variant | Removed from control |
|---|---|---|---|
| 0.25% touch | 1,049 at +0.214R (+0.058 ex-top-5%) | 18 at -0.235R | 37 at -0.056R |
| Strength 1000 | 1,037 at +0.225R (+0.069) | 122 at +0.301R (+0.152) | 49 at +0.142R (-0.044) |
| 0.25% merge | 791 at +0.238R (+0.078) | 365 at +0.161R (+0.023) | 295 at +0.129R (-0.008) |
| Grid without pivots | 428 at +0.232R (+0.100) | 392 at +0.176R (+0.046) | 658 at +0.208R (+0.041) |
Replacement mixes the retained and added cohorts and also discards the removed
cohort, so its portfolio result could not say which part helped. The archived
confirmation matrix therefore preserved frozen control geometry and decomposed
each selected variant into:
- **intersection** — only control setups also core-qualified by the variant;
- **union** — all core-qualified control setups plus genuinely added variant
setups, using tuned geometry only for those additions.
It also tests pre-registered intersections among the three high-breadth
confirmers. Its control path exactly reproduced the completed tuning matrix.
Result: **13/13 arms completed with exact control parity; no arm passed all six
guardrails.** The decomposition does identify one near-hit:
| Arm | Full Sharpe | Train | Post-2024 | CAGR | Max DD | Trades |
|---|---:|---:|---:|---:|---:|---:|
| Control | 2.03 | 1.28 | 2.78 | 50.0% | 21.4% | 321 |
| Strength-1000 intersection | **2.06** | **1.30** | **2.82** | **50.7%** | 21.7% | 316 |
Strength confirmation removes only 49 of 1,086 qualified control setups. Those
removed setups average +0.142R, but turn negative after removing their largest
5% of outcomes (-0.044R); the retained 1,037 average +0.212R and +0.054R
ex-top-5%. This is consistent with a weak tail-dependence filter. It is not yet
a winner: the original drawdown guardrail remains fixed, and 21.7% is worse
than 21.4% even though the difference is small.
The final parameter test was deliberately one-dimensional. It swept
coarse strength scales around 1000 (625, 750, 875, 1000, 1125, 1250, 1500,
2000), using intersection only. It reproduced both the frozen control and the
completed strength-1000 result exactly. Promotion required at least two
adjacent non-control scales to pass all six original checks; an isolated winner
was rejected as sensitivity.
Final result: **9/9 arms completed, control parity passed, and the
strength-1000 replication passed.** Scale 1500 was the only arm to clear all
six original checks, but neither adjacent scale (1250 or 2000) cleared them, so
the pre-registered stable-plateau requirement failed. It also traded away
return and setup quality despite its screen pass: CAGR fell from 50.0% to 48.8%
and qualified expectancy from +0.209R to +0.188R.
The sensitivity curve shows a real but non-dominating trade-off. Scales
7501000 produce small Sharpe improvements in parts of the sample but each
misses a different unchanged guardrail; higher scales eventually reduce
drawdown by filtering more setups, while CAGR, expectancy, and then Sharpe
decline. There is no robust parameter neighborhood that improves the whole
book.
**Final decision: keep the frozen Gate Target Ladder and do not deploy a
strength-confirmation gate.** The GTL remains the explicit, volume-free
compatibility component that exactly reproduces the validated production
screen. Clean Structural S/R remains the separate human-facing chart/alert
model. This snapshot is now exhausted for GTL fitting; any future challenger
must be pre-registered and evaluated on genuinely new forward data rather than
another iteration over the same history.
The scheduled backtest, Admin UI, and local snapshot runner therefore default
to `production_gtl`. A manual UI/local run may select `structural_sr` as an
explicit comparison; every report records the model and whether it is the
production path. After deployment, rerun the Admin backtest once to replace any
cached report produced by the former default.
The temporary GTL matrix scripts, configurable detector branches, and
confirmation hooks were removed after this decision. The normal snapshot
backtester now exposes only the production GTL and clean Structural S/R
comparison models.
The post-2024 window has been opened and is now analysis data, not a valid final
promotion holdout. These arms can isolate mechanism, but neither may ship without
new future data or a separately pre-registered walk-forward protocol.
**Next runs, if picked back up:**
- A **per-name target model** for clear-air setups instead of a constant k×ATR. This
+5 -2
View File
@@ -200,8 +200,11 @@ export interface TriggerJobResponse {
job: string;
status: 'triggered' | 'busy' | 'blocked' | 'not_found';
message: string;
target_model?: BacktestTargetModel;
}
export type BacktestTargetModel = 'production_gtl' | 'structural_sr';
export function listJobs() {
return apiClient.get<JobStatus[]>('admin/jobs').then((r) => r.data);
}
@@ -216,9 +219,9 @@ export function toggleJob(jobName: string, enabled: boolean) {
.then((r) => r.data);
}
export function triggerJob(jobName: string) {
export function triggerJob(jobName: string, options?: { target_model?: BacktestTargetModel }) {
return apiClient
.post<TriggerJobResponse>(`admin/jobs/${jobName}/trigger`)
.post<TriggerJobResponse>(`admin/jobs/${jobName}/trigger`, options)
.then((r) => r.data);
}
+7 -1
View File
@@ -1,8 +1,14 @@
import apiClient from './client';
import type { SRLevelResponse } from '../lib/types';
import type { GateTargetLadderResponse, SRLevelResponse } from '../lib/types';
export function getLevels(symbol: string) {
return apiClient
.get<SRLevelResponse>(`sr-levels/${symbol}`)
.then((r) => r.data);
}
export function getGateTargetLadder(symbol: string) {
return apiClient
.get<GateTargetLadderResponse>(`gate-target-ladder/${symbol}`)
.then((r) => r.data);
}
@@ -1,11 +1,23 @@
import { useRef, useEffect, useCallback, useState } from 'react';
import type { OHLCVBar, SRLevel, SRZone, TradeSetup } from '../../lib/types';
import type {
GateTargetLevel,
OHLCVBar,
SRLevel,
SRZone,
TradeSetup,
} from '../../lib/types';
import { formatPrice, formatDate, formatLargeNumber } from '../../lib/format';
interface CandlestickChartProps {
data: OHLCVBar[];
srLevels?: SRLevel[];
zones?: SRZone[];
gateTargetLevels?: GateTargetLevel[];
gateTargetLookbackBars?: number;
gateTargetLoading?: boolean;
gateTargetError?: boolean;
showGateTraffic?: boolean;
onShowGateTrafficChange?: (visible: boolean) => void;
tradeSetup?: TradeSetup;
currentPrice?: number;
}
@@ -74,7 +86,19 @@ function startIndexForPreset(data: OHLCVBar[], preset: RangePreset): number {
return idx < 0 ? 0 : idx;
}
export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup, currentPrice }: CandlestickChartProps) {
export function CandlestickChart({
data,
srLevels = [],
zones = [],
gateTargetLevels = [],
gateTargetLookbackBars = 0,
gateTargetLoading = false,
gateTargetError = false,
showGateTraffic = false,
onShowGateTrafficChange,
tradeSetup,
currentPrice,
}: CandlestickChartProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const overlayCanvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
@@ -210,6 +234,57 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
ctx.fillRect(x - volumeW / 2, yVolume, volumeW, hVolume);
});
// Gate Target Ladder diagnostic: a right-edge PRICE-traffic profile. It is
// intentionally one violet channel (not support/resistance colors), and
// width reflects relative historical bar crossings — never volume.
const visibleGateLevels = showGateTraffic
? gateTargetLevels.filter(
(level) => level.price_level >= lo && level.price_level <= hi,
)
: [];
const gateProfileMaxWidth = Math.min(cw * 0.24, 160);
const gateProfileEndX = ml + cw;
const maxGateTraffic = Math.max(
...visibleGateLevels.map((level) => level.traffic_count),
1,
);
const gateProfileRows = visibleGateLevels.map((level) => {
const width = Math.max(
3,
(level.traffic_count / maxGateTraffic) * gateProfileMaxWidth,
);
return { level, y: yScale(level.price_level), width };
});
if (gateProfileRows.length > 0) {
ctx.save();
ctx.strokeStyle = 'rgba(139, 92, 246, 0.22)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(gateProfileEndX, mt);
ctx.lineTo(gateProfileEndX, priceBottom);
ctx.stroke();
gateProfileRows.forEach(({ level, y, width }) => {
const alpha = 0.12 + (level.strength / 100) * 0.24;
ctx.fillStyle = `rgba(139, 92, 246, ${alpha})`;
ctx.fillRect(gateProfileEndX - width, y - 1.5, width, 3);
ctx.fillStyle = 'rgba(196, 181, 253, 0.7)';
ctx.fillRect(gateProfileEndX - width, y - 1.5, 1, 3);
});
if (gateProfileMaxWidth >= 120) {
ctx.fillStyle = 'rgba(139, 92, 246, 0.78)';
ctx.font = '9px "IBM Plex Mono", ui-monospace, monospace';
ctx.textAlign = 'left';
ctx.fillText(
'GTL PRICE TRAFFIC · NO VOLUME',
gateProfileEndX - gateProfileMaxWidth,
mt + 9,
);
}
ctx.restore();
}
// Nearest support/resistance only (band if it came from a zone)
markers.forEach((m) => {
const isSupport = m.role === 'support';
@@ -267,15 +342,11 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
ctx.stroke();
ctx.setLineDash([]);
// Take-profit zone: green semi-transparent rectangle between entry and target
const tpTop = Math.min(entryY, targetY);
const tpHeight = Math.max(Math.abs(targetY - entryY), 1);
ctx.fillStyle = 'rgba(47, 157, 178, 0.13)';
ctx.fillRect(ml, tpTop, cw, tpHeight);
// Target border
ctx.strokeStyle = 'rgba(47, 157, 178, 0.45)';
// Gate target: a diagnostic marker, not a take-profit zone. Violet ties
// it to the GTL profile without implying that the trade exits here.
ctx.strokeStyle = 'rgba(139, 92, 246, 0.65)';
ctx.lineWidth = 1;
ctx.setLineDash([4, 3]);
ctx.setLineDash([2, 3]);
ctx.beginPath();
ctx.moveTo(ml, targetY);
ctx.lineTo(ml + cw, targetY);
@@ -299,8 +370,8 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
ctx.fillText(`Entry ${formatPrice(tradeSetup.entry_price)}`, ml + cw + 4, entryY + 3);
ctx.fillStyle = 'rgba(239, 145, 130, 0.9)';
ctx.fillText(`SL ${formatPrice(tradeSetup.stop_loss)}`, ml + cw + 4, stopY + 3);
ctx.fillStyle = 'rgba(110, 201, 219, 0.9)';
ctx.fillText(`TP ${formatPrice(tradeSetup.target)}`, ml + cw + 4, targetY + 3);
ctx.fillStyle = 'rgba(196, 181, 253, 0.95)';
ctx.fillText(`Gate ${formatPrice(tradeSetup.target)}`, ml + cw + 4, targetY + 3);
}
// Current price line — the anchor for everything else (drawn on top)
@@ -367,6 +438,13 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
volumeTop,
volumeH,
volumeBottom,
gateProfile: gateProfileRows.length > 0
? {
startX: gateProfileEndX - gateProfileMaxWidth,
endX: gateProfileEndX,
rows: gateProfileRows,
}
: null,
};
// Size the overlay canvas to match
@@ -377,7 +455,16 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
overlay.style.width = `${W}px`;
overlay.style.height = `${H}px`;
}
}, [data, srLevels, visibleRange, zones, tradeSetup, currentPrice]);
}, [
currentPrice,
data,
gateTargetLevels,
showGateTraffic,
srLevels,
tradeSetup,
visibleRange,
zones,
]);
const drawCrosshair = useCallback(() => {
const overlay = overlayCanvasRef.current;
@@ -655,6 +742,43 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
tip.style.left = `${Math.min(mx + 14, rect.width - 180)}px`;
tip.style.top = `${Math.max(my - 80, 8)}px`;
let gateTooltipHtml = '';
const gateRows = (meta.gateProfile?.rows ?? []) as Array<{
level: GateTargetLevel;
y: number;
width: number;
}>;
if (
meta.gateProfile
&& mx >= meta.gateProfile.startX
&& mx <= meta.gateProfile.endX
) {
const hovered = gateRows
.filter(
({ y, width }) =>
Math.abs(my - y) <= 5
&& mx >= meta.gateProfile.endX - width - 4,
)
.sort((a, b) => Math.abs(my - a.y) - Math.abs(my - b.y))[0];
if (hovered) {
const level = hovered.level;
const sources = (level.sources.length
? level.sources
: [level.detection_method])
.map((source) => source.replace(/_/g, ' '))
.join(' + ');
gateTooltipHtml = `
<div class="border-t border-violet-400/30 mt-1.5 pt-1.5 text-violet-200 font-medium mb-1">GTL price traffic · not volume</div>
<div class="grid grid-cols-2 gap-x-3 gap-y-0.5 text-gray-400">
<span>Price</span><span class="text-right text-violet-200">${formatPrice(level.price_level)}</span>
<span>Crossings</span><span class="text-right text-gray-200">${level.traffic_count}</span>
<span>Strength</span><span class="text-right text-gray-200">${level.strength}</span>
<span>Side</span><span class="text-right text-gray-200">${level.type}</span>
<span>Source</span><span class="text-right text-gray-200">${sources}</span>
</div>`;
}
}
// Check if cursor is near trade overlay zone
let tradeTooltipHtml = '';
if (tradeSetup && meta.yScale) {
@@ -670,7 +794,7 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
<span>Direction</span><span class="text-right text-gray-200">${tradeSetup.direction}</span>
<span>Entry</span><span class="text-right text-blue-300">${formatPrice(tradeSetup.entry_price)}</span>
<span>Stop</span><span class="text-right text-red-300">${formatPrice(tradeSetup.stop_loss)}</span>
<span>Target</span><span class="text-right text-emerald-300">${formatPrice(tradeSetup.target)}</span>
<span>Gate target</span><span class="text-right text-violet-200">${formatPrice(tradeSetup.target)}</span>
<span>R:R</span><span class="text-right text-gray-200">${tradeSetup.rr_ratio.toFixed(2)}</span>
</div>`;
}
@@ -684,7 +808,7 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
<span>Low</span><span class="text-right text-gray-200">${formatPrice(bar.low)}</span>
<span>Close</span><span class="text-right text-gray-200">${formatPrice(bar.close)}</span>
<span>Vol</span><span class="text-right text-gray-200" title="${bar.volume.toLocaleString()}">${formatLargeNumber(bar.volume)}</span>
</div>${tradeTooltipHtml}`;
</div>${gateTooltipHtml}${tradeTooltipHtml}`;
} else {
tip.style.display = 'none';
}
@@ -733,6 +857,29 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
</button>
))}
<span className="ml-1 text-[10px] text-gray-600">scroll to zoom · drag to pan</span>
<button
type="button"
aria-pressed={showGateTraffic}
onClick={() => onShowGateTrafficChange?.(!showGateTraffic)}
title={
'Show the Gate Target Ladder as relative historical price traffic (not volume)'
}
className={`ml-auto inline-flex items-center gap-1.5 rounded px-2 py-1 text-[11px] font-medium transition-colors ${
showGateTraffic
? 'bg-violet-400/15 text-violet-200'
: 'text-gray-500 hover:text-violet-200'
}`}
>
<span
aria-hidden="true"
className="h-1.5 w-4 bg-gradient-to-l from-violet-400/80 to-violet-400/10"
/>
{gateTargetLoading
? 'Loading GTL…'
: gateTargetError
? 'GTL unavailable'
: 'GTL traffic'}
</button>
</div>
<div ref={containerRef} className="relative w-full" style={{ height: CHART_HEIGHT }}>
<canvas
@@ -756,6 +903,25 @@ export function CandlestickChart({ data, srLevels = [], zones = [], tradeSetup,
style={{ display: 'none' }}
/>
</div>
{showGateTraffic && gateTargetLevels.length > 0 && (
<div className="mt-2 flex flex-wrap items-center justify-between gap-x-4 gap-y-1 text-[10px] text-gray-500">
<span>
<span className="text-violet-300">GTL price traffic</span>
{' · '}bar width = relative historical crossings{' · '}not volume
</span>
<span className="num text-gray-600">
{gateTargetLevels.length} proposals
{gateTargetLookbackBars > 0 ? ` · ${gateTargetLookbackBars} bars` : ''}
</span>
</div>
)}
{showGateTraffic && !gateTargetLoading && gateTargetLevels.length === 0 && (
<p className="mt-2 text-[10px] text-gray-600">
{gateTargetError
? 'Gate Target Ladder diagnostic could not be loaded.'
: 'No Gate Target Ladder proposals are available for this history.'}
</p>
)}
</div>
);
}
@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useBacktestReport } from '../../hooks/useMarketRegime';
import { triggerJob } from '../../api/admin';
import type { BacktestTargetModel } from '../../api/admin';
import { Button } from '../ui/Button';
import { Callout } from '../ui/Callout';
import { Disclosure } from '../ui/Disclosure';
@@ -143,6 +144,7 @@ export function BacktestPanel() {
const toast = useToast();
const [selectedStrategy, setSelectedStrategy] = useState('');
const [selectedLookback, setSelectedLookback] = useState('');
const [targetModel, setTargetModel] = useState<BacktestTargetModel>('production_gtl');
const monitor = report?.portfolio_monitor ?? null;
const activeStrategy =
@@ -159,10 +161,11 @@ export function BacktestPanel() {
);
const run = useMutation({
mutationFn: () => triggerJob('backtest'),
mutationFn: () => triggerJob('backtest', { target_model: targetModel }),
onSuccess: (res) => {
if (res.status === 'triggered') {
toast.addToast('success', 'Backtest started — results appear when it finishes (a minute or two).');
const label = targetModel === 'production_gtl' ? 'Live GTL' : 'Structural S/R comparison';
toast.addToast('success', `${label} backtest started — results appear when it finishes.`);
setTimeout(() => queryClient.invalidateQueries({ queryKey: ['backtest-report'] }), 8000);
} else {
toast.addToast('info', res.message || 'Could not start backtest');
@@ -184,9 +187,61 @@ export function BacktestPanel() {
so read it as directional.
</p>
</Disclosure>
<Button onClick={() => run.mutate()} loading={run.isPending} className="shrink-0">
{run.isPending ? 'Starting…' : report ? 'Re-run backtest' : 'Run backtest'}
</Button>
<div className="flex w-full flex-col gap-3 sm:w-auto sm:items-end">
<fieldset className="grid w-full grid-cols-1 gap-2 sm:w-[34rem] sm:grid-cols-2">
<legend className="mb-1 text-[11px] font-medium uppercase tracking-wider text-gray-500">
Target model for this run
</legend>
<label
className={`cursor-pointer rounded-lg border px-3 py-2 transition-colors focus-within:ring-2 focus-within:ring-blue-400/60 ${
targetModel === 'production_gtl'
? 'border-blue-400/60 bg-blue-500/10'
: 'border-white/10 bg-white/[0.03] hover:border-white/20'
}`}
>
<input
className="sr-only"
type="radio"
name="backtest-target-model"
value="production_gtl"
checked={targetModel === 'production_gtl'}
onChange={() => setTargetModel('production_gtl')}
/>
<span className="flex items-center justify-between gap-2 text-sm font-medium text-gray-100">
Live GTL
<span className="rounded-full border border-blue-400/40 bg-blue-400/10 px-2 py-0.5 text-[9px] font-semibold uppercase tracking-widest text-blue-300">
Production
</span>
</span>
<span className="mt-1 block text-[11px] leading-4 text-gray-500">
Exact target path used by the live scanner and scheduled backtest.
</span>
</label>
<label
className={`cursor-pointer rounded-lg border px-3 py-2 transition-colors focus-within:ring-2 focus-within:ring-amber-400/60 ${
targetModel === 'structural_sr'
? 'border-amber-400/50 bg-amber-500/10'
: 'border-white/10 bg-white/[0.03] hover:border-white/20'
}`}
>
<input
className="sr-only"
type="radio"
name="backtest-target-model"
value="structural_sr"
checked={targetModel === 'structural_sr'}
onChange={() => setTargetModel('structural_sr')}
/>
<span className="text-sm font-medium text-gray-200">Structural S/R</span>
<span className="mt-1 block text-[11px] leading-4 text-gray-500">
Comparison only; uses chart structure as the target source.
</span>
</label>
</fieldset>
<Button onClick={() => run.mutate()} loading={run.isPending} className="shrink-0">
{run.isPending ? 'Starting…' : report ? 'Re-run backtest' : 'Run backtest'}
</Button>
</div>
</div>
{isLoading && <Callout variant="empty">Loading</Callout>}
@@ -206,6 +261,10 @@ export function BacktestPanel() {
{report.params.cost_per_side_pct != null && (
<> · net of {report.params.cost_per_side_pct}%/side costs</>
)}
{' '}· target model:{' '}
<span className={report.params.is_production_target_model === false ? 'text-amber-300' : 'text-blue-300'}>
{report.params.target_model_label ?? 'Unknown (legacy report)'}
</span>
</p>
{monitor && monitorRun ? (
@@ -0,0 +1,205 @@
import type { TradeSetup } from '../../lib/types';
const MOMENTUM_WEIGHT = 0.8;
const VOLATILITY_WEIGHT = 0.2;
interface ProductionRankStripProps {
setup?: TradeSetup;
momentumGate: number;
}
function clampPercent(value: number): number {
return Math.min(100, Math.max(0, value));
}
function topShare(value: number): string {
return `${Math.max(1, Math.round(100 - clampPercent(value)))}%`;
}
function PercentileRail({
value,
colorClass,
gate,
gateLabel,
}: {
value: number | null;
colorClass: string;
gate?: number;
gateLabel?: string;
}) {
const normalized = value == null ? 0 : clampPercent(value);
const normalizedGate = gate == null ? null : clampPercent(gate);
return (
<div
className="relative mt-2 h-1.5 rounded-full bg-white/[0.07]"
role="meter"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={value == null ? undefined : normalized}
aria-label={value == null ? 'Percentile unavailable' : `${normalized.toFixed(1)} percentile`}
>
{value != null && (
<span
className={`absolute inset-y-0 left-0 rounded-full ${colorClass}`}
style={{ width: `${normalized}%` }}
/>
)}
{normalizedGate != null && (
<span
className="absolute -top-1.5 h-4 w-px bg-gray-300/70"
style={{ left: `${normalizedGate}%` }}
title={gateLabel}
>
<span className="absolute -top-4 left-1/2 -translate-x-1/2 whitespace-nowrap text-[8px] uppercase tracking-[0.14em] text-gray-500">
gate
</span>
</span>
)}
</div>
);
}
export function ProductionRankStrip({ setup, momentumGate }: ProductionRankStripProps) {
const momentum = setup?.momentum_percentile ?? null;
const volatility = setup?.volatility_percentile ?? null;
const storedRank = setup?.strategy_rank ?? null;
const hasBlend = momentum != null && volatility != null;
const computedBlend = hasBlend
? momentum * MOMENTUM_WEIGHT + volatility * VOLATILITY_WEIGHT
: null;
const rank = storedRank ?? computedBlend ?? momentum;
if (rank == null && momentum == null && volatility == null) return null;
const normalizedRank = clampPercent(rank ?? 0);
const momentumContribution = hasBlend ? clampPercent(momentum) * MOMENTUM_WEIGHT : normalizedRank;
const volatilityContribution = hasBlend ? clampPercent(volatility) * VOLATILITY_WEIGHT : 0;
const gateEnabled = momentumGate > 0;
const gatePassed = momentum != null && (!gateEnabled || momentum >= momentumGate);
return (
<section
className="mt-4 border-y border-white/[0.07] py-4"
aria-label="Production ranking snapshot"
>
<div className="grid gap-5 lg:grid-cols-[minmax(170px,0.55fr)_minmax(0,1.8fr)] lg:gap-8">
<div className="flex items-end justify-between gap-4 lg:block">
<div>
<p className="num text-[9px] uppercase tracking-[0.22em] text-gray-500">
Production rank
</p>
{rank != null ? (
<p className="font-display mt-1 text-3xl font-semibold tracking-tight text-gray-100">
{normalizedRank.toFixed(1)}
<span className="ml-1 text-sm font-normal text-gray-500">%ile</span>
</p>
) : (
<p className="font-display mt-1 text-2xl font-semibold text-gray-500">Unavailable</p>
)}
</div>
<div className="text-right lg:mt-2 lg:text-left">
{rank != null && (
<p className="text-[11px] text-gray-400">top {topShare(normalizedRank)} of the universe</p>
)}
{momentum != null && gateEnabled && (
<p className={`mt-0.5 text-[10px] ${gatePassed ? 'text-blue-300' : 'text-red-300'}`}>
momentum gate {gatePassed ? 'passed' : 'not passed'}
</p>
)}
</div>
</div>
<div className="min-w-0">
<div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
<p className="text-[11px] font-medium text-gray-300">80/20 weighted rank</p>
<p className="num text-[10px] text-gray-500">
{hasBlend
? `${momentumContribution.toFixed(1)} momentum + ${volatilityContribution.toFixed(1)} volatility`
: 'momentum-only fallback / volatility rank unavailable'}
</p>
</div>
<div
className="relative mt-2 h-2.5 overflow-visible rounded-full bg-white/[0.07]"
role="meter"
aria-label={rank == null ? 'Production rank unavailable' : `Production rank ${normalizedRank.toFixed(1)} percentile`}
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={rank == null ? undefined : normalizedRank}
>
<span
className={`absolute inset-y-0 left-0 bg-blue-500 ${
volatilityContribution > 0 ? 'rounded-l-full' : 'rounded-full'
}`}
style={{ width: `${momentumContribution}%` }}
title={`Momentum contribution ${momentumContribution.toFixed(1)} points`}
/>
{volatilityContribution > 0 && (
<span
className="absolute inset-y-0 rounded-r-full bg-amber-400"
style={{ left: `${momentumContribution}%`, width: `${volatilityContribution}%` }}
title={`Volatility contribution ${volatilityContribution.toFixed(1)} points`}
/>
)}
{rank != null && (
<span
className="absolute -top-1.5 h-5 w-px bg-gray-100 shadow-[0_0_8px_rgba(237,238,243,0.55)]"
style={{ left: `${normalizedRank}%` }}
title={`Combined rank ${normalizedRank.toFixed(1)}`}
/>
)}
</div>
<div className="mt-5 grid gap-x-8 gap-y-4 sm:grid-cols-2">
<div>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-[11px] font-medium text-blue-200">Residual 12-1 momentum</p>
<p className="mt-0.5 text-[9px] text-gray-600">80% weight / activation signal</p>
</div>
<div className="text-right">
<p className="num text-sm text-gray-200">
{momentum == null ? '-' : momentum.toFixed(1)}
</p>
{momentum != null && (
<p className="text-[9px] text-gray-600">top {topShare(momentum)}</p>
)}
</div>
</div>
<PercentileRail
value={momentum}
colorClass="bg-blue-500"
gate={gateEnabled ? momentumGate : undefined}
gateLabel={gateEnabled ? `Activation requires at least the ${momentumGate.toFixed(0)}th percentile` : undefined}
/>
</div>
<div>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-[11px] font-medium text-amber-200">6-month realized volatility</p>
<p className="mt-0.5 text-[9px] text-gray-600">20% weight / ordering tilt only</p>
</div>
<div className="text-right">
<p className="num text-sm text-gray-200">
{volatility == null ? '-' : volatility.toFixed(1)}
</p>
{volatility != null && (
<p className="text-[9px] text-gray-600">top {topShare(volatility)}</p>
)}
</div>
</div>
<PercentileRail value={volatility} colorClass="bg-amber-400" />
</div>
</div>
</div>
</div>
<p className="mt-3 text-[9px] leading-relaxed text-gray-600">
Cross-sectional snapshot from the latest setup scan - not a historical price indicator.
Volatility can improve ordering, but it never opens the activation gate by itself.
</p>
</section>
);
}
@@ -118,7 +118,7 @@ function TargetTable({ setup, selectedPrice, onSelect, honorsTarget }: {
honorsTarget: boolean;
}) {
if (!setup.targets || setup.targets.length === 0) {
return <p className="text-xs text-gray-500">No overhead levels detected.</p>;
return <p className="text-xs text-gray-500">No gate target proposals detected.</p>;
}
return (
@@ -129,7 +129,7 @@ function TargetTable({ setup, selectedPrice, onSelect, honorsTarget }: {
aria-label={
honorsTarget
? 'Choose the take-profit level for the rail and paper trade'
: 'Choose a level to preview on the rail (does not affect the exit)'
: 'Choose a Gate Target Ladder proposal to preview (does not affect the exit)'
}
>
<thead>
@@ -140,8 +140,8 @@ function TargetTable({ setup, selectedPrice, onSelect, honorsTarget }: {
<th className="py-2 pr-3" title="Reward-to-risk if the trade were exited at this level. Used by the activation gate — not an exit.">
Gate R:R
</th>
<th className="py-2" title="Modelled odds of price TOUCHING this level within ~30 days. Not the odds of the trade winning — the trade does not exit here.">
Touch odds
<th className="py-2" title="Modelled probability of reaching this target before the stop within ~30 days. Not the odds of the trade winning — the production trade does not exit here.">
Reach probability
</th>
</tr>
</thead>
@@ -455,7 +455,7 @@ function SetupCard({ setup, action, currentPrice, risk, regime, exitPolicy, sele
>
{setup.targets.map((t) => (
<option key={`${t.sr_level_id}-${t.price}`} value={t.price} className="bg-[#14161f]">
{formatPrice(t.price)} · {t.probability.toFixed(0)}% touch odds · {t.classification}{t.is_primary ? ' · primary' : ''}
{formatPrice(t.price)} · {t.probability.toFixed(0)}% reach probability · {t.classification}{t.is_primary ? ' · primary' : ''}
</option>
))}
</select>
@@ -482,21 +482,21 @@ function SetupCard({ setup, action, currentPrice, risk, regime, exitPolicy, sele
document.body,
)}
{/* Levels ladder still fully explorable (clicking a row drives the rail
and the candlestick overlay), but framed as what it is: overhead
structure used to screen the setup, not a menu of exits. */}
{/* GTL targets remain explorable (clicking a row drives the rail and
candlestick marker), but they screen the setup rather than defining
production exits. */}
{setup.targets && setup.targets.length > 0 && (
<details className="mt-3" open>
<summary className="cursor-pointer text-[11px] font-medium text-gray-500 transition-colors hover:text-gray-300">
{honorsTarget
? `Take-profit levels (${setup.targets.length}) · select one to preview it and use it when taking`
: `Overhead levels (${setup.targets.length}) · select one to preview it on the rail and chart`}
: `Gate targets (${setup.targets.length}) · select one to preview it on the rail and chart`}
</summary>
{!honorsTarget && (
<p className="mt-1.5 text-[11px] leading-relaxed text-gray-600">
Resistance levels the scanner found. Their R:R and touch odds are what got this setup
through the gate but the trade exits on the trailing stop, so price reaching one of
these is not a sell signal. Clicking only moves the marker.
Gate Target Ladder proposals used by the scanner. Their headline R:R and reach
probability determine gate eligibility, but the trade exits on the trailing stop;
reaching one is not a sell signal. Clicking only moves the marker.
</p>
)}
<div className="mt-2">
+1
View File
@@ -39,6 +39,7 @@ export function useFetchSymbolData(options: UseFetchSymbolDataOptions = {}) {
queryClient.invalidateQueries({ queryKey: ['sentiment', symbol] });
queryClient.invalidateQueries({ queryKey: ['fundamentals', symbol] });
queryClient.invalidateQueries({ queryKey: ['sr-levels', symbol] });
queryClient.invalidateQueries({ queryKey: ['gate-target-ladder', symbol] });
queryClient.invalidateQueries({ queryKey: ['scores', symbol] });
// Fetch re-runs the scanner → setups/confidence change. Refresh both the
// per-ticker trades (['trades', symbol]) and the Overview list (['trades']).
+17 -3
View File
@@ -1,12 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { getOHLCV } from '../api/ohlcv';
import { getScores } from '../api/scores';
import { getLevels } from '../api/sr-levels';
import { getGateTargetLadder, getLevels } from '../api/sr-levels';
import { getSentiment } from '../api/sentiment';
import { getFundamentals } from '../api/fundamentals';
import * as tradesApi from '../api/trades';
export function useTickerDetail(symbol: string) {
export function useTickerDetail(symbol: string, includeGateTargetLadder = false) {
const ohlcv = useQuery({
queryKey: ['ohlcv', symbol],
queryFn: () => getOHLCV(symbol),
@@ -25,6 +25,12 @@ export function useTickerDetail(symbol: string) {
enabled: !!symbol,
});
const gateTargetLadder = useQuery({
queryKey: ['gate-target-ladder', symbol],
queryFn: () => getGateTargetLadder(symbol),
enabled: !!symbol && includeGateTargetLadder,
});
const sentiment = useQuery({
queryKey: ['sentiment', symbol],
queryFn: () => getSentiment(symbol),
@@ -43,5 +49,13 @@ export function useTickerDetail(symbol: string) {
enabled: !!symbol,
});
return { ohlcv, scores, srLevels, sentiment, fundamentals, trades };
return {
ohlcv,
scores,
srLevels,
gateTargetLadder,
sentiment,
fundamentals,
trades,
};
}
+2 -1
View File
@@ -2,7 +2,8 @@
* What actually closes a trade.
*
* The setup's `target` is NOT an exit under the production policy: it is a
* screening artifact the nearest S/R level, used to compute the R:R and
* screening artifact the headline Gate Target Ladder proposal, used to
* compute the R:R and
* probability that admit the setup through the activation gate. The live exit
* (`paper_trade_service.resolve_open_trades`) never reads it; `atr_trailing`
* closes on the initial stop, a trailing stop, or the max hold.
+19
View File
@@ -399,6 +399,9 @@ export interface BacktestReport {
horizon_days: number;
min_lookback: number;
cost_per_side_pct?: number;
target_model?: 'production_gtl' | 'structural_sr';
target_model_label?: string;
is_production_target_model?: boolean;
};
overall_qualified: BacktestBucket;
overall_all: BacktestBucket;
@@ -604,6 +607,22 @@ export interface SRLevelResponse {
count: number;
}
export interface GateTargetLevel {
price_level: number;
type: 'support' | 'resistance';
strength: number;
detection_method: string;
sources: string[];
traffic_count: number;
}
export interface GateTargetLadderResponse {
symbol: string;
levels: GateTargetLevel[];
count: number;
lookback_bars: number;
}
// Sentiment
export interface CitationItem {
url: string;
+5 -5
View File
@@ -92,7 +92,7 @@ function RadarSetupRow({ setup, rank, reason, name, selected, onSelect }: RadarR
className={`grid cursor-pointer grid-cols-[20px_minmax(92px,116px)_44px_1fr_auto] items-center gap-2.5 rounded-lg px-2 py-2.5 transition-colors ${
selected ? 'bg-blue-400/[0.08]' : 'hover:bg-white/[0.03]'
} ${qualified ? '' : 'opacity-60'}`}
title={`gate: R:R ${setup.rr_ratio.toFixed(1)}:1${prob != null ? ` · touch odds ${Math.round(prob)}%` : ''} (screening, not an exit) · click to focus`}
title={`gate: R:R ${setup.rr_ratio.toFixed(1)}:1${prob != null ? ` · reach probability ${Math.round(prob)}%` : ''} (screening, not an exit) · click to focus`}
>
<span className="num text-[11px] text-gray-500">{rank}</span>
<span className="min-w-0">
@@ -168,8 +168,8 @@ function FocusCard({ setup, name, badge, badgeTone, footNote, onReset }: {
</div>
</div>
{/* The headline stat is the signal that actually selected this ticker.
R:R and touch odds are gate inputs computed from an S/R level the
trade never exits at they get quiet, labelled treatment. */}
R:R and reach probability are gate inputs computed from a GTL
proposal the trade never exits at they get quiet treatment. */}
<div className="flex items-start gap-10 text-right">
{setup.momentum_percentile != null && (
<div title="Residual 12-1 month momentum percentile across the universe. This is why the ticker was selected.">
@@ -188,12 +188,12 @@ function FocusCard({ setup, name, badge, badgeTone, footNote, onReset }: {
)}
<div
className="max-w-[13rem]"
title="Gate metrics. The reward/risk and touch odds of the nearest S/R level are what admitted this setup through the activation gate. The trade does NOT exit at that level — it exits on the trailing stop."
title="Gate metrics. The reward/risk and reach probability of the headline Gate Target Ladder proposal are what admitted this setup. The trade does NOT exit there — it exits on the trailing stop."
>
<p className="section-index">gate metrics</p>
<p className="num mt-1.5 text-sm text-gray-300">
R:R {setup.rr_ratio.toFixed(1)}:1
{prob != null && <> · touch {Math.round(prob)}%</>}
{prob != null && <> · reach {Math.round(prob)}%</>}
</p>
<p className="mt-1 text-[10.5px] leading-relaxed text-gray-500">
screening only exits on the trailing stop, not at the level
+22 -1
View File
@@ -17,6 +17,7 @@ import { SentimentPanel } from '../components/ticker/SentimentPanel';
import { FundamentalsPanel } from '../components/ticker/FundamentalsPanel';
import { IndicatorSelector } from '../components/ticker/IndicatorSelector';
import { RecommendationPanel } from '../components/ticker/RecommendationPanel';
import { ProductionRankStrip } from '../components/ticker/ProductionRankStrip';
import { Button } from '../components/ui/Button';
import { Callout } from '../components/ui/Callout';
import { formatPrice } from '../lib/format';
@@ -120,8 +121,17 @@ function DataFreshnessBar({
export default function TickerDetailPage() {
const { symbol = '' } = useParams<{ symbol: string }>();
const [showGateTraffic, setShowGateTraffic] = useState(false);
const companyName = useTickerNames().get(symbol.toUpperCase());
const { ohlcv, scores, srLevels, sentiment, fundamentals, trades } = useTickerDetail(symbol);
const {
ohlcv,
scores,
srLevels,
gateTargetLadder,
sentiment,
fundamentals,
trades,
} = useTickerDetail(symbol, showGateTraffic);
const ingestion = useFetchSymbolData();
const watchlist = useWatchlist();
const addToWatchlist = useAddToWatchlist();
@@ -438,13 +448,24 @@ export default function TickerDetailPage() {
data={ohlcv.data}
srLevels={srLevels.data?.levels}
zones={srLevels.data?.zones}
gateTargetLevels={gateTargetLadder.data?.levels}
gateTargetLookbackBars={gateTargetLadder.data?.lookback_bars}
gateTargetLoading={gateTargetLadder.isLoading && showGateTraffic}
gateTargetError={gateTargetLadder.isError}
showGateTraffic={showGateTraffic}
onShowGateTrafficChange={setShowGateTraffic}
tradeSetup={overlayWithTarget}
currentPrice={priceInfo?.price}
/>
<p className="mt-2 text-[11px] text-gray-500">
Only the nearest support &amp; resistance are drawn. Full list in the S/R Levels tab.
{srLevels.isError && ' S/R levels unavailable.'}
{gateTargetLadder.isError && ' GTL diagnostic unavailable.'}
</p>
<ProductionRankStrip
setup={longSetup ?? shortSetup}
momentumGate={gateMomentum}
/>
</>
)}
{(longSetup || shortSetup) && (
+27
View File
@@ -0,0 +1,27 @@
# Backtest report index
Reports dated 2026-07-11 or earlier are the historical production research
record and remain untouched.
The completed 2026-07-12/13 S/R and Gate Target Ladder research is preserved as
compact decision evidence instead of full per-arm replay output:
- `sr-v2-validation-comparison.json` and `sr-v2-validation-cohorts.csv` record
the held-out detector comparison.
- `sr-full-production-vs-candidate-comparison.json` and its cohort CSV record
the full-period clean-structure replacement decision.
- `sr-explicit-target-ladder-comparison.json` and its cohort CSV record exact
GTL parity: 202,765 candidates, 1,086 qualified setups, 321 book trades,
Sharpe 2.03, CAGR 50.0%, and max drawdown 21.4% in both arms.
- The three `backtest-20260713-gtl-*.json/.md` pairs record the tuning,
confirmation, and strength-sensitivity decisions. No stable improvement was
found, so the production GTL stayed frozen.
The large `backtest-sr-*.json` replay files were removed after consolidation.
They duplicated hundreds of thousands of candidate rows while adding no
decision information beyond the compact comparisons and the narrative in
`docs/research/sr-levels-and-exits.md`. The original raw files remain available
in Git history if a forensic reconstruction is ever necessary.
The initial untracked `backtest-20260712-sr-detector-rewrite.json` is local-only
and is intentionally not part of the repository.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
# GTL confirmation/union matrix
Status: **complete**
Holdout split: `2024-07-01`
Completed arms: 13/13
| Arm | Mode | Qualified | Full Sharpe | CAGR | Max DD | Trades | Train Sharpe | Test Sharpe | Ex-top-5% R | Screen |
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| control | intersection | 1086 | 2.03 | 50.0 | 21.4 | 321 | 1.28 | 2.78 | 0.049 | 0/0 |
| touch_intersection | intersection | 1049 | 1.94 | 47.1 | 21.7 | 323 | 1.26 | 2.59 | 0.060 | 2/6 |
| touch_union | union | 1104 | 2.01 | 50.1 | 20.8 | 322 | 1.29 | 2.73 | 0.042 | 4/6 |
| strength_intersection | intersection | 1037 | 2.06 | 50.7 | 21.7 | 316 | 1.30 | 2.82 | 0.054 | 5/6 |
| strength_union | union | 1208 | 1.91 | 47.2 | 18.7 | 338 | 1.44 | 2.37 | 0.062 | 4/6 |
| merge_intersection | intersection | 791 | 2.01 | 47.5 | 18.8 | 293 | 1.44 | 2.56 | 0.071 | 4/6 |
| merge_union | union | 1418 | 1.72 | 41.0 | 19.7 | 342 | 1.40 | 1.89 | 0.042 | 4/6 |
| grid_intersection | intersection | 429 | 1.60 | 29.8 | 13.6 | 224 | 1.48 | 1.68 | 0.069 | 3/6 |
| grid_union | union | 1454 | 2.05 | 54.3 | 22.0 | 348 | 2.06 | 1.95 | 0.053 | 4/6 |
| touch_strength_intersection | intersection | 1009 | 1.99 | 47.9 | 18.7 | 317 | 1.35 | 2.59 | 0.068 | 4/6 |
| touch_merge_intersection | intersection | 767 | 1.79 | 40.5 | 22.7 | 297 | 1.18 | 2.31 | 0.070 | 2/6 |
| strength_merge_intersection | intersection | 750 | 1.99 | 46.7 | 17.9 | 290 | 1.44 | 2.50 | 0.067 | 4/6 |
| touch_strength_merge_intersection | intersection | 733 | 1.81 | 40.9 | 21.2 | 292 | 1.25 | 2.28 | 0.071 | 3/6 |
## Interpretation guardrail
Intersections test the retained control cohort; unions test control plus genuinely added setups. The post-2024 interval is a robustness check, not a pristine holdout. Passing does not authorize deployment.
@@ -0,0 +1,912 @@
{
"status": "complete",
"generated_at": "2026-07-13T13:48:09.363544+00:00",
"snapshot": "/Users/taathde3/git/lab/signal_platform/backtest_snapshots/prod.sqlite",
"workers": 12,
"holdout_split": "2024-07-01",
"arm_count": 9,
"strength_scales": [
625.0,
750.0,
875.0,
1000.0,
1125.0,
1250.0,
1500.0,
2000.0
],
"arms": [
{
"name": "control",
"config": {
"name": "control",
"mode": "intersection",
"confirmations": []
},
"candidates": 202765,
"qualified": 1086,
"qualified_net_avg_r": 0.209,
"qualified_net_avg_r_ex_top5": 0.049,
"full_book": {
"sharpe": 2.03,
"cagr_pct": 50.0,
"max_drawdown_pct": 21.4,
"trades": 321,
"win_rate": 37.4,
"avg_hold_days": 15.3,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.28,
"cagr_pct": 28.8,
"max_drawdown_pct": 21.4,
"trades": 175,
"win_rate": 33.7,
"avg_hold_days": 14.2,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.78,
"cagr_pct": 73.3,
"max_drawdown_pct": 11.7,
"trades": 150,
"win_rate": 42.0,
"avg_hold_days": 16.6,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 202765,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": null,
"description": "Frozen GTL composition-path parity control.",
"screen": {
"checks": {},
"passed": 0,
"total": 0,
"advances": false
}
},
{
"name": "strength_625_intersection",
"config": {
"name": "strength_625_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_625",
"strength_scale": 625.0
}
]
},
"candidates": 202765,
"qualified": 1070,
"qualified_net_avg_r": 0.206,
"qualified_net_avg_r_ex_top5": 0.047,
"full_book": {
"sharpe": 1.96,
"cagr_pct": 47.6,
"max_drawdown_pct": 21.4,
"trades": 318,
"win_rate": 37.7,
"avg_hold_days": 15.4,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.22,
"cagr_pct": 27.1,
"max_drawdown_pct": 21.4,
"trades": 173,
"win_rate": 34.1,
"avg_hold_days": 14.3,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.71,
"cagr_pct": 70.3,
"max_drawdown_pct": 11.7,
"trades": 149,
"win_rate": 42.3,
"avg_hold_days": 16.6,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14682,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 1070,
"net_avg_r": 0.2063,
"net_avg_r_ex_top5": 0.0468
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 16,
"net_avg_r": 0.3605,
"net_avg_r_ex_top5": 0.2131
}
},
"description": "Require control confirmation at traffic-strength scale 625.",
"screen": {
"checks": {
"full_sharpe_not_worse": false,
"train_sharpe_not_worse": false,
"test_sharpe_not_worse": false,
"drawdown_not_worse": true,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 3,
"total": 6,
"advances": false
}
},
{
"name": "strength_750_intersection",
"config": {
"name": "strength_750_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_750",
"strength_scale": 750.0
}
]
},
"candidates": 202765,
"qualified": 1061,
"qualified_net_avg_r": 0.209,
"qualified_net_avg_r_ex_top5": 0.05,
"full_book": {
"sharpe": 2.03,
"cagr_pct": 50.0,
"max_drawdown_pct": 21.2,
"trades": 316,
"win_rate": 38.6,
"avg_hold_days": 15.5,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.36,
"cagr_pct": 31.0,
"max_drawdown_pct": 21.2,
"trades": 171,
"win_rate": 35.7,
"avg_hold_days": 14.6,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.71,
"cagr_pct": 70.3,
"max_drawdown_pct": 11.7,
"trades": 149,
"win_rate": 42.3,
"avg_hold_days": 16.6,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14738,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 1061,
"net_avg_r": 0.2095,
"net_avg_r_ex_top5": 0.0499
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 25,
"net_avg_r": 0.1722,
"net_avg_r_ex_top5": -0.0891
}
},
"description": "Require control confirmation at traffic-strength scale 750.",
"screen": {
"checks": {
"full_sharpe_not_worse": true,
"train_sharpe_not_worse": true,
"test_sharpe_not_worse": false,
"drawdown_not_worse": true,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 5,
"total": 6,
"advances": false
}
},
{
"name": "strength_875_intersection",
"config": {
"name": "strength_875_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_875",
"strength_scale": 875.0
}
]
},
"candidates": 202765,
"qualified": 1044,
"qualified_net_avg_r": 0.21,
"qualified_net_avg_r_ex_top5": 0.051,
"full_book": {
"sharpe": 2.04,
"cagr_pct": 49.7,
"max_drawdown_pct": 21.2,
"trades": 316,
"win_rate": 38.3,
"avg_hold_days": 15.4,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.31,
"cagr_pct": 28.8,
"max_drawdown_pct": 21.2,
"trades": 171,
"win_rate": 35.1,
"avg_hold_days": 14.3,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.77,
"cagr_pct": 72.6,
"max_drawdown_pct": 11.7,
"trades": 149,
"win_rate": 42.3,
"avg_hold_days": 16.6,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14787,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 1044,
"net_avg_r": 0.2103,
"net_avg_r_ex_top5": 0.0506
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 42,
"net_avg_r": 0.1665,
"net_avg_r_ex_top5": -0.0502
}
},
"description": "Require control confirmation at traffic-strength scale 875.",
"screen": {
"checks": {
"full_sharpe_not_worse": true,
"train_sharpe_not_worse": true,
"test_sharpe_not_worse": false,
"drawdown_not_worse": true,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 5,
"total": 6,
"advances": false
}
},
{
"name": "strength_1000_intersection",
"config": {
"name": "strength_1000_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_1000",
"strength_scale": 1000.0
}
]
},
"candidates": 202765,
"qualified": 1037,
"qualified_net_avg_r": 0.212,
"qualified_net_avg_r_ex_top5": 0.054,
"full_book": {
"sharpe": 2.06,
"cagr_pct": 50.7,
"max_drawdown_pct": 21.7,
"trades": 316,
"win_rate": 38.6,
"avg_hold_days": 15.5,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.3,
"cagr_pct": 28.7,
"max_drawdown_pct": 21.7,
"trades": 173,
"win_rate": 35.3,
"avg_hold_days": 14.4,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.82,
"cagr_pct": 74.9,
"max_drawdown_pct": 11.7,
"trades": 147,
"win_rate": 42.9,
"avg_hold_days": 16.7,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14821,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 1037,
"net_avg_r": 0.2118,
"net_avg_r_ex_top5": 0.0536
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 49,
"net_avg_r": 0.1417,
"net_avg_r_ex_top5": -0.0437
}
},
"description": "Require control confirmation at traffic-strength scale 1000.",
"screen": {
"checks": {
"full_sharpe_not_worse": true,
"train_sharpe_not_worse": true,
"test_sharpe_not_worse": true,
"drawdown_not_worse": false,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 5,
"total": 6,
"advances": false
}
},
{
"name": "strength_1125_intersection",
"config": {
"name": "strength_1125_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_1125",
"strength_scale": 1125.0
}
]
},
"candidates": 202765,
"qualified": 1026,
"qualified_net_avg_r": 0.206,
"qualified_net_avg_r_ex_top5": 0.047,
"full_book": {
"sharpe": 2.0,
"cagr_pct": 48.4,
"max_drawdown_pct": 21.5,
"trades": 316,
"win_rate": 38.6,
"avg_hold_days": 15.3,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.2,
"cagr_pct": 26.0,
"max_drawdown_pct": 21.5,
"trades": 173,
"win_rate": 35.3,
"avg_hold_days": 14.3,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.82,
"cagr_pct": 73.4,
"max_drawdown_pct": 10.2,
"trades": 146,
"win_rate": 43.2,
"avg_hold_days": 16.7,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14845,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 1026,
"net_avg_r": 0.2061,
"net_avg_r_ex_top5": 0.0471
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 60,
"net_avg_r": 0.2517,
"net_avg_r_ex_top5": 0.0922
}
},
"description": "Require control confirmation at traffic-strength scale 1125.",
"screen": {
"checks": {
"full_sharpe_not_worse": false,
"train_sharpe_not_worse": false,
"test_sharpe_not_worse": true,
"drawdown_not_worse": false,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 3,
"total": 6,
"advances": false
}
},
{
"name": "strength_1250_intersection",
"config": {
"name": "strength_1250_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_1250",
"strength_scale": 1250.0
}
]
},
"candidates": 202765,
"qualified": 1025,
"qualified_net_avg_r": 0.205,
"qualified_net_avg_r_ex_top5": 0.046,
"full_book": {
"sharpe": 2.01,
"cagr_pct": 49.0,
"max_drawdown_pct": 21.5,
"trades": 316,
"win_rate": 38.6,
"avg_hold_days": 15.4,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.2,
"cagr_pct": 26.0,
"max_drawdown_pct": 21.5,
"trades": 173,
"win_rate": 35.3,
"avg_hold_days": 14.3,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.82,
"cagr_pct": 74.9,
"max_drawdown_pct": 11.7,
"trades": 147,
"win_rate": 42.9,
"avg_hold_days": 16.7,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14846,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 1025,
"net_avg_r": 0.2054,
"net_avg_r_ex_top5": 0.0463
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 61,
"net_avg_r": 0.2622,
"net_avg_r_ex_top5": 0.0578
}
},
"description": "Require control confirmation at traffic-strength scale 1250.",
"screen": {
"checks": {
"full_sharpe_not_worse": false,
"train_sharpe_not_worse": false,
"test_sharpe_not_worse": true,
"drawdown_not_worse": false,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 3,
"total": 6,
"advances": false
}
},
{
"name": "strength_1500_intersection",
"config": {
"name": "strength_1500_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_1500",
"strength_scale": 1500.0
}
]
},
"candidates": 202765,
"qualified": 1010,
"qualified_net_avg_r": 0.188,
"qualified_net_avg_r_ex_top5": 0.034,
"full_book": {
"sharpe": 2.03,
"cagr_pct": 48.8,
"max_drawdown_pct": 20.9,
"trades": 313,
"win_rate": 39.0,
"avg_hold_days": 15.4,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.28,
"cagr_pct": 27.9,
"max_drawdown_pct": 20.9,
"trades": 171,
"win_rate": 35.7,
"avg_hold_days": 14.5,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.79,
"cagr_pct": 71.5,
"max_drawdown_pct": 10.2,
"trades": 145,
"win_rate": 43.4,
"avg_hold_days": 16.7,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14866,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 1010,
"net_avg_r": 0.1876,
"net_avg_r_ex_top5": 0.0342
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 76,
"net_avg_r": 0.4876,
"net_avg_r_ex_top5": 0.2554
}
},
"description": "Require control confirmation at traffic-strength scale 1500.",
"screen": {
"checks": {
"full_sharpe_not_worse": true,
"train_sharpe_not_worse": true,
"test_sharpe_not_worse": true,
"drawdown_not_worse": true,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 6,
"total": 6,
"advances": true
}
},
{
"name": "strength_2000_intersection",
"config": {
"name": "strength_2000_intersection",
"mode": "intersection",
"confirmations": [
{
"name": "strength_2000",
"strength_scale": 2000.0
}
]
},
"candidates": 202765,
"qualified": 990,
"qualified_net_avg_r": 0.174,
"qualified_net_avg_r_ex_top5": 0.02,
"full_book": {
"sharpe": 1.95,
"cagr_pct": 46.3,
"max_drawdown_pct": 18.0,
"trades": 313,
"win_rate": 38.3,
"avg_hold_days": 15.4,
"skipped_book_full": 0
},
"holdout": {
"train": {
"sharpe": 1.23,
"cagr_pct": 26.4,
"max_drawdown_pct": 18.0,
"trades": 170,
"win_rate": 34.7,
"avg_hold_days": 14.2,
"skipped_book_full": 0
},
"test": {
"sharpe": 2.68,
"cagr_pct": 67.9,
"max_drawdown_pct": 10.2,
"trades": 146,
"win_rate": 43.2,
"avg_hold_days": 16.8,
"skipped_book_full": 0
}
},
"gtl_diagnostics": {
"variant": "gtl_confirmation",
"candidate_count": 202765,
"primary_source_counts": {
"pivot_point": 196290,
"range_grid": 180036
},
"primary_round_only": 0,
"primary_strength_100": 138596,
"avg_primary_strength": 80.109,
"avg_primary_distance_atr": 2.293,
"avg_primary_rejection_count": 41.908,
"avg_raw_level_count": 53.204,
"avg_gate_level_count": 53.204,
"avg_range_504_log": 0.612,
"range_factor_pass": 19566,
"structural_overlay_rows": 0,
"structural_overlay_pass": 0,
"structural_overlay_weight": null,
"gtl_confirmation_rows": 202765,
"gtl_confirmation_pass": 14873,
"gtl_confirmation_tuned_additions": 0
},
"cohort_vs_control": {
"retained": {
"count": 990,
"net_avg_r": 0.174,
"net_avg_r_ex_top5": 0.0196
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null
},
"removed": {
"count": 96,
"net_avg_r": 0.5661,
"net_avg_r_ex_top5": 0.3608
}
},
"description": "Require control confirmation at traffic-strength scale 2000.",
"screen": {
"checks": {
"full_sharpe_not_worse": false,
"train_sharpe_not_worse": false,
"test_sharpe_not_worse": false,
"drawdown_not_worse": true,
"retains_80pct_trades": true,
"robust_expectancy_positive": true
},
"passed": 3,
"total": 6,
"advances": false
}
}
],
"promotion_rule": "At least two adjacent non-control scales must pass all six original guardrails.",
"control_parity": "pass",
"completed_at": "2026-07-13T14:21:42.222381+00:00",
"strength_1000_replication": "pass",
"advancing_arms": [
"strength_1500_intersection"
],
"stable_plateau_pairs": [],
"stable_candidate": false,
"ranking_by_full_sharpe": [
"strength_1000_intersection",
"strength_875_intersection",
"control",
"strength_750_intersection",
"strength_1500_intersection",
"strength_1250_intersection",
"strength_1125_intersection",
"strength_625_intersection",
"strength_2000_intersection"
]
}
@@ -0,0 +1,21 @@
# GTL strength-confirmation sensitivity
Status: **complete**
Holdout split: `2024-07-01`
Completed arms: 9/9
| Arm | Qualified | Full Sharpe | CAGR | Max DD | Trades | Train Sharpe | Test Sharpe | Ex-top-5% R | Screen |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| control | 1086 | 2.03 | 50.0 | 21.4 | 321 | 1.28 | 2.78 | 0.049 | 0/0 |
| strength_625_intersection | 1070 | 1.96 | 47.6 | 21.4 | 318 | 1.22 | 2.71 | 0.047 | 3/6 |
| strength_750_intersection | 1061 | 2.03 | 50.0 | 21.2 | 316 | 1.36 | 2.71 | 0.050 | 5/6 |
| strength_875_intersection | 1044 | 2.04 | 49.7 | 21.2 | 316 | 1.31 | 2.77 | 0.051 | 5/6 |
| strength_1000_intersection | 1037 | 2.06 | 50.7 | 21.7 | 316 | 1.30 | 2.82 | 0.054 | 5/6 |
| strength_1125_intersection | 1026 | 2.00 | 48.4 | 21.5 | 316 | 1.20 | 2.82 | 0.047 | 3/6 |
| strength_1250_intersection | 1025 | 2.01 | 49.0 | 21.5 | 316 | 1.20 | 2.82 | 0.046 | 3/6 |
| strength_1500_intersection | 1010 | 2.03 | 48.8 | 20.9 | 313 | 1.28 | 2.79 | 0.034 | 6/6 |
| strength_2000_intersection | 990 | 1.95 | 46.3 | 18.0 | 313 | 1.23 | 2.68 | 0.020 | 3/6 |
## Pre-registered interpretation
The six original guardrails remain unchanged. A stable candidate requires at least two adjacent non-control scales to pass all six; an isolated passing scale is rejected as sensitivity, not promoted.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
# GTL tuning matrix
Status: **complete**
Holdout split: `2024-07-01`
Completed arms: 20/20
| Arm | Full Sharpe | CAGR | Max DD | Trades | Train Sharpe | Test Sharpe | Ex-top-5% R | Screen |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| control | 2.03 | 50.0 | 21.4 | 321 | 1.28 | 2.78 | 0.049 | 0/0 |
| lookback_252 | 1.43 | 26.0 | 22.8 | 241 | 0.78 | 2.07 | 0.152 | 1/6 |
| lookback_504 | 1.75 | 38.8 | 21.4 | 289 | 1.32 | 2.20 | 0.055 | 4/6 |
| lookback_756 | 1.60 | 36.4 | 21.4 | 318 | 1.28 | 1.81 | 0.013 | 4/6 |
| candidates_8 | 1.91 | 44.5 | 18.9 | 320 | 1.44 | 2.39 | 0.024 | 4/6 |
| candidates_all | 1.74 | 39.0 | 18.9 | 322 | 1.15 | 2.31 | 0.014 | 3/6 |
| max_atr_5_5 | 1.61 | 37.0 | 23.1 | 348 | 1.15 | 2.09 | -0.011 | 1/6 |
| max_atr_8 | 1.89 | 46.3 | 19.7 | 348 | 1.51 | 2.27 | 0.003 | 4/6 |
| touch_0 | 1.88 | 45.5 | 22.0 | 321 | 1.25 | 2.48 | 0.053 | 2/6 |
| touch_0_25pct | 1.94 | 47.9 | 20.8 | 323 | 1.27 | 2.57 | 0.050 | 3/6 |
| merge_0_25pct | 1.82 | 43.4 | 20.8 | 327 | 1.78 | 1.71 | 0.062 | 4/6 |
| merge_1pct | 1.52 | 34.8 | 19.4 | 319 | 1.49 | 1.42 | 0.045 | 4/6 |
| zones_1pct | 1.71 | 38.3 | 20.3 | 291 | 1.43 | 1.91 | -0.044 | 3/6 |
| zones_3pct | 1.71 | 41.9 | 17.8 | 339 | 1.34 | 1.92 | -0.006 | 3/6 |
| grid_12 | 1.74 | 42.7 | 22.0 | 337 | 1.25 | 2.16 | 0.023 | 2/6 |
| grid_32 | 1.63 | 37.8 | 17.3 | 325 | 1.54 | 1.57 | 0.043 | 4/6 |
| pivots_none | 1.89 | 44.7 | 17.3 | 309 | 1.72 | 1.96 | 0.077 | 4/6 |
| pivots_11bar | 1.31 | 27.9 | 18.7 | 334 | 1.15 | 1.43 | 0.011 | 3/6 |
| strength_250 | 1.82 | 45.1 | 18.9 | 326 | 1.29 | 2.32 | 0.056 | 4/6 |
| strength_1000 | 1.88 | 46.7 | 18.5 | 338 | 1.42 | 2.32 | 0.080 | 4/6 |
## Interpretation guardrail
The post-2024 interval has already informed prior research. The train/test columns are robustness checks, not a pristine holdout. A passing arm is a candidate for forward paper validation, not automatic production promotion.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
{
"control_report": "/Users/taathde3/git/lab/signal_platform/reports/backtest-sr-full-production_control.json",
"variant_report": "/Users/taathde3/git/lab/signal_platform/reports/backtest-sr-full-explicit_target_ladder.json",
"control_variant": "production_control",
"variant": "explicit_target_ladder",
"retained": {
"count": 1086,
"net_avg_r": 0.2086,
"net_avg_r_ex_top5": 0.0492,
"hold30_avg_r": 0.6307
},
"added": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null,
"hold30_avg_r": null
},
"removed": {
"count": 0,
"net_avg_r": null,
"net_avg_r_ex_top5": null,
"hold30_avg_r": null
},
"control_book": {
"sharpe": 2.03,
"cagr_pct": 50.0,
"max_drawdown_pct": 21.4,
"trades": 321,
"skipped_book_full": 0
},
"variant_book": {
"sharpe": 2.03,
"cagr_pct": 50.0,
"max_drawdown_pct": 21.4,
"trades": 321,
"skipped_book_full": 0
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
{
"control_report": "/Users/taathde3/git/lab/signal_platform/reports/backtest-sr-full-production_control.json",
"variant_report": "/Users/taathde3/git/lab/signal_platform/reports/backtest-sr-full-rewrite_range504_structural_legacy_primary.json",
"control_variant": "production_control",
"variant": "rewrite_range504_structural_legacy_primary",
"retained": {
"count": 115,
"net_avg_r": 0.4775,
"net_avg_r_ex_top5": 0.345,
"hold30_avg_r": 1.2952
},
"added": {
"count": 175,
"net_avg_r": 0.279,
"net_avg_r_ex_top5": 0.1477,
"hold30_avg_r": 0.7703
},
"removed": {
"count": 971,
"net_avg_r": 0.1789,
"net_avg_r_ex_top5": 0.024,
"hold30_avg_r": 0.552
},
"control_book": {
"sharpe": 2.03,
"cagr_pct": 50.0,
"max_drawdown_pct": 21.4,
"trades": 321,
"skipped_book_full": 0
},
"variant_book": {
"sharpe": 1.53,
"cagr_pct": 28.8,
"max_drawdown_pct": 14.3,
"trades": 170,
"skipped_book_full": 8
}
}
+590
View File
@@ -0,0 +1,590 @@
symbol,date,direction,cohort,control_rr,variant_rr,control_prob,variant_prob,control_sources,variant_sources,control_net_r,variant_net_r,control_hold30_r,variant_hold30_r
HOOD,2024-07-01,long,removed,2.363534,1.008379,20.78,53.96,pivot_point,volume_profile,-1.047696,0.979879,-1.019196,-1.019196
APP,2024-07-02,long,removed,2.593435,1.597531,31.89,40.48,pivot_point+volume_profile,volume_profile,-1.161328,-1.161328,-1.129295,-1.129295
AXON,2024-07-02,long,removed,2.088883,1.689069,34.97,38.94,pivot_point+volume_profile,volume_profile,2.034275,1.634462,-1.260972,-1.260972
CVNA,2024-07-02,long,added,1.620942,3.003727,35.47,25.26,pivot_point,volume_profile,1.59883,-0.022112,1.252546,1.252546
KEY,2024-07-02,long,retained,2.194943,2.010299,36.34,34.33,pivot_point+volume_profile,volume_profile,-1.174915,-1.174915,-1.121839,-1.121839
SMCI,2024-07-02,long,added,1.790945,2.609255,35.95,28.16,pivot_point+volume_profile,volume_profile,-1.018032,-1.018032,-1.0,-1.0
STX,2024-07-02,long,added,1.933209,2.395527,38.34,30.07,pivot_point+volume_profile,volume_profile,1.879937,2.342254,-1.0,-1.0
TPL,2024-07-02,long,retained,2.197984,2.138032,31.91,32.79,pivot_point+volume_profile,volume_profile,2.155173,2.095222,2.455028,2.455028
VLO,2024-07-02,long,removed,2.313153,1.768764,22.49,37.69,pivot_point+volume_profile,volume_profile,-1.050398,-1.050398,-1.0,-1.0
APP,2024-07-10,long,retained,2.366003,2.392551,33.96,30.1,pivot_point+volume_profile,volume_profile,-1.031019,-1.031019,-1.0,-1.0
CRH,2024-07-10,long,retained,2.366265,2.349782,32.76,30.52,pivot_point+volume_profile,volume_profile,2.311975,2.295492,-1.0,-1.0
CVNA,2024-07-10,long,retained,2.586053,2.786918,22.55,26.76,pivot_point+volume_profile,volume_profile,-1.066693,-1.066693,-1.043339,-1.043339
DELL,2024-07-10,long,retained,2.070397,2.070397,23.99,33.59,volume_profile,volume_profile,-1.026336,-1.026336,-1.0,-1.0
GEN,2024-07-10,long,removed,2.391085,1.689807,25.12,23.92,pivot_point+volume_profile,volume_profile,-0.064503,1.625303,1.507702,1.507702
KEY,2024-07-10,long,removed,7.212904,1.563743,25.59,41.07,pivot_point,volume_profile,-1.520191,1.509962,-1.46641,-1.46641
NVDA,2024-07-10,long,added,1.056776,2.988849,42.75,25.36,volume_profile,volume_profile,-1.028158,-1.028158,-1.0,-1.0
QCOM,2024-07-10,long,retained,2.214523,2.214523,22.33,31.93,pivot_point+volume_profile,volume_profile,-1.049717,-1.049717,-1.0,-1.0
SMCI,2024-07-10,long,added,1.803073,2.05868,31.57,33.73,pivot_point+volume_profile,volume_profile,-1.020646,-1.020646,-1.0,-1.0
STX,2024-07-10,long,removed,2.259748,1.550674,26.04,41.31,pivot_point+volume_profile,volume_profile,-1.058327,-1.058327,-1.0,-1.0
TPL,2024-07-10,long,removed,2.4469,1.856628,29.79,36.39,pivot_point+volume_profile,volume_profile,-1.047047,1.809581,-1.0,-1.0
HOOD,2024-07-16,long,added,1.202989,2.283322,39.07,31.2,pivot_point,volume_profile,-1.030546,-1.030546,-1.0,-1.0
COIN,2024-07-17,long,retained,2.024981,2.994532,27.15,25.32,pivot_point+volume_profile,volume_profile,-1.024552,-1.024552,-1.0,-1.0
CVNA,2024-07-17,long,added,1.892159,2.345824,27.9,30.56,pivot_point,volume_profile,-1.217536,-1.217536,-1.195482,-1.195482
PSX,2024-07-17,long,removed,2.638302,1.847648,24.12,36.52,pivot_point+volume_profile,volume_profile,-1.061989,-1.061989,-1.0,-1.0
TPL,2024-07-17,long,removed,2.191527,1.564777,28.58,41.06,pivot_point+volume_profile,volume_profile,-1.051591,-1.051591,-1.0,-1.0
COIN,2024-07-24,long,added,1.949161,2.798867,28.12,26.67,pivot_point+volume_profile,volume_profile,-1.021168,-1.021168,-1.0,-1.0
T,2024-07-24,long,removed,2.024326,1.762354,26.56,22.78,pivot_point,volume_profile,1.959139,1.697168,2.53466,2.53466
C,2024-07-31,long,removed,2.04091,0.679298,28.95,50.76,pivot_point+volume_profile,volume_profile,-1.056715,-1.056715,-1.0,-1.0
CVNA,2024-07-31,long,added,1.764983,2.179133,29.74,32.32,pivot_point,volume_profile,-1.073779,-1.073779,-1.053721,-1.053721
DECK,2024-07-31,long,retained,2.204131,2.230714,24.24,31.75,pivot_point+volume_profile,volume_profile,-1.035117,-1.035117,-1.0,-1.0
IP,2024-07-31,long,retained,2.061675,2.896383,28.69,25.98,pivot_point+volume_profile,volume_profile,-1.414336,-1.414336,-1.363237,-1.363237
MPC,2024-07-31,long,retained,2.070547,2.070547,26.39,33.59,pivot_point+volume_profile,volume_profile,-1.0517,-1.0517,-1.0,-1.0
PSX,2024-07-31,long,removed,2.68204,1.71537,22.76,38.51,pivot_point+volume_profile,volume_profile,-1.054284,-1.054284,-1.0,-1.0
RL,2024-07-31,long,removed,2.162234,1.801202,22.71,37.2,pivot_point,volume_profile,-1.203596,-1.203596,-1.157407,-1.157407
LHX,2024-08-07,long,removed,2.486849,1.866229,24.22,21.26,pivot_point+volume_profile,volume_profile,-0.060235,-0.060235,0.481976,0.481976
CVNA,2024-08-14,long,added,0.830134,2.688769,54.21,27.51,pivot_point,volume_profile,0.810487,-1.019647,-1.0,-1.0
DECK,2024-08-14,long,retained,2.250336,2.276737,23.74,31.26,pivot_point+volume_profile,volume_profile,-1.034755,-1.034755,-1.0,-1.0
IP,2024-08-14,long,added,1.832687,2.216827,46.74,31.9,pivot_point+volume_profile,volume_profile,1.780846,2.164986,1.646715,1.646715
PANW,2024-08-14,long,retained,2.056851,2.056851,24.55,33.75,pivot_point+volume_profile,volume_profile,2.016184,2.016184,-0.070585,-0.070585
UBER,2024-08-14,long,added,1.686961,2.109652,39.77,33.12,pivot_point+volume_profile,volume_profile,-1.03507,-1.03507,-1.0,-1.0
VRT,2024-08-14,long,added,1.982701,2.095018,38.68,33.29,pivot_point+volume_profile,volume_profile,-1.020453,-1.020453,-1.0,-1.0
CRWD,2024-08-21,long,added,1.638443,2.259128,49.77,31.45,pivot_point,volume_profile,-1.030148,-1.030148,-1.0,-1.0
IFF,2024-08-21,long,retained,2.083267,2.056829,34.83,33.75,pivot_point+volume_profile,volume_profile,-0.05916,-0.05916,0.112124,0.112124
IP,2024-08-21,long,retained,5.241123,2.180305,27.42,32.31,pivot_point+volume_profile,volume_profile,-1.059221,-1.059221,-1.0,-1.0
TFC,2024-08-21,long,removed,2.364212,1.315501,25.38,31.04,pivot_point+volume_profile,volume_profile,-0.057957,1.257544,-0.413095,-0.413095
VST,2024-08-21,long,added,1.802328,2.116052,35.18,33.04,pivot_point+volume_profile,volume_profile,-1.025569,-1.025569,-1.0,-1.0
CEG,2024-08-27,long,retained,2.152078,2.134751,40.63,32.83,pivot_point+volume_profile,volume_profile,-1.03271,-1.03271,-1.0,-1.0
HOOD,2024-08-27,long,added,1.976913,2.059984,26.16,33.71,pivot_point+volume_profile,volume_profile,-1.028923,-1.028923,-1.0,-1.0
BAC,2024-08-28,long,removed,2.358212,1.561372,25.44,26.12,pivot_point+volume_profile,volume_profile,-1.062807,-1.062807,-1.0,-1.0
CFG,2024-08-28,long,removed,2.897858,0.973092,20.97,40.03,pivot_point+volume_profile,volume_profile,-1.057175,-1.057175,-1.0,-1.0
COF,2024-08-28,long,removed,2.031659,1.595906,29.06,25.5,pivot_point+volume_profile,volume_profile,-1.057914,-1.057914,-1.0,-1.0
CRWD,2024-08-28,long,added,1.579228,3.019642,50.8,25.16,pivot_point+volume_profile,volume_profile,-1.03194,-1.03194,-1.0,-1.0
CVNA,2024-08-28,long,added,1.787492,2.250672,27.8,31.54,pivot_point,volume_profile,-1.026143,-1.026143,-1.0,-1.0
IP,2024-08-28,long,retained,5.632903,2.227531,26.85,31.79,pivot_point+volume_profile,volume_profile,-1.066284,-1.066284,-1.0,-1.0
KEY,2024-08-28,long,added,1.718821,2.105375,48.46,33.17,pivot_point+volume_profile,volume_profile,-1.044658,-1.044658,-1.0,-1.0
NVDA,2024-08-28,long,added,1.068723,2.960434,43.01,25.54,pivot_point+volume_profile,volume_profile,-1.025929,-1.025929,-1.0,-1.0
SW,2024-08-28,long,retained,2.747627,2.079496,37.05,33.48,pivot_point+volume_profile,volume_profile,-1.045941,-1.045941,-1.0,-1.0
TFC,2024-08-28,long,removed,2.90902,1.582712,20.89,25.74,pivot_point+volume_profile,volume_profile,-1.062366,-1.062366,-1.0,-1.0
AMT,2024-09-05,long,retained,2.079796,3.058994,43.47,24.91,pivot_point+volume_profile,volume_profile,-1.166052,-1.166052,-1.099904,-1.099904
APP,2024-09-05,long,retained,2.185527,2.355959,23.45,30.46,pivot_point+volume_profile,volume_profile,2.15693,2.327362,8.886403,8.886403
FITB,2024-09-05,long,removed,2.073653,1.910131,43.55,35.65,pivot_point+volume_profile,volume_profile,-1.063196,-1.063196,-1.0,-1.0
KKR,2024-09-05,long,removed,2.181887,1.723269,22.49,38.39,pivot_point,volume_profile,2.13195,1.673332,4.089721,4.089721
NRG,2024-09-05,long,removed,2.25116,1.894631,21.73,35.86,pivot_point,volume_profile,2.212092,1.855563,1.85814,1.85814
RL,2024-09-05,long,added,1.555085,2.24363,50.83,31.61,pivot_point,volume_profile,1.506102,2.194647,4.172316,4.172316
RMD,2024-09-05,long,added,1.99921,2.840897,44.47,26.37,pivot_point+volume_profile,volume_profile,-1.226172,-1.226172,-1.174599,-1.174599
CEG,2024-09-11,long,added,1.880922,2.172351,41.85,32.4,pivot_point+volume_profile,volume_profile,1.848098,2.139527,6.906646,6.906646
HOOD,2024-09-11,long,retained,2.083757,2.549773,26.63,28.66,pivot_point,volume_profile,2.055784,2.5218,4.106526,4.106526
AMT,2024-09-12,long,added,,2.5128,,28.99,,volume_profile,,-1.065809,,-1.0
CMG,2024-09-12,long,retained,2.010624,2.010624,37.13,34.33,pivot_point+volume_profile,volume_profile,-0.045527,-0.045527,1.248258,1.248258
DASH,2024-09-12,long,retained,2.285917,2.438839,25.57,29.66,pivot_point+volume_profile,volume_profile,2.239634,2.392556,4.088344,4.088344
FITB,2024-09-12,long,removed,2.134405,1.061322,27.83,37.42,pivot_point+volume_profile,volume_profile,2.080604,1.007521,1.888333,1.888333
RL,2024-09-12,long,added,,2.091438,,33.33,,volume_profile,,2.038762,,3.391383
RMD,2024-09-12,long,retained,2.562837,2.163551,29.95,32.5,pivot_point+volume_profile,volume_profile,-1.808118,-1.808118,-1.756551,-1.756551
VRT,2024-09-12,long,retained,2.005719,2.207135,25.99,32.01,pivot_point+volume_profile,volume_profile,1.980011,2.181427,3.447561,3.447561
CFG,2024-09-19,long,added,1.562785,2.226657,51.09,31.8,pivot_point,volume_profile,-1.051285,-1.051285,-1.0,-1.0
CPB,2024-09-19,long,removed,2.043159,0.894853,28.92,42.56,pivot_point+volume_profile,volume_profile,-1.131369,-1.131369,-1.069907,-1.069907
CRWD,2024-09-19,long,added,1.654913,2.384252,49.5,30.18,pivot_point,volume_profile,1.618946,2.348286,1.263574,1.263574
CVNA,2024-09-19,long,retained,2.373325,2.373325,20.69,30.29,volume_profile,volume_profile,2.345166,2.345166,6.312101,6.312101
DASH,2024-09-19,long,retained,2.097487,2.973728,26.66,25.45,pivot_point,volume_profile,2.0506,2.926841,3.314464,3.314464
DELL,2024-09-19,long,retained,2.078075,2.078075,31.49,33.49,pivot_point+volume_profile,volume_profile,2.04365,2.04365,0.856462,0.856462
FITB,2024-09-19,long,retained,2.259068,2.276905,31.45,31.26,pivot_point+volume_profile,volume_profile,-1.057874,-1.057874,-1.0,-1.0
IP,2024-09-19,long,retained,4.401095,2.251491,29.22,31.53,pivot_point+volume_profile,volume_profile,-1.065161,-1.065161,-1.0,-1.0
RMD,2024-09-19,long,added,1.992176,2.758242,44.56,26.97,pivot_point+volume_profile,volume_profile,-1.046594,-1.046594,-1.0,-1.0
TFC,2024-09-19,long,added,1.809614,2.2605,47.07,31.44,pivot_point+volume_profile,volume_profile,-1.0599,-1.0599,-1.0,-1.0
VRT,2024-09-19,long,added,1.947838,2.250443,26.14,31.54,pivot_point+volume_profile,volume_profile,1.919599,2.222204,2.642618,2.642618
VST,2024-09-19,long,retained,2.072291,2.072291,23.96,33.56,volume_profile,volume_profile,2.040867,2.040867,5.458704,5.458704
CVNA,2024-09-26,long,retained,10.231888,2.474562,20.25,29.33,pivot_point+volume_profile,volume_profile,-0.029905,2.444657,6.135639,6.135639
DASH,2024-09-26,long,retained,7.365516,2.10784,25.52,33.14,pivot_point+volume_profile,volume_profile,-0.0516,2.05624,4.973482,4.973482
FITB,2024-09-26,long,removed,2.083968,1.931414,21.22,20.36,pivot_point+volume_profile,volume_profile,-1.060556,-1.060556,-1.0,-1.0
RMD,2024-09-26,long,removed,2.186908,1.266636,27.23,32.15,pivot_point+volume_profile,volume_profile,-1.050048,-1.050048,-1.0,-1.0
TGT,2024-09-26,long,removed,2.292853,1.776621,26.1,22.57,pivot_point,volume_profile,-1.06012,-1.06012,-1.0,-1.0
WSM,2024-09-26,long,removed,2.402788,1.658261,20.4,39.44,pivot_point+volume_profile,volume_profile,-1.137583,-1.137583,-1.101101,-1.101101
CVNA,2024-10-03,long,added,1.664246,2.109813,29.74,33.12,pivot_point,volume_profile,1.631671,2.077238,5.882489,5.882489
DECK,2024-10-03,long,retained,2.168365,2.199366,24.44,32.1,pivot_point+volume_profile,volume_profile,2.126606,2.157607,2.672686,2.672686
MSTR,2024-10-03,long,retained,2.022121,2.022121,24.38,34.18,volume_profile,volume_profile,2.001418,2.001418,10.405324,10.405324
NEM,2024-10-03,long,removed,2.437475,1.6442,24.67,24.68,pivot_point+volume_profile,volume_profile,2.383997,1.590722,-1.0,-1.0
CVNA,2024-10-10,long,retained,9.123898,2.511664,20.12,29.0,pivot_point+volume_profile,volume_profile,-0.036109,2.475555,5.358405,5.358405
FITB,2024-10-10,long,retained,2.456651,2.29033,32.3,31.12,pivot_point+volume_profile,volume_profile,2.390999,2.224679,3.42334,3.42334
NVDA,2024-10-10,long,added,0.7889,2.583282,51.54,28.38,pivot_point,volume_profile,0.753151,-0.035748,1.572496,1.572496
PODD,2024-10-10,long,removed,2.231607,1.720271,26.74,23.44,pivot_point+volume_profile,volume_profile,2.180027,1.668691,3.435679,3.435679
COF,2024-10-17,long,added,,2.025877,,34.14,,volume_profile,,-1.058059,,-1.0
COIN,2024-10-17,long,added,1.642968,2.257497,49.7,31.47,pivot_point,volume_profile,-1.024139,-1.024139,-1.0,-1.0
CVNA,2024-10-17,long,retained,9.492877,2.588273,20.09,28.33,pivot_point+volume_profile,volume_profile,-0.037775,2.550498,6.74196,6.74196
DASH,2024-10-17,long,retained,4.392565,2.700376,26.45,27.42,pivot_point+volume_profile,volume_profile,4.334569,2.64238,5.569761,5.569761
GM,2024-10-17,long,retained,2.503676,2.380907,39.07,30.21,pivot_point+volume_profile,volume_profile,2.451817,2.329049,3.26087,3.26087
NVDA,2024-10-17,long,retained,2.240177,2.240177,21.85,31.65,volume_profile,volume_profile,-0.035332,-0.035332,0.170302,0.170302
PNC,2024-10-17,long,removed,2.403014,1.107655,25.0,36.14,pivot_point+volume_profile,volume_profile,2.340479,1.045121,4.292634,4.292634
CFG,2024-10-24,long,removed,2.405384,1.535265,23.18,26.59,pivot_point+volume_profile,volume_profile,2.352507,1.482388,3.350752,3.350752
COF,2024-10-24,long,removed,2.345874,1.529143,25.56,26.7,pivot_point+volume_profile,volume_profile,2.288967,1.472236,6.44221,6.44221
DASH,2024-10-24,long,retained,4.421262,2.573824,26.37,28.46,pivot_point+volume_profile,volume_profile,4.357196,2.509759,5.28825,5.28825
RMD,2024-10-24,long,removed,2.039677,0.914596,28.96,41.9,pivot_point+volume_profile,volume_profile,1.980043,0.854962,0.294367,0.294367
HOOD,2024-10-30,long,added,0.688621,2.735256,55.76,27.15,pivot_point,volume_profile,-1.531615,-1.531615,-1.493148,-1.493148
CFG,2024-10-31,long,retained,2.172117,2.386818,40.6,30.16,pivot_point+volume_profile,volume_profile,2.118575,2.333276,2.2754,2.2754
CVNA,2024-10-31,long,removed,2.780741,1.821707,31.41,36.9,pivot_point+volume_profile,volume_profile,-1.033504,-1.033504,-1.0,-1.0
DASH,2024-10-31,long,removed,5.013156,1.624669,27.42,40.01,pivot_point+volume_profile,volume_profile,-0.057868,1.566801,3.395652,3.395652
IP,2024-10-31,long,added,,2.088162,,33.37,,volume_profile,,2.038095,,0.0
PODD,2024-10-31,long,removed,2.039063,1.924042,28.97,20.46,pivot_point+volume_profile,volume_profile,1.980028,1.865007,4.820368,4.820368
RMD,2024-10-31,long,removed,2.182318,1.268032,27.29,32.12,pivot_point+volume_profile,volume_profile,-1.049721,1.218312,-1.0,-1.0
HOOD,2024-11-06,long,added,1.731126,2.790779,30.87,26.73,pivot_point,volume_profile,1.70455,2.764204,3.150657,3.150657
CFG,2024-11-07,long,added,1.952332,3.052252,41.48,24.95,pivot_point+volume_profile,volume_profile,-1.041993,-1.041993,-1.0,-1.0
CVNA,2024-11-07,long,retained,3.046097,2.126585,29.59,32.92,pivot_point+volume_profile,volume_profile,-1.031364,-1.031364,-1.0,-1.0
NTAP,2024-11-07,long,removed,2.609349,1.630808,20.16,39.9,pivot_point+volume_profile,volume_profile,-1.548219,-1.548219,-1.485722,-1.485722
NVDA,2024-11-07,long,retained,2.116497,2.116497,23.24,33.04,volume_profile,volume_profile,-1.042958,-1.042958,-1.0,-1.0
ZBRA,2024-11-07,long,retained,12.224687,2.898061,25.01,25.96,pivot_point+volume_profile,volume_profile,-1.058806,-1.058806,-1.0,-1.0
HOOD,2024-11-13,long,removed,2.810736,1.620896,25.78,40.07,pivot_point+volume_profile,volume_profile,2.78623,1.596391,2.7301,2.7301
CFG,2024-11-14,long,added,1.901703,2.258489,41.96,31.46,pivot_point+volume_profile,volume_profile,-1.047497,-1.047497,-1.0,-1.0
CVNA,2024-11-14,long,retained,3.294474,2.320747,27.94,30.81,pivot_point+volume_profile,volume_profile,-1.033075,-1.033075,-1.0,-1.0
FIS,2024-11-14,long,removed,2.102161,1.197044,28.21,33.82,pivot_point+volume_profile,volume_profile,-1.066206,-1.066206,-1.0,-1.0
IP,2024-11-14,long,removed,2.506606,1.626125,21.04,39.98,pivot_point+volume_profile,volume_profile,-1.057094,1.569031,-1.0,-1.0
NVDA,2024-11-14,long,added,1.135555,2.5309,40.59,28.83,volume_profile,volume_profile,-1.044243,-1.044243,-1.0,-1.0
TSN,2024-11-14,long,retained,2.596128,2.001554,25.87,34.44,pivot_point+volume_profile,volume_profile,-1.055266,-1.055266,-1.0,-1.0
ZBRA,2024-11-14,long,removed,15.069871,1.640643,25.0,39.74,pivot_point+volume_profile,volume_profile,-1.227627,-1.227627,-1.164907,-1.164907
HOOD,2024-11-20,long,retained,2.234166,2.76471,28.52,26.93,pivot_point,volume_profile,2.208817,-0.025349,2.329013,2.329013
CFG,2024-11-21,long,retained,2.044706,2.448911,40.1,29.57,pivot_point+volume_profile,volume_profile,-1.054029,-1.054029,-1.0,-1.0
CVNA,2024-11-21,long,removed,2.887708,1.864361,30.44,36.28,pivot_point+volume_profile,volume_profile,-1.03591,-1.03591,-1.0,-1.0
DASH,2024-11-21,long,added,1.571744,2.048168,50.13,33.86,pivot_point+volume_profile,volume_profile,-1.05097,-1.05097,-1.0,-1.0
NVDA,2024-11-21,long,retained,2.121212,2.121212,23.18,32.98,volume_profile,volume_profile,-1.036861,-1.036861,-1.0,-1.0
RMD,2024-11-21,long,retained,2.331234,2.277761,40.71,31.25,pivot_point+volume_profile,volume_profile,-1.056353,-1.056353,-1.0,-1.0
TSN,2024-11-21,long,retained,9.528478,2.276611,25.09,31.27,pivot_point+volume_profile,volume_profile,-1.059542,-1.059542,-1.0,-1.0
HOOD,2024-11-27,long,added,1.568666,2.063271,37.79,33.67,pivot_point,volume_profile,1.544006,-1.02466,-1.0,-1.0
CFG,2024-11-29,long,removed,2.778313,1.74038,22.02,38.12,pivot_point+volume_profile,volume_profile,-1.058389,-1.058389,-1.0,-1.0
CVNA,2024-11-29,long,retained,2.009852,2.333981,38.54,30.68,pivot_point+volume_profile,volume_profile,-1.03745,-1.03745,-1.0,-1.0
DASH,2024-11-29,long,retained,2.259005,2.709896,37.65,27.35,pivot_point+volume_profile,volume_profile,-1.056211,-1.056211,-1.0,-1.0
GM,2024-11-29,long,removed,2.328011,1.562034,28.34,41.1,pivot_point+volume_profile,volume_profile,-1.038933,-1.038933,-1.0,-1.0
INCY,2024-11-29,long,removed,2.225995,1.892311,41.8,35.89,pivot_point+volume_profile,volume_profile,-1.142421,-1.142421,-1.102218,-1.102218
NEE,2024-11-29,long,removed,2.084122,1.817583,28.42,21.96,pivot_point+volume_profile,volume_profile,-1.122428,-1.122428,-1.059965,-1.059965
UHS,2024-11-29,long,added,1.577296,2.002636,36.23,34.43,pivot_point+volume_profile,volume_profile,-1.045223,-1.045223,-1.0,-1.0
HOOD,2024-12-05,long,removed,2.527938,1.550767,22.45,41.31,pivot_point+volume_profile,volume_profile,-1.023997,-1.023997,-1.0,-1.0
CFG,2024-12-06,long,removed,2.094709,1.448606,24.3,28.24,pivot_point+volume_profile,volume_profile,-1.06007,-1.06007,-1.0,-1.0
HOOD,2024-12-12,long,retained,2.151963,2.356331,26.23,30.46,pivot_point+volume_profile,volume_profile,-1.187492,-1.187492,-1.165762,-1.165762
EBAY,2024-12-20,long,removed,2.41583,1.503526,39.88,42.18,pivot_point+volume_profile,volume_profile,-1.0524,-1.0524,-1.0,-1.0
HOOD,2024-12-27,long,retained,2.132954,2.327764,26.45,30.74,pivot_point+volume_profile,volume_profile,2.112404,2.307214,4.447604,4.447604
HOOD,2025-01-06,long,retained,2.316894,2.534928,22.25,28.79,pivot_point+volume_profile,volume_profile,-1.023902,-1.023902,-1.0,-1.0
TPL,2025-01-07,long,added,1.779801,2.801287,28.52,26.65,pivot_point+volume_profile,volume_profile,1.75231,-0.02749,0.953313,0.953313
LDOS,2025-01-15,long,retained,2.022643,2.271702,42.78,31.32,pivot_point+volume_profile,volume_profile,-1.054629,-1.054629,-1.0,-1.0
MTB,2025-01-15,long,added,1.506775,2.334803,37.12,30.67,pivot_point+volume_profile,volume_profile,-1.05847,-1.05847,-1.0,-1.0
SATS,2025-01-15,long,removed,2.084923,1.904701,32.21,35.72,pivot_point,volume_profile,2.05013,1.869907,4.79693,4.79693
HOOD,2025-01-22,long,added,1.485045,2.537047,32.93,28.77,volume_profile,volume_profile,1.45805,2.510052,-1.0,-1.0
CBOE,2025-01-23,long,removed,2.406105,1.814548,29.77,37.0,pivot_point+volume_profile,volume_profile,2.340848,1.749291,1.843355,1.843355
CHRW,2025-01-23,long,retained,2.066756,2.066756,36.03,33.63,pivot_point+volume_profile,volume_profile,-2.1852,-2.1852,-2.123782,-2.123782
DASH,2025-01-23,long,added,1.755187,2.183821,43.69,32.27,pivot_point+volume_profile,volume_profile,-1.054221,-1.054221,-1.0,-1.0
EBAY,2025-01-23,long,removed,2.148392,1.370809,27.67,29.84,pivot_point+volume_profile,volume_profile,2.103909,1.326326,-1.0,-1.0
GM,2025-01-23,long,removed,2.219727,1.556927,41.87,41.2,pivot_point+volume_profile,volume_profile,-1.377279,-1.377279,-1.332675,-1.332675
TPL,2025-01-23,long,retained,2.215306,2.215306,22.52,31.92,volume_profile,volume_profile,-1.034187,-1.034187,-1.0,-1.0
ZBRA,2025-01-23,long,retained,10.101825,2.592492,25.05,28.3,pivot_point+volume_profile,volume_profile,-1.239258,-1.239258,-1.179004,-1.179004
HOOD,2025-01-29,long,retained,2.182893,2.182893,22.68,32.28,volume_profile,volume_profile,2.156429,2.156429,-1.0,-1.0
AEP,2025-01-30,long,removed,2.291606,1.611789,33.31,40.23,pivot_point+volume_profile,volume_profile,2.226497,1.54668,2.500623,2.500623
D,2025-01-30,long,added,1.81942,2.484021,46.93,29.25,pivot_point+volume_profile,volume_profile,-1.062307,-1.062307,-1.0,-1.0
DASH,2025-01-30,long,retained,3.032273,2.965588,24.48,25.51,pivot_point+volume_profile,volume_profile,2.976645,2.909961,-1.0,-1.0
EBAY,2025-01-30,long,retained,2.31507,2.050187,40.87,33.83,pivot_point+volume_profile,volume_profile,-1.2003,-1.2003,-1.152131,-1.152131
NEE,2025-01-30,long,removed,2.856326,1.678544,21.26,24.11,pivot_point+volume_profile,volume_profile,-1.048252,-1.048252,-1.0,-1.0
PODD,2025-01-30,long,removed,3.011956,1.716997,35.21,38.49,pivot_point+volume_profile,volume_profile,-1.050309,-1.050309,-1.0,-1.0
SO,2025-01-30,long,removed,2.663459,1.959193,22.11,34.99,pivot_point+volume_profile,volume_profile,2.597821,1.893554,2.106036,2.106036
T,2025-01-30,long,retained,3.890785,2.286312,30.86,31.16,pivot_point+volume_profile,volume_profile,3.827951,2.223478,3.348375,3.348375
HOOD,2025-02-05,long,added,1.411529,2.435669,34.39,29.69,volume_profile,volume_profile,1.383132,2.407272,-1.0,-1.0
CFG,2025-02-06,long,retained,2.426162,2.426162,24.58,29.78,pivot_point+volume_profile,volume_profile,-1.053272,-1.053272,-1.0,-1.0
EBAY,2025-02-06,long,retained,2.564372,2.271866,38.54,31.32,pivot_point+volume_profile,volume_profile,-1.317578,-1.317578,-1.264402,-1.264402
FIX,2025-02-06,long,retained,2.001518,2.001518,24.84,34.44,volume_profile,volume_profile,-1.026408,-1.026408,-1.0,-1.0
MTB,2025-02-06,long,retained,2.398987,2.405595,22.84,29.98,pivot_point+volume_profile,volume_profile,-1.064938,-1.064938,-1.0,-1.0
PODD,2025-02-06,long,retained,2.44026,2.071586,39.65,33.57,pivot_point+volume_profile,volume_profile,-1.054228,-1.054228,-1.0,-1.0
VTR,2025-02-06,long,removed,2.336742,1.660532,40.65,39.4,pivot_point+volume_profile,volume_profile,2.270724,1.594514,3.446489,3.446489
CVNA,2025-02-13,long,retained,2.034467,2.549029,36.23,28.67,pivot_point+volume_profile,volume_profile,-1.035496,-1.035496,-1.0,-1.0
D,2025-02-13,long,removed,2.394779,1.857229,40.08,36.38,pivot_point+volume_profile,volume_profile,-1.059337,-1.059337,-1.0,-1.0
NVDA,2025-02-13,long,added,0.695595,2.97396,66.27,25.45,pivot_point+volume_profile,volume_profile,0.667121,-1.028474,-1.0,-1.0
HOOD,2025-02-20,long,added,1.380515,2.831765,35.23,26.43,volume_profile,volume_profile,-1.02081,-1.02081,-1.0,-1.0
DASH,2025-02-21,long,added,1.564233,2.063261,35.07,33.67,pivot_point,volume_profile,-1.042246,-1.042246,-1.0,-1.0
EBAY,2025-02-21,long,removed,2.020731,1.695948,44.2,38.83,pivot_point+volume_profile,volume_profile,-2.291229,-2.291229,-2.230532,-2.230532
CHRW,2025-02-28,long,removed,2.524211,1.154679,23.89,34.89,pivot_point+volume_profile,volume_profile,-1.056592,-1.056592,-1.0,-1.0
DASH,2025-02-28,long,added,1.553079,2.002972,35.27,34.42,pivot_point,volume_profile,-1.0378,-1.0378,-1.0,-1.0
CHRW,2025-03-07,long,removed,2.160874,1.608008,42.53,40.29,pivot_point+volume_profile,volume_profile,-1.053765,-1.053765,-1.0,-1.0
NEE,2025-03-07,long,added,1.87582,2.31029,46.12,30.92,pivot_point+volume_profile,volume_profile,-1.058424,-1.058424,-1.0,-1.0
NEM,2025-03-07,long,removed,2.53317,1.950455,23.81,20.11,pivot_point,volume_profile,2.491007,1.908292,5.449434,5.449434
T,2025-03-07,long,retained,2.028456,2.028456,25.3,34.1,pivot_point+volume_profile,volume_profile,-1.060056,-1.060056,-1.0,-1.0
KMI,2025-03-14,long,retained,2.170945,2.170945,22.81,32.41,volume_profile,volume_profile,-1.050685,-1.050685,-1.0,-1.0
NEM,2025-03-14,long,removed,2.932488,0.767514,20.73,47.16,pivot_point+volume_profile,volume_profile,-1.04203,0.725485,-1.0,-1.0
AXON,2025-03-21,long,removed,2.475348,1.752563,20.72,37.93,volume_profile,volume_profile,-1.025557,-1.025557,-1.0,-1.0
GDDY,2025-03-21,long,retained,2.701088,2.199113,32.01,32.1,pivot_point+volume_profile,volume_profile,-1.047347,-1.047347,-1.0,-1.0
NEM,2025-03-21,long,removed,2.205772,1.643242,42.03,39.69,pivot_point+volume_profile,volume_profile,-1.04556,-1.04556,-1.0,-1.0
AXON,2025-04-11,long,retained,2.030874,2.030874,25.47,34.07,volume_profile,volume_profile,2.007918,2.007918,3.59907,3.59907
CVNA,2025-04-11,long,removed,2.353936,1.948265,36.68,35.14,pivot_point+volume_profile,volume_profile,2.342119,1.936448,3.057394,3.057394
IBKR,2025-04-11,long,removed,2.24209,1.915206,24.63,35.58,pivot_point+volume_profile,volume_profile,-1.019948,-1.019948,-1.0,-1.0
TYL,2025-04-11,long,added,1.851443,2.070885,43.47,33.58,pivot_point+volume_profile,volume_profile,-1.036647,-1.036647,-1.0,-1.0
WMT,2025-04-11,long,retained,2.018185,2.122872,26.03,32.96,pivot_point+volume_profile,volume_profile,-0.036331,-0.036331,0.935686,0.935686
WMT,2025-04-21,long,removed,2.218352,1.642866,23.69,39.7,pivot_point+volume_profile,volume_profile,-0.038317,1.604549,1.569432,1.569432
APP,2025-04-28,long,removed,2.449157,1.806118,22.57,37.13,pivot_point+volume_profile,volume_profile,2.434952,1.791913,2.458197,2.458197
CHTR,2025-04-28,long,added,1.788035,2.03155,47.39,34.06,pivot_point+volume_profile,volume_profile,1.757898,2.001414,1.195488,1.195488
DASH,2025-04-28,long,added,1.53196,3.055881,40.85,24.93,pivot_point+volume_profile,volume_profile,1.504868,-0.027092,1.953171,1.953171
FICO,2025-04-28,long,removed,2.589579,1.804765,20.32,37.15,pivot_point+volume_profile,volume_profile,-1.034812,1.769953,-1.0,-1.0
GDDY,2025-04-28,long,added,1.783221,2.20482,41.07,32.04,pivot_point+volume_profile,volume_profile,-1.4347,-1.4347,-1.393018,-1.393018
GEN,2025-04-28,long,added,1.995704,2.618785,44.52,28.08,pivot_point+volume_profile,volume_profile,1.955837,2.578918,3.132201,3.132201
ISRG,2025-04-28,long,retained,2.051044,2.044118,33.02,33.91,pivot_point+volume_profile,volume_profile,-0.030325,-0.030325,0.459884,0.459884
LITE,2025-04-28,long,removed,2.424966,1.902659,39.79,35.75,pivot_point+volume_profile,volume_profile,2.408499,1.886192,2.998472,2.998472
LYV,2025-04-28,long,removed,2.22777,1.63153,23.59,39.89,pivot_point+volume_profile,volume_profile,-0.033994,1.597536,1.331497,1.331497
MAA,2025-04-28,long,removed,2.30841,1.614858,25.94,25.18,pivot_point+volume_profile,volume_profile,-1.050537,-1.050537,-1.0,-1.0
MSTR,2025-04-28,long,added,1.849639,2.909547,27.49,25.89,pivot_point,volume_profile,-0.019982,-0.019982,0.593368,0.593368
TPR,2025-04-28,long,retained,2.39514,2.600039,21.48,28.23,pivot_point+volume_profile,volume_profile,2.368427,2.573326,2.053804,2.053804
VST,2025-04-28,long,added,1.987113,2.84257,28.83,26.35,pivot_point+volume_profile,volume_profile,1.96779,2.823248,2.610974,2.610974
KVUE,2025-05-01,long,retained,2.541159,2.399402,34.74,30.04,pivot_point+volume_profile,volume_profile,-1.044749,-1.044749,-1.0,-1.0
HOOD,2025-05-02,long,added,1.579848,2.71337,31.59,27.32,pivot_point,volume_profile,1.56216,2.695683,5.125405,5.125405
ABBV,2025-05-05,long,added,1.695505,2.083795,32.43,33.43,pivot_point+volume_profile,volume_profile,-1.041348,-1.041348,-1.0,-1.0
AMT,2025-05-05,long,removed,2.615411,1.760432,38.11,37.81,pivot_point+volume_profile,volume_profile,-1.047871,-1.047871,-1.0,-1.0
APP,2025-05-05,long,retained,2.13066,2.13066,25.87,32.87,pivot_point+volume_profile,volume_profile,2.11486,2.11486,1.53399,1.53399
AXON,2025-05-05,long,added,1.907457,2.204538,27.28,32.04,pivot_point+volume_profile,volume_profile,1.872667,2.169748,4.37086,4.37086
BKR,2025-05-05,long,removed,2.586196,,38.35,,pivot_point+volume_profile,,-0.031936,,1.23835,
CBRE,2025-05-05,long,removed,2.028372,1.675005,39.7,39.17,pivot_point+volume_profile,volume_profile,-1.037015,-1.037015,-1.0,-1.0
CHTR,2025-05-05,long,retained,2.244983,2.782606,41.6,26.79,pivot_point+volume_profile,volume_profile,-1.034872,-1.034872,-1.0,-1.0
CVNA,2025-05-05,long,added,1.798961,2.121305,39.23,32.98,pivot_point+volume_profile,volume_profile,1.777774,2.100117,1.406108,1.406108
CVS,2025-05-05,long,added,1.677853,2.460807,49.12,29.46,pivot_point+volume_profile,volume_profile,-1.038343,-1.038343,-1.0,-1.0
EBAY,2025-05-05,long,added,1.690447,2.034162,38.11,34.03,pivot_point+volume_profile,volume_profile,1.65223,1.995945,1.926892,1.926892
EXC,2025-05-05,long,removed,2.478172,1.852594,20.3,21.45,pivot_point+volume_profile,volume_profile,-1.144068,-1.144068,-1.085003,-1.085003
FICO,2025-05-05,long,added,1.960447,2.3875,25.97,30.15,pivot_point,volume_profile,-1.036717,-1.036717,-1.0,-1.0
GDDY,2025-05-05,long,retained,2.20301,2.176304,28.66,32.35,pivot_point+volume_profile,volume_profile,-0.035309,-0.035309,-0.402143,-0.402143
GEN,2025-05-05,long,retained,2.047797,2.756485,43.86,26.99,pivot_point+volume_profile,volume_profile,2.002008,2.710697,3.55366,3.55366
IBKR,2025-05-05,long,removed,2.42902,1.979107,22.75,34.73,pivot_point+volume_profile,volume_profile,2.40025,1.950336,2.291226,2.291226
MAA,2025-05-05,long,removed,2.985414,1.645458,35.38,39.66,pivot_point+volume_profile,volume_profile,-1.048771,-1.048771,-1.0,-1.0
MMM,2025-05-05,long,removed,5.279289,1.693847,27.36,38.86,pivot_point+volume_profile,volume_profile,-0.038725,1.655122,0.193898,0.193898
PODD,2025-05-05,long,removed,2.035338,1.903346,44.02,35.74,pivot_point+volume_profile,volume_profile,2.002335,1.870343,2.907644,2.907644
SBAC,2025-05-05,long,removed,8.952552,1.863751,25.14,36.29,pivot_point+volume_profile,volume_profile,-1.046235,-1.046235,-1.0,-1.0
TPR,2025-05-05,long,added,1.840942,2.074077,28.02,33.54,pivot_point+volume_profile,volume_profile,1.808779,2.041914,2.077916,2.077916
VST,2025-05-05,long,removed,2.036511,1.522944,34.0,41.82,pivot_point+volume_profile,volume_profile,2.013574,1.500006,3.09245,3.09245
XEL,2025-05-05,long,removed,2.229859,1.692521,25.96,38.88,pivot_point+volume_profile,volume_profile,-1.053867,-1.053867,-1.0,-1.0
CBRE,2025-05-12,long,retained,2.072185,2.07875,25.97,33.49,pivot_point+volume_profile,volume_profile,-1.042494,-1.042494,-1.0,-1.0
CHTR,2025-05-12,long,retained,2.190034,2.816722,42.2,26.54,pivot_point+volume_profile,volume_profile,-1.041502,-1.041502,-1.0,-1.0
CVNA,2025-05-12,long,added,1.932575,2.069082,35.54,33.6,pivot_point+volume_profile,volume_profile,1.909703,2.04621,1.478573,1.478573
EBAY,2025-05-12,long,removed,2.158189,1.865706,31.76,36.26,pivot_point+volume_profile,volume_profile,2.117684,1.825201,1.569592,1.569592
FFIV,2025-05-12,long,retained,2.168648,2.182334,25.84,32.29,pivot_point+volume_profile,volume_profile,-0.043699,-0.043699,1.095796,1.095796
FICO,2025-05-12,long,added,1.700282,2.177095,29.76,32.34,pivot_point,volume_profile,-1.041973,-1.041973,-1.0,-1.0
GDDY,2025-05-12,long,removed,2.020186,1.989107,30.81,34.6,pivot_point+volume_profile,volume_profile,-1.042178,-1.042178,-1.0,-1.0
GLW,2025-05-12,long,added,1.836647,2.081473,30.28,33.45,pivot_point+volume_profile,volume_profile,1.794151,2.038977,1.999502,1.999502
IBKR,2025-05-12,long,added,1.896594,2.218736,28.43,31.88,pivot_point+volume_profile,volume_profile,-0.034383,-0.034383,1.028484,1.028484
IP,2025-05-12,long,removed,2.044365,1.784897,43.91,37.44,pivot_point+volume_profile,volume_profile,-1.030962,-1.030962,-1.0,-1.0
KMI,2025-05-12,long,removed,2.392809,1.721495,20.5,38.42,volume_profile,volume_profile,-0.044531,-0.044531,0.815295,0.815295
LITE,2025-05-12,long,added,1.998667,3.054193,44.48,24.94,pivot_point+volume_profile,volume_profile,1.977419,3.032945,2.968097,2.968097
LYV,2025-05-12,long,retained,2.131057,2.131057,23.47,32.87,pivot_point+volume_profile,volume_profile,-0.041161,-0.041161,0.783299,0.783299
MMM,2025-05-12,long,retained,3.434618,2.991348,32.62,25.34,pivot_point+volume_profile,volume_profile,-1.043943,-1.043943,-1.0,-1.0
MSTR,2025-05-12,long,retained,2.148607,2.148607,22.87,32.67,volume_profile,volume_profile,-1.024205,-1.024205,-1.0,-1.0
NVDA,2025-05-12,long,removed,2.065139,1.589846,36.85,40.61,pivot_point,volume_profile,2.034535,1.559242,3.895151,3.895151
SBUX,2025-05-12,long,retained,2.738132,2.531213,37.13,28.82,pivot_point+volume_profile,volume_profile,-0.033023,-0.033023,1.046545,1.046545
TRGP,2025-05-12,long,removed,2.362374,1.831072,40.4,36.76,pivot_point+volume_profile,volume_profile,-0.029157,-0.029157,0.377208,0.377208
UAL,2025-05-12,long,retained,2.451227,2.649603,31.95,27.83,pivot_point+volume_profile,volume_profile,-1.022953,-1.022953,-1.0,-1.0
VST,2025-05-12,long,removed,2.151812,1.71222,25.63,38.56,pivot_point+volume_profile,volume_profile,2.128723,1.689131,3.179119,3.179119
BKR,2025-05-19,long,removed,2.61815,1.744687,23.08,23.06,pivot_point+volume_profile,volume_profile,-1.041425,-1.041425,-1.0,-1.0
CBRE,2025-05-19,long,added,1.697692,2.018194,44.4,34.23,pivot_point+volume_profile,volume_profile,-1.04777,-1.04777,-1.0,-1.0
CCL,2025-05-19,long,removed,2.213689,1.88525,35.94,35.99,pivot_point,volume_profile,-1.035223,-1.035223,-1.0,-1.0
CHTR,2025-05-19,long,removed,2.534289,1.918738,38.8,35.53,pivot_point,volume_profile,-1.041697,-1.041697,-1.0,-1.0
CIEN,2025-05-19,long,added,1.711175,2.748682,31.18,27.05,pivot_point+volume_profile,volume_profile,-1.035445,-1.035445,-1.0,-1.0
DASH,2025-05-19,long,added,1.948308,2.354426,27.54,30.47,pivot_point+volume_profile,volume_profile,1.914409,2.320527,3.07001,3.07001
EXPE,2025-05-19,long,added,1.628529,2.673372,49.94,27.63,pivot_point,volume_profile,-0.030995,-0.030995,0.516934,0.516934
FFIV,2025-05-19,long,retained,2.044528,2.060502,27.3,33.71,pivot_point+volume_profile,volume_profile,-0.051978,-0.051978,0.947872,0.947872
GDDY,2025-05-19,long,retained,2.043438,2.006004,30.52,34.39,pivot_point+volume_profile,volume_profile,-1.051582,-1.051582,-1.0,-1.0
GLW,2025-05-19,long,removed,2.537345,1.747245,20.57,38.02,pivot_point+volume_profile,volume_profile,-0.048875,1.69837,2.330541,2.330541
IBKR,2025-05-19,long,retained,2.295831,2.295831,23.07,31.07,pivot_point+volume_profile,volume_profile,-1.039941,-1.039941,-1.0,-1.0
IP,2025-05-19,long,removed,2.023784,1.657927,44.16,39.45,pivot_point+volume_profile,volume_profile,-1.153729,-1.153729,-1.117559,-1.117559
KMI,2025-05-19,long,retained,2.104627,2.104627,23.58,33.18,volume_profile,volume_profile,-0.052501,-0.052501,0.468757,0.468757
NVDA,2025-05-19,long,retained,2.272964,2.440243,22.3,29.65,pivot_point+volume_profile,volume_profile,2.237626,2.404906,2.825566,2.825566
UAL,2025-05-19,long,added,1.512611,2.803624,34.81,26.64,pivot_point+volume_profile,volume_profile,-1.024287,-1.024287,-1.0,-1.0
HOOD,2025-05-23,long,removed,2.422622,1.637018,20.01,39.8,volume_profile,volume_profile,2.397235,1.611631,6.303564,6.303564
ADSK,2025-05-27,long,retained,2.568575,2.143189,25.1,32.73,pivot_point+volume_profile,volume_profile,-1.060214,-1.060214,-1.0,-1.0
CCL,2025-05-27,long,removed,2.020875,1.697979,38.2,38.79,pivot_point,volume_profile,-1.03494,-1.03494,-1.0,-1.0
CHTR,2025-05-27,long,retained,2.369859,2.594641,40.32,28.28,pivot_point+volume_profile,volume_profile,-1.046352,-1.046352,-1.0,-1.0
COHR,2025-05-27,long,retained,2.170188,2.896921,38.02,25.97,pivot_point+volume_profile,volume_profile,-1.027459,-1.027459,-1.0,-1.0
CVNA,2025-05-27,long,removed,2.294516,1.643299,22.28,39.69,pivot_point+volume_profile,volume_profile,-1.028604,1.614695,-1.0,-1.0
DASH,2025-05-27,long,added,1.752993,2.166633,30.33,32.46,pivot_point+volume_profile,volume_profile,1.718003,2.131643,2.847653,2.847653
EBAY,2025-05-27,long,added,1.535804,2.010702,40.78,34.33,pivot_point+volume_profile,volume_profile,1.481401,1.956299,1.794901,1.794901
FFIV,2025-05-27,long,retained,2.182513,2.200268,25.68,32.09,pivot_point+volume_profile,volume_profile,-0.057954,-0.057954,1.344759,1.344759
FIX,2025-05-27,long,retained,2.155536,2.155536,22.99,32.59,volume_profile,volume_profile,2.117869,2.117869,1.876737,1.876737
KMI,2025-05-27,long,retained,2.146558,2.146558,23.09,32.69,volume_profile,volume_profile,-1.058895,-1.058895,-1.0,-1.0
LITE,2025-05-27,long,removed,2.151883,1.901772,42.63,35.76,pivot_point+volume_profile,volume_profile,-1.028929,-1.028929,-1.0,-1.0
MMM,2025-05-27,long,removed,4.201405,1.643381,29.6,39.69,pivot_point+volume_profile,volume_profile,-1.051608,-1.051608,-1.0,-1.0
NVDA,2025-05-27,long,retained,2.432299,2.610589,20.72,28.15,pivot_point+volume_profile,volume_profile,2.394655,2.572945,3.972802,3.972802
VST,2025-05-27,long,retained,2.098277,2.098277,23.45,33.25,volume_profile,volume_profile,2.067344,2.067344,3.012884,3.012884
KVUE,2025-05-30,long,removed,2.050369,0.768132,28.83,47.13,pivot_point+volume_profile,volume_profile,-1.057804,-1.057804,-1.0,-1.0
HOOD,2025-06-02,long,retained,2.309116,2.309116,21.13,30.93,volume_profile,volume_profile,2.280974,2.280974,7.300464,7.300464
ADSK,2025-06-03,long,added,1.692598,2.410998,45.68,29.92,pivot_point+volume_profile,volume_profile,1.627602,-1.064996,-1.0,-1.0
AON,2025-06-03,long,retained,2.431709,2.105846,22.53,33.16,pivot_point+volume_profile,volume_profile,-1.065022,-1.065022,-1.0,-1.0
CIEN,2025-06-03,long,removed,2.142464,1.785979,24.54,37.43,pivot_point+volume_profile,volume_profile,-1.646023,-1.646023,-1.604345,-1.604345
EXPE,2025-06-03,long,added,1.567645,2.794189,51.0,26.71,pivot_point,volume_profile,1.530571,-0.037074,1.479698,1.479698
IBKR,2025-06-03,long,removed,2.607373,1.605808,20.17,40.33,pivot_point+volume_profile,volume_profile,-1.045192,-1.045192,-1.0,-1.0
LH,2025-06-03,long,removed,2.572651,1.68287,23.47,24.04,pivot_point+volume_profile,volume_profile,-0.062688,1.620182,-0.409144,-0.409144
LITE,2025-06-03,long,retained,2.233028,2.651965,41.73,27.81,pivot_point+volume_profile,volume_profile,2.205013,2.62395,4.507942,4.507942
MMM,2025-06-03,long,added,1.551705,2.053951,33.49,33.79,pivot_point+volume_profile,volume_profile,-1.055926,-1.055926,-1.0,-1.0
MSTR,2025-06-03,long,added,1.02871,2.142162,48.56,32.74,pivot_point+volume_profile,volume_profile,1.002307,2.115759,2.177754,2.177754
TRGP,2025-06-03,long,removed,3.733429,1.741375,31.48,38.11,pivot_point+volume_profile,volume_profile,-0.043686,-0.043686,0.205279,0.205279
UAL,2025-06-03,long,retained,2.628249,2.877458,30.4,26.11,pivot_point+volume_profile,volume_profile,-1.479331,-1.479331,-1.449594,-1.449594
XEL,2025-06-03,long,removed,2.225926,1.355463,26.81,30.16,pivot_point+volume_profile,volume_profile,-1.064194,-1.064194,-1.0,-1.0
APP,2025-06-10,long,removed,2.343753,1.558826,20.98,41.16,volume_profile,volume_profile,-1.023341,-1.023341,-1.0,-1.0
CBRE,2025-06-10,long,removed,2.125347,1.829022,26.34,36.79,pivot_point+volume_profile,volume_profile,2.069269,1.772945,2.477813,2.477813
CHTR,2025-06-10,long,removed,2.977021,1.341616,35.43,45.46,pivot_point+volume_profile,volume_profile,-1.052197,-1.052197,-1.0,-1.0
DASH,2025-06-10,long,added,1.013584,2.53042,46.2,28.83,pivot_point+volume_profile,volume_profile,0.971883,2.488718,2.785659,2.785659
FIX,2025-06-10,long,retained,2.108728,2.108728,23.53,33.13,volume_profile,volume_profile,2.070098,2.070098,2.975038,2.975038
LITE,2025-06-10,long,removed,2.14058,1.840276,42.76,36.63,pivot_point+volume_profile,volume_profile,2.11155,1.811246,3.699571,3.699571
LYV,2025-06-10,long,retained,2.026039,2.026039,24.73,34.13,pivot_point+volume_profile,volume_profile,-1.049761,-1.049761,-1.0,-1.0
DASH,2025-06-17,long,retained,2.272546,2.272546,21.71,31.31,volume_profile,volume_profile,2.226528,2.226528,3.236531,3.236531
LITE,2025-06-17,long,removed,2.35311,1.666804,36.49,39.3,pivot_point+volume_profile,volume_profile,2.323386,1.63708,4.085339,4.085339
SYF,2025-06-17,long,removed,2.052238,1.13273,28.81,35.47,pivot_point+volume_profile,volume_profile,2.004993,1.085485,3.679755,3.679755
TPR,2025-06-17,long,removed,2.183713,1.746025,22.27,38.03,pivot_point,volume_profile,2.139034,1.701347,6.821065,6.821065
TRGP,2025-06-17,long,retained,3.056467,2.267724,34.92,31.36,pivot_point+volume_profile,volume_profile,-1.044187,-1.044187,-1.0,-1.0
CBRE,2025-06-25,long,retained,2.179995,2.18858,24.11,32.22,pivot_point+volume_profile,volume_profile,2.123366,2.131951,4.007789,4.007789
CVNA,2025-06-25,long,removed,2.240201,1.56613,22.45,41.03,pivot_point+volume_profile,volume_profile,2.210323,1.536252,1.987084,1.987084
FIX,2025-06-25,long,removed,2.04309,1.498263,24.12,42.28,pivot_point,volume_profile,1.997489,1.452662,8.392007,8.392007
LITE,2025-06-25,long,added,1.526208,2.14951,47.36,32.66,pivot_point+volume_profile,volume_profile,1.492238,2.11554,3.583195,3.583195
MOS,2025-06-25,long,removed,2.178066,1.800481,27.33,22.21,pivot_point,volume_profile,-2.542438,1.750255,-2.492212,-2.492212
MSTR,2025-06-25,long,removed,2.443067,1.525476,20.22,41.77,pivot_point+volume_profile,volume_profile,2.40932,1.491729,0.579133,0.579133
SOLV,2025-06-25,long,removed,2.051014,1.621831,29.02,40.06,pivot_point+volume_profile,volume_profile,-1.058064,-1.058064,-1.0,-1.0
SYF,2025-06-25,long,added,,2.006152,,34.38,,volume_profile,,1.952892,,1.443658
TRGP,2025-06-25,long,retained,2.870589,2.102316,36.16,33.21,pivot_point+volume_profile,volume_profile,-1.043253,-1.043253,-1.0,-1.0
VRT,2025-06-25,long,added,,2.179303,,32.32,,volume_profile,,-1.033791,,-1.0
CF,2025-07-02,long,added,1.72572,2.203327,48.35,32.05,pivot_point+volume_profile,volume_profile,-1.04834,-1.04834,-1.0,-1.0
EXPE,2025-07-02,long,added,,2.249806,,31.55,,volume_profile,,2.202485,,5.007195
MOS,2025-07-02,long,added,,2.12156,,32.98,,volume_profile,,-1.052618,,-1.0
NEM,2025-07-02,long,retained,2.051223,2.10406,38.42,33.18,pivot_point+volume_profile,volume_profile,-1.051019,-1.051019,-1.0,-1.0
CF,2025-07-10,long,removed,2.079072,1.709908,43.48,38.6,pivot_point+volume_profile,volume_profile,-1.04928,-1.04928,-1.0,-1.0
CIEN,2025-07-10,long,removed,2.074956,1.510562,31.53,42.05,pivot_point+volume_profile,volume_profile,2.032894,1.4685,2.388612,2.388612
EXPE,2025-07-10,long,added,1.731643,2.279927,33.66,31.23,pivot_point,volume_profile,-1.046783,-1.046783,-1.0,-1.0
LITE,2025-07-10,long,retained,2.071854,2.071854,30.17,33.57,pivot_point+volume_profile,volume_profile,2.036198,2.036198,4.775516,4.775516
MOS,2025-07-10,long,removed,2.030196,1.394129,29.08,29.35,pivot_point,volume_profile,-2.731686,-2.731686,-2.683345,-2.683345
MSTR,2025-07-10,long,retained,2.254602,2.254602,21.7,31.5,volume_profile,volume_profile,-1.03455,-1.03455,-1.0,-1.0
UAL,2025-07-10,long,removed,2.166151,1.597686,28.47,40.47,pivot_point+volume_profile,volume_profile,-1.094723,-1.094723,-1.063476,-1.063476
WBD,2025-07-11,long,added,1.689693,2.64646,48.93,27.85,pivot_point,volume_profile,1.652945,2.609712,-1.0,-1.0
APP,2025-07-17,long,removed,2.361064,1.639012,20.81,39.76,pivot_point+volume_profile,volume_profile,2.334722,1.612669,4.343765,4.343765
CIEN,2025-07-17,long,added,1.862108,2.321977,27.72,30.8,pivot_point+volume_profile,volume_profile,1.817051,2.27692,3.474519,3.474519
DASH,2025-07-17,long,removed,2.259481,1.710911,21.45,38.59,pivot_point,volume_profile,2.211705,1.663136,1.251207,1.251207
MSTR,2025-07-17,long,retained,2.161028,2.161028,22.72,32.52,volume_profile,volume_profile,-1.037565,-1.037565,-1.0,-1.0
SBUX,2025-07-17,long,removed,2.694036,1.637838,22.47,24.78,pivot_point+volume_profile,volume_profile,-1.054383,1.583456,-1.0,-1.0
TSLA,2025-07-17,long,added,1.693777,2.98586,48.86,25.37,pivot_point+volume_profile,volume_profile,-1.030383,-1.030383,-1.0,-1.0
UAL,2025-07-17,long,removed,2.242615,1.674207,27.63,39.18,pivot_point+volume_profile,volume_profile,-1.03109,-1.03109,-1.0,-1.0
WBD,2025-07-18,long,added,1.69778,2.631175,48.8,27.98,pivot_point,volume_profile,1.6586,-1.03918,-1.0,-1.0
CF,2025-07-24,long,retained,2.027676,2.561976,44.11,28.56,pivot_point+volume_profile,volume_profile,-1.053798,-1.053798,-1.0,-1.0
CIEN,2025-07-24,long,removed,2.121631,1.775805,23.18,37.58,pivot_point,volume_profile,2.072951,1.727125,8.235278,8.235278
NEM,2025-07-24,long,retained,2.370375,2.370375,22.52,30.32,volume_profile,volume_profile,2.324462,2.324462,5.471272,5.471272
TMUS,2025-07-24,long,retained,2.008083,2.000731,39.96,34.45,pivot_point+volume_profile,volume_profile,-1.062418,-1.062418,-1.0,-1.0
UAL,2025-07-24,long,added,1.895682,2.090516,34.45,33.35,pivot_point+volume_profile,volume_profile,-1.033158,-1.033158,-1.0,-1.0
WBD,2025-07-25,long,removed,2.007694,1.558868,44.36,41.16,pivot_point,volume_profile,-1.043248,-1.043248,-1.0,-1.0
AXON,2025-07-31,long,removed,2.0208,1.515251,24.4,41.96,pivot_point,volume_profile,1.979936,1.474387,-1.0,-1.0
NEM,2025-07-31,long,added,1.550636,2.927566,40.31,25.76,pivot_point+volume_profile,volume_profile,1.5084,2.88533,5.832143,5.832143
NVDA,2025-07-31,long,added,,2.187503,,32.23,,volume_profile,,-1.057851,,-1.0
UAL,2025-07-31,long,removed,2.346512,1.699916,29.15,38.76,pivot_point+volume_profile,volume_profile,-1.035483,-1.035483,-1.0,-1.0
APP,2025-08-07,long,retained,2.2387,2.2387,21.87,31.67,volume_profile,volume_profile,-1.02614,-1.02614,-1.0,-1.0
PODD,2025-08-07,long,added,1.665084,2.014836,49.33,34.27,pivot_point+volume_profile,volume_profile,1.61996,1.969712,2.028378,2.028378
TSLA,2025-08-07,long,added,,2.112593,,33.08,,volume_profile,,2.07904,,5.403464
ULTA,2025-08-07,long,added,1.994095,2.160126,38.54,32.54,pivot_point+volume_profile,volume_profile,-1.061824,-1.061824,-1.0,-1.0
NVDA,2025-08-14,long,removed,2.160695,1.442764,22.53,43.36,pivot_point,volume_profile,-1.056467,-1.056467,-1.0,-1.0
TSLA,2025-08-14,long,added,1.98307,2.435478,43.28,29.69,pivot_point+volume_profile,volume_profile,-1.03507,-1.03507,-1.0,-1.0
UAL,2025-08-14,long,added,1.895586,2.046469,30.65,33.88,pivot_point+volume_profile,volume_profile,1.856758,2.007641,-0.240865,-0.240865
WYNN,2025-08-14,long,removed,2.010866,1.572488,25.12,40.92,pivot_point+volume_profile,volume_profile,1.957667,1.519288,4.200848,4.200848
WBD,2025-08-15,long,removed,2.585147,0.67591,23.36,50.91,pivot_point,volume_profile,2.554721,0.645484,9.005713,9.005713
AXON,2025-08-21,long,retained,2.129759,2.129759,23.28,32.88,volume_profile,volume_profile,-1.031785,-1.031785,-1.0,-1.0
UAL,2025-08-21,long,removed,2.369461,1.656179,25.13,39.48,pivot_point+volume_profile,volume_profile,2.329877,1.616595,-0.37777,-0.37777
WSM,2025-08-21,long,added,1.667664,2.310824,32.89,30.91,pivot_point+volume_profile,volume_profile,-1.04916,-1.04916,-1.0,-1.0
AXON,2025-08-28,long,retained,2.008032,2.008032,24.76,34.36,volume_profile,volume_profile,-1.187978,-1.187978,-1.150564,-1.150564
DLTR,2025-08-28,long,removed,2.61381,0.922035,23.12,41.66,pivot_point+volume_profile,volume_profile,-1.060616,-1.060616,-1.0,-1.0
MOS,2025-08-28,long,removed,2.660654,0.871184,22.74,43.36,pivot_point+volume_profile,volume_profile,-1.143849,-1.143849,-1.095243,-1.095243
NEM,2025-08-28,long,added,,2.558169,,28.59,,volume_profile,,2.498731,,4.956524
NFLX,2025-08-28,long,retained,2.015068,2.015068,24.47,34.27,volume_profile,volume_profile,-1.061855,-1.061855,-1.0,-1.0
TRMB,2025-08-28,long,removed,2.444595,1.624024,24.61,25.02,pivot_point+volume_profile,volume_profile,-1.061855,-1.061855,-1.0,-1.0
TSLA,2025-08-28,long,added,1.505925,3.036081,50.54,25.05,pivot_point+volume_profile,volume_profile,-1.037822,-1.037822,-1.0,-1.0
ULTA,2025-08-28,long,added,1.433885,2.155983,39.14,32.58,pivot_point+volume_profile,volume_profile,-1.061037,-1.061037,-1.0,-1.0
NEM,2025-09-05,long,added,1.519842,2.224862,32.88,31.82,pivot_point,volume_profile,1.462674,2.167693,5.478938,5.478938
TSLA,2025-09-05,long,removed,2.734901,1.679832,24.55,39.09,pivot_point+volume_profile,volume_profile,2.697302,1.642233,4.740624,4.740624
COIN,2025-09-12,long,retained,2.013059,2.324843,27.1,30.77,pivot_point+volume_profile,volume_profile,1.9826,2.294384,1.481272,1.481272
CRWD,2025-09-12,long,added,1.902125,2.491779,34.36,29.18,pivot_point+volume_profile,volume_profile,1.858615,2.448269,4.550534,4.550534
EXE,2025-09-12,long,removed,2.819644,1.77956,21.52,22.52,pivot_point+volume_profile,volume_profile,2.76661,1.726526,2.080664,2.080664
NEM,2025-09-12,long,retained,2.090577,2.090577,23.54,33.34,pivot_point+volume_profile,volume_profile,2.032407,2.032407,1.512065,1.512065
PWR,2025-09-12,long,retained,2.222758,2.222758,22.64,31.84,pivot_point+volume_profile,volume_profile,2.172589,2.172589,3.829571,3.829571
SMCI,2025-09-12,long,retained,2.923759,2.911473,20.19,25.87,pivot_point+volume_profile,volume_profile,2.895037,2.882751,1.049944,1.049944
TSLA,2025-09-12,long,retained,2.13463,2.145752,24.03,32.7,pivot_point+volume_profile,volume_profile,2.096238,2.10736,1.831651,1.831651
CEG,2025-09-18,long,removed,2.066235,1.591977,23.84,40.57,pivot_point,volume_profile,2.027344,1.553086,3.6016,3.6016
COIN,2025-09-19,long,retained,2.267824,2.267824,22.36,31.36,volume_profile,volume_profile,-1.030729,-1.030729,-1.0,-1.0
CVS,2025-09-19,long,removed,2.286544,1.63835,41.16,39.78,pivot_point+volume_profile,volume_profile,2.227776,1.579582,1.266816,1.266816
EXE,2025-09-19,long,retained,2.161204,2.156746,42.52,32.57,pivot_point+volume_profile,volume_profile,2.107659,2.103202,1.307349,1.307349
MSTR,2025-09-19,long,added,1.664773,2.275601,49.33,31.28,pivot_point,volume_profile,-1.247909,-1.247909,-1.218063,-1.218063
NFLX,2025-09-19,long,retained,2.035308,2.035308,24.22,34.02,volume_profile,volume_profile,-1.058942,-1.058942,-1.0,-1.0
SMCI,2025-09-19,long,retained,2.927459,2.913963,20.16,25.85,pivot_point+volume_profile,volume_profile,2.895343,2.881848,2.155739,2.155739
TRMB,2025-09-19,long,added,,2.441876,,29.63,,volume_profile,,-1.061322,,-1.0
TSLA,2025-09-19,long,retained,2.359784,2.359784,20.62,30.42,volume_profile,volume_profile,-0.038083,-0.038083,1.362642,1.362642
WBD,2025-09-22,long,added,1.899743,2.758206,26.59,26.97,pivot_point+volume_profile,volume_profile,-1.025516,-1.025516,-1.0,-1.0
CAH,2025-09-26,long,retained,2.096286,2.096286,26.08,33.28,pivot_point+volume_profile,volume_profile,2.041041,2.041041,8.978022,8.978022
CVS,2025-09-26,long,added,1.894977,2.553957,45.86,28.63,pivot_point+volume_profile,volume_profile,1.837314,2.496294,1.225266,1.225266
EQT,2025-09-26,long,retained,2.019186,2.441163,26.62,29.64,pivot_point+volume_profile,volume_profile,-1.045293,-1.045293,-1.0,-1.0
SMCI,2025-09-26,long,retained,2.71762,2.705077,21.49,27.38,pivot_point+volume_profile,volume_profile,2.687765,2.675222,-1.0,-1.0
WBD,2025-09-29,long,added,0.868167,2.816363,49.47,26.54,pivot_point,volume_profile,-1.02696,-1.02696,-1.0,-1.0
COIN,2025-10-03,long,retained,2.365012,2.365012,20.57,30.37,volume_profile,volume_profile,-1.033105,-1.033105,-1.0,-1.0
CVS,2025-10-03,long,removed,2.295793,1.839922,41.07,36.63,pivot_point,volume_profile,2.238669,1.782798,0.117948,0.117948
SMCI,2025-10-03,long,retained,2.211226,2.420371,25.37,29.84,pivot_point+volume_profile,volume_profile,-1.030886,-1.030886,-1.0,-1.0
WBD,2025-10-06,long,added,0.82203,3.063296,51.11,24.88,pivot_point,volume_profile,-1.031369,-1.031369,-1.0,-1.0
NFLX,2025-10-10,long,retained,2.215083,2.215083,22.12,31.92,volume_profile,volume_profile,-1.058985,-1.058985,-1.0,-1.0
DG,2025-10-17,long,removed,2.231381,1.14135,41.75,50.24,pivot_point,volume_profile,-1.050929,-1.050929,-1.0,-1.0
EQT,2025-10-17,long,added,1.678733,2.031022,31.5,34.07,pivot_point+volume_profile,volume_profile,-1.037827,-1.037827,-1.0,-1.0
INTC,2025-10-17,long,added,1.778496,2.790407,41.94,26.73,pivot_point+volume_profile,volume_profile,1.751902,-1.026595,-1.0,-1.0
KR,2025-10-17,long,retained,2.129798,2.323374,27.88,30.78,pivot_point+volume_profile,volume_profile,-1.079151,-1.079151,-1.014635,-1.014635
STX,2025-10-17,long,retained,2.174568,2.174568,22.77,32.37,volume_profile,volume_profile,-1.028836,-1.028836,-1.0,-1.0
AXON,2025-10-24,long,added,1.76636,2.241583,30.12,31.64,pivot_point+volume_profile,volume_profile,-4.337363,-4.337363,-4.300584,-4.300584
COIN,2025-10-24,long,retained,2.105666,2.105666,23.57,33.17,volume_profile,volume_profile,-1.025214,-1.025214,-1.0,-1.0
DLTR,2025-10-24,long,retained,2.80395,2.292387,36.63,31.1,pivot_point,volume_profile,2.762807,2.251244,4.41966,4.41966
FSLR,2025-10-24,long,retained,2.30447,2.30447,22.18,30.98,volume_profile,volume_profile,2.272186,2.272186,0.96756,0.96756
WBD,2025-10-27,long,retained,2.101189,2.101189,24.02,33.22,volume_profile,volume_profile,2.069718,2.069718,5.399746,5.399746
DLTR,2025-10-31,long,retained,2.786721,2.275651,36.76,31.28,pivot_point,volume_profile,2.745588,2.234518,6.650067,6.650067
WBD,2025-11-03,long,retained,2.085838,2.085838,24.2,33.4,volume_profile,volume_profile,2.050108,2.050108,5.297748,5.297748
APP,2025-11-07,long,retained,2.123208,2.123208,23.16,32.96,volume_profile,volume_profile,-1.024541,-1.024541,-1.0,-1.0
APTV,2025-11-07,long,retained,2.072254,2.042395,43.57,33.93,pivot_point+volume_profile,volume_profile,-1.229377,-1.229377,-1.180253,-1.180253
DLTR,2025-11-07,long,removed,2.012478,1.9107,44.3,35.64,pivot_point+volume_profile,volume_profile,-1.039683,-1.039683,-1.0,-1.0
WSM,2025-11-07,long,removed,2.170939,1.955635,22.61,35.04,pivot_point,volume_profile,-1.040974,-1.040974,-1.0,-1.0
DG,2025-11-14,long,removed,2.670283,1.541305,22.66,26.48,pivot_point,volume_profile,-1.05203,-1.05203,-1.0,-1.0
DLTR,2025-11-14,long,added,1.552266,2.22325,51.28,31.83,pivot_point,volume_profile,-1.041689,-1.041689,-1.0,-1.0
DLTR,2025-11-21,long,removed,2.793575,1.57706,21.71,25.84,pivot_point+volume_profile,volume_profile,2.75473,1.538215,5.673028,5.673028
CVS,2025-12-01,long,removed,2.300687,1.241893,26.02,32.73,pivot_point+volume_profile,volume_profile,-1.058096,-1.058096,-1.0,-1.0
DG,2025-12-01,long,retained,2.506071,2.506071,35.05,29.05,pivot_point+volume_profile,volume_profile,2.455923,2.455923,9.542155,9.542155
DLTR,2025-12-01,long,retained,2.303197,2.220009,40.99,31.87,pivot_point+volume_profile,volume_profile,2.262084,2.178895,5.686814,5.686814
FSLR,2025-12-01,long,retained,2.073126,2.073126,23.75,33.55,volume_profile,volume_profile,-1.029492,-1.029492,-1.0,-1.0
KR,2025-12-01,long,added,1.843739,2.203724,46.58,32.05,pivot_point+volume_profile,volume_profile,-2.012949,-2.012949,-1.947183,-1.947183
BA,2025-12-08,long,added,1.583825,2.232845,50.72,31.73,pivot_point+volume_profile,volume_profile,1.534769,2.183788,5.367771,5.367771
DG,2025-12-08,long,added,1.912045,2.232293,45.62,31.74,pivot_point+volume_profile,volume_profile,1.876424,2.196672,2.913693,2.913693
F,2025-12-08,long,removed,2.047935,2.245957,28.86,16.59,pivot_point+volume_profile,volume_profile,1.986783,2.184805,1.326353,1.326353
INTC,2025-12-08,long,added,1.892965,2.697443,45.88,27.44,pivot_point+volume_profile,volume_profile,-1.025599,-1.025599,-1.0,-1.0
MPWR,2025-12-08,long,retained,2.044393,2.044393,24.31,33.91,pivot_point+volume_profile,volume_profile,-1.033722,-1.033722,-1.0,-1.0
CVS,2025-12-15,long,added,1.866009,2.030843,46.26,34.07,pivot_point+volume_profile,volume_profile,-1.466298,-1.466298,-1.413892,-1.413892
DG,2025-12-15,long,retained,2.542671,2.503657,27.32,29.07,pivot_point+volume_profile,volume_profile,2.502824,2.46381,1.326439,1.326439
DLTR,2025-12-15,long,retained,2.748539,2.992958,37.05,25.33,pivot_point+volume_profile,volume_profile,-1.03973,-1.03973,-1.0,-1.0
F,2025-12-15,long,removed,2.281559,1.05922,41.21,52.48,pivot_point+volume_profile,volume_profile,-1.063525,-1.063525,-1.0,-1.0
PM,2025-12-15,long,removed,2.502584,1.899821,29.28,35.79,pivot_point+volume_profile,volume_profile,2.443037,1.840274,3.66131,3.66131
ALB,2025-12-22,long,retained,2.72812,2.697474,31.8,27.44,pivot_point+volume_profile,volume_profile,2.697029,2.666382,1.186944,1.186944
CVS,2025-12-22,long,removed,2.484968,1.462176,24.24,27.98,pivot_point+volume_profile,volume_profile,-1.109179,1.40658,-1.053584,-1.053584
DG,2025-12-22,long,retained,2.083862,2.040028,32.03,33.96,pivot_point+volume_profile,volume_profile,2.037546,1.993712,1.242769,1.242769
DLTR,2025-12-22,long,removed,2.826398,1.690429,21.47,23.91,pivot_point,volume_profile,2.788535,1.652567,-0.44666,-0.44666
EBAY,2025-12-22,long,removed,2.169577,1.859804,27.63,36.35,pivot_point,volume_profile,2.115095,1.805322,0.81724,0.81724
F,2025-12-22,long,removed,2.77107,1.527462,21.88,26.74,pivot_point+volume_profile,volume_profile,-0.063731,1.463731,0.61553,0.61553
HAS,2025-12-22,long,removed,2.964001,1.319322,20.52,30.95,pivot_point+volume_profile,volume_profile,2.905396,1.260717,4.983114,4.983114
MPWR,2025-12-22,long,added,1.682915,2.036712,29.44,34.0,pivot_point+volume_profile,volume_profile,1.649894,2.003691,3.682887,3.682887
NVDA,2025-12-22,long,added,0.816308,2.602582,51.52,28.21,pivot_point,volume_profile,0.775562,-1.040747,-1.0,-1.0
ALB,2025-12-30,long,removed,8.749341,1.607977,25.17,40.29,pivot_point,volume_profile,-0.031848,1.576129,1.897265,1.897265
DG,2025-12-30,long,retained,2.61558,2.567207,26.5,28.51,pivot_point+volume_profile,volume_profile,2.5651,2.516727,2.367546,2.367546
DLTR,2025-12-30,long,removed,2.83934,1.593978,36.38,40.54,pivot_point,volume_profile,2.797313,1.551951,-1.0,-1.0
EBAY,2025-12-30,long,retained,2.183133,2.060311,31.68,33.71,pivot_point+volume_profile,volume_profile,2.122264,1.999441,-1.0,-1.0
ALB,2026-01-07,long,retained,2.828773,2.393536,36.45,30.09,pivot_point,volume_profile,2.795567,2.36033,0.703918,0.703918
CVS,2026-01-07,long,retained,2.269166,2.473208,41.34,29.34,pivot_point+volume_profile,volume_profile,-1.855864,-1.855864,-1.790911,-1.790911
DG,2026-01-07,long,retained,2.447888,2.677215,29.38,27.6,pivot_point+volume_profile,volume_profile,-0.048288,-0.048288,1.199549,1.199549
DLTR,2026-01-07,long,added,1.507532,2.214072,52.11,31.93,pivot_point,volume_profile,1.464498,-1.043034,-1.0,-1.0
HAS,2026-01-07,long,removed,2.805479,1.686841,36.62,38.97,pivot_point+volume_profile,volume_profile,2.740518,1.62188,5.389769,5.389769
INTC,2026-01-07,long,added,1.877971,2.856229,46.09,26.26,pivot_point+volume_profile,volume_profile,1.848168,2.826426,0.517338,0.517338
NOC,2026-01-07,long,retained,2.002685,2.002685,25.23,34.43,volume_profile,volume_profile,1.947249,1.947249,7.039869,7.039869
ALB,2026-01-14,long,added,1.905518,2.113028,45.71,33.08,pivot_point+volume_profile,volume_profile,-1.146749,-1.146749,-1.11196,-1.11196
AMD,2026-01-14,long,retained,2.061087,2.018284,25.3,34.23,pivot_point+volume_profile,volume_profile,2.028316,1.985512,-1.0,-1.0
CVS,2026-01-14,long,removed,2.438088,1.2733,24.67,31.99,pivot_point+volume_profile,volume_profile,-1.655824,1.209203,-1.591726,-1.591726
DG,2026-01-14,long,added,1.993034,2.993141,44.55,25.33,pivot_point,volume_profile,-1.049482,-1.049482,-1.0,-1.0
EL,2026-01-14,long,removed,5.505627,0.961465,27.02,55.39,pivot_point,volume_profile,-2.498226,-2.498226,-2.451114,-2.451114
ES,2026-01-14,long,retained,2.158663,2.915613,42.55,25.84,pivot_point+volume_profile,volume_profile,-1.062037,-1.062037,-1.0,-1.0
MPWR,2026-01-14,long,removed,2.353425,1.54253,20.88,41.46,pivot_point+volume_profile,volume_profile,2.314598,1.503703,3.140984,3.140984
PM,2026-01-14,long,removed,2.516942,1.521953,24.35,41.84,pivot_point+volume_profile,volume_profile,-1.066575,-1.066575,-1.0,-1.0
ALB,2026-01-22,long,retained,2.66612,2.106006,37.69,33.16,pivot_point,volume_profile,-1.78932,-1.78932,-1.75766,-1.75766
BG,2026-01-22,long,added,,2.031162,,34.07,,volume_profile,,1.974765,,1.003692
COR,2026-01-22,long,removed,2.369274,1.740334,20.53,38.12,pivot_point,volume_profile,-1.066621,-1.066621,-1.0,-1.0
CVS,2026-01-22,long,retained,2.782609,2.259288,36.79,31.45,pivot_point+volume_profile,volume_profile,-2.563433,-2.563433,-2.506576,-2.506576
DG,2026-01-22,long,removed,2.984281,0.811673,20.39,45.49,pivot_point,volume_profile,-0.046626,0.765046,0.275695,0.275695
EL,2026-01-22,long,retained,2.222014,3.841923,27.85,21.05,pivot_point,volume_profile,-1.049674,-1.049674,-1.0,-1.0
EW,2026-01-22,long,removed,2.009026,1.960466,29.35,19.97,pivot_point+volume_profile,volume_profile,-1.060973,-1.060973,-1.0,-1.0
HAS,2026-01-22,long,retained,2.033706,2.088676,44.04,33.37,pivot_point+volume_profile,volume_profile,1.97132,2.02629,2.017433,2.017433
HSY,2026-01-22,long,removed,3.035643,1.089931,20.05,36.62,pivot_point+volume_profile,volume_profile,2.980952,1.035241,4.925416,4.925416
NUE,2026-01-22,long,retained,2.1872,2.156377,28.43,32.58,pivot_point+volume_profile,volume_profile,-1.254102,-1.254102,-1.198898,-1.198898
PM,2026-01-22,long,removed,2.010398,1.552206,34.53,41.28,pivot_point+volume_profile,volume_profile,1.950761,1.49257,-0.012275,-0.012275
AES,2026-01-29,long,removed,2.745556,1.501027,37.07,42.23,pivot_point+volume_profile,volume_profile,2.702953,1.458424,-1.0,-1.0
ALB,2026-01-29,long,added,1.653071,2.58279,49.53,28.38,pivot_point,volume_profile,-1.079102,-1.079102,-1.050535,-1.050535
BIIB,2026-01-29,long,removed,2.414377,0.794286,24.89,46.14,pivot_point+volume_profile,volume_profile,2.367156,0.747065,0.719641,0.719641
CRL,2026-01-29,long,removed,2.311041,1.831363,25.91,21.76,pivot_point,volume_profile,-1.04234,-1.04234,-1.0,-1.0
F,2026-01-29,long,retained,2.223061,2.234271,38.44,31.72,pivot_point+volume_profile,volume_profile,-1.064059,-1.064059,-1.0,-1.0
HSY,2026-01-29,long,retained,2.125632,2.186276,42.93,32.24,pivot_point+volume_profile,volume_profile,2.068357,2.129002,3.990351,3.990351
ADM,2026-02-05,long,added,1.849616,2.96559,46.49,25.51,pivot_point+volume_profile,volume_profile,1.805728,-0.043888,0.248181,0.248181
BIIB,2026-02-05,long,retained,2.2605,3.004306,41.44,25.26,pivot_point,volume_profile,-0.048519,-0.048519,-0.510424,-0.510424
DG,2026-02-05,long,removed,2.78933,0.725195,21.74,48.84,pivot_point,volume_profile,-2.089286,0.680805,-2.044896,-2.044896
F,2026-02-05,long,removed,2.105446,0.890739,28.17,42.7,pivot_point+volume_profile,volume_profile,2.041993,0.827286,-1.0,-1.0
WELL,2026-02-05,long,retained,2.414449,2.388452,21.29,30.14,pivot_point+volume_profile,volume_profile,2.350069,2.324072,0.822192,0.822192
AES,2026-02-12,long,added,1.819168,2.479199,46.93,29.29,pivot_point+volume_profile,volume_profile,1.77782,-2.408627,-2.367279,-2.367279
BIIB,2026-02-12,long,added,1.64856,2.768015,49.6,26.9,pivot_point+volume_profile,volume_profile,-1.041757,-1.041757,-1.0,-1.0
DHI,2026-02-12,long,added,1.684037,2.107728,41.62,33.14,pivot_point+volume_profile,volume_profile,-1.039232,-1.039232,-1.0,-1.0
EL,2026-02-12,long,removed,2.135451,1.855177,20.42,21.41,pivot_point,volume_profile,-1.151489,-1.151489,-1.125617,-1.125617
F,2026-02-12,long,removed,2.536476,2.192144,23.78,17.18,pivot_point,volume_profile,-1.062851,-1.062851,-1.0,-1.0
HSY,2026-02-12,long,retained,3.280295,3.005439,29.42,25.25,pivot_point+volume_profile,volume_profile,-1.050958,-1.050958,-1.0,-1.0
MRK,2026-02-12,long,removed,2.301212,1.577912,41.01,40.82,pivot_point+volume_profile,volume_profile,-1.050697,-1.050697,-1.0,-1.0
ADM,2026-02-20,long,removed,2.857416,1.214979,21.25,33.38,pivot_point+volume_profile,volume_profile,-0.047161,1.167818,1.910618,1.910618
AES,2026-02-20,long,added,1.553838,2.274272,51.25,31.29,pivot_point+volume_profile,volume_profile,1.507842,-3.061736,-3.01574,-3.01574
BIIB,2026-02-20,long,removed,2.280691,1.964439,41.22,34.92,pivot_point+volume_profile,volume_profile,-1.046329,-1.046329,-1.0,-1.0
DG,2026-02-20,long,added,1.807443,2.691872,47.11,27.49,pivot_point,volume_profile,-1.043669,-1.043669,-1.0,-1.0
DHI,2026-02-20,long,added,1.86088,2.313992,38.93,30.88,pivot_point+volume_profile,volume_profile,-1.041837,-1.041837,-1.0,-1.0
DLTR,2026-02-20,long,removed,2.168662,1.550498,42.44,41.31,pivot_point+volume_profile,volume_profile,-1.039397,-1.039397,-1.0,-1.0
F,2026-02-20,long,removed,2.452913,2.116951,24.53,18.03,pivot_point,volume_profile,-1.061367,-1.061367,-1.0,-1.0
HSY,2026-02-20,long,added,1.777447,2.741403,47.55,27.1,pivot_point+volume_profile,volume_profile,1.727495,-1.049952,-1.0,-1.0
ADM,2026-02-27,long,removed,2.405563,1.781489,39.98,37.49,pivot_point+volume_profile,volume_profile,-1.047012,-1.047012,-1.0,-1.0
AES,2026-02-27,long,added,1.714359,2.425565,48.53,29.79,pivot_point,volume_profile,-3.822565,-3.822565,-3.778078,-3.778078
ALB,2026-02-27,long,added,1.50417,2.275291,52.17,31.28,pivot_point,volume_profile,-1.023428,-1.023428,-1.0,-1.0
BIIB,2026-02-27,long,removed,2.258936,1.949142,41.45,35.12,pivot_point+volume_profile,volume_profile,-1.045333,-1.045333,-1.0,-1.0
CF,2026-02-27,long,added,1.72868,2.073351,48.3,33.55,pivot_point+volume_profile,volume_profile,1.689394,2.034064,4.369061,4.369061
DG,2026-02-27,long,removed,2.034541,1.979068,35.83,34.73,pivot_point+volume_profile,volume_profile,-1.047674,-1.047674,-1.0,-1.0
LVS,2026-02-27,long,removed,2.116964,1.550234,32.63,41.32,pivot_point+volume_profile,volume_profile,-1.040159,-1.040159,-1.0,-1.0
MRK,2026-02-27,long,added,,2.042053,,33.93,,volume_profile,,-1.054868,,-1.0
APA,2026-03-06,long,added,1.831683,2.405864,46.75,29.97,pivot_point,volume_profile,1.799478,2.373659,1.618625,1.618625
PLTR,2026-03-06,long,retained,2.165026,2.159874,36.08,32.54,pivot_point+volume_profile,volume_profile,-1.026994,-1.026994,-1.0,-1.0
ADM,2026-03-13,long,removed,2.868953,1.581314,36.17,40.76,pivot_point,volume_profile,-1.043884,-1.043884,-1.0,-1.0
AMD,2026-03-20,long,added,1.748995,2.062867,37.79,33.68,pivot_point+volume_profile,volume_profile,1.719912,2.033784,10.127012,10.127012
PLTR,2026-03-20,long,retained,2.218591,2.235503,41.89,31.7,pivot_point+volume_profile,volume_profile,-1.031268,-1.031268,-1.0,-1.0
ADM,2026-03-27,long,added,1.847544,3.062681,46.52,24.89,pivot_point+volume_profile,volume_profile,-1.041243,-1.041243,-1.0,-1.0
ALB,2026-03-27,long,added,1.555558,2.38109,51.22,30.21,pivot_point,volume_profile,1.530367,2.355899,2.143564,2.143564
MRK,2026-03-27,long,removed,2.642683,1.781011,22.88,22.5,pivot_point+volume_profile,volume_profile,-1.060594,-1.060594,-1.0,-1.0
ADM,2026-04-06,long,added,1.618843,2.913662,50.11,25.86,pivot_point+volume_profile,volume_profile,-1.431888,-1.431888,-1.387241,-1.387241
AMD,2026-04-06,long,removed,2.050665,1.983637,28.23,34.67,pivot_point+volume_profile,volume_profile,2.022452,1.955424,12.865385,12.865385
CMI,2026-04-06,long,removed,2.030737,1.993522,25.28,34.55,pivot_point+volume_profile,volume_profile,1.99083,1.953614,4.550433,4.550433
FSLR,2026-04-06,long,added,1.901145,2.416746,45.77,29.87,pivot_point,volume_profile,1.870523,2.386124,2.980447,2.980447
GOOG,2026-04-06,long,removed,2.156045,1.620573,31.78,40.08,pivot_point+volume_profile,volume_profile,2.105673,1.570201,8.076424,8.076424
GOOGL,2026-04-06,long,added,1.817453,2.344971,36.76,30.57,pivot_point+volume_profile,volume_profile,1.76908,2.296599,7.816437,7.816437
IBKR,2026-04-06,long,added,1.654872,2.122334,31.7,32.97,pivot_point,volume_profile,1.62217,2.089632,4.169943,4.169943
MRK,2026-04-06,long,retained,2.348703,2.293801,40.53,31.09,pivot_point+volume_profile,volume_profile,-1.061605,-1.061605,-1.0,-1.0
NVDA,2026-04-06,long,retained,2.017951,2.397549,36.84,30.05,pivot_point+volume_profile,volume_profile,1.974149,2.353746,5.508603,5.508603
TEL,2026-04-06,long,removed,2.030485,1.949998,44.08,35.11,pivot_point+volume_profile,volume_profile,1.994454,1.913968,-1.0,-1.0
WMT,2026-04-06,long,removed,2.064388,1.439845,23.86,43.42,pivot_point,volume_profile,-1.066264,-1.066264,-1.0,-1.0
ALB,2026-04-13,long,added,1.632672,2.971695,49.87,25.47,pivot_point+volume_profile,volume_profile,1.606556,-1.026116,-1.0,-1.0
APH,2026-04-13,long,retained,2.078347,2.078347,23.49,33.49,volume_profile,volume_profile,-1.03288,-1.03288,-1.0,-1.0
BALL,2026-04-13,long,added,1.793025,2.156109,47.32,32.58,pivot_point+volume_profile,volume_profile,-1.050157,-1.050157,-1.0,-1.0
FSLR,2026-04-13,long,added,1.548381,3.052353,51.35,24.95,pivot_point,volume_profile,-1.031811,-1.031811,-1.0,-1.0
GNRC,2026-04-13,long,added,1.579353,2.951036,46.4,25.6,pivot_point,volume_profile,1.550083,2.921766,4.969183,4.969183
GOOG,2026-04-13,long,added,1.829459,2.019351,27.58,34.22,pivot_point+volume_profile,volume_profile,1.776345,1.966238,5.460089,5.460089
IVZ,2026-04-13,long,added,1.746362,2.119681,47.03,33.0,pivot_point+volume_profile,volume_profile,1.712682,2.086001,2.349272,2.349272
LVS,2026-04-13,long,removed,2.407554,1.725531,24.96,23.35,pivot_point+volume_profile,volume_profile,-1.49832,-1.49832,-1.453044,-1.453044
MRK,2026-04-13,long,removed,2.334923,1.53367,25.67,26.62,pivot_point+volume_profile,volume_profile,-1.05659,-1.05659,-1.0,-1.0
NEM,2026-04-13,long,retained,2.111857,2.129673,23.49,32.88,pivot_point+volume_profile,volume_profile,-1.031519,-1.031519,-1.0,-1.0
ADM,2026-04-20,long,removed,2.378361,1.814786,25.24,22.0,pivot_point+volume_profile,volume_profile,2.336319,1.772744,4.332119,4.332119
ALB,2026-04-20,long,added,1.704982,2.891679,48.68,26.01,pivot_point+volume_profile,volume_profile,-1.023945,-1.023945,-1.0,-1.0
APP,2026-04-20,long,added,1.597975,2.981364,35.07,25.4,pivot_point,volume_profile,-1.023277,-1.023277,-1.0,-1.0
BIIB,2026-04-20,long,retained,2.318083,2.990785,40.84,25.34,pivot_point,volume_profile,2.274173,-0.04391,0.657434,0.657434
DG,2026-04-20,long,added,1.764519,2.118746,47.75,33.01,pivot_point+volume_profile,volume_profile,-1.040034,-1.040034,-1.0,-1.0
DLTR,2026-04-20,long,retained,2.99618,2.358253,35.31,30.44,pivot_point+volume_profile,volume_profile,-1.034868,-1.034868,-1.0,-1.0
GM,2026-04-20,long,removed,2.031536,1.627283,24.47,39.96,pivot_point,volume_profile,-1.047255,-1.047255,-1.0,-1.0
GNRC,2026-04-20,long,retained,2.536521,2.128052,26.38,32.9,pivot_point,volume_profile,2.504928,2.096459,4.894647,4.894647
IVZ,2026-04-20,long,removed,2.0925,1.716515,29.72,38.5,pivot_point+volume_profile,volume_profile,2.056939,1.680955,1.874221,1.874221
LUV,2026-04-20,long,removed,2.068451,1.627587,43.61,39.96,pivot_point+volume_profile,volume_profile,-1.178575,-1.178575,-1.151594,-1.151594
MCHP,2026-04-20,long,added,1.53215,2.09942,51.65,33.24,pivot_point+volume_profile,volume_profile,1.492661,2.059932,4.069653,4.069653
MNST,2026-04-20,long,retained,2.298378,2.464221,23.04,29.43,pivot_point+volume_profile,volume_profile,-1.06053,-1.06053,-1.0,-1.0
ON,2026-04-20,long,removed,2.205227,1.655239,37.03,39.49,pivot_point+volume_profile,volume_profile,2.168538,1.61855,9.236509,9.236509
ULTA,2026-04-20,long,removed,3.177791,1.895238,21.4,35.85,pivot_point+volume_profile,volume_profile,-1.042209,-1.042209,-1.0,-1.0
VTRS,2026-04-20,long,added,1.906578,2.159994,28.9,32.54,pivot_point+volume_profile,volume_profile,1.859326,2.112742,1.302828,1.302828
FSLR,2026-04-27,long,added,1.762167,2.280875,47.79,31.22,pivot_point,volume_profile,1.73106,2.249768,5.09658,5.09658
GNRC,2026-04-27,long,retained,2.431647,2.002688,26.93,34.43,pivot_point,volume_profile,2.398006,1.969046,3.106866,3.106866
HAS,2026-04-27,long,retained,2.080713,2.116798,25.86,33.03,pivot_point+volume_profile,volume_profile,-1.163106,-1.163106,-1.125577,-1.125577
INCY,2026-04-27,long,added,1.54251,2.311158,51.46,30.91,pivot_point+volume_profile,volume_profile,1.492363,2.261011,1.968643,1.968643
IVZ,2026-04-27,long,added,1.879775,2.17108,32.47,32.41,pivot_point+volume_profile,volume_profile,1.839966,2.131271,1.898289,1.898289
NEM,2026-04-27,long,retained,2.157639,2.175364,22.96,32.36,pivot_point+volume_profile,volume_profile,-1.098504,-1.098504,-1.067259,-1.067259
ON,2026-04-27,long,retained,2.093542,2.093542,23.51,33.31,pivot_point+volume_profile,volume_profile,-1.03632,-1.03632,-1.0,-1.0
VTRS,2026-04-27,long,removed,2.04878,1.526016,27.05,41.76,pivot_point+volume_profile,volume_profile,2.000618,1.477854,2.260163,2.260163
ADM,2026-05-04,long,added,1.68048,2.194278,49.08,32.15,pivot_point,volume_profile,1.63043,2.144227,0.574203,0.574203
ALB,2026-05-04,long,added,1.610684,2.377,50.25,30.25,pivot_point,volume_profile,1.586143,-1.024541,-1.0,-1.0
BIIB,2026-05-04,long,added,1.742645,2.374724,48.09,30.27,pivot_point,volume_profile,1.700516,-0.04213,0.945164,0.945164
C,2026-05-04,long,removed,2.012374,1.506573,24.31,42.12,pivot_point,volume_profile,-1.052343,-1.052343,-1.0,-1.0
DOW,2026-05-04,long,removed,2.152554,1.517886,41.82,41.91,pivot_point+volume_profile,volume_profile,-1.028888,-1.028888,-1.0,-1.0
FSLR,2026-05-04,long,removed,2.07299,1.925998,43.56,35.43,pivot_point,volume_profile,2.043281,1.896289,3.722247,3.722247
GNRC,2026-05-04,long,retained,2.620386,2.216863,25.66,31.9,pivot_point,volume_profile,-1.033618,-1.033618,-1.0,-1.0
IVZ,2026-05-04,long,removed,2.325783,1.684083,22.56,39.02,pivot_point+volume_profile,volume_profile,2.286261,1.644561,2.398027,2.398027
OXY,2026-05-04,long,removed,2.45361,1.418712,24.52,28.84,pivot_point+volume_profile,volume_profile,-1.581139,-1.581139,-1.542008,-1.542008
ADM,2026-05-11,long,removed,2.143232,1.69859,42.73,38.78,pivot_point+volume_profile,volume_profile,-1.045111,-1.045111,-1.0,-1.0
BIIB,2026-05-11,long,added,1.569096,2.835602,50.98,26.4,pivot_point+volume_profile,volume_profile,-1.047834,-1.047834,-1.0,-1.0
FSLR,2026-05-11,long,added,1.847053,2.025176,45.73,34.14,pivot_point+volume_profile,volume_profile,1.815814,1.993938,1.010405,1.010405
GNRC,2026-05-11,long,added,1.769106,2.872088,35.28,26.14,pivot_point,volume_profile,-1.037299,-1.037299,-1.0,-1.0
HAS,2026-05-11,long,removed,2.341129,1.630847,23.01,39.9,pivot_point+volume_profile,volume_profile,-1.491403,-1.491403,-1.446832,-1.446832
MRNA,2026-05-11,long,removed,4.417063,0.908038,21.18,57.12,pivot_point,volume_profile,-1.019052,-1.019052,-1.0,-1.0
ADM,2026-05-18,long,added,1.860181,2.237304,46.34,31.68,pivot_point+volume_profile,volume_profile,-1.046193,-1.046193,-1.0,-1.0
APA,2026-05-18,long,removed,2.025758,1.981919,44.14,34.69,pivot_point+volume_profile,volume_profile,-1.031291,-1.031291,-1.0,-1.0
BIIB,2026-05-18,long,removed,2.090766,1.802678,28.34,22.18,pivot_point+volume_profile,volume_profile,2.048589,1.760502,1.959265,1.959265
CAH,2026-05-18,long,added,1.751088,2.249334,47.96,31.55,pivot_point+volume_profile,volume_profile,1.705581,2.203827,4.322422,4.322422
COP,2026-05-18,long,added,1.777297,2.206045,31.36,32.02,pivot_point+volume_profile,volume_profile,-1.131613,-1.131613,-1.08494,-1.08494
CVS,2026-05-18,long,removed,2.060862,1.843965,43.7,36.57,pivot_point+volume_profile,volume_profile,-1.052069,-1.052069,-1.0,-1.0
CVX,2026-05-18,long,added,1.961291,2.230118,25.56,31.76,pivot_point+volume_profile,volume_profile,-1.056276,-1.056276,-1.0,-1.0
DVN,2026-05-18,long,added,1.796719,2.467367,47.26,29.4,pivot_point,volume_profile,-1.039565,-1.039565,-1.0,-1.0
OXY,2026-05-18,long,removed,2.716157,1.65086,37.3,39.57,pivot_point+volume_profile,volume_profile,-1.075823,-1.075823,-1.035923,-1.035923
1 symbol date direction cohort control_rr variant_rr control_prob variant_prob control_sources variant_sources control_net_r variant_net_r control_hold30_r variant_hold30_r
2 HOOD 2024-07-01 long removed 2.363534 1.008379 20.78 53.96 pivot_point volume_profile -1.047696 0.979879 -1.019196 -1.019196
3 APP 2024-07-02 long removed 2.593435 1.597531 31.89 40.48 pivot_point+volume_profile volume_profile -1.161328 -1.161328 -1.129295 -1.129295
4 AXON 2024-07-02 long removed 2.088883 1.689069 34.97 38.94 pivot_point+volume_profile volume_profile 2.034275 1.634462 -1.260972 -1.260972
5 CVNA 2024-07-02 long added 1.620942 3.003727 35.47 25.26 pivot_point volume_profile 1.59883 -0.022112 1.252546 1.252546
6 KEY 2024-07-02 long retained 2.194943 2.010299 36.34 34.33 pivot_point+volume_profile volume_profile -1.174915 -1.174915 -1.121839 -1.121839
7 SMCI 2024-07-02 long added 1.790945 2.609255 35.95 28.16 pivot_point+volume_profile volume_profile -1.018032 -1.018032 -1.0 -1.0
8 STX 2024-07-02 long added 1.933209 2.395527 38.34 30.07 pivot_point+volume_profile volume_profile 1.879937 2.342254 -1.0 -1.0
9 TPL 2024-07-02 long retained 2.197984 2.138032 31.91 32.79 pivot_point+volume_profile volume_profile 2.155173 2.095222 2.455028 2.455028
10 VLO 2024-07-02 long removed 2.313153 1.768764 22.49 37.69 pivot_point+volume_profile volume_profile -1.050398 -1.050398 -1.0 -1.0
11 APP 2024-07-10 long retained 2.366003 2.392551 33.96 30.1 pivot_point+volume_profile volume_profile -1.031019 -1.031019 -1.0 -1.0
12 CRH 2024-07-10 long retained 2.366265 2.349782 32.76 30.52 pivot_point+volume_profile volume_profile 2.311975 2.295492 -1.0 -1.0
13 CVNA 2024-07-10 long retained 2.586053 2.786918 22.55 26.76 pivot_point+volume_profile volume_profile -1.066693 -1.066693 -1.043339 -1.043339
14 DELL 2024-07-10 long retained 2.070397 2.070397 23.99 33.59 volume_profile volume_profile -1.026336 -1.026336 -1.0 -1.0
15 GEN 2024-07-10 long removed 2.391085 1.689807 25.12 23.92 pivot_point+volume_profile volume_profile -0.064503 1.625303 1.507702 1.507702
16 KEY 2024-07-10 long removed 7.212904 1.563743 25.59 41.07 pivot_point volume_profile -1.520191 1.509962 -1.46641 -1.46641
17 NVDA 2024-07-10 long added 1.056776 2.988849 42.75 25.36 volume_profile volume_profile -1.028158 -1.028158 -1.0 -1.0
18 QCOM 2024-07-10 long retained 2.214523 2.214523 22.33 31.93 pivot_point+volume_profile volume_profile -1.049717 -1.049717 -1.0 -1.0
19 SMCI 2024-07-10 long added 1.803073 2.05868 31.57 33.73 pivot_point+volume_profile volume_profile -1.020646 -1.020646 -1.0 -1.0
20 STX 2024-07-10 long removed 2.259748 1.550674 26.04 41.31 pivot_point+volume_profile volume_profile -1.058327 -1.058327 -1.0 -1.0
21 TPL 2024-07-10 long removed 2.4469 1.856628 29.79 36.39 pivot_point+volume_profile volume_profile -1.047047 1.809581 -1.0 -1.0
22 HOOD 2024-07-16 long added 1.202989 2.283322 39.07 31.2 pivot_point volume_profile -1.030546 -1.030546 -1.0 -1.0
23 COIN 2024-07-17 long retained 2.024981 2.994532 27.15 25.32 pivot_point+volume_profile volume_profile -1.024552 -1.024552 -1.0 -1.0
24 CVNA 2024-07-17 long added 1.892159 2.345824 27.9 30.56 pivot_point volume_profile -1.217536 -1.217536 -1.195482 -1.195482
25 PSX 2024-07-17 long removed 2.638302 1.847648 24.12 36.52 pivot_point+volume_profile volume_profile -1.061989 -1.061989 -1.0 -1.0
26 TPL 2024-07-17 long removed 2.191527 1.564777 28.58 41.06 pivot_point+volume_profile volume_profile -1.051591 -1.051591 -1.0 -1.0
27 COIN 2024-07-24 long added 1.949161 2.798867 28.12 26.67 pivot_point+volume_profile volume_profile -1.021168 -1.021168 -1.0 -1.0
28 T 2024-07-24 long removed 2.024326 1.762354 26.56 22.78 pivot_point volume_profile 1.959139 1.697168 2.53466 2.53466
29 C 2024-07-31 long removed 2.04091 0.679298 28.95 50.76 pivot_point+volume_profile volume_profile -1.056715 -1.056715 -1.0 -1.0
30 CVNA 2024-07-31 long added 1.764983 2.179133 29.74 32.32 pivot_point volume_profile -1.073779 -1.073779 -1.053721 -1.053721
31 DECK 2024-07-31 long retained 2.204131 2.230714 24.24 31.75 pivot_point+volume_profile volume_profile -1.035117 -1.035117 -1.0 -1.0
32 IP 2024-07-31 long retained 2.061675 2.896383 28.69 25.98 pivot_point+volume_profile volume_profile -1.414336 -1.414336 -1.363237 -1.363237
33 MPC 2024-07-31 long retained 2.070547 2.070547 26.39 33.59 pivot_point+volume_profile volume_profile -1.0517 -1.0517 -1.0 -1.0
34 PSX 2024-07-31 long removed 2.68204 1.71537 22.76 38.51 pivot_point+volume_profile volume_profile -1.054284 -1.054284 -1.0 -1.0
35 RL 2024-07-31 long removed 2.162234 1.801202 22.71 37.2 pivot_point volume_profile -1.203596 -1.203596 -1.157407 -1.157407
36 LHX 2024-08-07 long removed 2.486849 1.866229 24.22 21.26 pivot_point+volume_profile volume_profile -0.060235 -0.060235 0.481976 0.481976
37 CVNA 2024-08-14 long added 0.830134 2.688769 54.21 27.51 pivot_point volume_profile 0.810487 -1.019647 -1.0 -1.0
38 DECK 2024-08-14 long retained 2.250336 2.276737 23.74 31.26 pivot_point+volume_profile volume_profile -1.034755 -1.034755 -1.0 -1.0
39 IP 2024-08-14 long added 1.832687 2.216827 46.74 31.9 pivot_point+volume_profile volume_profile 1.780846 2.164986 1.646715 1.646715
40 PANW 2024-08-14 long retained 2.056851 2.056851 24.55 33.75 pivot_point+volume_profile volume_profile 2.016184 2.016184 -0.070585 -0.070585
41 UBER 2024-08-14 long added 1.686961 2.109652 39.77 33.12 pivot_point+volume_profile volume_profile -1.03507 -1.03507 -1.0 -1.0
42 VRT 2024-08-14 long added 1.982701 2.095018 38.68 33.29 pivot_point+volume_profile volume_profile -1.020453 -1.020453 -1.0 -1.0
43 CRWD 2024-08-21 long added 1.638443 2.259128 49.77 31.45 pivot_point volume_profile -1.030148 -1.030148 -1.0 -1.0
44 IFF 2024-08-21 long retained 2.083267 2.056829 34.83 33.75 pivot_point+volume_profile volume_profile -0.05916 -0.05916 0.112124 0.112124
45 IP 2024-08-21 long retained 5.241123 2.180305 27.42 32.31 pivot_point+volume_profile volume_profile -1.059221 -1.059221 -1.0 -1.0
46 TFC 2024-08-21 long removed 2.364212 1.315501 25.38 31.04 pivot_point+volume_profile volume_profile -0.057957 1.257544 -0.413095 -0.413095
47 VST 2024-08-21 long added 1.802328 2.116052 35.18 33.04 pivot_point+volume_profile volume_profile -1.025569 -1.025569 -1.0 -1.0
48 CEG 2024-08-27 long retained 2.152078 2.134751 40.63 32.83 pivot_point+volume_profile volume_profile -1.03271 -1.03271 -1.0 -1.0
49 HOOD 2024-08-27 long added 1.976913 2.059984 26.16 33.71 pivot_point+volume_profile volume_profile -1.028923 -1.028923 -1.0 -1.0
50 BAC 2024-08-28 long removed 2.358212 1.561372 25.44 26.12 pivot_point+volume_profile volume_profile -1.062807 -1.062807 -1.0 -1.0
51 CFG 2024-08-28 long removed 2.897858 0.973092 20.97 40.03 pivot_point+volume_profile volume_profile -1.057175 -1.057175 -1.0 -1.0
52 COF 2024-08-28 long removed 2.031659 1.595906 29.06 25.5 pivot_point+volume_profile volume_profile -1.057914 -1.057914 -1.0 -1.0
53 CRWD 2024-08-28 long added 1.579228 3.019642 50.8 25.16 pivot_point+volume_profile volume_profile -1.03194 -1.03194 -1.0 -1.0
54 CVNA 2024-08-28 long added 1.787492 2.250672 27.8 31.54 pivot_point volume_profile -1.026143 -1.026143 -1.0 -1.0
55 IP 2024-08-28 long retained 5.632903 2.227531 26.85 31.79 pivot_point+volume_profile volume_profile -1.066284 -1.066284 -1.0 -1.0
56 KEY 2024-08-28 long added 1.718821 2.105375 48.46 33.17 pivot_point+volume_profile volume_profile -1.044658 -1.044658 -1.0 -1.0
57 NVDA 2024-08-28 long added 1.068723 2.960434 43.01 25.54 pivot_point+volume_profile volume_profile -1.025929 -1.025929 -1.0 -1.0
58 SW 2024-08-28 long retained 2.747627 2.079496 37.05 33.48 pivot_point+volume_profile volume_profile -1.045941 -1.045941 -1.0 -1.0
59 TFC 2024-08-28 long removed 2.90902 1.582712 20.89 25.74 pivot_point+volume_profile volume_profile -1.062366 -1.062366 -1.0 -1.0
60 AMT 2024-09-05 long retained 2.079796 3.058994 43.47 24.91 pivot_point+volume_profile volume_profile -1.166052 -1.166052 -1.099904 -1.099904
61 APP 2024-09-05 long retained 2.185527 2.355959 23.45 30.46 pivot_point+volume_profile volume_profile 2.15693 2.327362 8.886403 8.886403
62 FITB 2024-09-05 long removed 2.073653 1.910131 43.55 35.65 pivot_point+volume_profile volume_profile -1.063196 -1.063196 -1.0 -1.0
63 KKR 2024-09-05 long removed 2.181887 1.723269 22.49 38.39 pivot_point volume_profile 2.13195 1.673332 4.089721 4.089721
64 NRG 2024-09-05 long removed 2.25116 1.894631 21.73 35.86 pivot_point volume_profile 2.212092 1.855563 1.85814 1.85814
65 RL 2024-09-05 long added 1.555085 2.24363 50.83 31.61 pivot_point volume_profile 1.506102 2.194647 4.172316 4.172316
66 RMD 2024-09-05 long added 1.99921 2.840897 44.47 26.37 pivot_point+volume_profile volume_profile -1.226172 -1.226172 -1.174599 -1.174599
67 CEG 2024-09-11 long added 1.880922 2.172351 41.85 32.4 pivot_point+volume_profile volume_profile 1.848098 2.139527 6.906646 6.906646
68 HOOD 2024-09-11 long retained 2.083757 2.549773 26.63 28.66 pivot_point volume_profile 2.055784 2.5218 4.106526 4.106526
69 AMT 2024-09-12 long added 2.5128 28.99 volume_profile -1.065809 -1.0
70 CMG 2024-09-12 long retained 2.010624 2.010624 37.13 34.33 pivot_point+volume_profile volume_profile -0.045527 -0.045527 1.248258 1.248258
71 DASH 2024-09-12 long retained 2.285917 2.438839 25.57 29.66 pivot_point+volume_profile volume_profile 2.239634 2.392556 4.088344 4.088344
72 FITB 2024-09-12 long removed 2.134405 1.061322 27.83 37.42 pivot_point+volume_profile volume_profile 2.080604 1.007521 1.888333 1.888333
73 RL 2024-09-12 long added 2.091438 33.33 volume_profile 2.038762 3.391383
74 RMD 2024-09-12 long retained 2.562837 2.163551 29.95 32.5 pivot_point+volume_profile volume_profile -1.808118 -1.808118 -1.756551 -1.756551
75 VRT 2024-09-12 long retained 2.005719 2.207135 25.99 32.01 pivot_point+volume_profile volume_profile 1.980011 2.181427 3.447561 3.447561
76 CFG 2024-09-19 long added 1.562785 2.226657 51.09 31.8 pivot_point volume_profile -1.051285 -1.051285 -1.0 -1.0
77 CPB 2024-09-19 long removed 2.043159 0.894853 28.92 42.56 pivot_point+volume_profile volume_profile -1.131369 -1.131369 -1.069907 -1.069907
78 CRWD 2024-09-19 long added 1.654913 2.384252 49.5 30.18 pivot_point volume_profile 1.618946 2.348286 1.263574 1.263574
79 CVNA 2024-09-19 long retained 2.373325 2.373325 20.69 30.29 volume_profile volume_profile 2.345166 2.345166 6.312101 6.312101
80 DASH 2024-09-19 long retained 2.097487 2.973728 26.66 25.45 pivot_point volume_profile 2.0506 2.926841 3.314464 3.314464
81 DELL 2024-09-19 long retained 2.078075 2.078075 31.49 33.49 pivot_point+volume_profile volume_profile 2.04365 2.04365 0.856462 0.856462
82 FITB 2024-09-19 long retained 2.259068 2.276905 31.45 31.26 pivot_point+volume_profile volume_profile -1.057874 -1.057874 -1.0 -1.0
83 IP 2024-09-19 long retained 4.401095 2.251491 29.22 31.53 pivot_point+volume_profile volume_profile -1.065161 -1.065161 -1.0 -1.0
84 RMD 2024-09-19 long added 1.992176 2.758242 44.56 26.97 pivot_point+volume_profile volume_profile -1.046594 -1.046594 -1.0 -1.0
85 TFC 2024-09-19 long added 1.809614 2.2605 47.07 31.44 pivot_point+volume_profile volume_profile -1.0599 -1.0599 -1.0 -1.0
86 VRT 2024-09-19 long added 1.947838 2.250443 26.14 31.54 pivot_point+volume_profile volume_profile 1.919599 2.222204 2.642618 2.642618
87 VST 2024-09-19 long retained 2.072291 2.072291 23.96 33.56 volume_profile volume_profile 2.040867 2.040867 5.458704 5.458704
88 CVNA 2024-09-26 long retained 10.231888 2.474562 20.25 29.33 pivot_point+volume_profile volume_profile -0.029905 2.444657 6.135639 6.135639
89 DASH 2024-09-26 long retained 7.365516 2.10784 25.52 33.14 pivot_point+volume_profile volume_profile -0.0516 2.05624 4.973482 4.973482
90 FITB 2024-09-26 long removed 2.083968 1.931414 21.22 20.36 pivot_point+volume_profile volume_profile -1.060556 -1.060556 -1.0 -1.0
91 RMD 2024-09-26 long removed 2.186908 1.266636 27.23 32.15 pivot_point+volume_profile volume_profile -1.050048 -1.050048 -1.0 -1.0
92 TGT 2024-09-26 long removed 2.292853 1.776621 26.1 22.57 pivot_point volume_profile -1.06012 -1.06012 -1.0 -1.0
93 WSM 2024-09-26 long removed 2.402788 1.658261 20.4 39.44 pivot_point+volume_profile volume_profile -1.137583 -1.137583 -1.101101 -1.101101
94 CVNA 2024-10-03 long added 1.664246 2.109813 29.74 33.12 pivot_point volume_profile 1.631671 2.077238 5.882489 5.882489
95 DECK 2024-10-03 long retained 2.168365 2.199366 24.44 32.1 pivot_point+volume_profile volume_profile 2.126606 2.157607 2.672686 2.672686
96 MSTR 2024-10-03 long retained 2.022121 2.022121 24.38 34.18 volume_profile volume_profile 2.001418 2.001418 10.405324 10.405324
97 NEM 2024-10-03 long removed 2.437475 1.6442 24.67 24.68 pivot_point+volume_profile volume_profile 2.383997 1.590722 -1.0 -1.0
98 CVNA 2024-10-10 long retained 9.123898 2.511664 20.12 29.0 pivot_point+volume_profile volume_profile -0.036109 2.475555 5.358405 5.358405
99 FITB 2024-10-10 long retained 2.456651 2.29033 32.3 31.12 pivot_point+volume_profile volume_profile 2.390999 2.224679 3.42334 3.42334
100 NVDA 2024-10-10 long added 0.7889 2.583282 51.54 28.38 pivot_point volume_profile 0.753151 -0.035748 1.572496 1.572496
101 PODD 2024-10-10 long removed 2.231607 1.720271 26.74 23.44 pivot_point+volume_profile volume_profile 2.180027 1.668691 3.435679 3.435679
102 COF 2024-10-17 long added 2.025877 34.14 volume_profile -1.058059 -1.0
103 COIN 2024-10-17 long added 1.642968 2.257497 49.7 31.47 pivot_point volume_profile -1.024139 -1.024139 -1.0 -1.0
104 CVNA 2024-10-17 long retained 9.492877 2.588273 20.09 28.33 pivot_point+volume_profile volume_profile -0.037775 2.550498 6.74196 6.74196
105 DASH 2024-10-17 long retained 4.392565 2.700376 26.45 27.42 pivot_point+volume_profile volume_profile 4.334569 2.64238 5.569761 5.569761
106 GM 2024-10-17 long retained 2.503676 2.380907 39.07 30.21 pivot_point+volume_profile volume_profile 2.451817 2.329049 3.26087 3.26087
107 NVDA 2024-10-17 long retained 2.240177 2.240177 21.85 31.65 volume_profile volume_profile -0.035332 -0.035332 0.170302 0.170302
108 PNC 2024-10-17 long removed 2.403014 1.107655 25.0 36.14 pivot_point+volume_profile volume_profile 2.340479 1.045121 4.292634 4.292634
109 CFG 2024-10-24 long removed 2.405384 1.535265 23.18 26.59 pivot_point+volume_profile volume_profile 2.352507 1.482388 3.350752 3.350752
110 COF 2024-10-24 long removed 2.345874 1.529143 25.56 26.7 pivot_point+volume_profile volume_profile 2.288967 1.472236 6.44221 6.44221
111 DASH 2024-10-24 long retained 4.421262 2.573824 26.37 28.46 pivot_point+volume_profile volume_profile 4.357196 2.509759 5.28825 5.28825
112 RMD 2024-10-24 long removed 2.039677 0.914596 28.96 41.9 pivot_point+volume_profile volume_profile 1.980043 0.854962 0.294367 0.294367
113 HOOD 2024-10-30 long added 0.688621 2.735256 55.76 27.15 pivot_point volume_profile -1.531615 -1.531615 -1.493148 -1.493148
114 CFG 2024-10-31 long retained 2.172117 2.386818 40.6 30.16 pivot_point+volume_profile volume_profile 2.118575 2.333276 2.2754 2.2754
115 CVNA 2024-10-31 long removed 2.780741 1.821707 31.41 36.9 pivot_point+volume_profile volume_profile -1.033504 -1.033504 -1.0 -1.0
116 DASH 2024-10-31 long removed 5.013156 1.624669 27.42 40.01 pivot_point+volume_profile volume_profile -0.057868 1.566801 3.395652 3.395652
117 IP 2024-10-31 long added 2.088162 33.37 volume_profile 2.038095 0.0
118 PODD 2024-10-31 long removed 2.039063 1.924042 28.97 20.46 pivot_point+volume_profile volume_profile 1.980028 1.865007 4.820368 4.820368
119 RMD 2024-10-31 long removed 2.182318 1.268032 27.29 32.12 pivot_point+volume_profile volume_profile -1.049721 1.218312 -1.0 -1.0
120 HOOD 2024-11-06 long added 1.731126 2.790779 30.87 26.73 pivot_point volume_profile 1.70455 2.764204 3.150657 3.150657
121 CFG 2024-11-07 long added 1.952332 3.052252 41.48 24.95 pivot_point+volume_profile volume_profile -1.041993 -1.041993 -1.0 -1.0
122 CVNA 2024-11-07 long retained 3.046097 2.126585 29.59 32.92 pivot_point+volume_profile volume_profile -1.031364 -1.031364 -1.0 -1.0
123 NTAP 2024-11-07 long removed 2.609349 1.630808 20.16 39.9 pivot_point+volume_profile volume_profile -1.548219 -1.548219 -1.485722 -1.485722
124 NVDA 2024-11-07 long retained 2.116497 2.116497 23.24 33.04 volume_profile volume_profile -1.042958 -1.042958 -1.0 -1.0
125 ZBRA 2024-11-07 long retained 12.224687 2.898061 25.01 25.96 pivot_point+volume_profile volume_profile -1.058806 -1.058806 -1.0 -1.0
126 HOOD 2024-11-13 long removed 2.810736 1.620896 25.78 40.07 pivot_point+volume_profile volume_profile 2.78623 1.596391 2.7301 2.7301
127 CFG 2024-11-14 long added 1.901703 2.258489 41.96 31.46 pivot_point+volume_profile volume_profile -1.047497 -1.047497 -1.0 -1.0
128 CVNA 2024-11-14 long retained 3.294474 2.320747 27.94 30.81 pivot_point+volume_profile volume_profile -1.033075 -1.033075 -1.0 -1.0
129 FIS 2024-11-14 long removed 2.102161 1.197044 28.21 33.82 pivot_point+volume_profile volume_profile -1.066206 -1.066206 -1.0 -1.0
130 IP 2024-11-14 long removed 2.506606 1.626125 21.04 39.98 pivot_point+volume_profile volume_profile -1.057094 1.569031 -1.0 -1.0
131 NVDA 2024-11-14 long added 1.135555 2.5309 40.59 28.83 volume_profile volume_profile -1.044243 -1.044243 -1.0 -1.0
132 TSN 2024-11-14 long retained 2.596128 2.001554 25.87 34.44 pivot_point+volume_profile volume_profile -1.055266 -1.055266 -1.0 -1.0
133 ZBRA 2024-11-14 long removed 15.069871 1.640643 25.0 39.74 pivot_point+volume_profile volume_profile -1.227627 -1.227627 -1.164907 -1.164907
134 HOOD 2024-11-20 long retained 2.234166 2.76471 28.52 26.93 pivot_point volume_profile 2.208817 -0.025349 2.329013 2.329013
135 CFG 2024-11-21 long retained 2.044706 2.448911 40.1 29.57 pivot_point+volume_profile volume_profile -1.054029 -1.054029 -1.0 -1.0
136 CVNA 2024-11-21 long removed 2.887708 1.864361 30.44 36.28 pivot_point+volume_profile volume_profile -1.03591 -1.03591 -1.0 -1.0
137 DASH 2024-11-21 long added 1.571744 2.048168 50.13 33.86 pivot_point+volume_profile volume_profile -1.05097 -1.05097 -1.0 -1.0
138 NVDA 2024-11-21 long retained 2.121212 2.121212 23.18 32.98 volume_profile volume_profile -1.036861 -1.036861 -1.0 -1.0
139 RMD 2024-11-21 long retained 2.331234 2.277761 40.71 31.25 pivot_point+volume_profile volume_profile -1.056353 -1.056353 -1.0 -1.0
140 TSN 2024-11-21 long retained 9.528478 2.276611 25.09 31.27 pivot_point+volume_profile volume_profile -1.059542 -1.059542 -1.0 -1.0
141 HOOD 2024-11-27 long added 1.568666 2.063271 37.79 33.67 pivot_point volume_profile 1.544006 -1.02466 -1.0 -1.0
142 CFG 2024-11-29 long removed 2.778313 1.74038 22.02 38.12 pivot_point+volume_profile volume_profile -1.058389 -1.058389 -1.0 -1.0
143 CVNA 2024-11-29 long retained 2.009852 2.333981 38.54 30.68 pivot_point+volume_profile volume_profile -1.03745 -1.03745 -1.0 -1.0
144 DASH 2024-11-29 long retained 2.259005 2.709896 37.65 27.35 pivot_point+volume_profile volume_profile -1.056211 -1.056211 -1.0 -1.0
145 GM 2024-11-29 long removed 2.328011 1.562034 28.34 41.1 pivot_point+volume_profile volume_profile -1.038933 -1.038933 -1.0 -1.0
146 INCY 2024-11-29 long removed 2.225995 1.892311 41.8 35.89 pivot_point+volume_profile volume_profile -1.142421 -1.142421 -1.102218 -1.102218
147 NEE 2024-11-29 long removed 2.084122 1.817583 28.42 21.96 pivot_point+volume_profile volume_profile -1.122428 -1.122428 -1.059965 -1.059965
148 UHS 2024-11-29 long added 1.577296 2.002636 36.23 34.43 pivot_point+volume_profile volume_profile -1.045223 -1.045223 -1.0 -1.0
149 HOOD 2024-12-05 long removed 2.527938 1.550767 22.45 41.31 pivot_point+volume_profile volume_profile -1.023997 -1.023997 -1.0 -1.0
150 CFG 2024-12-06 long removed 2.094709 1.448606 24.3 28.24 pivot_point+volume_profile volume_profile -1.06007 -1.06007 -1.0 -1.0
151 HOOD 2024-12-12 long retained 2.151963 2.356331 26.23 30.46 pivot_point+volume_profile volume_profile -1.187492 -1.187492 -1.165762 -1.165762
152 EBAY 2024-12-20 long removed 2.41583 1.503526 39.88 42.18 pivot_point+volume_profile volume_profile -1.0524 -1.0524 -1.0 -1.0
153 HOOD 2024-12-27 long retained 2.132954 2.327764 26.45 30.74 pivot_point+volume_profile volume_profile 2.112404 2.307214 4.447604 4.447604
154 HOOD 2025-01-06 long retained 2.316894 2.534928 22.25 28.79 pivot_point+volume_profile volume_profile -1.023902 -1.023902 -1.0 -1.0
155 TPL 2025-01-07 long added 1.779801 2.801287 28.52 26.65 pivot_point+volume_profile volume_profile 1.75231 -0.02749 0.953313 0.953313
156 LDOS 2025-01-15 long retained 2.022643 2.271702 42.78 31.32 pivot_point+volume_profile volume_profile -1.054629 -1.054629 -1.0 -1.0
157 MTB 2025-01-15 long added 1.506775 2.334803 37.12 30.67 pivot_point+volume_profile volume_profile -1.05847 -1.05847 -1.0 -1.0
158 SATS 2025-01-15 long removed 2.084923 1.904701 32.21 35.72 pivot_point volume_profile 2.05013 1.869907 4.79693 4.79693
159 HOOD 2025-01-22 long added 1.485045 2.537047 32.93 28.77 volume_profile volume_profile 1.45805 2.510052 -1.0 -1.0
160 CBOE 2025-01-23 long removed 2.406105 1.814548 29.77 37.0 pivot_point+volume_profile volume_profile 2.340848 1.749291 1.843355 1.843355
161 CHRW 2025-01-23 long retained 2.066756 2.066756 36.03 33.63 pivot_point+volume_profile volume_profile -2.1852 -2.1852 -2.123782 -2.123782
162 DASH 2025-01-23 long added 1.755187 2.183821 43.69 32.27 pivot_point+volume_profile volume_profile -1.054221 -1.054221 -1.0 -1.0
163 EBAY 2025-01-23 long removed 2.148392 1.370809 27.67 29.84 pivot_point+volume_profile volume_profile 2.103909 1.326326 -1.0 -1.0
164 GM 2025-01-23 long removed 2.219727 1.556927 41.87 41.2 pivot_point+volume_profile volume_profile -1.377279 -1.377279 -1.332675 -1.332675
165 TPL 2025-01-23 long retained 2.215306 2.215306 22.52 31.92 volume_profile volume_profile -1.034187 -1.034187 -1.0 -1.0
166 ZBRA 2025-01-23 long retained 10.101825 2.592492 25.05 28.3 pivot_point+volume_profile volume_profile -1.239258 -1.239258 -1.179004 -1.179004
167 HOOD 2025-01-29 long retained 2.182893 2.182893 22.68 32.28 volume_profile volume_profile 2.156429 2.156429 -1.0 -1.0
168 AEP 2025-01-30 long removed 2.291606 1.611789 33.31 40.23 pivot_point+volume_profile volume_profile 2.226497 1.54668 2.500623 2.500623
169 D 2025-01-30 long added 1.81942 2.484021 46.93 29.25 pivot_point+volume_profile volume_profile -1.062307 -1.062307 -1.0 -1.0
170 DASH 2025-01-30 long retained 3.032273 2.965588 24.48 25.51 pivot_point+volume_profile volume_profile 2.976645 2.909961 -1.0 -1.0
171 EBAY 2025-01-30 long retained 2.31507 2.050187 40.87 33.83 pivot_point+volume_profile volume_profile -1.2003 -1.2003 -1.152131 -1.152131
172 NEE 2025-01-30 long removed 2.856326 1.678544 21.26 24.11 pivot_point+volume_profile volume_profile -1.048252 -1.048252 -1.0 -1.0
173 PODD 2025-01-30 long removed 3.011956 1.716997 35.21 38.49 pivot_point+volume_profile volume_profile -1.050309 -1.050309 -1.0 -1.0
174 SO 2025-01-30 long removed 2.663459 1.959193 22.11 34.99 pivot_point+volume_profile volume_profile 2.597821 1.893554 2.106036 2.106036
175 T 2025-01-30 long retained 3.890785 2.286312 30.86 31.16 pivot_point+volume_profile volume_profile 3.827951 2.223478 3.348375 3.348375
176 HOOD 2025-02-05 long added 1.411529 2.435669 34.39 29.69 volume_profile volume_profile 1.383132 2.407272 -1.0 -1.0
177 CFG 2025-02-06 long retained 2.426162 2.426162 24.58 29.78 pivot_point+volume_profile volume_profile -1.053272 -1.053272 -1.0 -1.0
178 EBAY 2025-02-06 long retained 2.564372 2.271866 38.54 31.32 pivot_point+volume_profile volume_profile -1.317578 -1.317578 -1.264402 -1.264402
179 FIX 2025-02-06 long retained 2.001518 2.001518 24.84 34.44 volume_profile volume_profile -1.026408 -1.026408 -1.0 -1.0
180 MTB 2025-02-06 long retained 2.398987 2.405595 22.84 29.98 pivot_point+volume_profile volume_profile -1.064938 -1.064938 -1.0 -1.0
181 PODD 2025-02-06 long retained 2.44026 2.071586 39.65 33.57 pivot_point+volume_profile volume_profile -1.054228 -1.054228 -1.0 -1.0
182 VTR 2025-02-06 long removed 2.336742 1.660532 40.65 39.4 pivot_point+volume_profile volume_profile 2.270724 1.594514 3.446489 3.446489
183 CVNA 2025-02-13 long retained 2.034467 2.549029 36.23 28.67 pivot_point+volume_profile volume_profile -1.035496 -1.035496 -1.0 -1.0
184 D 2025-02-13 long removed 2.394779 1.857229 40.08 36.38 pivot_point+volume_profile volume_profile -1.059337 -1.059337 -1.0 -1.0
185 NVDA 2025-02-13 long added 0.695595 2.97396 66.27 25.45 pivot_point+volume_profile volume_profile 0.667121 -1.028474 -1.0 -1.0
186 HOOD 2025-02-20 long added 1.380515 2.831765 35.23 26.43 volume_profile volume_profile -1.02081 -1.02081 -1.0 -1.0
187 DASH 2025-02-21 long added 1.564233 2.063261 35.07 33.67 pivot_point volume_profile -1.042246 -1.042246 -1.0 -1.0
188 EBAY 2025-02-21 long removed 2.020731 1.695948 44.2 38.83 pivot_point+volume_profile volume_profile -2.291229 -2.291229 -2.230532 -2.230532
189 CHRW 2025-02-28 long removed 2.524211 1.154679 23.89 34.89 pivot_point+volume_profile volume_profile -1.056592 -1.056592 -1.0 -1.0
190 DASH 2025-02-28 long added 1.553079 2.002972 35.27 34.42 pivot_point volume_profile -1.0378 -1.0378 -1.0 -1.0
191 CHRW 2025-03-07 long removed 2.160874 1.608008 42.53 40.29 pivot_point+volume_profile volume_profile -1.053765 -1.053765 -1.0 -1.0
192 NEE 2025-03-07 long added 1.87582 2.31029 46.12 30.92 pivot_point+volume_profile volume_profile -1.058424 -1.058424 -1.0 -1.0
193 NEM 2025-03-07 long removed 2.53317 1.950455 23.81 20.11 pivot_point volume_profile 2.491007 1.908292 5.449434 5.449434
194 T 2025-03-07 long retained 2.028456 2.028456 25.3 34.1 pivot_point+volume_profile volume_profile -1.060056 -1.060056 -1.0 -1.0
195 KMI 2025-03-14 long retained 2.170945 2.170945 22.81 32.41 volume_profile volume_profile -1.050685 -1.050685 -1.0 -1.0
196 NEM 2025-03-14 long removed 2.932488 0.767514 20.73 47.16 pivot_point+volume_profile volume_profile -1.04203 0.725485 -1.0 -1.0
197 AXON 2025-03-21 long removed 2.475348 1.752563 20.72 37.93 volume_profile volume_profile -1.025557 -1.025557 -1.0 -1.0
198 GDDY 2025-03-21 long retained 2.701088 2.199113 32.01 32.1 pivot_point+volume_profile volume_profile -1.047347 -1.047347 -1.0 -1.0
199 NEM 2025-03-21 long removed 2.205772 1.643242 42.03 39.69 pivot_point+volume_profile volume_profile -1.04556 -1.04556 -1.0 -1.0
200 AXON 2025-04-11 long retained 2.030874 2.030874 25.47 34.07 volume_profile volume_profile 2.007918 2.007918 3.59907 3.59907
201 CVNA 2025-04-11 long removed 2.353936 1.948265 36.68 35.14 pivot_point+volume_profile volume_profile 2.342119 1.936448 3.057394 3.057394
202 IBKR 2025-04-11 long removed 2.24209 1.915206 24.63 35.58 pivot_point+volume_profile volume_profile -1.019948 -1.019948 -1.0 -1.0
203 TYL 2025-04-11 long added 1.851443 2.070885 43.47 33.58 pivot_point+volume_profile volume_profile -1.036647 -1.036647 -1.0 -1.0
204 WMT 2025-04-11 long retained 2.018185 2.122872 26.03 32.96 pivot_point+volume_profile volume_profile -0.036331 -0.036331 0.935686 0.935686
205 WMT 2025-04-21 long removed 2.218352 1.642866 23.69 39.7 pivot_point+volume_profile volume_profile -0.038317 1.604549 1.569432 1.569432
206 APP 2025-04-28 long removed 2.449157 1.806118 22.57 37.13 pivot_point+volume_profile volume_profile 2.434952 1.791913 2.458197 2.458197
207 CHTR 2025-04-28 long added 1.788035 2.03155 47.39 34.06 pivot_point+volume_profile volume_profile 1.757898 2.001414 1.195488 1.195488
208 DASH 2025-04-28 long added 1.53196 3.055881 40.85 24.93 pivot_point+volume_profile volume_profile 1.504868 -0.027092 1.953171 1.953171
209 FICO 2025-04-28 long removed 2.589579 1.804765 20.32 37.15 pivot_point+volume_profile volume_profile -1.034812 1.769953 -1.0 -1.0
210 GDDY 2025-04-28 long added 1.783221 2.20482 41.07 32.04 pivot_point+volume_profile volume_profile -1.4347 -1.4347 -1.393018 -1.393018
211 GEN 2025-04-28 long added 1.995704 2.618785 44.52 28.08 pivot_point+volume_profile volume_profile 1.955837 2.578918 3.132201 3.132201
212 ISRG 2025-04-28 long retained 2.051044 2.044118 33.02 33.91 pivot_point+volume_profile volume_profile -0.030325 -0.030325 0.459884 0.459884
213 LITE 2025-04-28 long removed 2.424966 1.902659 39.79 35.75 pivot_point+volume_profile volume_profile 2.408499 1.886192 2.998472 2.998472
214 LYV 2025-04-28 long removed 2.22777 1.63153 23.59 39.89 pivot_point+volume_profile volume_profile -0.033994 1.597536 1.331497 1.331497
215 MAA 2025-04-28 long removed 2.30841 1.614858 25.94 25.18 pivot_point+volume_profile volume_profile -1.050537 -1.050537 -1.0 -1.0
216 MSTR 2025-04-28 long added 1.849639 2.909547 27.49 25.89 pivot_point volume_profile -0.019982 -0.019982 0.593368 0.593368
217 TPR 2025-04-28 long retained 2.39514 2.600039 21.48 28.23 pivot_point+volume_profile volume_profile 2.368427 2.573326 2.053804 2.053804
218 VST 2025-04-28 long added 1.987113 2.84257 28.83 26.35 pivot_point+volume_profile volume_profile 1.96779 2.823248 2.610974 2.610974
219 KVUE 2025-05-01 long retained 2.541159 2.399402 34.74 30.04 pivot_point+volume_profile volume_profile -1.044749 -1.044749 -1.0 -1.0
220 HOOD 2025-05-02 long added 1.579848 2.71337 31.59 27.32 pivot_point volume_profile 1.56216 2.695683 5.125405 5.125405
221 ABBV 2025-05-05 long added 1.695505 2.083795 32.43 33.43 pivot_point+volume_profile volume_profile -1.041348 -1.041348 -1.0 -1.0
222 AMT 2025-05-05 long removed 2.615411 1.760432 38.11 37.81 pivot_point+volume_profile volume_profile -1.047871 -1.047871 -1.0 -1.0
223 APP 2025-05-05 long retained 2.13066 2.13066 25.87 32.87 pivot_point+volume_profile volume_profile 2.11486 2.11486 1.53399 1.53399
224 AXON 2025-05-05 long added 1.907457 2.204538 27.28 32.04 pivot_point+volume_profile volume_profile 1.872667 2.169748 4.37086 4.37086
225 BKR 2025-05-05 long removed 2.586196 38.35 pivot_point+volume_profile -0.031936 1.23835
226 CBRE 2025-05-05 long removed 2.028372 1.675005 39.7 39.17 pivot_point+volume_profile volume_profile -1.037015 -1.037015 -1.0 -1.0
227 CHTR 2025-05-05 long retained 2.244983 2.782606 41.6 26.79 pivot_point+volume_profile volume_profile -1.034872 -1.034872 -1.0 -1.0
228 CVNA 2025-05-05 long added 1.798961 2.121305 39.23 32.98 pivot_point+volume_profile volume_profile 1.777774 2.100117 1.406108 1.406108
229 CVS 2025-05-05 long added 1.677853 2.460807 49.12 29.46 pivot_point+volume_profile volume_profile -1.038343 -1.038343 -1.0 -1.0
230 EBAY 2025-05-05 long added 1.690447 2.034162 38.11 34.03 pivot_point+volume_profile volume_profile 1.65223 1.995945 1.926892 1.926892
231 EXC 2025-05-05 long removed 2.478172 1.852594 20.3 21.45 pivot_point+volume_profile volume_profile -1.144068 -1.144068 -1.085003 -1.085003
232 FICO 2025-05-05 long added 1.960447 2.3875 25.97 30.15 pivot_point volume_profile -1.036717 -1.036717 -1.0 -1.0
233 GDDY 2025-05-05 long retained 2.20301 2.176304 28.66 32.35 pivot_point+volume_profile volume_profile -0.035309 -0.035309 -0.402143 -0.402143
234 GEN 2025-05-05 long retained 2.047797 2.756485 43.86 26.99 pivot_point+volume_profile volume_profile 2.002008 2.710697 3.55366 3.55366
235 IBKR 2025-05-05 long removed 2.42902 1.979107 22.75 34.73 pivot_point+volume_profile volume_profile 2.40025 1.950336 2.291226 2.291226
236 MAA 2025-05-05 long removed 2.985414 1.645458 35.38 39.66 pivot_point+volume_profile volume_profile -1.048771 -1.048771 -1.0 -1.0
237 MMM 2025-05-05 long removed 5.279289 1.693847 27.36 38.86 pivot_point+volume_profile volume_profile -0.038725 1.655122 0.193898 0.193898
238 PODD 2025-05-05 long removed 2.035338 1.903346 44.02 35.74 pivot_point+volume_profile volume_profile 2.002335 1.870343 2.907644 2.907644
239 SBAC 2025-05-05 long removed 8.952552 1.863751 25.14 36.29 pivot_point+volume_profile volume_profile -1.046235 -1.046235 -1.0 -1.0
240 TPR 2025-05-05 long added 1.840942 2.074077 28.02 33.54 pivot_point+volume_profile volume_profile 1.808779 2.041914 2.077916 2.077916
241 VST 2025-05-05 long removed 2.036511 1.522944 34.0 41.82 pivot_point+volume_profile volume_profile 2.013574 1.500006 3.09245 3.09245
242 XEL 2025-05-05 long removed 2.229859 1.692521 25.96 38.88 pivot_point+volume_profile volume_profile -1.053867 -1.053867 -1.0 -1.0
243 CBRE 2025-05-12 long retained 2.072185 2.07875 25.97 33.49 pivot_point+volume_profile volume_profile -1.042494 -1.042494 -1.0 -1.0
244 CHTR 2025-05-12 long retained 2.190034 2.816722 42.2 26.54 pivot_point+volume_profile volume_profile -1.041502 -1.041502 -1.0 -1.0
245 CVNA 2025-05-12 long added 1.932575 2.069082 35.54 33.6 pivot_point+volume_profile volume_profile 1.909703 2.04621 1.478573 1.478573
246 EBAY 2025-05-12 long removed 2.158189 1.865706 31.76 36.26 pivot_point+volume_profile volume_profile 2.117684 1.825201 1.569592 1.569592
247 FFIV 2025-05-12 long retained 2.168648 2.182334 25.84 32.29 pivot_point+volume_profile volume_profile -0.043699 -0.043699 1.095796 1.095796
248 FICO 2025-05-12 long added 1.700282 2.177095 29.76 32.34 pivot_point volume_profile -1.041973 -1.041973 -1.0 -1.0
249 GDDY 2025-05-12 long removed 2.020186 1.989107 30.81 34.6 pivot_point+volume_profile volume_profile -1.042178 -1.042178 -1.0 -1.0
250 GLW 2025-05-12 long added 1.836647 2.081473 30.28 33.45 pivot_point+volume_profile volume_profile 1.794151 2.038977 1.999502 1.999502
251 IBKR 2025-05-12 long added 1.896594 2.218736 28.43 31.88 pivot_point+volume_profile volume_profile -0.034383 -0.034383 1.028484 1.028484
252 IP 2025-05-12 long removed 2.044365 1.784897 43.91 37.44 pivot_point+volume_profile volume_profile -1.030962 -1.030962 -1.0 -1.0
253 KMI 2025-05-12 long removed 2.392809 1.721495 20.5 38.42 volume_profile volume_profile -0.044531 -0.044531 0.815295 0.815295
254 LITE 2025-05-12 long added 1.998667 3.054193 44.48 24.94 pivot_point+volume_profile volume_profile 1.977419 3.032945 2.968097 2.968097
255 LYV 2025-05-12 long retained 2.131057 2.131057 23.47 32.87 pivot_point+volume_profile volume_profile -0.041161 -0.041161 0.783299 0.783299
256 MMM 2025-05-12 long retained 3.434618 2.991348 32.62 25.34 pivot_point+volume_profile volume_profile -1.043943 -1.043943 -1.0 -1.0
257 MSTR 2025-05-12 long retained 2.148607 2.148607 22.87 32.67 volume_profile volume_profile -1.024205 -1.024205 -1.0 -1.0
258 NVDA 2025-05-12 long removed 2.065139 1.589846 36.85 40.61 pivot_point volume_profile 2.034535 1.559242 3.895151 3.895151
259 SBUX 2025-05-12 long retained 2.738132 2.531213 37.13 28.82 pivot_point+volume_profile volume_profile -0.033023 -0.033023 1.046545 1.046545
260 TRGP 2025-05-12 long removed 2.362374 1.831072 40.4 36.76 pivot_point+volume_profile volume_profile -0.029157 -0.029157 0.377208 0.377208
261 UAL 2025-05-12 long retained 2.451227 2.649603 31.95 27.83 pivot_point+volume_profile volume_profile -1.022953 -1.022953 -1.0 -1.0
262 VST 2025-05-12 long removed 2.151812 1.71222 25.63 38.56 pivot_point+volume_profile volume_profile 2.128723 1.689131 3.179119 3.179119
263 BKR 2025-05-19 long removed 2.61815 1.744687 23.08 23.06 pivot_point+volume_profile volume_profile -1.041425 -1.041425 -1.0 -1.0
264 CBRE 2025-05-19 long added 1.697692 2.018194 44.4 34.23 pivot_point+volume_profile volume_profile -1.04777 -1.04777 -1.0 -1.0
265 CCL 2025-05-19 long removed 2.213689 1.88525 35.94 35.99 pivot_point volume_profile -1.035223 -1.035223 -1.0 -1.0
266 CHTR 2025-05-19 long removed 2.534289 1.918738 38.8 35.53 pivot_point volume_profile -1.041697 -1.041697 -1.0 -1.0
267 CIEN 2025-05-19 long added 1.711175 2.748682 31.18 27.05 pivot_point+volume_profile volume_profile -1.035445 -1.035445 -1.0 -1.0
268 DASH 2025-05-19 long added 1.948308 2.354426 27.54 30.47 pivot_point+volume_profile volume_profile 1.914409 2.320527 3.07001 3.07001
269 EXPE 2025-05-19 long added 1.628529 2.673372 49.94 27.63 pivot_point volume_profile -0.030995 -0.030995 0.516934 0.516934
270 FFIV 2025-05-19 long retained 2.044528 2.060502 27.3 33.71 pivot_point+volume_profile volume_profile -0.051978 -0.051978 0.947872 0.947872
271 GDDY 2025-05-19 long retained 2.043438 2.006004 30.52 34.39 pivot_point+volume_profile volume_profile -1.051582 -1.051582 -1.0 -1.0
272 GLW 2025-05-19 long removed 2.537345 1.747245 20.57 38.02 pivot_point+volume_profile volume_profile -0.048875 1.69837 2.330541 2.330541
273 IBKR 2025-05-19 long retained 2.295831 2.295831 23.07 31.07 pivot_point+volume_profile volume_profile -1.039941 -1.039941 -1.0 -1.0
274 IP 2025-05-19 long removed 2.023784 1.657927 44.16 39.45 pivot_point+volume_profile volume_profile -1.153729 -1.153729 -1.117559 -1.117559
275 KMI 2025-05-19 long retained 2.104627 2.104627 23.58 33.18 volume_profile volume_profile -0.052501 -0.052501 0.468757 0.468757
276 NVDA 2025-05-19 long retained 2.272964 2.440243 22.3 29.65 pivot_point+volume_profile volume_profile 2.237626 2.404906 2.825566 2.825566
277 UAL 2025-05-19 long added 1.512611 2.803624 34.81 26.64 pivot_point+volume_profile volume_profile -1.024287 -1.024287 -1.0 -1.0
278 HOOD 2025-05-23 long removed 2.422622 1.637018 20.01 39.8 volume_profile volume_profile 2.397235 1.611631 6.303564 6.303564
279 ADSK 2025-05-27 long retained 2.568575 2.143189 25.1 32.73 pivot_point+volume_profile volume_profile -1.060214 -1.060214 -1.0 -1.0
280 CCL 2025-05-27 long removed 2.020875 1.697979 38.2 38.79 pivot_point volume_profile -1.03494 -1.03494 -1.0 -1.0
281 CHTR 2025-05-27 long retained 2.369859 2.594641 40.32 28.28 pivot_point+volume_profile volume_profile -1.046352 -1.046352 -1.0 -1.0
282 COHR 2025-05-27 long retained 2.170188 2.896921 38.02 25.97 pivot_point+volume_profile volume_profile -1.027459 -1.027459 -1.0 -1.0
283 CVNA 2025-05-27 long removed 2.294516 1.643299 22.28 39.69 pivot_point+volume_profile volume_profile -1.028604 1.614695 -1.0 -1.0
284 DASH 2025-05-27 long added 1.752993 2.166633 30.33 32.46 pivot_point+volume_profile volume_profile 1.718003 2.131643 2.847653 2.847653
285 EBAY 2025-05-27 long added 1.535804 2.010702 40.78 34.33 pivot_point+volume_profile volume_profile 1.481401 1.956299 1.794901 1.794901
286 FFIV 2025-05-27 long retained 2.182513 2.200268 25.68 32.09 pivot_point+volume_profile volume_profile -0.057954 -0.057954 1.344759 1.344759
287 FIX 2025-05-27 long retained 2.155536 2.155536 22.99 32.59 volume_profile volume_profile 2.117869 2.117869 1.876737 1.876737
288 KMI 2025-05-27 long retained 2.146558 2.146558 23.09 32.69 volume_profile volume_profile -1.058895 -1.058895 -1.0 -1.0
289 LITE 2025-05-27 long removed 2.151883 1.901772 42.63 35.76 pivot_point+volume_profile volume_profile -1.028929 -1.028929 -1.0 -1.0
290 MMM 2025-05-27 long removed 4.201405 1.643381 29.6 39.69 pivot_point+volume_profile volume_profile -1.051608 -1.051608 -1.0 -1.0
291 NVDA 2025-05-27 long retained 2.432299 2.610589 20.72 28.15 pivot_point+volume_profile volume_profile 2.394655 2.572945 3.972802 3.972802
292 VST 2025-05-27 long retained 2.098277 2.098277 23.45 33.25 volume_profile volume_profile 2.067344 2.067344 3.012884 3.012884
293 KVUE 2025-05-30 long removed 2.050369 0.768132 28.83 47.13 pivot_point+volume_profile volume_profile -1.057804 -1.057804 -1.0 -1.0
294 HOOD 2025-06-02 long retained 2.309116 2.309116 21.13 30.93 volume_profile volume_profile 2.280974 2.280974 7.300464 7.300464
295 ADSK 2025-06-03 long added 1.692598 2.410998 45.68 29.92 pivot_point+volume_profile volume_profile 1.627602 -1.064996 -1.0 -1.0
296 AON 2025-06-03 long retained 2.431709 2.105846 22.53 33.16 pivot_point+volume_profile volume_profile -1.065022 -1.065022 -1.0 -1.0
297 CIEN 2025-06-03 long removed 2.142464 1.785979 24.54 37.43 pivot_point+volume_profile volume_profile -1.646023 -1.646023 -1.604345 -1.604345
298 EXPE 2025-06-03 long added 1.567645 2.794189 51.0 26.71 pivot_point volume_profile 1.530571 -0.037074 1.479698 1.479698
299 IBKR 2025-06-03 long removed 2.607373 1.605808 20.17 40.33 pivot_point+volume_profile volume_profile -1.045192 -1.045192 -1.0 -1.0
300 LH 2025-06-03 long removed 2.572651 1.68287 23.47 24.04 pivot_point+volume_profile volume_profile -0.062688 1.620182 -0.409144 -0.409144
301 LITE 2025-06-03 long retained 2.233028 2.651965 41.73 27.81 pivot_point+volume_profile volume_profile 2.205013 2.62395 4.507942 4.507942
302 MMM 2025-06-03 long added 1.551705 2.053951 33.49 33.79 pivot_point+volume_profile volume_profile -1.055926 -1.055926 -1.0 -1.0
303 MSTR 2025-06-03 long added 1.02871 2.142162 48.56 32.74 pivot_point+volume_profile volume_profile 1.002307 2.115759 2.177754 2.177754
304 TRGP 2025-06-03 long removed 3.733429 1.741375 31.48 38.11 pivot_point+volume_profile volume_profile -0.043686 -0.043686 0.205279 0.205279
305 UAL 2025-06-03 long retained 2.628249 2.877458 30.4 26.11 pivot_point+volume_profile volume_profile -1.479331 -1.479331 -1.449594 -1.449594
306 XEL 2025-06-03 long removed 2.225926 1.355463 26.81 30.16 pivot_point+volume_profile volume_profile -1.064194 -1.064194 -1.0 -1.0
307 APP 2025-06-10 long removed 2.343753 1.558826 20.98 41.16 volume_profile volume_profile -1.023341 -1.023341 -1.0 -1.0
308 CBRE 2025-06-10 long removed 2.125347 1.829022 26.34 36.79 pivot_point+volume_profile volume_profile 2.069269 1.772945 2.477813 2.477813
309 CHTR 2025-06-10 long removed 2.977021 1.341616 35.43 45.46 pivot_point+volume_profile volume_profile -1.052197 -1.052197 -1.0 -1.0
310 DASH 2025-06-10 long added 1.013584 2.53042 46.2 28.83 pivot_point+volume_profile volume_profile 0.971883 2.488718 2.785659 2.785659
311 FIX 2025-06-10 long retained 2.108728 2.108728 23.53 33.13 volume_profile volume_profile 2.070098 2.070098 2.975038 2.975038
312 LITE 2025-06-10 long removed 2.14058 1.840276 42.76 36.63 pivot_point+volume_profile volume_profile 2.11155 1.811246 3.699571 3.699571
313 LYV 2025-06-10 long retained 2.026039 2.026039 24.73 34.13 pivot_point+volume_profile volume_profile -1.049761 -1.049761 -1.0 -1.0
314 DASH 2025-06-17 long retained 2.272546 2.272546 21.71 31.31 volume_profile volume_profile 2.226528 2.226528 3.236531 3.236531
315 LITE 2025-06-17 long removed 2.35311 1.666804 36.49 39.3 pivot_point+volume_profile volume_profile 2.323386 1.63708 4.085339 4.085339
316 SYF 2025-06-17 long removed 2.052238 1.13273 28.81 35.47 pivot_point+volume_profile volume_profile 2.004993 1.085485 3.679755 3.679755
317 TPR 2025-06-17 long removed 2.183713 1.746025 22.27 38.03 pivot_point volume_profile 2.139034 1.701347 6.821065 6.821065
318 TRGP 2025-06-17 long retained 3.056467 2.267724 34.92 31.36 pivot_point+volume_profile volume_profile -1.044187 -1.044187 -1.0 -1.0
319 CBRE 2025-06-25 long retained 2.179995 2.18858 24.11 32.22 pivot_point+volume_profile volume_profile 2.123366 2.131951 4.007789 4.007789
320 CVNA 2025-06-25 long removed 2.240201 1.56613 22.45 41.03 pivot_point+volume_profile volume_profile 2.210323 1.536252 1.987084 1.987084
321 FIX 2025-06-25 long removed 2.04309 1.498263 24.12 42.28 pivot_point volume_profile 1.997489 1.452662 8.392007 8.392007
322 LITE 2025-06-25 long added 1.526208 2.14951 47.36 32.66 pivot_point+volume_profile volume_profile 1.492238 2.11554 3.583195 3.583195
323 MOS 2025-06-25 long removed 2.178066 1.800481 27.33 22.21 pivot_point volume_profile -2.542438 1.750255 -2.492212 -2.492212
324 MSTR 2025-06-25 long removed 2.443067 1.525476 20.22 41.77 pivot_point+volume_profile volume_profile 2.40932 1.491729 0.579133 0.579133
325 SOLV 2025-06-25 long removed 2.051014 1.621831 29.02 40.06 pivot_point+volume_profile volume_profile -1.058064 -1.058064 -1.0 -1.0
326 SYF 2025-06-25 long added 2.006152 34.38 volume_profile 1.952892 1.443658
327 TRGP 2025-06-25 long retained 2.870589 2.102316 36.16 33.21 pivot_point+volume_profile volume_profile -1.043253 -1.043253 -1.0 -1.0
328 VRT 2025-06-25 long added 2.179303 32.32 volume_profile -1.033791 -1.0
329 CF 2025-07-02 long added 1.72572 2.203327 48.35 32.05 pivot_point+volume_profile volume_profile -1.04834 -1.04834 -1.0 -1.0
330 EXPE 2025-07-02 long added 2.249806 31.55 volume_profile 2.202485 5.007195
331 MOS 2025-07-02 long added 2.12156 32.98 volume_profile -1.052618 -1.0
332 NEM 2025-07-02 long retained 2.051223 2.10406 38.42 33.18 pivot_point+volume_profile volume_profile -1.051019 -1.051019 -1.0 -1.0
333 CF 2025-07-10 long removed 2.079072 1.709908 43.48 38.6 pivot_point+volume_profile volume_profile -1.04928 -1.04928 -1.0 -1.0
334 CIEN 2025-07-10 long removed 2.074956 1.510562 31.53 42.05 pivot_point+volume_profile volume_profile 2.032894 1.4685 2.388612 2.388612
335 EXPE 2025-07-10 long added 1.731643 2.279927 33.66 31.23 pivot_point volume_profile -1.046783 -1.046783 -1.0 -1.0
336 LITE 2025-07-10 long retained 2.071854 2.071854 30.17 33.57 pivot_point+volume_profile volume_profile 2.036198 2.036198 4.775516 4.775516
337 MOS 2025-07-10 long removed 2.030196 1.394129 29.08 29.35 pivot_point volume_profile -2.731686 -2.731686 -2.683345 -2.683345
338 MSTR 2025-07-10 long retained 2.254602 2.254602 21.7 31.5 volume_profile volume_profile -1.03455 -1.03455 -1.0 -1.0
339 UAL 2025-07-10 long removed 2.166151 1.597686 28.47 40.47 pivot_point+volume_profile volume_profile -1.094723 -1.094723 -1.063476 -1.063476
340 WBD 2025-07-11 long added 1.689693 2.64646 48.93 27.85 pivot_point volume_profile 1.652945 2.609712 -1.0 -1.0
341 APP 2025-07-17 long removed 2.361064 1.639012 20.81 39.76 pivot_point+volume_profile volume_profile 2.334722 1.612669 4.343765 4.343765
342 CIEN 2025-07-17 long added 1.862108 2.321977 27.72 30.8 pivot_point+volume_profile volume_profile 1.817051 2.27692 3.474519 3.474519
343 DASH 2025-07-17 long removed 2.259481 1.710911 21.45 38.59 pivot_point volume_profile 2.211705 1.663136 1.251207 1.251207
344 MSTR 2025-07-17 long retained 2.161028 2.161028 22.72 32.52 volume_profile volume_profile -1.037565 -1.037565 -1.0 -1.0
345 SBUX 2025-07-17 long removed 2.694036 1.637838 22.47 24.78 pivot_point+volume_profile volume_profile -1.054383 1.583456 -1.0 -1.0
346 TSLA 2025-07-17 long added 1.693777 2.98586 48.86 25.37 pivot_point+volume_profile volume_profile -1.030383 -1.030383 -1.0 -1.0
347 UAL 2025-07-17 long removed 2.242615 1.674207 27.63 39.18 pivot_point+volume_profile volume_profile -1.03109 -1.03109 -1.0 -1.0
348 WBD 2025-07-18 long added 1.69778 2.631175 48.8 27.98 pivot_point volume_profile 1.6586 -1.03918 -1.0 -1.0
349 CF 2025-07-24 long retained 2.027676 2.561976 44.11 28.56 pivot_point+volume_profile volume_profile -1.053798 -1.053798 -1.0 -1.0
350 CIEN 2025-07-24 long removed 2.121631 1.775805 23.18 37.58 pivot_point volume_profile 2.072951 1.727125 8.235278 8.235278
351 NEM 2025-07-24 long retained 2.370375 2.370375 22.52 30.32 volume_profile volume_profile 2.324462 2.324462 5.471272 5.471272
352 TMUS 2025-07-24 long retained 2.008083 2.000731 39.96 34.45 pivot_point+volume_profile volume_profile -1.062418 -1.062418 -1.0 -1.0
353 UAL 2025-07-24 long added 1.895682 2.090516 34.45 33.35 pivot_point+volume_profile volume_profile -1.033158 -1.033158 -1.0 -1.0
354 WBD 2025-07-25 long removed 2.007694 1.558868 44.36 41.16 pivot_point volume_profile -1.043248 -1.043248 -1.0 -1.0
355 AXON 2025-07-31 long removed 2.0208 1.515251 24.4 41.96 pivot_point volume_profile 1.979936 1.474387 -1.0 -1.0
356 NEM 2025-07-31 long added 1.550636 2.927566 40.31 25.76 pivot_point+volume_profile volume_profile 1.5084 2.88533 5.832143 5.832143
357 NVDA 2025-07-31 long added 2.187503 32.23 volume_profile -1.057851 -1.0
358 UAL 2025-07-31 long removed 2.346512 1.699916 29.15 38.76 pivot_point+volume_profile volume_profile -1.035483 -1.035483 -1.0 -1.0
359 APP 2025-08-07 long retained 2.2387 2.2387 21.87 31.67 volume_profile volume_profile -1.02614 -1.02614 -1.0 -1.0
360 PODD 2025-08-07 long added 1.665084 2.014836 49.33 34.27 pivot_point+volume_profile volume_profile 1.61996 1.969712 2.028378 2.028378
361 TSLA 2025-08-07 long added 2.112593 33.08 volume_profile 2.07904 5.403464
362 ULTA 2025-08-07 long added 1.994095 2.160126 38.54 32.54 pivot_point+volume_profile volume_profile -1.061824 -1.061824 -1.0 -1.0
363 NVDA 2025-08-14 long removed 2.160695 1.442764 22.53 43.36 pivot_point volume_profile -1.056467 -1.056467 -1.0 -1.0
364 TSLA 2025-08-14 long added 1.98307 2.435478 43.28 29.69 pivot_point+volume_profile volume_profile -1.03507 -1.03507 -1.0 -1.0
365 UAL 2025-08-14 long added 1.895586 2.046469 30.65 33.88 pivot_point+volume_profile volume_profile 1.856758 2.007641 -0.240865 -0.240865
366 WYNN 2025-08-14 long removed 2.010866 1.572488 25.12 40.92 pivot_point+volume_profile volume_profile 1.957667 1.519288 4.200848 4.200848
367 WBD 2025-08-15 long removed 2.585147 0.67591 23.36 50.91 pivot_point volume_profile 2.554721 0.645484 9.005713 9.005713
368 AXON 2025-08-21 long retained 2.129759 2.129759 23.28 32.88 volume_profile volume_profile -1.031785 -1.031785 -1.0 -1.0
369 UAL 2025-08-21 long removed 2.369461 1.656179 25.13 39.48 pivot_point+volume_profile volume_profile 2.329877 1.616595 -0.37777 -0.37777
370 WSM 2025-08-21 long added 1.667664 2.310824 32.89 30.91 pivot_point+volume_profile volume_profile -1.04916 -1.04916 -1.0 -1.0
371 AXON 2025-08-28 long retained 2.008032 2.008032 24.76 34.36 volume_profile volume_profile -1.187978 -1.187978 -1.150564 -1.150564
372 DLTR 2025-08-28 long removed 2.61381 0.922035 23.12 41.66 pivot_point+volume_profile volume_profile -1.060616 -1.060616 -1.0 -1.0
373 MOS 2025-08-28 long removed 2.660654 0.871184 22.74 43.36 pivot_point+volume_profile volume_profile -1.143849 -1.143849 -1.095243 -1.095243
374 NEM 2025-08-28 long added 2.558169 28.59 volume_profile 2.498731 4.956524
375 NFLX 2025-08-28 long retained 2.015068 2.015068 24.47 34.27 volume_profile volume_profile -1.061855 -1.061855 -1.0 -1.0
376 TRMB 2025-08-28 long removed 2.444595 1.624024 24.61 25.02 pivot_point+volume_profile volume_profile -1.061855 -1.061855 -1.0 -1.0
377 TSLA 2025-08-28 long added 1.505925 3.036081 50.54 25.05 pivot_point+volume_profile volume_profile -1.037822 -1.037822 -1.0 -1.0
378 ULTA 2025-08-28 long added 1.433885 2.155983 39.14 32.58 pivot_point+volume_profile volume_profile -1.061037 -1.061037 -1.0 -1.0
379 NEM 2025-09-05 long added 1.519842 2.224862 32.88 31.82 pivot_point volume_profile 1.462674 2.167693 5.478938 5.478938
380 TSLA 2025-09-05 long removed 2.734901 1.679832 24.55 39.09 pivot_point+volume_profile volume_profile 2.697302 1.642233 4.740624 4.740624
381 COIN 2025-09-12 long retained 2.013059 2.324843 27.1 30.77 pivot_point+volume_profile volume_profile 1.9826 2.294384 1.481272 1.481272
382 CRWD 2025-09-12 long added 1.902125 2.491779 34.36 29.18 pivot_point+volume_profile volume_profile 1.858615 2.448269 4.550534 4.550534
383 EXE 2025-09-12 long removed 2.819644 1.77956 21.52 22.52 pivot_point+volume_profile volume_profile 2.76661 1.726526 2.080664 2.080664
384 NEM 2025-09-12 long retained 2.090577 2.090577 23.54 33.34 pivot_point+volume_profile volume_profile 2.032407 2.032407 1.512065 1.512065
385 PWR 2025-09-12 long retained 2.222758 2.222758 22.64 31.84 pivot_point+volume_profile volume_profile 2.172589 2.172589 3.829571 3.829571
386 SMCI 2025-09-12 long retained 2.923759 2.911473 20.19 25.87 pivot_point+volume_profile volume_profile 2.895037 2.882751 1.049944 1.049944
387 TSLA 2025-09-12 long retained 2.13463 2.145752 24.03 32.7 pivot_point+volume_profile volume_profile 2.096238 2.10736 1.831651 1.831651
388 CEG 2025-09-18 long removed 2.066235 1.591977 23.84 40.57 pivot_point volume_profile 2.027344 1.553086 3.6016 3.6016
389 COIN 2025-09-19 long retained 2.267824 2.267824 22.36 31.36 volume_profile volume_profile -1.030729 -1.030729 -1.0 -1.0
390 CVS 2025-09-19 long removed 2.286544 1.63835 41.16 39.78 pivot_point+volume_profile volume_profile 2.227776 1.579582 1.266816 1.266816
391 EXE 2025-09-19 long retained 2.161204 2.156746 42.52 32.57 pivot_point+volume_profile volume_profile 2.107659 2.103202 1.307349 1.307349
392 MSTR 2025-09-19 long added 1.664773 2.275601 49.33 31.28 pivot_point volume_profile -1.247909 -1.247909 -1.218063 -1.218063
393 NFLX 2025-09-19 long retained 2.035308 2.035308 24.22 34.02 volume_profile volume_profile -1.058942 -1.058942 -1.0 -1.0
394 SMCI 2025-09-19 long retained 2.927459 2.913963 20.16 25.85 pivot_point+volume_profile volume_profile 2.895343 2.881848 2.155739 2.155739
395 TRMB 2025-09-19 long added 2.441876 29.63 volume_profile -1.061322 -1.0
396 TSLA 2025-09-19 long retained 2.359784 2.359784 20.62 30.42 volume_profile volume_profile -0.038083 -0.038083 1.362642 1.362642
397 WBD 2025-09-22 long added 1.899743 2.758206 26.59 26.97 pivot_point+volume_profile volume_profile -1.025516 -1.025516 -1.0 -1.0
398 CAH 2025-09-26 long retained 2.096286 2.096286 26.08 33.28 pivot_point+volume_profile volume_profile 2.041041 2.041041 8.978022 8.978022
399 CVS 2025-09-26 long added 1.894977 2.553957 45.86 28.63 pivot_point+volume_profile volume_profile 1.837314 2.496294 1.225266 1.225266
400 EQT 2025-09-26 long retained 2.019186 2.441163 26.62 29.64 pivot_point+volume_profile volume_profile -1.045293 -1.045293 -1.0 -1.0
401 SMCI 2025-09-26 long retained 2.71762 2.705077 21.49 27.38 pivot_point+volume_profile volume_profile 2.687765 2.675222 -1.0 -1.0
402 WBD 2025-09-29 long added 0.868167 2.816363 49.47 26.54 pivot_point volume_profile -1.02696 -1.02696 -1.0 -1.0
403 COIN 2025-10-03 long retained 2.365012 2.365012 20.57 30.37 volume_profile volume_profile -1.033105 -1.033105 -1.0 -1.0
404 CVS 2025-10-03 long removed 2.295793 1.839922 41.07 36.63 pivot_point volume_profile 2.238669 1.782798 0.117948 0.117948
405 SMCI 2025-10-03 long retained 2.211226 2.420371 25.37 29.84 pivot_point+volume_profile volume_profile -1.030886 -1.030886 -1.0 -1.0
406 WBD 2025-10-06 long added 0.82203 3.063296 51.11 24.88 pivot_point volume_profile -1.031369 -1.031369 -1.0 -1.0
407 NFLX 2025-10-10 long retained 2.215083 2.215083 22.12 31.92 volume_profile volume_profile -1.058985 -1.058985 -1.0 -1.0
408 DG 2025-10-17 long removed 2.231381 1.14135 41.75 50.24 pivot_point volume_profile -1.050929 -1.050929 -1.0 -1.0
409 EQT 2025-10-17 long added 1.678733 2.031022 31.5 34.07 pivot_point+volume_profile volume_profile -1.037827 -1.037827 -1.0 -1.0
410 INTC 2025-10-17 long added 1.778496 2.790407 41.94 26.73 pivot_point+volume_profile volume_profile 1.751902 -1.026595 -1.0 -1.0
411 KR 2025-10-17 long retained 2.129798 2.323374 27.88 30.78 pivot_point+volume_profile volume_profile -1.079151 -1.079151 -1.014635 -1.014635
412 STX 2025-10-17 long retained 2.174568 2.174568 22.77 32.37 volume_profile volume_profile -1.028836 -1.028836 -1.0 -1.0
413 AXON 2025-10-24 long added 1.76636 2.241583 30.12 31.64 pivot_point+volume_profile volume_profile -4.337363 -4.337363 -4.300584 -4.300584
414 COIN 2025-10-24 long retained 2.105666 2.105666 23.57 33.17 volume_profile volume_profile -1.025214 -1.025214 -1.0 -1.0
415 DLTR 2025-10-24 long retained 2.80395 2.292387 36.63 31.1 pivot_point volume_profile 2.762807 2.251244 4.41966 4.41966
416 FSLR 2025-10-24 long retained 2.30447 2.30447 22.18 30.98 volume_profile volume_profile 2.272186 2.272186 0.96756 0.96756
417 WBD 2025-10-27 long retained 2.101189 2.101189 24.02 33.22 volume_profile volume_profile 2.069718 2.069718 5.399746 5.399746
418 DLTR 2025-10-31 long retained 2.786721 2.275651 36.76 31.28 pivot_point volume_profile 2.745588 2.234518 6.650067 6.650067
419 WBD 2025-11-03 long retained 2.085838 2.085838 24.2 33.4 volume_profile volume_profile 2.050108 2.050108 5.297748 5.297748
420 APP 2025-11-07 long retained 2.123208 2.123208 23.16 32.96 volume_profile volume_profile -1.024541 -1.024541 -1.0 -1.0
421 APTV 2025-11-07 long retained 2.072254 2.042395 43.57 33.93 pivot_point+volume_profile volume_profile -1.229377 -1.229377 -1.180253 -1.180253
422 DLTR 2025-11-07 long removed 2.012478 1.9107 44.3 35.64 pivot_point+volume_profile volume_profile -1.039683 -1.039683 -1.0 -1.0
423 WSM 2025-11-07 long removed 2.170939 1.955635 22.61 35.04 pivot_point volume_profile -1.040974 -1.040974 -1.0 -1.0
424 DG 2025-11-14 long removed 2.670283 1.541305 22.66 26.48 pivot_point volume_profile -1.05203 -1.05203 -1.0 -1.0
425 DLTR 2025-11-14 long added 1.552266 2.22325 51.28 31.83 pivot_point volume_profile -1.041689 -1.041689 -1.0 -1.0
426 DLTR 2025-11-21 long removed 2.793575 1.57706 21.71 25.84 pivot_point+volume_profile volume_profile 2.75473 1.538215 5.673028 5.673028
427 CVS 2025-12-01 long removed 2.300687 1.241893 26.02 32.73 pivot_point+volume_profile volume_profile -1.058096 -1.058096 -1.0 -1.0
428 DG 2025-12-01 long retained 2.506071 2.506071 35.05 29.05 pivot_point+volume_profile volume_profile 2.455923 2.455923 9.542155 9.542155
429 DLTR 2025-12-01 long retained 2.303197 2.220009 40.99 31.87 pivot_point+volume_profile volume_profile 2.262084 2.178895 5.686814 5.686814
430 FSLR 2025-12-01 long retained 2.073126 2.073126 23.75 33.55 volume_profile volume_profile -1.029492 -1.029492 -1.0 -1.0
431 KR 2025-12-01 long added 1.843739 2.203724 46.58 32.05 pivot_point+volume_profile volume_profile -2.012949 -2.012949 -1.947183 -1.947183
432 BA 2025-12-08 long added 1.583825 2.232845 50.72 31.73 pivot_point+volume_profile volume_profile 1.534769 2.183788 5.367771 5.367771
433 DG 2025-12-08 long added 1.912045 2.232293 45.62 31.74 pivot_point+volume_profile volume_profile 1.876424 2.196672 2.913693 2.913693
434 F 2025-12-08 long removed 2.047935 2.245957 28.86 16.59 pivot_point+volume_profile volume_profile 1.986783 2.184805 1.326353 1.326353
435 INTC 2025-12-08 long added 1.892965 2.697443 45.88 27.44 pivot_point+volume_profile volume_profile -1.025599 -1.025599 -1.0 -1.0
436 MPWR 2025-12-08 long retained 2.044393 2.044393 24.31 33.91 pivot_point+volume_profile volume_profile -1.033722 -1.033722 -1.0 -1.0
437 CVS 2025-12-15 long added 1.866009 2.030843 46.26 34.07 pivot_point+volume_profile volume_profile -1.466298 -1.466298 -1.413892 -1.413892
438 DG 2025-12-15 long retained 2.542671 2.503657 27.32 29.07 pivot_point+volume_profile volume_profile 2.502824 2.46381 1.326439 1.326439
439 DLTR 2025-12-15 long retained 2.748539 2.992958 37.05 25.33 pivot_point+volume_profile volume_profile -1.03973 -1.03973 -1.0 -1.0
440 F 2025-12-15 long removed 2.281559 1.05922 41.21 52.48 pivot_point+volume_profile volume_profile -1.063525 -1.063525 -1.0 -1.0
441 PM 2025-12-15 long removed 2.502584 1.899821 29.28 35.79 pivot_point+volume_profile volume_profile 2.443037 1.840274 3.66131 3.66131
442 ALB 2025-12-22 long retained 2.72812 2.697474 31.8 27.44 pivot_point+volume_profile volume_profile 2.697029 2.666382 1.186944 1.186944
443 CVS 2025-12-22 long removed 2.484968 1.462176 24.24 27.98 pivot_point+volume_profile volume_profile -1.109179 1.40658 -1.053584 -1.053584
444 DG 2025-12-22 long retained 2.083862 2.040028 32.03 33.96 pivot_point+volume_profile volume_profile 2.037546 1.993712 1.242769 1.242769
445 DLTR 2025-12-22 long removed 2.826398 1.690429 21.47 23.91 pivot_point volume_profile 2.788535 1.652567 -0.44666 -0.44666
446 EBAY 2025-12-22 long removed 2.169577 1.859804 27.63 36.35 pivot_point volume_profile 2.115095 1.805322 0.81724 0.81724
447 F 2025-12-22 long removed 2.77107 1.527462 21.88 26.74 pivot_point+volume_profile volume_profile -0.063731 1.463731 0.61553 0.61553
448 HAS 2025-12-22 long removed 2.964001 1.319322 20.52 30.95 pivot_point+volume_profile volume_profile 2.905396 1.260717 4.983114 4.983114
449 MPWR 2025-12-22 long added 1.682915 2.036712 29.44 34.0 pivot_point+volume_profile volume_profile 1.649894 2.003691 3.682887 3.682887
450 NVDA 2025-12-22 long added 0.816308 2.602582 51.52 28.21 pivot_point volume_profile 0.775562 -1.040747 -1.0 -1.0
451 ALB 2025-12-30 long removed 8.749341 1.607977 25.17 40.29 pivot_point volume_profile -0.031848 1.576129 1.897265 1.897265
452 DG 2025-12-30 long retained 2.61558 2.567207 26.5 28.51 pivot_point+volume_profile volume_profile 2.5651 2.516727 2.367546 2.367546
453 DLTR 2025-12-30 long removed 2.83934 1.593978 36.38 40.54 pivot_point volume_profile 2.797313 1.551951 -1.0 -1.0
454 EBAY 2025-12-30 long retained 2.183133 2.060311 31.68 33.71 pivot_point+volume_profile volume_profile 2.122264 1.999441 -1.0 -1.0
455 ALB 2026-01-07 long retained 2.828773 2.393536 36.45 30.09 pivot_point volume_profile 2.795567 2.36033 0.703918 0.703918
456 CVS 2026-01-07 long retained 2.269166 2.473208 41.34 29.34 pivot_point+volume_profile volume_profile -1.855864 -1.855864 -1.790911 -1.790911
457 DG 2026-01-07 long retained 2.447888 2.677215 29.38 27.6 pivot_point+volume_profile volume_profile -0.048288 -0.048288 1.199549 1.199549
458 DLTR 2026-01-07 long added 1.507532 2.214072 52.11 31.93 pivot_point volume_profile 1.464498 -1.043034 -1.0 -1.0
459 HAS 2026-01-07 long removed 2.805479 1.686841 36.62 38.97 pivot_point+volume_profile volume_profile 2.740518 1.62188 5.389769 5.389769
460 INTC 2026-01-07 long added 1.877971 2.856229 46.09 26.26 pivot_point+volume_profile volume_profile 1.848168 2.826426 0.517338 0.517338
461 NOC 2026-01-07 long retained 2.002685 2.002685 25.23 34.43 volume_profile volume_profile 1.947249 1.947249 7.039869 7.039869
462 ALB 2026-01-14 long added 1.905518 2.113028 45.71 33.08 pivot_point+volume_profile volume_profile -1.146749 -1.146749 -1.11196 -1.11196
463 AMD 2026-01-14 long retained 2.061087 2.018284 25.3 34.23 pivot_point+volume_profile volume_profile 2.028316 1.985512 -1.0 -1.0
464 CVS 2026-01-14 long removed 2.438088 1.2733 24.67 31.99 pivot_point+volume_profile volume_profile -1.655824 1.209203 -1.591726 -1.591726
465 DG 2026-01-14 long added 1.993034 2.993141 44.55 25.33 pivot_point volume_profile -1.049482 -1.049482 -1.0 -1.0
466 EL 2026-01-14 long removed 5.505627 0.961465 27.02 55.39 pivot_point volume_profile -2.498226 -2.498226 -2.451114 -2.451114
467 ES 2026-01-14 long retained 2.158663 2.915613 42.55 25.84 pivot_point+volume_profile volume_profile -1.062037 -1.062037 -1.0 -1.0
468 MPWR 2026-01-14 long removed 2.353425 1.54253 20.88 41.46 pivot_point+volume_profile volume_profile 2.314598 1.503703 3.140984 3.140984
469 PM 2026-01-14 long removed 2.516942 1.521953 24.35 41.84 pivot_point+volume_profile volume_profile -1.066575 -1.066575 -1.0 -1.0
470 ALB 2026-01-22 long retained 2.66612 2.106006 37.69 33.16 pivot_point volume_profile -1.78932 -1.78932 -1.75766 -1.75766
471 BG 2026-01-22 long added 2.031162 34.07 volume_profile 1.974765 1.003692
472 COR 2026-01-22 long removed 2.369274 1.740334 20.53 38.12 pivot_point volume_profile -1.066621 -1.066621 -1.0 -1.0
473 CVS 2026-01-22 long retained 2.782609 2.259288 36.79 31.45 pivot_point+volume_profile volume_profile -2.563433 -2.563433 -2.506576 -2.506576
474 DG 2026-01-22 long removed 2.984281 0.811673 20.39 45.49 pivot_point volume_profile -0.046626 0.765046 0.275695 0.275695
475 EL 2026-01-22 long retained 2.222014 3.841923 27.85 21.05 pivot_point volume_profile -1.049674 -1.049674 -1.0 -1.0
476 EW 2026-01-22 long removed 2.009026 1.960466 29.35 19.97 pivot_point+volume_profile volume_profile -1.060973 -1.060973 -1.0 -1.0
477 HAS 2026-01-22 long retained 2.033706 2.088676 44.04 33.37 pivot_point+volume_profile volume_profile 1.97132 2.02629 2.017433 2.017433
478 HSY 2026-01-22 long removed 3.035643 1.089931 20.05 36.62 pivot_point+volume_profile volume_profile 2.980952 1.035241 4.925416 4.925416
479 NUE 2026-01-22 long retained 2.1872 2.156377 28.43 32.58 pivot_point+volume_profile volume_profile -1.254102 -1.254102 -1.198898 -1.198898
480 PM 2026-01-22 long removed 2.010398 1.552206 34.53 41.28 pivot_point+volume_profile volume_profile 1.950761 1.49257 -0.012275 -0.012275
481 AES 2026-01-29 long removed 2.745556 1.501027 37.07 42.23 pivot_point+volume_profile volume_profile 2.702953 1.458424 -1.0 -1.0
482 ALB 2026-01-29 long added 1.653071 2.58279 49.53 28.38 pivot_point volume_profile -1.079102 -1.079102 -1.050535 -1.050535
483 BIIB 2026-01-29 long removed 2.414377 0.794286 24.89 46.14 pivot_point+volume_profile volume_profile 2.367156 0.747065 0.719641 0.719641
484 CRL 2026-01-29 long removed 2.311041 1.831363 25.91 21.76 pivot_point volume_profile -1.04234 -1.04234 -1.0 -1.0
485 F 2026-01-29 long retained 2.223061 2.234271 38.44 31.72 pivot_point+volume_profile volume_profile -1.064059 -1.064059 -1.0 -1.0
486 HSY 2026-01-29 long retained 2.125632 2.186276 42.93 32.24 pivot_point+volume_profile volume_profile 2.068357 2.129002 3.990351 3.990351
487 ADM 2026-02-05 long added 1.849616 2.96559 46.49 25.51 pivot_point+volume_profile volume_profile 1.805728 -0.043888 0.248181 0.248181
488 BIIB 2026-02-05 long retained 2.2605 3.004306 41.44 25.26 pivot_point volume_profile -0.048519 -0.048519 -0.510424 -0.510424
489 DG 2026-02-05 long removed 2.78933 0.725195 21.74 48.84 pivot_point volume_profile -2.089286 0.680805 -2.044896 -2.044896
490 F 2026-02-05 long removed 2.105446 0.890739 28.17 42.7 pivot_point+volume_profile volume_profile 2.041993 0.827286 -1.0 -1.0
491 WELL 2026-02-05 long retained 2.414449 2.388452 21.29 30.14 pivot_point+volume_profile volume_profile 2.350069 2.324072 0.822192 0.822192
492 AES 2026-02-12 long added 1.819168 2.479199 46.93 29.29 pivot_point+volume_profile volume_profile 1.77782 -2.408627 -2.367279 -2.367279
493 BIIB 2026-02-12 long added 1.64856 2.768015 49.6 26.9 pivot_point+volume_profile volume_profile -1.041757 -1.041757 -1.0 -1.0
494 DHI 2026-02-12 long added 1.684037 2.107728 41.62 33.14 pivot_point+volume_profile volume_profile -1.039232 -1.039232 -1.0 -1.0
495 EL 2026-02-12 long removed 2.135451 1.855177 20.42 21.41 pivot_point volume_profile -1.151489 -1.151489 -1.125617 -1.125617
496 F 2026-02-12 long removed 2.536476 2.192144 23.78 17.18 pivot_point volume_profile -1.062851 -1.062851 -1.0 -1.0
497 HSY 2026-02-12 long retained 3.280295 3.005439 29.42 25.25 pivot_point+volume_profile volume_profile -1.050958 -1.050958 -1.0 -1.0
498 MRK 2026-02-12 long removed 2.301212 1.577912 41.01 40.82 pivot_point+volume_profile volume_profile -1.050697 -1.050697 -1.0 -1.0
499 ADM 2026-02-20 long removed 2.857416 1.214979 21.25 33.38 pivot_point+volume_profile volume_profile -0.047161 1.167818 1.910618 1.910618
500 AES 2026-02-20 long added 1.553838 2.274272 51.25 31.29 pivot_point+volume_profile volume_profile 1.507842 -3.061736 -3.01574 -3.01574
501 BIIB 2026-02-20 long removed 2.280691 1.964439 41.22 34.92 pivot_point+volume_profile volume_profile -1.046329 -1.046329 -1.0 -1.0
502 DG 2026-02-20 long added 1.807443 2.691872 47.11 27.49 pivot_point volume_profile -1.043669 -1.043669 -1.0 -1.0
503 DHI 2026-02-20 long added 1.86088 2.313992 38.93 30.88 pivot_point+volume_profile volume_profile -1.041837 -1.041837 -1.0 -1.0
504 DLTR 2026-02-20 long removed 2.168662 1.550498 42.44 41.31 pivot_point+volume_profile volume_profile -1.039397 -1.039397 -1.0 -1.0
505 F 2026-02-20 long removed 2.452913 2.116951 24.53 18.03 pivot_point volume_profile -1.061367 -1.061367 -1.0 -1.0
506 HSY 2026-02-20 long added 1.777447 2.741403 47.55 27.1 pivot_point+volume_profile volume_profile 1.727495 -1.049952 -1.0 -1.0
507 ADM 2026-02-27 long removed 2.405563 1.781489 39.98 37.49 pivot_point+volume_profile volume_profile -1.047012 -1.047012 -1.0 -1.0
508 AES 2026-02-27 long added 1.714359 2.425565 48.53 29.79 pivot_point volume_profile -3.822565 -3.822565 -3.778078 -3.778078
509 ALB 2026-02-27 long added 1.50417 2.275291 52.17 31.28 pivot_point volume_profile -1.023428 -1.023428 -1.0 -1.0
510 BIIB 2026-02-27 long removed 2.258936 1.949142 41.45 35.12 pivot_point+volume_profile volume_profile -1.045333 -1.045333 -1.0 -1.0
511 CF 2026-02-27 long added 1.72868 2.073351 48.3 33.55 pivot_point+volume_profile volume_profile 1.689394 2.034064 4.369061 4.369061
512 DG 2026-02-27 long removed 2.034541 1.979068 35.83 34.73 pivot_point+volume_profile volume_profile -1.047674 -1.047674 -1.0 -1.0
513 LVS 2026-02-27 long removed 2.116964 1.550234 32.63 41.32 pivot_point+volume_profile volume_profile -1.040159 -1.040159 -1.0 -1.0
514 MRK 2026-02-27 long added 2.042053 33.93 volume_profile -1.054868 -1.0
515 APA 2026-03-06 long added 1.831683 2.405864 46.75 29.97 pivot_point volume_profile 1.799478 2.373659 1.618625 1.618625
516 PLTR 2026-03-06 long retained 2.165026 2.159874 36.08 32.54 pivot_point+volume_profile volume_profile -1.026994 -1.026994 -1.0 -1.0
517 ADM 2026-03-13 long removed 2.868953 1.581314 36.17 40.76 pivot_point volume_profile -1.043884 -1.043884 -1.0 -1.0
518 AMD 2026-03-20 long added 1.748995 2.062867 37.79 33.68 pivot_point+volume_profile volume_profile 1.719912 2.033784 10.127012 10.127012
519 PLTR 2026-03-20 long retained 2.218591 2.235503 41.89 31.7 pivot_point+volume_profile volume_profile -1.031268 -1.031268 -1.0 -1.0
520 ADM 2026-03-27 long added 1.847544 3.062681 46.52 24.89 pivot_point+volume_profile volume_profile -1.041243 -1.041243 -1.0 -1.0
521 ALB 2026-03-27 long added 1.555558 2.38109 51.22 30.21 pivot_point volume_profile 1.530367 2.355899 2.143564 2.143564
522 MRK 2026-03-27 long removed 2.642683 1.781011 22.88 22.5 pivot_point+volume_profile volume_profile -1.060594 -1.060594 -1.0 -1.0
523 ADM 2026-04-06 long added 1.618843 2.913662 50.11 25.86 pivot_point+volume_profile volume_profile -1.431888 -1.431888 -1.387241 -1.387241
524 AMD 2026-04-06 long removed 2.050665 1.983637 28.23 34.67 pivot_point+volume_profile volume_profile 2.022452 1.955424 12.865385 12.865385
525 CMI 2026-04-06 long removed 2.030737 1.993522 25.28 34.55 pivot_point+volume_profile volume_profile 1.99083 1.953614 4.550433 4.550433
526 FSLR 2026-04-06 long added 1.901145 2.416746 45.77 29.87 pivot_point volume_profile 1.870523 2.386124 2.980447 2.980447
527 GOOG 2026-04-06 long removed 2.156045 1.620573 31.78 40.08 pivot_point+volume_profile volume_profile 2.105673 1.570201 8.076424 8.076424
528 GOOGL 2026-04-06 long added 1.817453 2.344971 36.76 30.57 pivot_point+volume_profile volume_profile 1.76908 2.296599 7.816437 7.816437
529 IBKR 2026-04-06 long added 1.654872 2.122334 31.7 32.97 pivot_point volume_profile 1.62217 2.089632 4.169943 4.169943
530 MRK 2026-04-06 long retained 2.348703 2.293801 40.53 31.09 pivot_point+volume_profile volume_profile -1.061605 -1.061605 -1.0 -1.0
531 NVDA 2026-04-06 long retained 2.017951 2.397549 36.84 30.05 pivot_point+volume_profile volume_profile 1.974149 2.353746 5.508603 5.508603
532 TEL 2026-04-06 long removed 2.030485 1.949998 44.08 35.11 pivot_point+volume_profile volume_profile 1.994454 1.913968 -1.0 -1.0
533 WMT 2026-04-06 long removed 2.064388 1.439845 23.86 43.42 pivot_point volume_profile -1.066264 -1.066264 -1.0 -1.0
534 ALB 2026-04-13 long added 1.632672 2.971695 49.87 25.47 pivot_point+volume_profile volume_profile 1.606556 -1.026116 -1.0 -1.0
535 APH 2026-04-13 long retained 2.078347 2.078347 23.49 33.49 volume_profile volume_profile -1.03288 -1.03288 -1.0 -1.0
536 BALL 2026-04-13 long added 1.793025 2.156109 47.32 32.58 pivot_point+volume_profile volume_profile -1.050157 -1.050157 -1.0 -1.0
537 FSLR 2026-04-13 long added 1.548381 3.052353 51.35 24.95 pivot_point volume_profile -1.031811 -1.031811 -1.0 -1.0
538 GNRC 2026-04-13 long added 1.579353 2.951036 46.4 25.6 pivot_point volume_profile 1.550083 2.921766 4.969183 4.969183
539 GOOG 2026-04-13 long added 1.829459 2.019351 27.58 34.22 pivot_point+volume_profile volume_profile 1.776345 1.966238 5.460089 5.460089
540 IVZ 2026-04-13 long added 1.746362 2.119681 47.03 33.0 pivot_point+volume_profile volume_profile 1.712682 2.086001 2.349272 2.349272
541 LVS 2026-04-13 long removed 2.407554 1.725531 24.96 23.35 pivot_point+volume_profile volume_profile -1.49832 -1.49832 -1.453044 -1.453044
542 MRK 2026-04-13 long removed 2.334923 1.53367 25.67 26.62 pivot_point+volume_profile volume_profile -1.05659 -1.05659 -1.0 -1.0
543 NEM 2026-04-13 long retained 2.111857 2.129673 23.49 32.88 pivot_point+volume_profile volume_profile -1.031519 -1.031519 -1.0 -1.0
544 ADM 2026-04-20 long removed 2.378361 1.814786 25.24 22.0 pivot_point+volume_profile volume_profile 2.336319 1.772744 4.332119 4.332119
545 ALB 2026-04-20 long added 1.704982 2.891679 48.68 26.01 pivot_point+volume_profile volume_profile -1.023945 -1.023945 -1.0 -1.0
546 APP 2026-04-20 long added 1.597975 2.981364 35.07 25.4 pivot_point volume_profile -1.023277 -1.023277 -1.0 -1.0
547 BIIB 2026-04-20 long retained 2.318083 2.990785 40.84 25.34 pivot_point volume_profile 2.274173 -0.04391 0.657434 0.657434
548 DG 2026-04-20 long added 1.764519 2.118746 47.75 33.01 pivot_point+volume_profile volume_profile -1.040034 -1.040034 -1.0 -1.0
549 DLTR 2026-04-20 long retained 2.99618 2.358253 35.31 30.44 pivot_point+volume_profile volume_profile -1.034868 -1.034868 -1.0 -1.0
550 GM 2026-04-20 long removed 2.031536 1.627283 24.47 39.96 pivot_point volume_profile -1.047255 -1.047255 -1.0 -1.0
551 GNRC 2026-04-20 long retained 2.536521 2.128052 26.38 32.9 pivot_point volume_profile 2.504928 2.096459 4.894647 4.894647
552 IVZ 2026-04-20 long removed 2.0925 1.716515 29.72 38.5 pivot_point+volume_profile volume_profile 2.056939 1.680955 1.874221 1.874221
553 LUV 2026-04-20 long removed 2.068451 1.627587 43.61 39.96 pivot_point+volume_profile volume_profile -1.178575 -1.178575 -1.151594 -1.151594
554 MCHP 2026-04-20 long added 1.53215 2.09942 51.65 33.24 pivot_point+volume_profile volume_profile 1.492661 2.059932 4.069653 4.069653
555 MNST 2026-04-20 long retained 2.298378 2.464221 23.04 29.43 pivot_point+volume_profile volume_profile -1.06053 -1.06053 -1.0 -1.0
556 ON 2026-04-20 long removed 2.205227 1.655239 37.03 39.49 pivot_point+volume_profile volume_profile 2.168538 1.61855 9.236509 9.236509
557 ULTA 2026-04-20 long removed 3.177791 1.895238 21.4 35.85 pivot_point+volume_profile volume_profile -1.042209 -1.042209 -1.0 -1.0
558 VTRS 2026-04-20 long added 1.906578 2.159994 28.9 32.54 pivot_point+volume_profile volume_profile 1.859326 2.112742 1.302828 1.302828
559 FSLR 2026-04-27 long added 1.762167 2.280875 47.79 31.22 pivot_point volume_profile 1.73106 2.249768 5.09658 5.09658
560 GNRC 2026-04-27 long retained 2.431647 2.002688 26.93 34.43 pivot_point volume_profile 2.398006 1.969046 3.106866 3.106866
561 HAS 2026-04-27 long retained 2.080713 2.116798 25.86 33.03 pivot_point+volume_profile volume_profile -1.163106 -1.163106 -1.125577 -1.125577
562 INCY 2026-04-27 long added 1.54251 2.311158 51.46 30.91 pivot_point+volume_profile volume_profile 1.492363 2.261011 1.968643 1.968643
563 IVZ 2026-04-27 long added 1.879775 2.17108 32.47 32.41 pivot_point+volume_profile volume_profile 1.839966 2.131271 1.898289 1.898289
564 NEM 2026-04-27 long retained 2.157639 2.175364 22.96 32.36 pivot_point+volume_profile volume_profile -1.098504 -1.098504 -1.067259 -1.067259
565 ON 2026-04-27 long retained 2.093542 2.093542 23.51 33.31 pivot_point+volume_profile volume_profile -1.03632 -1.03632 -1.0 -1.0
566 VTRS 2026-04-27 long removed 2.04878 1.526016 27.05 41.76 pivot_point+volume_profile volume_profile 2.000618 1.477854 2.260163 2.260163
567 ADM 2026-05-04 long added 1.68048 2.194278 49.08 32.15 pivot_point volume_profile 1.63043 2.144227 0.574203 0.574203
568 ALB 2026-05-04 long added 1.610684 2.377 50.25 30.25 pivot_point volume_profile 1.586143 -1.024541 -1.0 -1.0
569 BIIB 2026-05-04 long added 1.742645 2.374724 48.09 30.27 pivot_point volume_profile 1.700516 -0.04213 0.945164 0.945164
570 C 2026-05-04 long removed 2.012374 1.506573 24.31 42.12 pivot_point volume_profile -1.052343 -1.052343 -1.0 -1.0
571 DOW 2026-05-04 long removed 2.152554 1.517886 41.82 41.91 pivot_point+volume_profile volume_profile -1.028888 -1.028888 -1.0 -1.0
572 FSLR 2026-05-04 long removed 2.07299 1.925998 43.56 35.43 pivot_point volume_profile 2.043281 1.896289 3.722247 3.722247
573 GNRC 2026-05-04 long retained 2.620386 2.216863 25.66 31.9 pivot_point volume_profile -1.033618 -1.033618 -1.0 -1.0
574 IVZ 2026-05-04 long removed 2.325783 1.684083 22.56 39.02 pivot_point+volume_profile volume_profile 2.286261 1.644561 2.398027 2.398027
575 OXY 2026-05-04 long removed 2.45361 1.418712 24.52 28.84 pivot_point+volume_profile volume_profile -1.581139 -1.581139 -1.542008 -1.542008
576 ADM 2026-05-11 long removed 2.143232 1.69859 42.73 38.78 pivot_point+volume_profile volume_profile -1.045111 -1.045111 -1.0 -1.0
577 BIIB 2026-05-11 long added 1.569096 2.835602 50.98 26.4 pivot_point+volume_profile volume_profile -1.047834 -1.047834 -1.0 -1.0
578 FSLR 2026-05-11 long added 1.847053 2.025176 45.73 34.14 pivot_point+volume_profile volume_profile 1.815814 1.993938 1.010405 1.010405
579 GNRC 2026-05-11 long added 1.769106 2.872088 35.28 26.14 pivot_point volume_profile -1.037299 -1.037299 -1.0 -1.0
580 HAS 2026-05-11 long removed 2.341129 1.630847 23.01 39.9 pivot_point+volume_profile volume_profile -1.491403 -1.491403 -1.446832 -1.446832
581 MRNA 2026-05-11 long removed 4.417063 0.908038 21.18 57.12 pivot_point volume_profile -1.019052 -1.019052 -1.0 -1.0
582 ADM 2026-05-18 long added 1.860181 2.237304 46.34 31.68 pivot_point+volume_profile volume_profile -1.046193 -1.046193 -1.0 -1.0
583 APA 2026-05-18 long removed 2.025758 1.981919 44.14 34.69 pivot_point+volume_profile volume_profile -1.031291 -1.031291 -1.0 -1.0
584 BIIB 2026-05-18 long removed 2.090766 1.802678 28.34 22.18 pivot_point+volume_profile volume_profile 2.048589 1.760502 1.959265 1.959265
585 CAH 2026-05-18 long added 1.751088 2.249334 47.96 31.55 pivot_point+volume_profile volume_profile 1.705581 2.203827 4.322422 4.322422
586 COP 2026-05-18 long added 1.777297 2.206045 31.36 32.02 pivot_point+volume_profile volume_profile -1.131613 -1.131613 -1.08494 -1.08494
587 CVS 2026-05-18 long removed 2.060862 1.843965 43.7 36.57 pivot_point+volume_profile volume_profile -1.052069 -1.052069 -1.0 -1.0
588 CVX 2026-05-18 long added 1.961291 2.230118 25.56 31.76 pivot_point+volume_profile volume_profile -1.056276 -1.056276 -1.0 -1.0
589 DVN 2026-05-18 long added 1.796719 2.467367 47.26 29.4 pivot_point volume_profile -1.039565 -1.039565 -1.0 -1.0
590 OXY 2026-05-18 long removed 2.716157 1.65086 37.3 39.57 pivot_point+volume_profile volume_profile -1.075823 -1.075823 -1.035923 -1.035923
+38
View File
@@ -0,0 +1,38 @@
{
"control_report": "/Users/taathde3/git/lab/signal_platform/reports/backtest-sr-v2-validation-production_control.json",
"variant_report": "/Users/taathde3/git/lab/signal_platform/reports/backtest-sr-v2-validation-legacy_range_grid_neutral.json",
"control_variant": "production_control",
"variant": "legacy_range_grid_neutral",
"retained": {
"count": 193,
"net_avg_r": 0.23,
"net_avg_r_ex_top5": 0.0924,
"hold30_avg_r": 0.7591
},
"added": {
"count": 179,
"net_avg_r": 0.1223,
"net_avg_r_ex_top5": -0.0191,
"hold30_avg_r": 0.4528
},
"removed": {
"count": 217,
"net_avg_r": 0.1318,
"net_avg_r_ex_top5": -0.0093,
"hold30_avg_r": 0.527
},
"control_book": {
"sharpe": 2.78,
"cagr_pct": 73.3,
"max_drawdown_pct": 11.7,
"trades": 150,
"skipped_book_full": 0
},
"variant_book": {
"sharpe": 1.85,
"cagr_pct": 43.2,
"max_drawdown_pct": 15.2,
"trades": 154,
"skipped_book_full": 0
}
}
+44 -2
View File
@@ -11,7 +11,7 @@ import asyncio
import json
import os
import sys
from datetime import datetime
from datetime import date, datetime
from pathlib import Path
from typing import Any
@@ -46,6 +46,20 @@ def _parse_args() -> argparse.Namespace:
help="Allow spawn multiprocessing for offline CLI runs, useful on Windows.",
)
parser.add_argument("--quiet", action="store_true", help="Hide progress output.")
parser.add_argument(
"--target-model",
choices=("production_gtl", "structural_sr"),
default="production_gtl",
help=(
"Target source: production_gtl matches the live scanner; "
"structural_sr is a comparison-only chart-S/R model."
),
)
parser.add_argument(
"--holdout-split",
default=None,
help="Add a disjoint train/test portfolio report split at YYYY-MM-DD.",
)
return parser.parse_args()
@@ -128,6 +142,24 @@ def _print_summary(report: dict) -> None:
f"trades {row.get('trades')}"
)
monitor_rows = [
row
for row in ((report.get("portfolio_monitor") or {}).get("runs") or [])
if row.get("lookback") == "all"
and row.get("is_production")
]
if monitor_rows:
print(" live-path full-period comparison:")
for row in monitor_rows:
print(
" "
f"{row.get('strategy')}: "
f"Sharpe {row.get('sharpe')}, "
f"CAGR {_pct(row.get('cagr_pct'))}, "
f"DD {_drawdown_pct(row.get('max_drawdown_pct'))}, "
f"trades {row.get('trades')}"
)
async def _main() -> None:
args = _parse_args()
@@ -138,6 +170,12 @@ async def _main() -> None:
os.environ["BACKTEST_SNAPSHOT_OFFLINE"] = "1"
if args.allow_spawn:
os.environ["BACKTEST_ALLOW_SPAWN"] = "1"
if args.holdout_split:
try:
date.fromisoformat(args.holdout_split)
except ValueError as exc:
raise SystemExit("--holdout-split must use YYYY-MM-DD") from exc
os.environ["BACKTEST_HOLDOUT_SPLIT"] = args.holdout_split
from app.config import settings
from app.services.backtest_service import run_backtest
@@ -166,7 +204,11 @@ async def _main() -> None:
try:
async with Session() as db:
report = await run_backtest(db, progress_cb=progress)
report = await run_backtest(
db,
progress_cb=progress,
target_model=args.target_model,
)
finally:
await engine.dispose()
+5 -1
View File
@@ -213,7 +213,11 @@ def sr_levels(draw: st.DrawFn) -> dict[str, Any]:
"price_level": draw(st.floats(min_value=0.01, max_value=10000.0, allow_nan=False, allow_infinity=False)),
"type": draw(st.sampled_from(["support", "resistance"])),
"strength": draw(st.integers(min_value=0, max_value=100)),
"detection_method": draw(st.sampled_from(["volume_profile", "pivot_point", "merged"])),
"detection_method": draw(
st.sampled_from(
["volume_profile", "pivot_point", "merged", "round_number"]
)
),
}
+77 -1
View File
@@ -604,7 +604,6 @@ class TestSimulatePortfolio:
def test_nothing_qualified_returns_none(self):
assert bt._simulate_portfolio([], {}, None, "hold", 30) is None
def test_bucket_stats_counts_and_expectancy():
cands = [
_cand(70, OUTCOME_TARGET_HIT, 3.0), # +3R win
@@ -730,6 +729,80 @@ def test_window_setups_too_short_returns_empty():
assert bt._window_setups([], {}, {}) == []
def test_backtest_target_model_is_small_and_validated():
assert bt.validate_backtest_target_model(" PRODUCTION_GTL ") == "production_gtl"
assert bt.validate_backtest_target_model("structural_sr") == "structural_sr"
with pytest.raises(ValueError, match="Unknown backtest target model"):
bt.validate_backtest_target_model("legacy_range_grid_touch")
def _flat_window_records():
return [
SimpleNamespace(
date=date(2024, 1, 1) + timedelta(days=i),
open=100.0,
high=101.0,
low=99.0,
close=100.0,
volume=1_000_000,
)
for i in range(bt.MIN_LOOKBACK)
]
def test_window_setups_routes_production_gtl_by_default(monkeypatch):
captured = {}
def fake_detector(highs, lows, closes):
captured.update({"highs": highs, "lows": lows, "closes": closes})
return []
monkeypatch.setattr(bt, "detect_gate_target_ladder", fake_detector)
assert bt._window_setups(_flat_window_records(), {}, {}) == []
assert captured == {
"highs": [101.0] * bt.MIN_LOOKBACK,
"lows": [99.0] * bt.MIN_LOOKBACK,
"closes": [100.0] * bt.MIN_LOOKBACK,
}
def test_window_setups_routes_structural_comparison(monkeypatch):
captured = {}
def fake_detector(highs, lows, closes, volumes):
captured.update({
"highs": highs,
"lows": lows,
"closes": closes,
"volumes": volumes,
})
return []
monkeypatch.setattr(bt, "detect_sr_levels", fake_detector)
assert bt._window_setups(
_flat_window_records(),
{},
{},
target_model=bt.STRUCTURAL_SR_TARGET_MODEL,
) == []
assert captured == {
"highs": [101.0] * bt.MIN_LOOKBACK,
"lows": [99.0] * bt.MIN_LOOKBACK,
"closes": [100.0] * bt.MIN_LOOKBACK,
"volumes": [1_000_000] * bt.MIN_LOOKBACK,
}
def test_window_setups_rejects_removed_research_arm():
with pytest.raises(ValueError, match="Unknown backtest target model"):
bt._window_setups(
_flat_window_records(),
{},
{},
target_model="production_control",
)
def test_replay_ticker_candidates_carry_gate_fields():
"""The ablation recomputes floors from candidate fields — a candidate missing
action/risk_level silently zeroes the ablation rows (July 2026 regression)."""
@@ -755,6 +828,7 @@ def test_replay_ticker_candidates_carry_gate_fields():
for c in cands:
assert c.get("action") is not None
assert "risk_level" in c
assert c["target_model"] == bt.PRODUCTION_GTL_TARGET_MODEL
async def _seed_oscillating_ticker(session, symbol: str, n: int = 160) -> None:
@@ -794,6 +868,8 @@ async def test_run_backtest_smoke(session):
# cost assumption is reported, and every bucket carries net numbers
assert report["params"]["cost_per_side_pct"] == pytest.approx(bt.COST_PER_SIDE * 100)
assert report["params"]["target_model"] == bt.PRODUCTION_GTL_TARGET_MODEL
assert report["params"]["is_production_target_model"] is True
assert "net_avg_r" in report["overall_all"]
# ablation baseline reproduces the qualified set exactly, and every row
+27
View File
@@ -85,6 +85,33 @@ class TestClusterSrZonesStrength:
zones = cluster_sr_zones(levels, current_price=200.0, tolerance=0.02)
assert zones[0]["strength"] == 30
def test_soft_strength_uses_max_plus_confluence(self):
levels = [
{
"price_level": 100.0,
"strength": 60,
"detection_method": "pivot_point",
"sources": ["pivot_point"],
"rejection_count": 3,
},
{
"price_level": 100.5,
"strength": 60,
"detection_method": "round_number",
"sources": ["round_number"],
"rejection_count": 1,
},
]
zones = cluster_sr_zones(
levels,
current_price=200.0,
tolerance=0.02,
strength_mode="soft",
)
assert zones[0]["strength"] == 65
assert set(zones[0]["sources"]) == {"pivot_point", "round_number"}
assert zones[0]["rejection_count"] == 3
class TestClusterSrZonesTypeTagging:
"""Support vs resistance tagging."""
+260
View File
@@ -0,0 +1,260 @@
"""Unit tests for detect_sr_levels and related pure helpers."""
from __future__ import annotations
from app.services.sr_service import (
MAX_LEVELS,
_bar_respect_weight,
_cap_levels,
_gate_target_range_centers,
_merge_levels,
_round_number_candidates,
_strength_from_respects,
detect_gate_target_ladder,
detect_sr_levels,
)
def _make_series(
n: int = 300,
*,
base: float = 100.0,
support: float = 95.0,
resistance: float = 110.0,
) -> tuple[list[float], list[float], list[float], list[int]]:
"""Synthetic OHLCV that repeatedly tests support/resistance."""
highs: list[float] = []
lows: list[float] = []
closes: list[float] = []
volumes: list[int] = []
price = base
for i in range(n):
phase = i % 40
if phase < 15:
# Drift down toward support, bounce
target = support
price = price + (target - price) * 0.25
low = min(price, support) - 0.3
high = price + 1.0
close = max(price, support + 0.5) if phase > 12 else price
elif phase < 30:
# Drift up toward resistance, reject
target = resistance
price = price + (target - price) * 0.25
high = max(price, resistance) + 0.3
low = price - 1.0
close = min(price, resistance - 0.5) if phase > 27 else price
else:
price = base + (i % 7) * 0.2
high = price + 1.0
low = price - 1.0
close = price
# Occasional clear swing extremes
if i % 55 == 25:
high = resistance + 1.0
close = resistance - 1.0
low = close - 1.0
if i % 55 == 50:
low = support - 1.0
close = support + 1.0
high = close + 1.0
highs.append(high)
lows.append(low)
closes.append(close)
volumes.append(1000 + (i % 10) * 50)
price = close
return highs, lows, closes, volumes
class TestBarRespectWeight:
def test_no_interaction(self):
assert _bar_respect_weight(100.0, 90.0, 85.0, 88.0, 87.0, 0.005) == 0.0
def test_support_rejection(self):
# Low probes at 100, closes above with recovery wick
w = _bar_respect_weight(100.0, 103.0, 99.8, 102.0, 101.0, 0.005)
assert w >= 0.9
def test_resistance_rejection(self):
# High probes at 100, closes below
w = _bar_respect_weight(100.0, 100.2, 97.0, 98.0, 99.0, 0.005)
assert w >= 0.9
def test_pass_through_lower_weight(self):
# Prev below, close above, bar spans through without probing extremes at level
w = _bar_respect_weight(100.0, 105.0, 95.0, 104.0, 96.0, 0.005)
assert w < 0.5
class TestStrengthFromRespects:
def test_pass_through_not_maximal(self):
"""Central pass-through levels should not pin at strength 100."""
n = 200
# Trending series that passes through 100 many times
closes = [80.0 + i * 0.25 for i in range(n)]
highs = [c + 1.0 for c in closes]
lows = [c - 1.0 for c in closes]
strength = _strength_from_respects(100.0, highs, lows, closes, 0.005)
assert strength < 100
def test_repeated_rejection_stronger_than_no_touch(self):
n = 120
level = 100.0
# Bars that repeatedly probe support (low near level) and close above
highs = [103.0] * n
lows = [99.8] * n
closes = [102.0] * n
strong = _strength_from_respects(level, highs, lows, closes, 0.01, base=10)
far_highs = [120.0] * n
far_lows = [118.0] * n
far_closes = [119.0] * n
weak = _strength_from_respects(level, far_highs, far_lows, far_closes, 0.01, base=10)
assert strong > weak
class TestRoundNumbers:
def test_near_spot(self):
levels = _round_number_candidates(103.0)
assert levels
assert all(abs(p - 103.0) / 103.0 <= 0.15 + 1e-9 for p in levels)
assert len(levels) <= 8
def test_non_positive_price(self):
assert _round_number_candidates(0.0) == []
assert _round_number_candidates(-5.0) == []
class TestCapLevels:
def test_interleaves_sides(self):
levels = [
{"price_level": 90.0, "type": "support", "strength": 80, "detection_method": "x"},
{"price_level": 91.0, "type": "support", "strength": 70, "detection_method": "x"},
{"price_level": 92.0, "type": "support", "strength": 60, "detection_method": "x"},
{"price_level": 110.0, "type": "resistance", "strength": 50, "detection_method": "x"},
{"price_level": 111.0, "type": "resistance", "strength": 40, "detection_method": "x"},
]
capped = _cap_levels(levels, max_levels=4)
assert len(capped) == 4
types = {lvl["type"] for lvl in capped}
assert "support" in types
assert "resistance" in types
class TestLevelEvidence:
def test_merge_preserves_sources_and_rejection_evidence(self):
levels = [
{
"price_level": 100.0,
"type": "",
"strength": 55,
"detection_method": "pivot_point",
"sources": ["pivot_point"],
"rejection_count": 3,
"last_rejection_age": 12,
"weighted_respects": 1.5,
},
{
"price_level": 100.3,
"type": "",
"strength": 40,
"detection_method": "round_number",
"sources": ["round_number"],
"rejection_count": 1,
"last_rejection_age": 4,
"weighted_respects": 0.5,
},
]
merged = _merge_levels(levels, tolerance=0.005)
assert len(merged) == 1
assert set(merged[0]["sources"]) == {"pivot_point", "round_number"}
assert merged[0]["rejection_count"] == 3
assert merged[0]["last_rejection_age"] == 4
class TestDetectSrLevels:
def test_returns_capped_tagged_levels(self):
highs, lows, closes, volumes = _make_series()
levels = detect_sr_levels(highs, lows, closes, volumes)
assert levels
assert len(levels) <= MAX_LEVELS
for lvl in levels:
assert lvl["type"] in ("support", "resistance")
assert 0 <= lvl["strength"] <= 100
assert lvl["detection_method"] in (
"volume_profile",
"pivot_point",
"merged",
"round_number",
)
assert lvl["price_level"] > 0
assert lvl["sources"]
assert lvl["rejection_count"] >= 0
# Sorted by strength desc
strengths = [lvl["strength"] for lvl in levels]
assert strengths == sorted(strengths, reverse=True)
def test_far_fewer_than_old_grid(self):
"""Should not produce a near-1%-spacing grid of ~70 levels."""
highs, lows, closes, volumes = _make_series(n=500)
levels = detect_sr_levels(highs, lows, closes, volumes)
assert len(levels) <= MAX_LEVELS
def test_empty_input(self):
assert detect_sr_levels([], [], [], []) == []
def test_explicit_tolerance(self):
highs, lows, closes, volumes = _make_series()
tight = detect_sr_levels(highs, lows, closes, volumes, tolerance=0.001)
wide = detect_sr_levels(highs, lows, closes, volumes, tolerance=0.05)
# Wider merge should not produce more levels
assert len(wide) <= len(tight) + 2 # allow small jitter from scoring
def test_levels_near_structural_areas(self):
"""At least some levels should land near the synthetic S/R band."""
highs, lows, closes, volumes = _make_series(
n=400, support=95.0, resistance=110.0
)
levels = detect_sr_levels(highs, lows, closes, volumes)
prices = [lvl["price_level"] for lvl in levels]
near_support = any(abs(p - 95.0) / 95.0 < 0.05 for p in prices)
near_resist = any(abs(p - 110.0) / 110.0 < 0.05 for p in prices)
# Round numbers / VP may dominate; require at least one structural band hit
assert near_support or near_resist or any(
abs(p - 100.0) / 100.0 < 0.08 for p in prices
)
def test_strength_not_all_pinned_at_100(self):
highs, lows, closes, volumes = _make_series(n=400)
levels = detect_sr_levels(highs, lows, closes, volumes)
if len(levels) >= 3:
pinned = sum(1 for lvl in levels if lvl["strength"] == 100)
assert pinned < len(levels)
def test_gate_target_range_centers_cover_the_observed_range(self):
highs, lows, closes, _ = _make_series(n=500)
centers = _gate_target_range_centers(highs, lows, closes)
assert len(centers) == 20
assert centers == sorted(centers)
assert min(lows) < centers[0] < centers[-1] < max(highs)
def test_gate_target_ladder_is_dense_transient_price_traffic(self):
highs, lows, closes, _ = _make_series(n=500)
levels = detect_gate_target_ladder(highs, lows, closes)
assert len(levels) > MAX_LEVELS
assert any("range_grid" in level["sources"] for level in levels)
assert all("volume_profile" not in level["sources"] for level in levels)
assert all(0 <= level["strength"] <= 100 for level in levels)
assert all(level["rejection_count"] >= 0 for level in levels)
def test_gate_target_ladder_is_deterministic(self):
highs, lows, closes, _ = _make_series(n=500)
assert detect_gate_target_ladder(highs, lows, closes) == (
detect_gate_target_ladder(list(highs), list(lows), list(closes))
)
+48
View File
@@ -164,6 +164,34 @@ class TestComputeVolumeProfile:
with pytest.raises(ValidationError, match="Volume Profile requires"):
compute_volume_profile(highs, lows, closes, volumes)
def test_close_bin_volume_no_double_count(self):
"""Each bar's volume is counted once (close bin), not per span."""
# Wide bars that would span many bins under the old algorithm
n = 25
closes = [100.0 + (i % 5) for i in range(n)]
highs = [c + 20 for c in closes] # wide range
lows = [c - 20 for c in closes]
volumes = [1000] * n
result = compute_volume_profile(highs, lows, closes, volumes, num_bins=20)
# Binned total equals true volume (close-bin assignment)
# We only expose poc/hvn; reconstruct by checking score fields exist
assert result["poc"] > 0
# With volume concentrated on a few close prices, HVNs should be few local peaks
assert len(result["hvn"]) < 20
def test_hvn_are_local_peaks_not_all_above_mean(self):
"""HVN should be local histogram peaks, not every above-mean bin."""
# Two clusters of closes → two volume peaks
closes = [80.0] * 10 + [120.0] * 10 + [100.0] * 5
highs = [c + 1 for c in closes]
lows = [c - 1 for c in closes]
volumes = [1000] * len(closes)
result = compute_volume_profile(highs, lows, closes, volumes, num_bins=20)
# At most a handful of local peaks (not ~half of 20 bins)
assert len(result["hvn"]) <= 6
# POC should land near one of the high-volume clusters
assert result["poc"] < 95 or result["poc"] > 105
# ---------------------------------------------------------------------------
# Pivot Points
@@ -184,6 +212,26 @@ class TestComputePivotPoints:
with pytest.raises(ValidationError, match="Pivot Points requires"):
compute_pivot_points([1, 2], [0, 1], [0.5, 1.5])
def test_prominence_filters_tiny_swings(self):
# Mix of a large swing (depth ~10) and tiny fractal noise (depth ~1)
closes = [
10, 10.2, 10.5, 10.2, 10, # tiny high around idx 2
10, 15, 20, 15, 10, # large high around idx 7
10, 10.3, 10.6, 10.3, 10, # tiny high around idx 12
]
highs = list(closes)
lows = [c - 0.5 for c in closes]
highs[2] = 10.8
highs[7] = 20.5
highs[12] = 10.9
lows[7] = 10.0 # large window range at major swing
unfiltered = compute_pivot_points(highs, lows, closes, min_prominence=None)
filtered = compute_pivot_points(highs, lows, closes, min_prominence=5.0)
assert unfiltered["pivot_count"] > 0
assert filtered["pivot_count"] < unfiltered["pivot_count"]
# Major swing high should survive
assert any(h >= 20.0 for h in filtered["swing_highs"])
# ---------------------------------------------------------------------------
# EMA Cross
+40 -5
View File
@@ -110,7 +110,7 @@ def test_primary_target_is_most_likely_worthwhile_not_lottery():
{"price": 120.0, "rr_ratio": 3.5, "probability": 50.0},
{"price": 140.0, "rr_ratio": 6.0, "probability": 15.0}, # far lottery — not chosen
]
primary = _select_primary_target(targets)
primary = _select_primary_target(targets, min_rr=1.5)
assert primary is not None
assert primary["price"] == 110.0
@@ -120,13 +120,13 @@ def test_primary_target_skips_sub_threshold_rr():
{"price": 102.0, "rr_ratio": 1.0, "probability": 95.0}, # high prob but trivial R:R — skipped
{"price": 115.0, "rr_ratio": 2.5, "probability": 60.0}, # most likely above the R:R floor ← primary
]
primary = _select_primary_target(targets)
primary = _select_primary_target(targets, min_rr=1.5)
assert primary is not None
assert primary["price"] == 115.0
def test_primary_target_none_when_empty():
assert _select_primary_target([]) is None
assert _select_primary_target([], min_rr=1.5) is None
def test_primary_target_never_headlines_a_lottery():
@@ -138,7 +138,7 @@ def test_primary_target_never_headlines_a_lottery():
{"price": 101.0, "rr_ratio": 0.9, "probability": 55.0}, # likely, no asymmetry
{"price": 140.0, "rr_ratio": 5.0, "probability": 3.0}, # asymmetric lottery
]
primary = _select_primary_target(targets)
primary = _select_primary_target(targets, min_rr=1.5)
assert primary is not None
assert primary["price"] == 101.0
@@ -150,7 +150,17 @@ def test_primary_target_requires_probability_floor():
{"price": 130.0, "rr_ratio": 4.0, "probability": 12.0}, # asymmetric but unlikely
{"price": 112.0, "rr_ratio": 1.8, "probability": 38.0}, # clears both floors ← primary
]
primary = _select_primary_target(targets)
primary = _select_primary_target(targets, min_rr=1.5)
assert primary is not None
assert primary["price"] == 112.0
def test_primary_target_uses_activation_rr_not_scanner_floor():
targets = [
{"price": 108.0, "rr_ratio": 1.6, "probability": 60.0},
{"price": 112.0, "rr_ratio": 2.2, "probability": 35.0},
]
primary = _select_primary_target(targets, min_rr=2.0)
assert primary is not None
assert primary["price"] == 112.0
@@ -318,3 +328,28 @@ def test_zone_representative_levels_singletons_unchanged():
reps = _zone_representative_levels(levels, entry_price=100.0)
assert len(reps) == 2
assert {round(r.price_level) for r in reps} == {120, 150}
def test_zone_representative_levels_soft_strength_avoids_resaturation():
from types import SimpleNamespace
from app.services.recommendation_service import _zone_representative_levels
levels = [
SimpleNamespace(
id=1, price_level=183.0, type="resistance", strength=60,
detection_method="pivot_point", sources=["pivot_point"],
rejection_count=3, last_rejection_age=5,
),
SimpleNamespace(
id=2, price_level=185.0, type="resistance", strength=60,
detection_method="round_number", sources=["round_number"],
rejection_count=1, last_rejection_age=10,
),
]
reps = _zone_representative_levels(
levels, entry_price=180.0, strength_mode="soft"
)
assert len(reps) == 1
assert reps[0].strength == 65
assert set(reps[0].sources) == {"pivot_point", "round_number"}
assert reps[0].rejection_count == 3
+18 -3
View File
@@ -110,7 +110,12 @@ async def test_long_prefers_strong_near_over_weak_far(scan_session: AsyncSession
scan_session.add_all([near_level, far_level])
await scan_session.flush()
setups = await scan_ticker(scan_session, "EXPLR", rr_threshold=1.5)
setups = await scan_ticker(
scan_session,
"EXPLR",
rr_threshold=1.5,
gate_levels_override=[near_level, far_level],
)
long_setups = [s for s in setups if s.direction == "long"]
assert len(long_setups) == 1, "Expected exactly one long setup"
@@ -162,7 +167,12 @@ async def test_short_prefers_strong_near_over_weak_far(scan_session: AsyncSessio
scan_session.add_all([near_level, far_level])
await scan_session.flush()
setups = await scan_ticker(scan_session, "EXPLS", rr_threshold=1.5)
setups = await scan_ticker(
scan_session,
"EXPLS",
rr_threshold=1.5,
gate_levels_override=[near_level, far_level],
)
short_setups = [s for s in setups if s.direction == "short"]
assert len(short_setups) == 1, "Expected exactly one short setup"
@@ -256,7 +266,12 @@ async def test_property_scanner_does_not_always_pick_most_distant(
session.add_all([near_level, far_level])
await session.commit()
setups = await scan_ticker(session, "PROP", rr_threshold=1.5)
setups = await scan_ticker(
session,
"PROP",
rr_threshold=1.5,
gate_levels_override=[near_level, far_level],
)
long_setups = [s for s in setups if s.direction == "long"]
assert len(long_setups) == 1, "Expected exactly one long setup"
+24 -4
View File
@@ -169,7 +169,12 @@ async def test_property_long_selects_highest_quality(
session.add_all(sr_levels)
await session.commit()
setups = await scan_ticker(session, "FIXL", rr_threshold=1.5)
setups = await scan_ticker(
session,
"FIXL",
rr_threshold=1.5,
gate_levels_override=sr_levels,
)
long_setups = [s for s in setups if s.direction == "long"]
assert len(long_setups) == 1, "Expected exactly one long setup"
@@ -225,7 +230,12 @@ async def test_property_short_selects_highest_quality(
session.add_all(sr_levels)
await session.commit()
setups = await scan_ticker(session, "FIXS", rr_threshold=1.5)
setups = await scan_ticker(
session,
"FIXS",
rr_threshold=1.5,
gate_levels_override=sr_levels,
)
short_setups = [s for s in setups if s.direction == "short"]
assert len(short_setups) == 1, "Expected exactly one short setup"
@@ -283,7 +293,12 @@ async def test_deterministic_long_three_levels(scan_session: AsyncSession):
scan_session.add_all([level_a, level_b, level_c])
await scan_session.flush()
setups = await scan_ticker(scan_session, "DET3L", rr_threshold=1.5)
setups = await scan_ticker(
scan_session,
"DET3L",
rr_threshold=1.5,
gate_levels_override=[level_a, level_b, level_c],
)
long_setups = [s for s in setups if s.direction == "long"]
assert len(long_setups) == 1, "Expected exactly one long setup"
@@ -341,7 +356,12 @@ async def test_deterministic_short_three_levels(scan_session: AsyncSession):
scan_session.add_all([level_a, level_b, level_c])
await scan_session.flush()
setups = await scan_ticker(scan_session, "DET3S", rr_threshold=1.5)
setups = await scan_ticker(
scan_session,
"DET3S",
rr_threshold=1.5,
gate_levels_override=[level_a, level_b, level_c],
)
short_setups = [s for s in setups if s.direction == "short"]
assert len(short_setups) == 1, "Expected exactly one short setup"
+66 -1
View File
@@ -8,6 +8,7 @@ correct TradeSetup field population, and database persistence.
from __future__ import annotations
import json
from datetime import date, datetime, timedelta, timezone
import pytest
@@ -152,7 +153,13 @@ async def test_scan_ticker_full_flow_quality_selection_and_persistence(
assert len(pre_setups) == 1, "Dummy old setup should exist before scan"
# -- Act: run scan_ticker --
setups = await scan_ticker(scan_session, "INTEG", rr_threshold=1.5, atr_multiplier=1.5)
setups = await scan_ticker(
scan_session,
"INTEG",
rr_threshold=1.5,
atr_multiplier=1.5,
gate_levels_override=sr_levels,
)
# -- Assert: both directions produced --
assert len(setups) == 2, f"Expected 2 setups (long + short), got {len(setups)}"
@@ -255,3 +262,61 @@ async def test_scan_ticker_full_flow_quality_selection_and_persistence(
assert persisted_short.entry_price == short_setup.entry_price
assert persisted_short.stop_loss == short_setup.stop_loss
assert persisted_short.composite_score == short_setup.composite_score
@pytest.mark.asyncio
async def test_scan_ticker_uses_transient_ladder_not_persisted_chart_levels(
scan_session: AsyncSession,
monkeypatch,
):
ticker = Ticker(symbol="DUAL")
scan_session.add(ticker)
await scan_session.flush()
scan_session.add_all(_make_ohlcv_bars(ticker.id, num_bars=20, base_close=100.0))
scan_session.add(SRLevel(
ticker_id=ticker.id,
price_level=130.0,
type="resistance",
strength=100,
detection_method="pivot_point",
))
await scan_session.commit()
ladder = [
{
"price_level": 105.0,
"type": "resistance",
"strength": 90,
"detection_method": "range_grid",
"sources": ["range_grid"],
"rejection_count": 5,
"last_rejection_age": None,
},
{
"price_level": 95.0,
"type": "support",
"strength": 85,
"detection_method": "range_grid",
"sources": ["range_grid"],
"rejection_count": 4,
"last_rejection_age": None,
},
]
monkeypatch.setattr(
"app.services.rr_scanner_service.detect_gate_target_ladder",
lambda highs, lows, closes: ladder,
)
setups = await scan_ticker(
scan_session,
"DUAL",
rr_threshold=1.5,
)
long_setup = next(setup for setup in setups if setup.direction == "long")
assert long_setup.target == pytest.approx(105.0, abs=0.01)
assert long_setup.target != pytest.approx(130.0, abs=0.01)
targets = json.loads(long_setup.targets_json or "[]")
assert targets
assert all(target["sr_level_id"] < 0 for target in targets)
assert all(target["sr_sources"] == ["range_grid"] for target in targets)
+38 -9
View File
@@ -197,17 +197,25 @@ async def test_property_zero_candidates_produce_no_setup(
bars = _make_ohlcv_bars(ticker.id, num_bars=20, base_close=100.0)
session.add_all(bars)
gate_levels = []
for lv_data in scenario.get("levels", []):
session.add(SRLevel(
level = SRLevel(
ticker_id=ticker.id,
price_level=lv_data["price"],
type=lv_data["type"],
strength=lv_data["strength"],
detection_method="volume_profile",
))
)
session.add(level)
gate_levels.append(level)
await session.commit()
setups = await scan_ticker(session, "PRSV0", rr_threshold=1.5)
setups = await scan_ticker(
session,
"PRSV0",
rr_threshold=1.5,
gate_levels_override=gate_levels,
)
assert setups == [], (
f"Expected no setups for zero-candidate scenario "
@@ -247,16 +255,22 @@ async def test_property_single_candidate_selected_unchanged(
session.add_all(bars)
lv = scenario["level"]
session.add(SRLevel(
level = SRLevel(
ticker_id=ticker.id,
price_level=lv["price"],
type=lv["type"],
strength=lv["strength"],
detection_method="volume_profile",
))
)
session.add(level)
await session.commit()
setups = await scan_ticker(session, "PRSV1", rr_threshold=1.5)
setups = await scan_ticker(
session,
"PRSV1",
rr_threshold=1.5,
gate_levels_override=[level],
)
direction = scenario["direction"]
dir_setups = [s for s in setups if s.direction == direction]
@@ -292,7 +306,12 @@ async def test_no_sr_levels_produces_no_setup(scan_session: AsyncSession):
scan_session.add_all(bars)
await scan_session.flush()
setups = await scan_ticker(scan_session, "NOSRL", rr_threshold=1.5)
setups = await scan_ticker(
scan_session,
"NOSRL",
rr_threshold=1.5,
gate_levels_override=[],
)
assert setups == [], (
f"Expected no setups when no SR levels exist, got {len(setups)}"
@@ -329,7 +348,12 @@ async def test_single_resistance_above_threshold_selected(scan_session: AsyncSes
scan_session.add(level)
await scan_session.flush()
setups = await scan_ticker(scan_session, "SINGL", rr_threshold=1.5)
setups = await scan_ticker(
scan_session,
"SINGL",
rr_threshold=1.5,
gate_levels_override=[level],
)
long_setups = [s for s in setups if s.direction == "long"]
assert len(long_setups) == 1, (
@@ -366,7 +390,12 @@ async def test_single_support_below_threshold_selected(scan_session: AsyncSessio
scan_session.add(level)
await scan_session.flush()
setups = await scan_ticker(scan_session, "SINGS", rr_threshold=1.5)
setups = await scan_ticker(
scan_session,
"SINGS",
rr_threshold=1.5,
gate_levels_override=[level],
)
short_setups = [s for s in setups if s.direction == "short"]
assert len(short_setups) == 1, (
+3
View File
@@ -31,9 +31,11 @@ async def test_scan_proceeds_when_score_refresh_fails(session, monkeypatch):
raise RuntimeError("scoring unavailable")
scanned: list[str] = []
primary_floors: list[float] = []
async def _fake_scan_ticker(db, symbol, *args, **kwargs):
scanned.append(symbol)
primary_floors.append(kwargs["primary_min_rr"])
return []
monkeypatch.setattr(scoring_service, "compute_all_dimensions", _boom)
@@ -42,6 +44,7 @@ async def test_scan_proceeds_when_score_refresh_fails(session, monkeypatch):
setups = await rr_scanner_service.scan_all_tickers(session)
assert scanned == ["AAA"]
assert primary_floors == [rr_scanner_service.PRIMARY_TARGET_MIN_RR]
assert setups == []
+13 -1
View File
@@ -3,15 +3,27 @@
import pytest
from app.scheduler import (
_is_job_enabled,
_consume_backtest_target_model,
_parse_frequency,
_resume_tickers,
_last_successful,
configure_scheduler,
queue_backtest_target_model,
scheduler,
)
def test_manual_backtest_target_model_is_one_shot():
assert queue_backtest_target_model("structural_sr") == "structural_sr"
assert _consume_backtest_target_model() == "structural_sr"
assert _consume_backtest_target_model() == "production_gtl"
def test_manual_backtest_target_model_rejects_removed_research_arms():
with pytest.raises(ValueError, match="Unknown backtest target model"):
queue_backtest_target_model("production_control")
class TestParseFrequency:
def test_hourly(self):
assert _parse_frequency("hourly") == {"hours": 1}
+70 -4
View File
@@ -3,7 +3,6 @@
from datetime import datetime
from unittest.mock import AsyncMock, patch
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
@@ -29,10 +28,12 @@ class _FakeLevel:
class _FakeOHLCV:
"""Mimics an OHLCVRecord with a close attribute."""
"""Mimics an OHLCVRecord with price attributes."""
def __init__(self, close: float):
def __init__(self, close: float, high: float | None = None, low: float | None = None):
self.close = close
self.high = high if high is not None else close + 1.0
self.low = low if low is not None else close - 1.0
def _make_app() -> FastAPI:
@@ -62,6 +63,71 @@ SAMPLE_LEVELS = [
SAMPLE_OHLCV = [_FakeOHLCV(100.0)]
class TestGateTargetLadderRouter:
@patch("app.routers.sr_levels.detect_gate_target_ladder")
@patch("app.routers.sr_levels.query_ohlcv", new_callable=AsyncMock)
def test_returns_transient_price_traffic_proposals(self, mock_ohlcv, mock_detect):
mock_ohlcv.return_value = [
_FakeOHLCV(100.0, high=101.0, low=99.0),
_FakeOHLCV(102.0, high=103.0, low=100.0),
]
mock_detect.return_value = [
{
"price_level": 105.0,
"type": "resistance",
"strength": 80,
"detection_method": "merged",
"sources": ["pivot_point", "range_grid"],
"rejection_count": 7,
},
{
"price_level": 95.0,
"type": "support",
"strength": 60,
"detection_method": "range_grid",
"sources": ["range_grid"],
"rejection_count": 4,
},
]
response = TestClient(_make_app()).get("/api/v1/gate-target-ladder/aapl")
assert response.status_code == 200
data = response.json()["data"]
assert data["symbol"] == "AAPL"
assert data["lookback_bars"] == 2
assert [level["price_level"] for level in data["levels"]] == [95.0, 105.0]
assert data["levels"][1] == {
"price_level": 105.0,
"type": "resistance",
"strength": 80,
"detection_method": "merged",
"sources": ["pivot_point", "range_grid"],
"traffic_count": 7,
}
mock_detect.assert_called_once_with(
[101.0, 103.0],
[99.0, 100.0],
[100.0, 102.0],
)
@patch("app.routers.sr_levels.detect_gate_target_ladder")
@patch("app.routers.sr_levels.query_ohlcv", new_callable=AsyncMock)
def test_empty_history_returns_empty_ladder(self, mock_ohlcv, mock_detect):
mock_ohlcv.return_value = []
response = TestClient(_make_app()).get("/api/v1/gate-target-ladder/AAPL")
assert response.status_code == 200
assert response.json()["data"] == {
"symbol": "AAPL",
"levels": [],
"count": 0,
"lookback_bars": 0,
}
mock_detect.assert_not_called()
class TestSRLevelsRouterZones:
"""Tests for max_zones parameter and zone inclusion in response."""
@@ -207,7 +273,7 @@ class TestSRLevelsRouterVisibleLevels:
), f"visible level price {price} not within any zone bounds"
# visible_levels must be a subset of levels (by id)
level_ids = {l["id"] for l in data["levels"]}
level_ids = {level["id"] for level in data["levels"]}
for lvl in visible:
assert lvl["id"] in level_ids