1. Squeezes from zero
I'm new here · 1/6 I'm working on SD itself · 1/6 I want to change the indicator · 1/4
You are watching a stock. For three weeks it has barely moved: up a little, down a little, every day smaller than the last. Traders call this a squeeze, and they have a saying about it: the longer the coil, the bigger the spring. Quiet markets do not stay quiet. Sooner or later something happens (earnings, a rate decision, a big buyer) and price breaks out.
SD exists to answer two questions about that moment:
- How big will the move be?
- Which way will it go?
This chapter builds SD v1, the indicator described in the paper
(Paper/SahastaDirectionality.tex), one failure at a time. Each time
something breaks, we add exactly one piece. By the end you will have met
every piece of Implementations/rust/src/sd2/squeeze.rs and
classic.rs, in the order they depend on each other.
#"Quiet" needs a ruler
"The stock has been quiet" is a feeling. We need a number.
A first attempt: measure how spread out the last 21 closes are. That is the standard deviation σ. Draw lines two σ above and below the 21-day average and you have Bollinger Bands. When the bands are narrow, closes have been bunched together.
That fails at once. A $500 stock's σ is naturally bigger than a $20 stock's, and a volatile biotech's is bigger than a utility's. "Narrow" has to mean narrow for this stock.
So we need a second ruler that measures the stock's usual daily travel. That is the average true range (ATR): how far, on average, price moves from low to high in a day (counting overnight gaps). Draw lines k × ATR around the same average and you have Keltner Channels.
Now compare the two rulers:
normal market squeeze
─────────────────────── BB upper
- - - - - - - - - - - - KC upper - - - - - - - - - - - - KC upper
─────────────────────── BB upper
~~~~~~~~ price ~~~~~~~~ ~~~~~~~~ price ~~~~~~~~
─────────────────────── BB lower
- - - - - - - - - - - - KC lower - - - - - - - - - - - - KC lower
─────────────────────── BB lower
When the Bollinger Bands slip inside the Keltner Channels, closes are bunched together more tightly than the stock's usual daily travel would suggest. That is a squeeze, and the comparison is fair across every stock because both rulers are in the same stock's own units.
#One squeeze, three depths
A squeeze can be slight or extreme. SD uses three Keltner widths (k = 2.0, 1.5, 1.0) and calls the deepest one the bands fit inside the tier:
| Tier | Condition | Group C mark |
|---|---|---|
| 3, high | BB inside KC(1.0) | *** |
| 2, mid | BB inside KC(1.5) | ** |
| 1, low | BB inside KC(2.0) | * |
| 0, none | BB outside KC(2.0) |
A shortcut you will see in v2. Both bands share the same center, so "BB upper ≤ KC upper" is just
2σ ≤ k·ATR, orq = 2σ/ATR ≤ k. The numberqis the squeeze depth as one continuous value: below 1 is tier 3, below 2 is tier 1, and how far below 2 tells you how tightly coiled price is. Classic SD throwsqaway and keeps only the tier.
#When does a squeeze end?
The squeeze ends when the bands burst back out. SD calls that firing. More precisely, a squeeze has fired as soon as the tier drops below the highest tier this squeeze reached. A squeeze that reached tier 3 and is now at tier 1 has fired, even though it is technically still slightly compressed.
tier: 0 1 2 3 3 3 2 1 0 0
▲ highest = 3
▲ fired (2 < 3)
What if it re-compresses before fully expanding (3 → 1 → 2)? SD calls
that unfiring: the old squeeze is over and a new one starts with
highest = 2.
#How long does a fire last?
A fired squeeze will not drive price forever. After some number of bars the move is spent, and old momentum is just noise. SD needs a stopwatch.
How long? It depends on the stock and the season. So SD learns it: over the last 125 bars, how many bars does a squeeze usually last? That average is θ (theta). A fire is active while the bars since it fired, τ (tau), are at most ⌈θ⌉.
The paper refines θ per tier (a fire into the mid tier uses the average
length of mid-tier stretches, and so on). That refinement is in
run_clock in squeeze.rs, next to a comment pointing at the paper
section.
#Which way?
So far everything is direction-free. To pick a side, v1 uses one momentum gauge, the Awesome Oscillator (AO): the 5-bar average of the bar midpoints minus the 34-bar average. When AO is rising, momentum is up.
It also uses the slope of the 8-bar EMA, measured with a smoothed derivative called the SMF. The SMF weights the last five bar-to-bar changes by how strongly they have recently tended to persist (their auto-covariance).
Group A is SD's definitive call:
+if a fire is active, AO is rising, and the EMA-8 slope ≥ 0;-if a fire is active, AO is falling, and the EMA-8 slope ≤ 0;~otherwise.
One more rule ends a fire early. If AO changes colour (from rising to falling or back) after the fire, momentum has changed its mind, and the fire stops being active.
Group B is consulted only when Group A says ~. It looks for
exhaustion (price stretched far from its 21 EMA and slowing) and band
breakouts, and says (+), (-) or (~).
Group C is the squeeze lifecycle in symbols: * for each tier still
compressed and . for each tier fired, e.g. **. means "reached tier 3,
now at tier 2, still active".
Finally, the three groups collapse into one number between 0 and 1 (the
"half-continuous model" in Paper/Hypotheses/). + is 1.0, - is 0.0,
(+) is 0.75, (-) is 0.25, (~) is 0.5, and each * or . nudges the
number a little further from 0.5.
#You have built SD v1
Every piece above lives in two files:
| Piece | Code |
|---|---|
| BB, KC, ATR, AO, SMF | src/indicators.rs, src/sd2/primitives.rs |
| Tiers, firing, unfiring, θ, active | src/sd2/squeeze.rs (run_clock) |
| Groups A/B/C and the 0-1 score | src/sd2/classic.rs (one state per bar), src/sd.rs (latest bar only) |
| ThinkScript | Indicator/SahastaDirectionalitySTUDY.ts |
classic.rs has a test, matches_sd_rs_on_the_last_bar, that checks the
two Rust versions agree. If you change one, that test will tell you.
Next: what v1 got wrong, and how we found out.