capitulation-mean-reversion — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited capitulation-mean-reversion (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Source: Lance Breitstein — verified $100M+ trader, Trillium Capital's #1 trader (2020 & 2021), all-time firm PNL record holder, adviser to SMB Capital, featured in Jack Schwager's upcoming Market Wizards: The Next Generation. Chart Fanatics interview (March 2026).
Markets are extremely efficient. The starting price is usually the RIGHT price. The strategy focuses on identifying moments when price has moved FAR from equilibrium and stacking variables to create massive positive expected value for the reversion trade.
Expected Value Formula:
EV = (Win Rate x Reward) - (Loss Rate x Risk)The entire system is about shifting EVERY component of this equation in your favor simultaneously.
At any given moment, Lance is constantly weighing ALL factors dynamically. Each variable is scored on a mental 0-10 scale.
| Slope | Description | Tradeable? |
|---|---|---|
| -0.5 | Slow, steady decline | NO — grinding, not capitulating |
| -1 | Moderate acceleration | Getting interesting |
| -3 | Sharp selloff | Good setup territory |
| -10 | Asymptotic waterfall | BEST setups — "we love this" |
extreme dislocation from fair value.
Lance runs a mental tally across all variables, each scored 0-10:
Example: Rate of Change = 9
Daily Chart = 9
Intraday Setup = 6
Boringness = 2
────────────────────
TOTAL = 26| Score | Grade | Action |
|---|---|---|
| 35-40+ | A++ / A+ | Max conviction, aggressive sizing, exponential bet |
| 26-34 | A / B+ | Strong trade, meaningful size |
| 20-25 | B / C+ | Acceptable, moderate-to-small size |
| 15-19 | C | Marginal, very small size if taken at all |
| <15 | NO TRADE | Pass. Not enough variables aligned |
Critical insight: No single variable makes or breaks the trade. It's the COMBINATION. Like drafting a basketball player: "tall" matters, but tall + no arms + no coordination = bad pick.
Buying on the way DOWN leads to:
Wait for the TURN.
#### Method 1: Break of Trendline
Price action: \
\
\ <-- Trendline drawn across the declining highs/lows
\
\ / <-- Break above trendline = ENTRY
\/#### Method 2: Break of Prior Bar Highs (Most Common)
Price action: | | | |
|H |H |H |H
|| || || ||
|L |L |L |L ← LOWEST BAR
Entry: When price breaks ABOVE the HIGH of the prior bar after the lowest bar.
Stop: LOW of the move (the lowest point).#### Method 3: Break of Lower-Lows / Lower-Highs Pattern
HH
/ \
/ \ ← HIGHER HIGH forms
HL \ ← HIGHER LOW holds
\
LL LL ← Was making LOWER LOWS
LH LH ← Was making LOWER HIGHS#### Exception: Intra-Bar Turn (Extreme Scenarios Only)
and whether the R:R is still acceptable.
stop moves up to that bar's low.
stop moves down to that bar's high.
| Setup Quality | Estimated Win Rate | Expected Retracement |
|---|---|---|
| A++ (score 40) | 80-90% | 80-100% to MA |
| A (score 30+) | 70-80% | 50-80% retracement |
| B (score 25) | 60-70% | 40-60% retracement |
| C (score 20) | 50-55% | 20-40% retracement |
| Below C | <50% (coin flip) | Don't trade |
Key insight: Even 1:1 R:R is profitable with 70-80% win rate. You don't need huge R:R on every trade — the high win rate on A-grade setups drives the PnL.
Scan for:
1. Price BELOW lower Bollinger Band (20, 2.0)
2. Price ABOVE upper Bollinger Band (20, 2.0)
3. Consecutive UP days >= 4 (for short setups)
4. Consecutive DOWN days >= 4 (for long setups)
5. Volume > 3x 20-day average volume
6. Distance from 20MA > 2x ATR(14)
7. ATR expansion: today's range > 3x ATR(14)Scan for:
1. Holding prior bar highs (declining, clean waterfall)
2. Holding prior bar lows (rising, clean melt-up)
3. Volume capitulation spike (> 5x average bar volume)
4. Distance from intraday VWAP or Bollinger midline
5. Multiple ATR moves from openimport pandas as pd
import numpy as np
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class CapitulationScore:
"""Multi-variable rubric for scoring capitulation setups."""
size_of_move: float = 0 # 0-10
rate_of_change: float = 0 # 0-10 (THE most important)
news_absence: float = 0 # 0-10 (10 = no news)
consecutive_bars: float = 0 # 0-10
forced_flow: float = 0 # 0-10
sentiment_extreme: float = 0 # 0-10
boringness: float = 0 # 0-10
daily_chart: float = 0 # 0-10
intraday_setup: float = 0 # 0-10
volume_capitulation: float = 0 # 0-10
distance_from_ma: float = 0 # 0-10
num_legs: float = 0 # 0-10
long_vs_short: float = 0 # 0-10 (10 = long with $0 floor, lower for shorts)
@property
def total_score(self) -> float:
return sum([
self.size_of_move, self.rate_of_change, self.news_absence,
self.consecutive_bars, self.forced_flow, self.sentiment_extreme,
self.boringness, self.daily_chart, self.intraday_setup,
self.volume_capitulation, self.distance_from_ma, self.num_legs,
self.long_vs_short
])
@property
def max_possible(self) -> float:
return 130.0 # 13 variables x 10
@property
def normalized_score(self) -> float:
"""Normalized to 0-100 scale."""
return round((self.total_score / self.max_possible) * 100, 1)
@property
def grade(self) -> str:
s = self.normalized_score
if s >= 80: return "A++"
if s >= 70: return "A+"
if s >= 60: return "A"
if s >= 50: return "B+"
if s >= 40: return "B"
if s >= 30: return "C+"
if s >= 25: return "C"
return "NO TRADE"
@property
def action(self) -> str:
g = self.grade
if g in ("A++", "A+"): return "MAX SIZE — exponential bet sizing"
if g in ("A", "B+"): return "Meaningful size — strong conviction"
if g in ("B", "C+"): return "Small size — acceptable but marginal"
if g == "C": return "Very small or skip"
return "DO NOT TRADE"
@property
def estimated_win_rate(self) -> str:
s = self.normalized_score
if s >= 70: return "75-90%"
if s >= 55: return "65-75%"
if s >= 40: return "55-65%"
if s >= 25: return "45-55%"
return "<45% (coin flip or worse)"
@property
def expected_retracement(self) -> str:
s = self.normalized_score
if s >= 70: return "80-100% to MA"
if s >= 55: return "50-80%"
if s >= 40: return "30-50%"
return "<30% (weak or no bounce)"
class CapitulationDetector:
"""Detect capitulation setups using Breitstein's framework."""
@staticmethod
def detect_waterfall(df: pd.DataFrame, bb_period: int = 20, bb_std: float = 2.0) -> dict:
"""
Analyze price action for capitulation/waterfall pattern.
df must have columns: open, high, low, close, volume
"""
close = df["close"]
high = df["high"]
low = df["low"]
volume = df["volume"]
# Bollinger Bands
ma = close.rolling(bb_period).mean()
std = close.rolling(bb_period).std()
upper_bb = ma + bb_std * std
lower_bb = ma - bb_std * std
# ATR for range measurement
tr = pd.concat([
high - low,
(high - close.shift(1)).abs(),
(low - close.shift(1)).abs()
], axis=1).max(axis=1)
atr = tr.rolling(14).mean()
current = df.iloc[-1]
prev = df.iloc[-2] if len(df) > 1 else current
# --- Score each variable ---
score = CapitulationScore()
# 1. Size of move: how far from MA in ATR multiples
dist_from_ma = abs(current["close"] - ma.iloc[-1])
atr_multiples = dist_from_ma / atr.iloc[-1] if atr.iloc[-1] > 0 else 0
score.distance_from_ma = min(10, atr_multiples * 2)
score.size_of_move = min(10, atr_multiples * 2.5)
# 2. Rate of change / slope analysis
if len(df) >= 5:
recent_bars = df.tail(5)
bar_ranges = (recent_bars["high"] - recent_bars["low"]).values
range_acceleration = bar_ranges[-1] / (np.mean(bar_ranges[:-1]) + 1e-10)
score.rate_of_change = min(10, range_acceleration * 2)
# 3. Consecutive bars in same direction
direction = "down" if current["close"] < ma.iloc[-1] else "up"
consecutive = 0
for i in range(len(df) - 1, 0, -1):
if direction == "down" and df.iloc[i]["close"] < df.iloc[i]["open"]:
consecutive += 1
elif direction == "up" and df.iloc[i]["close"] > df.iloc[i]["open"]:
consecutive += 1
else:
break
score.consecutive_bars = min(10, consecutive * 1.5)
# 4. Volume capitulation
avg_vol = volume.rolling(20).mean().iloc[-1]
vol_ratio = current["volume"] / avg_vol if avg_vol > 0 else 1
score.volume_capitulation = min(10, vol_ratio * 2)
# 5. Bollinger Band breach
below_lower = current["close"] < lower_bb.iloc[-1]
above_upper = current["close"] > upper_bb.iloc[-1]
# 6. Prior bar highs/lows pattern (clean waterfall check)
holding_prior_bar = True
if direction == "down":
for i in range(len(df) - 1, max(0, len(df) - 6), -1):
if i > 0 and df.iloc[i]["high"] > df.iloc[i-1]["high"]:
holding_prior_bar = False
break
else:
for i in range(len(df) - 1, max(0, len(df) - 6), -1):
if i > 0 and df.iloc[i]["low"] < df.iloc[i-1]["low"]:
holding_prior_bar = False
break
# 7. Determine signal
is_capitulating = (
(below_lower or above_upper) and
vol_ratio > 2.0 and
consecutive >= 3 and
atr_multiples > 1.5
)
# Entry levels
if direction == "down" and is_capitulating:
entry_level = prev["high"] # Break of prior bar high
stop_level = df.tail(10)["low"].min() # Low of move
target_level = ma.iloc[-1] # 20MA equilibrium
elif direction == "up" and is_capitulating:
entry_level = prev["low"] # Break of prior bar low
stop_level = df.tail(10)["high"].max() # High of move
target_level = ma.iloc[-1]
else:
entry_level = stop_level = target_level = None
risk = abs(entry_level - stop_level) if entry_level and stop_level else 0
reward = abs(target_level - entry_level) if entry_level and target_level else 0
rr_ratio = round(reward / risk, 2) if risk > 0 else 0
return {
"is_capitulating": is_capitulating,
"direction": "LONG (buy the panic)" if direction == "down" else "SHORT (sell the euphoria)",
"score": score,
"grade": score.grade,
"normalized_score": score.normalized_score,
"action": score.action,
"estimated_win_rate": score.estimated_win_rate,
"expected_retracement": score.expected_retracement,
"entry": round(entry_level, 5) if entry_level else None,
"stop": round(stop_level, 5) if stop_level else None,
"target_ma": round(target_level, 5) if target_level else None,
"risk_reward": rr_ratio,
"bb_lower": round(lower_bb.iloc[-1], 5),
"bb_upper": round(upper_bb.iloc[-1], 5),
"ma_20": round(ma.iloc[-1], 5),
"atr_multiples_from_ma": round(atr_multiples, 2),
"volume_ratio": round(vol_ratio, 2),
"consecutive_bars": consecutive,
"holding_prior_bar_pattern": holding_prior_bar,
"slope_assessment": (
"WATERFALL (asymptotic)" if score.rate_of_change >= 8 else
"Sharp selloff" if score.rate_of_change >= 5 else
"Moderate acceleration" if score.rate_of_change >= 3 else
"Slow/steady (AVOID)"
),
}
@staticmethod
def trail_prior_bar(df: pd.DataFrame, direction: str = "long") -> dict:
"""
Calculate trailing stop using prior bar lows (long) or prior bar highs (short).
"""
current = df.iloc[-1]
prev = df.iloc[-2] if len(df) > 1 else current
if direction == "long":
trail_stop = prev["low"]
status = "HOLD" if current["close"] > trail_stop else "STOPPED OUT"
else:
trail_stop = prev["high"]
status = "HOLD" if current["close"] < trail_stop else "STOPPED OUT"
return {
"direction": direction,
"trail_stop": round(trail_stop, 5),
"current_price": round(current["close"], 5),
"status": status,
}
@staticmethod
def scan_capitulation_candidates(
symbols_data: dict,
min_consecutive: int = 3,
min_volume_ratio: float = 2.0,
min_atr_multiples: float = 1.5
) -> list:
"""
Scan multiple symbols for capitulation setups.
symbols_data: dict of {symbol: DataFrame}
Returns sorted list of candidates by score.
"""
candidates = []
detector = CapitulationDetector()
for symbol, df in symbols_data.items():
try:
result = detector.detect_waterfall(df)
if result["is_capitulating"]:
candidates.append({
"symbol": symbol,
**result
})
except Exception:
continue
# Sort by normalized score (best first)
candidates.sort(key=lambda x: x["normalized_score"], reverse=True)
return candidates"Don't buy the front side. Don't buy the front side. Wait for the turn. Wait for the turn."
| Skill | How It Integrates |
|---|---|
mean-reversion-engine | Use Bollinger/RSI for quantitative confirmation alongside this framework |
market-regime-classifier | Capitulation trades work in ALL regimes (unlike standard mean reversion) |
risk-and-portfolio | Scale position size to rubric grade (A++ = max, C = minimum) |
volume-profile-strategy | Volume capitulation spike is a key variable in the scoring rubric |
trading-brain | Route extreme-move alerts to this capitulation framework |
mt5-ea-code-generator | Can codify the prior-bar-highs/lows trailing system as an EA |
trade-journal-analytics | Log rubric scores alongside trades to refine the scoring system over time |
CAPITULATION TRADE CHECKLIST:
[] Size of move: How many ATRs from equilibrium?
[] Rate of change: Waterfall or grind? (Need waterfall)
[] News: No news = best. Old news = acceptable.
[] Consecutive bars: 4+ in same direction?
[] Forced flow: Evidence of liquidations/margin calls?
[] Sentiment: At an extreme?
[] Boringness: Was this a boring/stable security before?
[] Volume: Capitulation spike (>2x average)?
[] Daily chart: Does higher TF support the trade?
[] Bollinger: Beyond the bands?
[] Distance from MA: Multiple ATRs away?
[] Number of legs: 3+ legs in the move?
ENTRY: Right Side of V (break prior bar highs/lows)
STOP: Low/high of the move
TRAIL: Prior bar lows (long) / Prior bar highs (short)
TARGET: 20-period moving average (equilibrium)
GRADE -> SIZE:
A++ = Max | A = Strong | B = Small | C = Tiny | <C = NO TRADE~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.