In partnership with

7 Ways to Take Control of Your Legacy

Planning your estate might not sound like the most exciting thing on your to-do list, but trust us, it’s worth it. And with The Investor’s Guide to Estate Planning, preparing isn’t as daunting as it may seem.

Inside, you’ll find {straightforward advice} on tackling key documents to clearly spell out your wishes.

Plus, there’s help for having those all-important family conversations about your financial legacy to make sure everyone’s on the same page (and avoid negative future surprises).

Why leave things to chance when you can take control? Explore ways to start, review or refine your estate plan today with The Investor’s Guide to Estate Planning.

Elite Quant Plan – 14-Day Free Trial (This Week Only)

No card needed. Cancel anytime. Zero risk.

You get immediate access to:

  • Full code from every article (including today’s HMM notebook)

  • Private GitHub repos & templates

  • All premium deep dives (3–5 per month)

  • 2 × 1-on-1 calls with me

  • One custom bot built/fixed for you

Try the entire Elite experience for 14 days — completely free.

→ Start your free trial now 👇

(Doors close in 7 days or when the post goes out of the spotlight — whichever comes first.)

See you on the inside.

👉 Upgrade Now

🔔 Limited-Time Holiday Deal: 20% Off Our Complete 2026 Playbook! 🔔

Level up before the year ends!

AlgoEdge Insights: 30+ Python-Powered Trading Strategies – The Complete 2026 Playbook

30+ battle-tested algorithmic trading strategies from the AlgoEdge Insights newsletter – fully coded in Python, backtested, and ready to deploy. Your full arsenal for dominating 2026 markets.

Special Promo: Use code DECEMBER2025 for 20% off

Valid only until December 20, 2025 — act fast!

👇 Buy Now & Save 👇

Instant access to every strategy we've shared, plus exclusive extras.

— AlgoEdge Insights Team

Premium Members – Your Full Notebook Is Ready

The complete Google Colab notebook from today’s article (with live data, full Hidden Markov Model, interactive charts, statistics, and one-click CSV export) is waiting for you.

Preview of what you’ll get:

Inside:

  • Automatic stock data download using yfinance (default NVDA, 2y daily)

  • Custom Fair Value Gap (FVG) trailing stop indicator with dynamic memory, displacement smoothing, and regime detection

  • Tweakable parameters for ATR length, base FVG length, smoothing, gap threshold, envelope multiplier, volatility sensitivity, etc.

  • Matplotlib chart displaying price, bull/bear displacement lines, and trailing stops (with optional inactive side masking)

  • No explicit tables or CSV export (focus is on computation and visualization)

  • Bonus: works on any ticker, period, or interval with one line change

Free readers – you already got the full breakdown and visuals in the article. Paid members – you get the actual tool.

Not upgraded yet? Fix that in 10 seconds here👇

Google Collab Notebook With Full Code Is Available In the End Of The Article Behind The Paywall 👇 (For Paid Subs Only)

Fair value gaps mark points where price moves quickly and leave a gap between buyers and sellers.

These gaps can signal support / resistance zones and bullish / bearish swings. This is especially so when the gaps are scaled by volatilility.

With the method presented here, we get two key lines on the chart: one for potential support, one for potential resistance.

When price crosses above the resistance line, it signals bullish control. If price falls below the support line, bearish momentum takes over.

What investment is rudimentary for billionaires but ‘revolutionary’ for 70,571+ investors entering 2026?

Imagine this. You open your phone to an alert. It says, “you spent $236,000,000 more this month than you did last month.”

If you were the top bidder at Sotheby’s fall auctions, it could be reality.

Sounds crazy, right? But when the ultra-wealthy spend staggering amounts on blue-chip art, it’s not just for decoration.

The scarcity of these treasured artworks has helped drive their prices, in exceptional cases, to thin-air heights, without moving in lockstep with other asset classes.

The contemporary and post war segments have even outpaced the S&P 500 overall since 1995.*

Now, over 70,000 people have invested $1.2 billion+ across 500 iconic artworks featuring Banksy, Basquiat, Picasso, and more.

How? You don’t need Medici money to invest in multimillion dollar artworks with Masterworks.

Thousands of members have gotten annualized net returns like 14.6%, 17.6%, and 17.8% from 26 sales to date.

*Based on Masterworks data. Past performance is not indicative of future returns. Important Reg A disclosures: masterworks.com/cd

1. How FVG Trailing Stop Works

