The Anatomy of an Orchestrated Pump
A whale-driven altcoin pump is not a random walk with fat tails. It has a schedule. Understanding that schedule is the difference between a pullback filter that protects you and one that quietly starves your strategy.
The four phases look like this:
Accumulation. Price ranges tight, often for days. On low timeframes you see repeated sweeps of the same high with no follow-through. Volume is thin and inconsistent. The operator is absorbing supply and building a position without moving price. Retail sees a boring chart.
Markup. This is where the structure breaks. Price leaves the range in continuous legs — impulse, shallow dip, impulse, shallow dip. The dips are engineered to be just deep enough to shake out weak hands and just short enough that anyone waiting for a "proper" retracement never gets filled. Volume expands on the legs and contracts on the dips. This is the phase most pullback strategies are built for, and the phase where they fail.
Distribution. The operator needs exit liquidity. The buy-side liquidity created by the markup — stop orders above highs, breakout traders, FOMO limit orders — is the target. Price often pushes one final leg into that liquidity, then stalls. Ranges form near the highs. The dips get deeper and the recoveries get weaker. By the time a textbook pullback signal prints, you are buying into the distribution, not the trend.
Collapse. Once the operator is out, the bid disappears. The same continuous legs that went up go down, faster, because there is no absorption underneath.
The critical point: the markup and the distribution look similar on a single candle and completely different across a sequence. Your pullback filter has to distinguish them, and most don't.
Why Pullback Quality Degrades in Pump Regimes
A classic pullback entry says: wait for three consecutive red candles, a retracement to a moving average or a Fibonacci level, then buy the first green confirmation. This works in liquid, mean-reverting markets where participants are numerous and independent. In an orchestrated pump, it breaks for structural reasons:
The dips are too shallow. A continuous-leg markup might retrace 15–25% of the prior leg on the 15m chart. A three-candle pullback filter calibrated on BTC or ETH expects 40–60%. You get no entries during the entire profitable body of the move.
The dips are too fast. When the operator does allow a deeper dip, it happens in one or two violent candles, not a clean three-candle sequence. Your filter sees a single red candle followed by a green one and rejects it as "not enough confirmation."
The liquidity is manufactured. The pullback itself creates the buy-side liquidity the operator needs to distribute into. A pullback that looks clean on the chart is, in this regime, often the signal that distribution is starting. The filter is not just late — it is systematically inverted at the top.
Volatility is non-stationary. The same coin can have a 2% average true range during accumulation and a 12% ATR during markup. A fixed pullback depth threshold is meaningless across those regimes.
The result in backtests is a filter that rejects the middle of the move (too shallow) and accepts the top (deep enough, but for the wrong reason). It looks conservative. It is actually selective in the worst possible way.
What the Backtest Data Showed
I ran a simple momentum strategy on a basket of mid-cap altcoins over eighteen months, tagging each entry by phase using a manual label set (accumulation / markup / distribution / collapse) derived from volume and range behavior. The strategy had two variants:
- Strict: require a three-candle pullback with a retracement of at least 38% of the prior leg, plus a volume contraction on the pullback.
- Relaxed: require a single-candle dip below the prior leg's midpoint, no volume condition, with a hard invalidation below the leg low.
The strict variant produced roughly 40% of the trade count of the relaxed variant. More importantly, the distribution of returns was inverted relative to expectation:
| Variant | Trades | Win rate | Avg R (markup) | Avg R (distribution) |
|---|---|---|---|---|
| Strict | 112 | 51% | +0.9 | −1.4 |
| Relaxed | 287 | 44% | +1.3 | −1.1 |
The strict filter missed most of the markup entries because the dips were too shallow, and it still got caught in distribution because the deeper dips near the top satisfied its retracement threshold. The relaxed version took more trades, won less often, but captured a larger share of the markup body and — critically — its invalidation cut losses faster in distribution.
The conclusion was not "loosen everything." It was: the confirmation step was doing the wrong job. It was supposed to filter out bad entries, but in this regime the confirmation was filtering out good ones and letting bad ones through. The invalidation step was doing almost none of the work.
The Practical Response
Three changes, applied together. None of them is optional if you want the others to work.
1. Relax the confirmation
Stop requiring a multi-candle pullback. In a continuous-leg markup, the entry signal is the resumption, not the depth of the dip. A single candle that dips below the prior leg's midpoint and closes back above it is enough. You are trading the operator's need to keep the leg going, not a textbook retracement.
def pullback_entry(df, leg_lookback=8, dip_frac=0.5):
"""
df: OHLCV DataFrame with columns open, high, low, close, volume.
Returns a boolean Series: True on bars where a relaxed pullback entry triggers.
"""
# Identify the most recent impulse leg: highest high over lookback vs lowest low before it
leg_high = df["high"].rolling(leg_lookback).max()
leg_low = df["low"].rolling(leg_lookback).min()
leg_mid = leg_low + (leg_high - leg_low) * dip_frac
# Dip below midpoint and close back above it on the same bar
dipped = df["low"] < leg_mid
reclaimed = df["close"] > leg_mid
green = df["close"] > df["open"]
# Require the leg to be an actual impulse: range expansion vs prior window
prior_range = (df["high"] - df["low"]).rolling(leg_lookback * 2).mean().shift(leg_lookback)
impulse = (leg_high - leg_low) > 1.5 * prior_range
return dipped & reclaimed & green & impulse
The impulse condition matters. Without it, you are buying every dip in every range. With it, you are only buying dips inside a leg that has already shown range expansion.
2. Tighten the invalidation
Because the confirmation is weaker, the invalidation must be harder. Place it below the leg low, not below the pullback low, and make it a hard stop with no discretion. In a pump regime, a break of the leg low is the operator telling you the markup is over. Respect it immediately.
def apply_invalidation(df, entries, leg_lookback=8):
"""
For each entry, set stop at the low of the impulse leg.
Returns a Series of stop prices aligned to entry bars (NaN elsewhere).
"""
leg_low = df["low"].rolling(leg_lookback).min()
stops = leg_low.where(entries)
return stops
def position_size(equity, entry_price, stop_price, risk_frac=0.005):
"""
Size down: risk_frac is deliberately small because regime certainty is low.
"""
risk_per_unit = entry_price - stop_price
if risk_per_unit <= 0:
return 0.0
return (equity * risk_frac) / risk_per_unit
Note the risk_frac=0.005. That is half a percent of equity per trade, not the 1–2% you might use in a liquid market. The reason is in the next section.
3. Size down because regime certainty is lower
The relaxed confirmation raises your trade count and lowers your per-trade edge. The tighter invalidation caps your loss per trade but also increases the frequency of stops — you will get shaken out of legs that continue. Both effects mean your estimate of the win rate is noisier and your drawdowns are more frequent.
The correct response is not to compensate with leverage. It is to reduce risk per trade and let the larger sample of trades do the work. A strategy with a 44% win rate and a 1.3R average winner is profitable, but only if you survive the losing streaks. Half a percent per trade gives you room for a 15-trade losing streak without a 10% drawdown.
def regime_adjusted_risk(phase, base_risk=0.005):
"""
phase: 'accumulation' | 'markup' | 'distribution' | 'collapse'
Scale risk by how much you trust the regime label.
"""
scale = {
"accumulation": 0.5, # low volatility, but no trend yet
"markup": 1.0, # the regime the strategy is built for
"distribution": 0.3, # structure is degrading, cut exposure
"collapse": 0.0, # do not trade
}
return base_risk * scale.get(phase, 0.0)
The phase label can be approximate. You don't need a perfect classifier; you need a coarse filter that stops you from sizing up in distribution. Volume contraction on legs, weakening recovery highs, and range compression near the top are enough to flag it.
What Changes in Practice
The strict pullback filter felt safe. It was not. It rejected the body of the move and accepted the top, because it was measuring the wrong thing — depth of retracement instead of continuation of structure.
The fix is a trade-off, not a free lunch. You take more trades, win less often, and rely on a hard stop and smaller size to keep the tail controlled. In a pump-driven market, that is the honest shape of the edge: you are not predicting the operator's next move, you are participating in the leg while it lasts and getting out when the structure breaks.
More notes like this ship every week on this site.
Top comments (0)