AgentStack
SKILL verified MIT Self-run

Capitulation Mean Reversion

skill-mahmoud20138-tradecraft-capitulation-mean-reversion · by mahmoud20138

>

No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add skill-mahmoud20138-tradecraft-capitulation-mean-reversion

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Capitulation Mean Reversion? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Lance Breitstein's Capitulation Mean Reversion Framework

> 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).

Core Philosophy

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.


The 7-Variable Checklist

At any given moment, Lance is constantly weighing ALL factors dynamically. Each variable is scored on a mental 0-10 scale.

Variable 1: Size of Move

  • S&P down 1 penny? Not interesting. S&P down $100? Very interesting.
  • The LARGER the move from equilibrium, the more appealing for mean reversion.
  • Measure in % terms AND absolute terms relative to normal ranges.

Variable 2: Speed / Rate of Change (THE MOST IMPORTANT)

  • A 5% move over a year = not interesting.
  • A 5% move in 1 minute = extremely interesting.
  • Slope analysis grades:

| 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" |

  • The ideal pattern: Boring/stable -> steady decline -> acceleration -> WATERFALL (asymptotic).
  • Rate of change CRESCENDO is the key visual signature.

Variable 3: News Presence

  • No news = best. The move is purely technical/sentiment-driven, equilibrium hasn't changed.
  • Fundamental news that changes fair value SHIFTS the equilibrium — less appealing for reversion.
  • Old/stale news driving fresh panic = still acceptable (news already priced in prior move).

Variable 4: Number of Days/Bars in a Row

  • Efficient market theory says each day is independent 50/50. Lance says this is absolutely not true.
  • S&P down 3% for 8 days straight? The probability of bouncing on day 9 is FAR above 50%.
  • More consecutive directional bars = higher probability of reversal.
  • This is a fractal — applies on 2-min bars, hourly, daily, weekly.

Variable 5: Forced Buying/Selling

  • Forced liquidations (margin calls, redemptions, loss of borrow).
  • When hedge funds MUST sell billions regardless of price, the clearing mechanism creates

extreme dislocation from fair value.

  • COVID crash was a textbook example of forced selling creating capitulation opportunities.

Variable 6: Sentiment

  • Euphoria = "nothing can ever go wrong" = short opportunity.
  • Despair = "nothing will ever go right again" = long opportunity.
  • Truth is always somewhere in the middle.
  • Sentiment EXTREMES are what create the best mean reversion setups.

Variable 7: Diversification / Market Cap / "Boringness"

  • The more boring and stable = the better the mean reversion trade.
  • Ranking (safest to riskiest for mean reversion):
  1. Government bonds (quantifiable par value, near-zero default risk)
  2. S&P 500 / diversified indices
  3. Large-cap blue chips (Bank of America, Apple)
  4. Mid-caps with analyst coverage
  5. Small/micro-caps (least safe, but can produce biggest moves)
  6. Crypto/Bitcoin (no cash flows, but increasingly institutionalized)
  • Sub-factors within this variable:
  • How quantitative/calculable is fair value? (Bonds > stocks > crypto)
  • How many "eyes" are on it? (More attention = faster mean reversion)
  • How stable historically? (Low ATR relative to current move = better)
  • What are normal average trading ranges? (Current bars should be MULTIPLES of normal)

Bonus Variable: Long vs Short (Structural Asymmetry)

  • Longs have a $0 floor = maximum loss is capped.
  • Shorts have unlimited risk = no upper bound.
  • This structural difference means long-side capitulation trades are inherently safer.
  • Short-side waterfall plays require SMALLER size to compensate for tail risk.

The Mental Rubric / Scoring System

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

Grade Thresholds

| 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 | | $60.

  • Daily: Volume so extreme prior bars' volume is invisible on chart.
  • Intraday: 7 consecutive green bars, massive distance from MA ($26 MA vs $60 price).
  • Entry: Short the break of prior bar lows.
  • Result: ~$20/share gain, ~3:1 R:R, ~70%+ win rate.
  • Risk factor: Short side = uncapped risk. Used smaller size despite A++ grade.
  • Lesson: Best trade of 2020 for many. One of the most extreme examples of the framework.

Example 4: UAMY (Oct 2025) — Grade: B+ / A-

  • Setup: Rare earth stock, historically boring (20-30 cents). Doubled in 1-2 weeks on trade war.
  • Daily bars expanding: $1 range -> $3 -> $4 -> $5 range bars.
  • Cleanly held prior bar lows: 8-9 consecutive bars up.
  • Entry: Short break of prior bar lows at ~$15.50.
  • Deductions: Short side, low market cap, less liquid.
  • Result: ~$3/share gain. Trailing prior bar highs.

Example 5: Bitcoin (Nov 2025) — Grade: A (Swing Long)

  • Setup: 3 legs down, 30%+ decline over weeks. Stable -> panicked 5% in 4 minutes at 2:30am.
  • Daily: Highest volume flush, most extended below Bollinger. Normal bar range $1-2K, this bar $8K.
  • Entry: Had blind bids in (front-side, small size). Added on trend break above MA.
  • Intra-bar bounce: Nearly 100% retracement on the flush candle.
  • Swing management: Still holding core, trailing prior bar lows.
  • Deduction: Bitcoin not quantifiable (no cash flows), but institutionalized.

Scanning / Finding Setups

Daily Chart Filters

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)

Intraday Filters

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 open

Mindset

  • Never pre-commit to specific tickers. Trade whatever offers the BEST opportunity.
  • Move to where the extreme setups are — different stocks/products each day.
  • Pocket aces (A++ setups) are rare. Most days are B/C setups or no-trades.

Python Implementation

import 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 " 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 " 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"]  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"]  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"]  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"]  list:
        """
        Scan multiple symbols for capitulation setups.
        symbols_data: dict of {symbol: DataFrame}
        Returns sorted list of candidates by score.
        """
        candidates = []
        detector = CapitulationDetector()

        for sy

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [mahmoud20138](https://github.com/mahmoud20138)
- **Source:** [mahmoud20138/Tradecraft](https://github.com/mahmoud20138/Tradecraft)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.