A fair value gap is a section of the chart where price moves so fast, it skips over certain levels. No trades happen in that range.

The market leaves behind ‘unfinished business’, i.e. zones where aggressive buyers or sellers overwhelmed the other side.

In practical terms:

A fair value gap forms when the low of a bar is higher than the high of the previous bar (up-gap), or the high of a bar is lower than the low of the previous bar (down-gap):

In liquid markets, this type of gap shows strong and sudden imbalance, usually after important news, a breakout, or heavy volume.

These gaps are magnets for price. The market often returns to “fill” them.

Figure 1. Fair Value Gap Analysis: Price gaps up, leaves an unfilled zone, then drifts back to fill it.

But not all gaps matter equally.

Gaps in quiet markets are common noise. The signal comes when a gap forms and volatility is high.

That’s why we scale gap detection by volatility, using ATR.

Volatility-Adjusted Gap Memory

To avoid treating every gap the same, the method adapts how long a gap “counts” as significant.

The gap memory length changes as volatility changes:

  • When ATR is above normal, the method remembers gaps longer.

  • When ATR drops, it forgets gaps faster.

This focuses attention on gaps formed during meaningful price moves.

Tracking Swings and Marking Levels

The algorithm looks for two types of fair value gaps:

  • Bullish gap (signals a burst of buying strength).

  • Bearish gap (signals a flush of selling pressure).

Each time a new gap appears, it’s added to the list of active levels. If price returns and “fills” a gap, that level drops off the list.

Constructing the Support and Resistance Lines

For each bar, the method takes the average of all active bullish gaps (potential support) and the average of all active bearish gaps (potential resistance).

Both lines are smoothed with an exponential moving average:

where

Regime Detection: When Price Crosses a Line

  • If price closes above the resistance line, it signals bullish control.

  • If price closes below the support line, bearish momentum takes over.

Say NVDA gaps up after earnings, jumping $12 above the previous day’s high while ATR is high.

The method marks this as a new resistance. If price holds above the resistance line, it stays in a bullish regime.

If price falls and closes below the support line (maybe on a high-volume reversal), the signal flips to bearish.

2. FVG Trailing Stops in Python

2.1 Setting the Parameters

First, import the necessary libraries.

Then, we set key parameters to control how sensitive the method is, how wide stops are, and how volatility scaling works.

  • Increase ATR_LEN: fewer, smoother gaps.

  • Increase BASE_FVG_LENgaps last longer, regime flips are slower.

  • Increase SMOOTH_LEN: fewer whipsaws.

  • Increase GAP_Z: only large gaps count.

  • Increase ENV_MULT: wider stops, looser risk.

  • Increase VOL_SENSITIVITY: memory expands more when volatility rises.

# Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import yfinance as yf

# Parameters — tweak here
TICKER             = "NVDA"
PERIOD             = "2y"      # e.g. "60d", "1y"
INTERVAL           = "1d"      # e.g. "15m", "1d"

ATR_LEN           = 20   # ↑ smoother ATR, fewer gaps; ↓ faster ATR, more gaps
BASE_FVG_LEN      = 5    # ↑ gaps persist, slower flips; ↓ gaps decay, quicker flips
SMOOTH_LEN        = 9    # ↑ slower displacement, fewer whipsaws; ↓ faster, more whipsaws
GAP_Z             = 0.8  # ↑ only big gaps count; ↓ small gaps count too
ENV_MULT          = 0.6  # ↑ wider stop, looser risk; ↓ tighter stop, quicker exits
VOL_SENSITIVITY   = 0.5  # ↑ memory expands with vol; ↓ memory stays near base
SHOW_INACTIVE_SIDE = False  # True shows both lines; False hides inactive line

2.2 Getting the Data Function

The download function pulls clean OHLCV data with yfinance.

Can be daily, hourly, etc.

def download_ohlc(ticker: str, period: str, interval: str) -> pd.DataFrame:
    df = yf.download(
        ticker, period=period, interval=interval,
        auto_adjust=True, progress=False
    )
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.get_level_values(0)
    df.columns = df.columns.map(str.title)
    return df.dropna(subset=["Open", "High", "Low", "Close"])

2.3 Technical Indicator Functions

ATR and EMA are the core indicators. ATR measures recent volatility. EMA smooths noisy levels.

def atr(df: pd.DataFrame, length: int) -> pd.Series:
    high, low, close = df["High"], df["Low"], df["Close"]
    tr = pd.concat(
        [high - low, (high - close.shift()).abs(), (low - close.shift()).abs()],
        axis=1
    ).max(axis=1)
    return tr.rolling(length).mean()

