In partnership with

The headlines that actually moves markets

Tired of missing the trades that actually move markets?

Every weekday, you’ll get a 5-minute Elite Trade Club newsletter covering the top stories, market-moving headlines, and the hottest stocks — delivered before the opening bell.

Whether you’re a casual trader or a serious investor, it’s everything you need to know before making your next move.

Join 200K+ traders who read our 5-minute premarket report to see which stocks are setting up for the day, what news is breaking, and where the smart money’s moving.

By joining, you’ll receive Elite Trade Club emails and select partner insights. See Privacy Policy.

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 WINTER2025 for 20% off

Valid only until January 10, 2026 — 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 via Yahoo Finance (customizable ticker, date range, or full history)

  • Flexible linear regression modes: Static (fixed segments) or Rolling (bar-by-bar updates)

  • Periodic or fixed anchors for resetting regressions (daily, weekly, monthly, or from first bar)

  • Trend lines with deviation bands based on residual standard deviation (multiplier-adjustable)

  • Beautiful dark-themed Matplotlib charts with price overlay, dashed bands, and optional gradient fills

  • Dynamic bicolor lines (green for uptrends, red for downtrends) or monochrome option

  • Console dashboard with method details, segment stats, slopes, and correlations

  • Ready-to-customize: works on any ticker (stocks, indices, crypto) with one-line parameter 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)

Linear regressions can reveal more than one trend. Anchored over time, and applied in segments, they show how market structure evolves.

In this article, we model price action using time-anchored regressions, slicing the series by calendar intervals and fitting separate lines to each.

Static and rolling modes supported. Static mode splits data into windows. Rolling mode updates regressions bar-by-bar, anchored to periodic resets.

You’ll also see how to visualize these segments with slope-based coloring and confidence bands derived from residual volatility.

End-to-end Implementation Python Notebook provided below for paid members only

This article is structured as follows:

  • Time-Anchored Linear Regression

  • Building Regression Segments

  • Plotting Results

  • Benefits and Limitations

1. A Structural Approach to Trend Detection

Traditional trendlines oversimplify market behavior. They assume a single trend across the entire price history.

But markets evolve, sometimes gradually, sometimes abruptly. A single regression line can’t capture that complexity.

Time-anchored linear regression provide an alternative. It segments the price series into smaller, calendar-based intervals.

Each segment receives its own linear model. Regressions adapt to local structure and show short-term directional changes and trend persistence.

Here’s how it works.

Segmenting the Price Series by Time

The first step is to divide the data based on time-based anchors, i.e. daily, weekly, or monthly.

Each segment contains a slice of price data that corresponds to a fixed calendar period.

This avoids lookahead bias and aligns with how traders think in terms of weeks, months, or quarters.

For each segment, we fit a simple linear regression:

  • y is the price

  • x is the index position (e.g. bar number)

  • m is the slope (trend strength and direction)

  • b is the intercept

The slope m indicates whether the local trend is rising or falling. The steeper the slope, the stronger the trend.

To assess the fit quality, we calculate the residual standard deviation:

This gives us a way to define upper and lower bands:

k is a user-defined multiplier. These bands visualize uncertainty. A wider band signals more variability within the trend.

Static vs. Rolling Regression Modes

There are two core modes implemented here:

  1. Static Regression
    Each time segment is treated independently. Once a window closes (e.g. end of the week), the regression is calculated and fixed.

  2. Rolling Regression
    The regression updates continuously from a reset point. As new bars arrive, the line evolves. Anchors reset periodically (e.g. every Monday).

2. Python Implementation

Everything here is self-contained , just copy the code and run it. You can adjust the analysis using a few parameters.

2.1 Modeling Settings

Here, you can modify the ticker symbol, date range, regression mode (“Static” or “Rolling”), anchor type, resolution, and styling options.

ANCHOR_TYPE sets where regressions begin.

  • "First Bar" starts one long regression from the dataset's beginning.

  • "Periodic" restarts regressions at each time window (e.g. weekly, monthly).

MULT controls band width by scaling the regression’s residual standard deviation.

  • Higher MULT = wider bands, more noise tolerance.

  • Lower MULT = tighter bands, more sensitivity to outliers.

import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime

plt.style.use("dark_background")

# USER PARAMETERS
TICKER = "ASML.AS"           # Stock symbol to fetch data from Yahoo Finance
START_DATE = "2023-01-01"    # Start date of historical data
END_DATE   = "2025-06-01"    # End date of historical data

# --- Main Logic Mode ---
METHOD = "Rolling"           # "Static" => regression per fixed window. "Rolling" => regression updates bar-by-bar

