# Pre-trade screening: minimum ATR% / move size, by trading style

**Goal**: before checking win rate at all, check whether a symbol even *moves enough*, at your
intended hold duration, to clear Bybit's fees + funding + realistic slippage. If a symbol's
typical ATR% for your hold window sits below these thresholds, no win rate fixes that — the cost
floor eats the edge before it can accumulate. This is a **screen**, meant to run before you invest
any time building or backtesting a strategy on a given symbol/timeframe — not a full viability
check (pair it with the `p_min`/`W_min` calculator once a symbol passes this screen).

## What timeframe are these numbers on?

Not a fixed chart timeframe — each style's threshold is the minimum **net price move over the
trade's entire hold duration** `H` (entry → exit), not a per-bar ATR reading:

| Style | H used | Rough ATR proxy |
|---|---|---|
| Scalp | 0.5h | ATR on a 5-15min chart, short period (e.g. `ATR(6)` on 5min) |
| Intraday | 4h | `ATR(4)` on 1H candles, or `ATR(1)` on a 4H chart |
| Swing | 24h (1D) | `ATR(1)` on a Daily chart |
| Position | 96h (4D) | `ATR(4)` on a Daily chart |

**Treat the ATR proxy column as an upper-bound sanity check, not an exact substitute.** A textbook
ATR indicator averages *per-bar true range* (high-low-close path, including back-and-forth wick
noise) — that's a different quantity from *net directional displacement* over a hold window, and
will generally overstate it, since price chops around within its range rather than moving in a
straight line. If you have real historical trades, use their actual average win/loss size instead
— ATR is only meant to answer "is this symbol even in the ballpark" before you have that data.

## How this is computed

Instead of solving `p_min(H)` (which needs a win-rate guess), this solves for the minimum average
loss / ATR / stop width `L` such that:

```
cost_in_R = C(H) / L  ≤  target ceiling
```

`cost_in_R` is the exact quantity SD1H4H's own real BTCUSD backtests report as `avg_cost_r` — and
this project's own validation (`scripts/validate_against_sd1h4h.py`) gives two **real, not
hypothetical** calibration points for what ceiling actually matters:

| SD1H4H engine | avg stop width | `cost_in_R` (avg_cost_r) | Real outcome |
|---|---|---|---|
| Original engine | ~1.05% | **0.168 - 0.176** (~17%) | Edge did **NOT** survive costs |
| Variant engine | ~2.29% | **0.077 - 0.102** (~8-10%) | Edge **DID** survive costs |

So this isn't an arbitrary round-number rule of thumb — it's calibrated against a case where the
exact same underlying signal, on the exact same symbol, survived costs at one stop width and died
at another. The thresholds below:

- **Comfortable (`cost_in_R ≤ 10%`)** — validated survivor territory.
- **Workable (`cost_in_R ≤ 15%`)** — tighter, but with room for a real edge above the cost floor.
- **Danger line (`cost_in_R ≤ 25%`)** — beyond the point (~17%) where a real edge is known to have
  died on this project's own data. Below this line, costs need an unusually strong edge to
  overcome; above it, don't bother backtesting.

## Cost model used: fees + funding + slippage

`C(H) = fee_roundtrip(fee-mix) + slippage_roundtrip(liquidity tier, fee-mix) + funding(H)`

**Slippage is an estimate, not a fetched/validated figure** (Bybit doesn't publish one — it's
execution-dependent). Applied only to the **taker** fraction of each leg (a resting maker/limit
order fills at its own price or not at all — no price slippage, though there's a separate
fill-probability cost this model doesn't capture):

| Liquidity tier | Per-side taker slippage estimate |
|---|---|
| High (BTC, ETH) | 0.010% |
| Mid (top ~20 alt perps) | 0.040% |
| Low (thin/small-cap perps) | 0.150% |

**Funding** uses BTCUSDT's real historical mean signed rate (~0.0069%/event) and assumes
`directional_bias = 1.0` (a single-direction strategy, the conservative default — a genuinely
balanced long/short strategy pays much less, see `derivation.md`).

## Reference table (taker-only fees, BTC/ETH-class liquidity)

| Style | Hold range | H used | ATR proxy | `C(H)` | Comfortable | Workable | Danger line |
|---|---|---|---|---|---|---|---|
| Scalp | < 1h | 0.5h (funding off) | ATR on 5-15min, short period | 0.1300% | **1.30%** | 0.87% | 0.52% |
| Intraday | 1-8h | 4h | `ATR(4)` on 1H, or `ATR(1)` on 4H | 0.1334% | **1.33%** | 0.89% | 0.53% |
| Swing | 8h-3d | 24h (1D) | `ATR(1)` on Daily | 0.1506% | **1.51%** | 1.00% | 0.60% |
| Position | 3d+ | 96h (4D) | `ATR(4)` on Daily | 0.2123% | **2.12%** | 1.42% | 0.85% |

**Read it as**: for a swing trade (8h-3d hold), taker fills on a BTC/ETH-class perp, you want your
symbol's typical move over that window (ATR% or average realized move) to be **at least ~1.5%**
for a comfortable margin, or **≥1.0%** as a bare workable floor. Below ~0.6%, you're in territory
where SD1H4H's own data shows a real edge died from costs alone.

**These numbers barely change from Scalp to Intraday** (0.130% → 0.133%) — funding hasn't kicked
in yet at those hold times, so cost is fee-dominated. The jump shows up at Swing (funding starts
accumulating meaningfully) and gets much bigger at Position (multiple funding events).

## How fee mix and liquidity change the answer

Cheaper execution (more maker fills) and better liquidity both lower the floor substantially. Swing example, full breakdown:

| Fee mix | Liquidity | `C(H)` | Comfortable | Workable | Danger line |
|---|---|---|---|---|---|
| Taker-only | High (BTC/ETH) | 0.1506% | 1.51% | 1.00% | 0.60% |
| Taker-only | Mid (alts) | 0.2106% | 2.11% | 1.40% | 0.84% |
| Taker-only | Low (thin) | 0.4306% | 4.31% | 2.87% | 1.72% |
| 50/50 maker/taker | High | 0.1056% | 1.06% | 0.70% | 0.42% |
| Maker-only | Any tier | 0.0606% | 0.61% | 0.40% | 0.24% |

Maker-only execution is liquidity-tier-invariant here because this model assigns zero slippage to
maker (limit) fills regardless of symbol depth — the real-world tradeoff maker orders make instead
is fill probability, which isn't modeled.

**Practical implication**: a scalping strategy on a thin small-cap perp needs roughly **3-4x** the
average move of the same strategy on BTC to clear costs at the same taker-only execution style —
this is the single biggest lever in the whole table, bigger than hold-duration or funding.

## What this does NOT tell you

- It doesn't validate that your win rate/edge assumption is realistic — pair this with the
  `p_min`/`W_min` calculator (see [`HOW_TO_READ_THIS.md`](HOW_TO_READ_THIS.md)) once you've
  confirmed the symbol moves enough in the first place.
- Slippage figures are estimates for *normal* conditions, not a flash-crash/liquidation-cascade
  tail scenario, which would be materially worse across all tiers.
- The ATR proxy column ([above](#what-timeframe-are-these-numbers-on)) is an upper-bound sanity
  check, not an exact substitute for a real average trade move — see that section for why.

Full JSON (all 4 styles × 3 fee-mixes × 3 liquidity tiers × 3 thresholds):
[`output/pre_trade_screening.json`](output/pre_trade_screening.json). Regenerate with
`python scripts/build_pre_trade_screening.py`.