def ema(prev, value, alpha):
    return value if np.isnan(prev) else alpha * value + (1 - alpha) * prev

2.4 Core Indicator: FVG Trailing Stop

This function runs the logic:

  • Finds fair value gaps.

  • Tracks regime flips.

  • Adjusts support/resistance and stops in real time.

def fvg_trailing_stop(df: pd.DataFrame,base_fvg_len: int,smooth_len: int,gap_z: float,env_mult: float,vol_sensitivity: float,atr_len: int) -> pd.DataFrame:
    n     = len(df)
    a     = atr(df, atr_len)
    a_med = a.rolling(100, min_periods=1).median()

    bull_lvls, bear_lvls = [], []
    bull_empty = bear_empty = -1
    bull_disp  = np.full(n, np.nan)
    bear_disp  = np.full(n, np.nan)
    os         = np.zeros(n, dtype=int)
    ts         = np.full(n, np.nan)

    alpha = 2 / (smooth_len + 1)

    for i in range(n):
        if i < 2 or np.isnan(a.iat[i]):
            continue

        # dynamic memory
        atr_ratio = a.iat[i] / a_med.iat[i]
        dyn_len   = int(
            np.clip(
                round(base_fvg_len * (1 + vol_sensitivity * (atr_ratio - 1))),
                3, 20
            )
        )

        hi2, lo2 = df["High"].iat[i - 2], df["Low"].iat[i - 2]
        c1       = df["Close"].iat[i - 1]
        price    = df["Close"].iat[i]
        atr_i    = a.iat[i]

        # bullish gap
        if price > hi2 and (price - hi2) / atr_i > gap_z and c1 > hi2:
            bull_lvls.append(hi2)
            bull_lvls = bull_lvls[-dyn_len:]

        # bearish gap
        if lo2 > price and (lo2 - price) / atr_i > gap_z and c1 < lo2:
            bear_lvls.append(lo2)
            bear_lvls = bear_lvls[-dyn_len:]

        # purge repaired gaps
        bull_lvls = [lvl for lvl in bull_lvls if price >= lvl]
        bear_lvls = [lvl for lvl in bear_lvls if price <= lvl]
        if not bull_lvls: bull_empty = i
        if not bear_lvls: bear_empty = i

        # displacement sources
        b_bs  = max(i - bull_empty, 1)
        br_bs = max(i - bear_empty, 1)
        b_sma  = df["Close"].iloc[i - b_bs + 1 : i + 1].mean()
        br_sma = df["Close"].iloc[i - br_bs + 1 : i + 1].mean()

        bull_src = np.mean(bull_lvls) if bull_lvls else b_sma
        bear_src = np.mean(bear_lvls) if bear_lvls else br_sma

        bull_disp[i] = ema(bull_disp[i - 1], bull_src, alpha)
        bear_disp[i] = ema(bear_disp[i - 1], bear_src, alpha)

        # regime detection & trailing stop
        prev_os = os[i - 1] if i else 0
        prev_ts = ts[i - 1] if i else np.nan

        if price > bear_disp[i]:
            os[i] = 1
        elif price < bull_disp[i]:
            os[i] = -1
        else:
            os[i] = prev_os

        bull_stop = bull_disp[i] + env_mult * atr_i
        bear_stop = bear_disp[i] - env_mult * atr_i

        if os[i] > prev_os:                # new long
            ts[i] = bull_stop
        elif os[i] < prev_os:              # new short
            ts[i] = bear_stop
        else:
            if os[i] == 1:
                ts[i] = max(bull_stop, prev_ts)
            elif os[i] == -1:
                ts[i] = min(bear_stop, prev_ts)

    out = df.copy()
    out["ATR"]       = a
    out["bull_disp"] = bull_disp
    out["bear_disp"] = bear_disp
    out["os"]        = os
    out["ts"]        = ts
    return out

2.5 Plotting Function

This displays price, regime lines, and trailing stops, color-coded by market regime.

