SD book

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

6 min read

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:

  1. How big will the move be?
  2. 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:

TierConditionGroup C mark
3, highBB inside KC(1.0)***
2, midBB inside KC(1.5)**
1, lowBB inside KC(2.0)*
0, noneBB 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, or q = 2σ/ATR ≤ k. The number q is 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 throws q away 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:

PieceCode
BB, KC, ATR, AO, SMFsrc/indicators.rs, src/sd2/primitives.rs
Tiers, firing, unfiring, θ, activesrc/sd2/squeeze.rs (run_clock)
Groups A/B/C and the 0-1 scoresrc/sd2/classic.rs (one state per bar), src/sd.rs (latest bar only)
ThinkScriptIndicator/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.