# --- Anchor Logic ---
ANCHOR_TYPE = "Periodic"     # "First Bar" => anchor from start. "Periodic" => reset based on time
ANCHOR_RES  = "weekly"       # Defines how frequently the anchor resets ("daily", "weekly", "monthly", etc.)

# --- Price Column ---
SOURCE_COL = "Close"         # Which column to use for regression (e.g., "Close", "Adj Close")

# --- Band Settings ---
MULT = 1.0                   # Multiplier for standard deviation bands (higher = wider bands)
SHOW_BANDS = True            # If True, draw upper/lower deviation bands around regression line

# --- Color Config ---
BICOLOR    = True            # If True, use separate colors based on slope sign (up/down)
MONO_COLOR = "#FF5D00"       # If not bicolor, this color will be used for all lines
COL_A = "#089981"            # Slope >= 0 color
COL_B = "#F23645"            # Slope < 0 color

# --- Visual Enhancements ---
GRADIENT = True              # If True, fill area between regression line and bands with a soft gradient
SHOW_DASHBOARD = True        # If True, print summary stats of each regression segment to console

2.2 Load and Clean Price Data

We download daily price data for the selected ticker and clean it. The closing price is standardized as the main reference:

The date index is also converted to a NumPy array for easier positional indexing later.

# Data Download & Preprocessing
# Download daily price data and clean it up.
df = yf.download(TICKER, start=START_DATE, end=END_DATE, interval="1d")
if df.empty:
    print("No data returned for the given ticker/date range.")
    exit(1)

# Flatten MultiIndex columns if present
if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.map("_".join)

# Check if the SOURCE_COL exists; if not, try to find a column starting with it.
if SOURCE_COL not in df.columns:
    found = False
    for col in df.columns:
        if col.startswith(SOURCE_COL):
            df["Close"] = df[col]
            found = True
            print(f"Column '{col}' found. Renaming it to 'Close'.")
            break
    if not found:
        print(f"Column '{SOURCE_COL}' not found. Available columns: {df.columns}")
        exit(1)
else:
    df["Close"] = df[SOURCE_COL]

df.dropna(inplace=True)
if df.empty:
    print("Data is empty after cleaning. Exiting.")
    exit(1)

# Standardize columns for ease of use
df["Price"] = df["Close"]
df["Time"]  = df.index
# Convert date index to a NumPy array for positional indexing later.
dates = df.index.to_numpy()

2.3 Segment Price Series by Time

The price is segmented and a linear regression is fitted on each segment. Two modes previously discussed are supported: static and rolling.

# Build Regression Segments
# In this step, we segment the price series and fit linear regressions to each segment.
# Two modes exist:
#   - "Static": Compute regression for fixed segments (either from the first bar or periodic segments).
#   - "Rolling": Continuously update regression from an anchor point that resets periodically.

# Define helper function: get_timeframe_key, to segment the data by a chosen resolution.
def get_timeframe_key(timestamp, res="W"):
    dt = timestamp.to_pydatetime()
    if res.upper() == "W":
        y, w, _ = dt.isocalendar()
        return f"{y}-W{w}"
    elif res.upper() == "M":
        return f"{dt.year}-{dt.month:02d}"
    elif res.upper() == "D":
        return f"{dt.year}-{dt.month:02d}-{dt.day:02d}"
    elif res.upper().startswith("H"):
        hours = int(res[1:]) if len(res) > 1 else 1
        total_hours = dt.day * 24 + dt.hour
        chunk = total_hours // hours
        return f"{dt.year}-{dt.month:02d}-{dt.day:02d}-H{chunk}"
    else:
        y, w, _ = dt.isocalendar()
        return f"{y}-W{w}"

# Define helper function: linear_regression, which computes slope, intercept, correlation, and residual stdev.
def linear_regression(xs, ys):
    x = np.array(xs, dtype=float)
    y = np.array(ys, dtype=float)
    if len(x) < 2:
        return np.nan, np.nan, 0.0, 0.0
    N = len(x)
    Ex = x.sum()
    Ey = y.sum()
    Ex2 = (x * x).sum()
    Exy = (x * y).sum()
    denom = (N * Ex2 - Ex**2)
    m = (N * Exy - Ex * Ey) / denom if abs(denom) > 1e-12 else 0.0
    b = (Ey - m * Ex) / N
    r = np.corrcoef(x, y)[0, 1] if len(x) >= 2 else 0.0
    yhat = m * x + b
    residuals = y - yhat
    rss = np.mean(residuals**2)
    stdev_resid = np.sqrt(rss)
    return m, b, r, stdev_resid