def plot_fvg_trailing(df: pd.DataFrame, title: str, mask_inactive: bool) -> None:
    plt.style.use("dark_background")
    GREEN, RED = "#089981", "#f23645"
    p = df.copy()
    p["x"] = mdates.date2num(p.index)

    fig, ax = plt.subplots(figsize=(15, 7))
    fig.patch.set_facecolor("#0f1117")
    ax.set_facecolor("#0f1117")

    # 5.1 price
    ax.plot(p["x"], p["Close"], color="white", linewidth=0.8)

    # 5.2 displacement
    if mask_inactive:
        bull_disp = p["bull_disp"].where(p["os"] == 1, np.nan)
        bear_disp = p["bear_disp"].where(p["os"] == -1, np.nan)
    else:
        bull_disp, bear_disp = p["bull_disp"], p["bear_disp"]

    ax.plot(p["x"], bull_disp, color=GREEN, linewidth=1.0)
    ax.plot(p["x"], bear_disp, color=RED,   linewidth=1.0)

    # 5.3 trailing stop (always side-masked)
    ts_bull = p["ts"].where(p["os"] == 1, np.nan)
    ts_bear = p["ts"].where(p["os"] == -1, np.nan)
    ax.plot(p["x"], ts_bull, color=GREEN, linewidth=1.4)
    ax.plot(p["x"], ts_bear, color=RED,   linewidth=1.4)

    # 5.4 filled bands
    if mask_inactive:
        ax.fill_between(p["x"], ts_bull, bull_disp,
                        where=p["os"] == 1, color=GREEN, alpha=0.20)
        ax.fill_between(p["x"], ts_bear, bear_disp,
                        where=p["os"] == -1, color=RED,   alpha=0.20)
    else:
        ax.fill_between(p["x"], ts_bull, p["bull_disp"],
                        where=p["os"] == 1, color=GREEN, alpha=0.20)
        ax.fill_between(p["x"], ts_bear, p["bear_disp"],
                        where=p["os"] == -1, color=RED,   alpha=0.20)

    # cosmetics
    ax.xaxis_date()
    ax.set_title(title, color="white", pad=12)
    ax.tick_params(axis="x", colors="#aaaaaa")
    ax.tick_params(axis="y", colors="#aaaaaa")
    ax.grid(False)
    plt.tight_layout()
    plt.show()

2.6 Run and Get the Results

Put it all together show the plot:

# Run
raw = download_ohlc(TICKER, PERIOD, INTERVAL)
res = fvg_trailing_stop(
    raw,
    base_fvg_len=BASE_FVG_LEN,
    smooth_len=SMOOTH_LEN,
    gap_z=GAP_Z,
    env_mult=ENV_MULT,
    vol_sensitivity=VOL_SENSITIVITY,
    atr_len=ATR_LEN
)
plot_fvg_trailing(
    res,
    title=f"{TICKER}{INTERVAL}  FVG Trailing Stop ({PERIOD}) ",
    mask_inactive=not SHOW_INACTIVE_SIDE
)

Figure 2. Bullish and bearish regime zones for NVDA, updated in real time as volatility shifts.

Green bands show active support during bullish swings. Red bands show resistance during bearish swings.

When the price is above green, the trend is bullish. When price drops below red, the trend is bearish.

A regime flip occurs when price crosses from one zone to the other. Only one regime is active at any time.

The width of each band adapts to market volatility, so zones widen during fast moves and contract when the market is quiet.

To read the chart:

  • Track which color the price sits above or below to see current regime.

  • A switch from green to red, or vice versa, signals a shift in control, i.e. bulls to bears or bears to bulls.

  • The trailing band marks stop levels for each side, updating as volatility changes.

  • Large regime flips often cluster around big price moves or breakouts.

3. Limitations and Extensions

This method is built for trending, liquid markets. In slow, range-bound periods, fair value gaps are less meaningful and regime flips cluster together. Very tight ATR or smoothing settings can exaggerate this effect.

False signals can occur during major news events, when volatility spikes and gaps widen sharply. In thinly traded assets, gaps are often just noise, not real supply/demand imbalances.

The model relies on recent price and volatility. It does not account for volume, fundamentals, or broader macro context.

Possible extensions:

  • Add a volume filter to reduce noise from illiquid moves.

  • Combine with momentum or trend strength filters to confirm regime flips.

  • Use ensemble approaches: combine FVG stops with classic trailing ATR, Keltner Channels, or moving averages.

  • Tune parameters using historical walk-forward analysis to reduce overfitting.

Concluding Thoughts

Unlike static stop rules, this method adapts to current conditions and highlights the zones where actual buying or selling pressure is unfinished.

The key information sits in the gaps themselves, not just the price bars.

logo

Subscribe to our premium content to read the rest.

Become a paying subscriber to get access to this post and other subscriber-only content.

Upgrade

Keep Reading