# Define helper function: color_from_slope_or_corr. Returns a color based on slope and settings.
def color_from_slope_or_corr(m, r, bicolor, colA, colB, mono):
    if bicolor:
        return colA if m >= 0 else colB
    else:
        return mono

prices = df["Price"].values
xs = np.arange(len(df))  # Using bar indices for regression

segments = []  # To hold regression segments

if METHOD == "Static":
    if ANCHOR_TYPE == "First Bar":
        # Use the entire series as one segment.
        m, b, r, resid_std = linear_regression(xs, prices)
        segments.append({
            "start_idx": 0,
            "end_idx": len(df) - 1,
            "slope": m,
            "intercept": b,
            "stdev": resid_std,
            "corr": r
        })
    else:
        # Periodic segmentation based on a timeframe key (e.g., weekly).
        last_key = get_timeframe_key(df["Time"].iloc[0], ANCHOR_RES)
        seg_start = 0
        for i in range(1, len(df)):
            this_key = get_timeframe_key(df["Time"].iloc[i], ANCHOR_RES)
            if this_key != last_key or i == len(df) - 1:
                end_i = i - 1 if this_key != last_key else i
                if end_i >= seg_start:
                    x_slice = xs[seg_start:end_i + 1]
                    y_slice = prices[seg_start:end_i + 1]
                    m, b, r, resid_std = linear_regression(x_slice, y_slice)
                    segments.append({
                        "start_idx": seg_start,
                        "end_idx": end_i,
                        "slope": m,
                        "intercept": b,
                        "stdev": resid_std,
                        "corr": r
                    })
                seg_start = i
                last_key = this_key
else:
    # Rolling regression: continuously update regression from an anchor that resets periodically.
    mid_line = np.full(len(df), np.nan)
    upper_line = np.full(len(df), np.nan)
    lower_line = np.full(len(df), np.nan)
    color_line = [MONO_COLOR] * len(df)
    if ANCHOR_TYPE == "First Bar":
        seg_start = 0
        last_key = None
    else:
        last_key = get_timeframe_key(df["Time"].iloc[0], ANCHOR_RES)
        seg_start = 0
    for i in range(len(df)):
        if i == 0:
            continue
        if ANCHOR_TYPE == "Periodic":
            this_key = get_timeframe_key(df["Time"].iloc[i], ANCHOR_RES)
            if this_key != last_key:
                seg_start = i
                last_key = this_key
        x_slice = xs[seg_start:i + 1]
        y_slice = prices[seg_start:i + 1]
        m, b, r, resid_std = linear_regression(x_slice, y_slice)
        mid_val = m * i + b
        dist = resid_std * MULT
        up_val = mid_val + dist
        dn_val = mid_val - dist
        mid_line[i] = mid_val
        upper_line[i] = up_val
        lower_line[i] = dn_val
        c = COL_A if (BICOLOR and m >= 0) else (COL_B if BICOLOR else MONO_COLOR)
        color_line[i] = c
    segments.append({
        "rolling_mid": mid_line,
        "rolling_up": upper_line,
        "rolling_dn": lower_line,
        "rolling_col": color_line
    })

2.4 Plot Regression Lines and Bands

The final chart displays:

  • The original price series.

  • The regression lines (or rolling regression) overlaying the price.

  • The upper and lower bands that show the regression’s variability.

Optionally, gradient fills between the regression line and the bands for better visual cues.

# Plotting
fig, ax = plt.subplots(figsize=(16, 8))
ax.set_facecolor("#1F1B1B")
ax.grid(True, alpha=0.2, color="white")
ax.set_title(f"Periodic Linear Regressions [{METHOD}] - {TICKER}", color="white")

# Use the date index as our x-axis; convert it to a NumPy array for positional indexing.
dates = df.index.to_numpy()
price = df["Price"]

ax.plot(dates, price, color="white", linewidth=1.2, label="Price")

if METHOD == "Static":
    for seg in segments:
        start_i = seg["start_idx"]
        end_i = seg["end_idx"]
        m = seg["slope"]
        b = seg["intercept"]
        stdev = seg["stdev"]
        r = seg["corr"]
        seg_x = np.arange(start_i, end_i + 1)
        seg_dates = dates[start_i:end_i + 1]
        seg_mid = m * seg_x + b
        dist = stdev * MULT
        seg_up = seg_mid + dist
        seg_down = seg_mid - dist
        c = COL_A if (BICOLOR and m >= 0) else (COL_B if BICOLOR else MONO_COLOR)
        ax.plot(seg_dates, seg_mid, color=c, linewidth=2)
        if SHOW_BANDS:
            ax.plot(seg_dates, seg_up, color=c, linestyle="--", linewidth=1)
            ax.plot(seg_dates, seg_down, color=c, linestyle="--", linewidth=1)
            if GRADIENT:
                ax.fill_between(seg_dates, seg_mid, seg_up, color=c, alpha=0.1)
                ax.fill_between(seg_dates, seg_down, seg_mid, color=c, alpha=0.1)
else:
    seg = segments[0]
    mid_line = seg["rolling_mid"]
    upper_line = seg["rolling_up"]
    lower_line = seg["rolling_dn"]
    color_line = seg["rolling_col"]
    for i in range(1, len(df)):
        c = color_line[i]
        ax.plot([dates[i - 1], dates[i]],
                [mid_line[i - 1], mid_line[i]],
                color=c, linewidth=3)
        if SHOW_BANDS:
            ax.plot([dates[i - 1], dates[i]],
                    [upper_line[i - 1], upper_line[i]],
                    color=c, linestyle="--", linewidth=1)
            ax.plot([dates[i - 1], dates[i]],
                    [lower_line[i - 1], lower_line[i]],
                    color=c, linestyle="--", linewidth=1)
            if not np.isnan(mid_line[i]) and not np.isnan(upper_line[i]):
                ax.fill_between([dates[i - 1], dates[i]],
                                [mid_line[i - 1], mid_line[i]],
                                [upper_line[i - 1], upper_line[i]],
                                color=c, alpha=0.05 if GRADIENT else 0.0)
                ax.fill_between([dates[i - 1], dates[i]],
                                [lower_line[i - 1], lower_line[i]],
                                [mid_line[i - 1], mid_line[i]],
                                color=c, alpha=0.05 if GRADIENT else 0.0)

ax.legend(loc="best", facecolor="#1F1B1B", edgecolor="white")
fig.autofmt_xdate()
plt.tight_layout()

# Optional: Print dashboard info to console.
if SHOW_DASHBOARD:
    print("=== Dashboard ===")
    print(f"Method: {METHOD}")
    print(f"Anchor Type: {ANCHOR_TYPE}")
    print(f"Anchor Period: {ANCHOR_RES}")
    print(f"Bicolor: {BICOLOR}, Gradient: {GRADIENT}")
    if METHOD == "Static":
        print(f"Segments: {len(segments)} total.")
        for i, seg in enumerate(segments):
            slope = seg["slope"]
            corr = seg["corr"]
            print(f" Segment {i}: slope = {slope:.4f}, corr = {corr:.4f}")
    else:
        print("Rolling mode => 1 big rolling segment.")

plt.show()

Figure 1. Price series overlaid with time-anchored linear regressions. Each segment reflects a fixed calendar window with its own slope and residual volatility bands. Positive trends are shown in green, negative in red. Bands represent ±1 standard deviation from the fitted line, scaled by the MULT parameter.

3. Use Cases in Trading

  • Regime Detection: Markets don’t trend forever. When slope direction changes consistently across segments, it signals a potential regime shift.

  • Volatility Context: Bands derived from residuals show if price is hugging the trend or drifting. Tight bands with a steep slope = strong trend. Wide bands = noise or instability.

  • Time-Aligned Analysis: Because the model resets on fixed time anchors, it naturally aligns with trading calendars. That’s useful when building systems tied to rebalancing schedules or options expiry cycles.

  • Backtest-Ready: No lookahead bias. Each regression uses only past and present data.

4. Limitations and Edge Cases

  • Linear Fit Assumption:It forces a straight-line fit per segment. This works well when trends are clean. But it won’t capture non-linear patterns, curved structures, or mean-reverting behaviors.

  • Sensitivity to Volatility: In low-vol regimes, the regression might overfit noise. In high-vol periods, wide bands can make slope detection less meaningful.

  • Anchor Choice Affects Outcomes: Changing ANCHOR_RES from weekly to monthly can shift segment boundaries. That affects slope estimates. There’s no “correct” choice—just trade-offs.

  • Rolling Mode Is Noisy: Rolling regression updates bar-by-bar. That makes it more reactive, but also more unstable. You’ll see more jitter in slope and band values.

Concluding Thoughts

This method breaks price into interpretable chunks, each with its own trend and uncertainty.

It won’t predict the future. But it shows you how price has behaved, objectively, and time-aligned.

Use it to build structure into your market view. Layer it on top of other tools. Or just explore price action through a different lens.

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