Automated Detection of Support and Resistance Levels in Python

Mapping Market Psychology

In partnership with

`

Want Real Insights on the Future of Finance? Start Here.

The rise of digital assets and evolving global regulations raise a critical question—are we witnessing a financial revolution or a market reckoning? Staying informed has never been more important. That’s why we read The Daily Upside.

Founded by career journalists, investment bankers, and finance professionals, The Daily Upside delivers exclusive news, in-depth analysis, and expert commentary on the forces shaping the world economy. Join over 1 million readers and subscribe for free today.

Exciting News: Paid Subscriptions Have Launched! 🚀

On September 1, we officially rolled out our new paid subscription plans at GuruFinance Insights, offering you the chance to take your investing journey to the next level! Whether you're just starting or are a seasoned trader, these plans are packed with exclusive trading strategies, in-depth research paper analysis, ad-free content, monthly AMAsessions, coding tutorials for automating trading strategies, and much more.

Our three tailored plans—Starter Investor, Pro Trader, and Elite Investor—provide a range of valuable tools and personalized support to suit different needs and goals. Don’t miss this opportunity to get real-time trade alerts, access to masterclasses, one-on-one strategy consultations, and be part of our private community group. Click here to explore the plans and see how becoming a premium member can elevate your investment strategy!

Check Out Latest Premium Articles

Image created by the author

Every experienced trader understands the importance of key price boundaries — levels where the market has historically “bounced” (supports) or “fallen back” (resistances). These levels aren’t just interesting points on a chart; they are psychological and behavioral markers where large numbers of participants have placed orders or changed their sentiment. Knowing when these levels appeared on the chart can provide clues about the market’s possible future behavior. For instance, once a resistance forms, traders might anticipate the price struggling to climb above that zone. Conversely, once a support forms, it might serve as a cushion that prevents prices from sinking lower, at least temporarily.

In this enhanced explanation, we’ll highlight not only how to detect these levels using fractals, but we’ll also mark their origin points on the chart. By visually indicating where on the timeline a support or resistance became “official,” we make it easier to understand how traders might alter their expectations from that candle forward. Seeing the exact candle that triggered the detection helps interpret market psychology: “At this point in time, traders recognized a barrier — and from that candle onward, expectations about upward or downward momentum may have shifted.”

Don’t wait for the “right time.” Start your estate plan today for just $199.

Your legacy deserves to be protected. With Trust & Will, you can create an estate plan that ensures your wishes are followed, no matter what. From easy-to-understand documents to fast and secure processing, Trust & Will helps you get your affairs in order without the hassle of traditional lawyers.

Whether you're starting a will, creating a trust, or setting healthcare directives, their easy, step-by-step process ensures you stay on track. Plus, with expert customer support available 5 days a week, you’re never alone in the process.

Why Mark the Origin of Support and Resistance?

When reviewing historical data, a horizontal line showing a key level is undeniably useful. However, if you don’t know when that line was identified, you lose a layer of contextual understanding. Did that resistance level first form weeks ago and has since been tested multiple times? Or did it just appear yesterday, signaling a fresh and possibly fragile turning point?

By marking the candle at which a support or resistance level was first detected, we gain:

  1. Temporal Context: You can see how the market behaved before and after the level’s detection.

  2. Expectational Insight: At the moment of detection, a trader might shift from a neutral stance to anticipating bounces or breakouts around that level.

  3. Improved Strategy Timing: If you’re building an algorithmic strategy, knowing exactly when the system identified a level can help you code logic that reacts immediately (e.g., placing orders around that new support) rather than waiting for subsequent candles.

Recap: The Fractal Approach

We use a five-candle pattern called a fractal. For a support fractal, the middle candle of five has the lowest low. For a resistance fractal, it has the highest high. These patterns help filter out insignificant fluctuations, focusing on points where price genuinely turned around. After identifying these raw fractal points, we apply a volatility-based threshold to avoid plotting multiple lines at nearly the same price. The result is a cleaner set of clearly defined support and resistance levels.

Step-by-Step Implementation with Enhanced Markings

1. Data Setup

We start by loading the necessary packages, acquiring our price data (using SPY as an example), and preparing it for analysis. This step remains unchanged from previous examples, but let’s show the code again for completeness.

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

symbol = "SPY"
data = yf.download(symbol, start="2020-03-15", end="2020-07-15", interval="1d")
data.columns = data.columns.droplevel(0)
data.columns = ['Adj Close', 'Close', 'High', 'Low', 'Open', 'Volume']
data.reset_index(inplace=True)
data['DateNum'] = mdates.date2num(data['Date'])
df = data[['DateNum','Open','High','Low','Close']]

At this point, df has our prices and converted dates, making it easy for plotting.

2. Fractal Detection Functions

We write two functions — one for supports and one for resistances — that confirm the presence of a fractal pattern at a given candle index. This logic remains the same: the middle candle must be the extreme point within a five-candle sequence.

def detect_support_point(df, idx):
    if idx < 2 or idx > df.shape[0] - 3:
        return False
    segment_lows = [df['Low'][idx-2], df['Low'][idx-1], df['Low'][idx], df['Low'][idx+1], df['Low'][idx+2]]
    return df['Low'][idx] == min(segment_lows)

def detect_resistance_point(df, idx):
    if idx < 2 or idx > df.shape[0] - 3:
        return False
    segment_highs = [df['High'][idx-2], df['High'][idx-1], df['High'][idx], df['High'][idx+1], df['High'][idx+2]]
    return df['High'][idx] == max(segment_highs)

3. Identifying Raw Levels and Their Origin Candles

We now iterate through the data, identify fractal points, and store both the index and the price at those points. Crucially, we’ll keep track not only of the price level but also of the exact candle (by its index and DateNum) that generated the signal.

raw_levels = []
for i in range(len(df)):
    if detect_support_point(df, i):
        raw_levels.append({
            'index': i,
            'price': df['Low'][i],
            'date': df['DateNum'][i],
            'type': 'support'
        })
    elif detect_resistance_point(df, i):
        raw_levels.append({
            'index': i,
            'price': df['High'][i],
            'date': df['DateNum'][i],
            'type': 'resistance'
        })

Here, each entry in raw_levels is now a dictionary containing the relevant information, including the candle type (support or resistance) and the originating date.

4. Filtering Out Duplicate/Close-By Levels

To refine our list of raw levels, we define a tolerance based on average candle height. We then filter out levels that appear too close to previously accepted ones. This step ensures the chart isn’t cluttered with redundant lines.

avg_candle_height = np.mean(df['High'] - df['Low'])

def sufficiently_far(new_level_price, accepted_prices, threshold):
    return all(abs(new_level_price - ap) > threshold for ap in accepted_prices)

final_levels = []
unique_prices = []

for lvl in raw_levels:
    if sufficiently_far(lvl['price'], unique_prices, avg_candle_height):
        final_levels.append(lvl)
        unique_prices.append(lvl['price'])

Now final_levels is a cleaner, more focused set of key price levels, each with its original detection candle information intact.

5. Visualizing With Origin Markings

Here’s the fun part: plotting the chart so it not only shows the candlesticks and the discovered key levels but also marks the exact candle that gave rise to each support or resistance. We’ll draw a small marker (e.g., a circle) at the candle and annotate it, making clear that “From this candle onward, a certain expectation formed.”

Interpretation:

  • Each blue line indicates a key level (support or resistance) that emerged at some point in the past.

  • A small marker and annotation at the origin candle remind us that, at that candle’s close, traders might have identified a new zone to watch.

  • From that candle forward, market participants may anticipate the price reacting whenever it returns to that level.

Code:

fig, ax = plt.subplots(figsize=(12,6))

# Plot each candle
for idx, row in df.iterrows():
    color = 'green' if row['Close'] >= row['Open'] else 'red'
    # Candle wick
    ax.plot([row['DateNum'], row['DateNum']], [row['Low'], row['High']], color='black', linewidth=1)
    # Candle body
    ax.plot([row['DateNum'], row['DateNum']], [row['Open'], row['Close']], color=color, linewidth=4)

# Draw key levels
for lvl in final_levels:
    ax.axhline(y=lvl['price'], xmin=0, xmax=1, color='blue', linestyle='--', alpha=0.7)
    # Mark the origin candle on the chart
    ax.plot(lvl['date'], lvl['price'], 'o', color='blue', markersize=6, alpha=0.9)
    # Annotate to clarify what happened here
    level_type = "Support" if lvl['type'] == 'support' else "Resistance"
    ax.annotate(
        f"{level_type} detected\non {mdates.num2date(lvl['date']).strftime('%Y-%m-%d')}",
        xy=(lvl['date'], lvl['price']),
        xytext=(lvl['date']+5, lvl['price']),
        arrowprops=dict(facecolor='black', shrink=0.05),
        fontsize=10,
        bbox=dict(boxstyle="round", fc="w", ec="black", alpha=0.7)
    )

ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
fig.autofmt_xdate()
ax.set_title(f"{symbol} Price with Detected Support & Resistance Levels")
ax.set_xlabel("Date")
ax.set_ylabel("Price")
plt.tight_layout()
plt.show()

Image created by the author

Interpreting the Results

After running this code, you’ll see a chart with clearly defined horizontal lines where key levels have been identified. Next to each line, a small marker and annotation show you exactly which candle triggered the detection. By examining the chart from that candle forward, you can note how the price interacted with the new key level. For instance:

  • Newly Detected Support: If you see the price rising shortly after the level appears, it might validate that support. If the market retests that line multiple times without breaking below, you confirm that the support is meaningful.

  • Newly Detected Resistance: Once a resistance level is established, subsequent attempts by the price to move above that line may fail or stall. Observing these patterns helps you understand market reluctance to buy at higher levels, reinforcing the significance of this ceiling.

In a live trading scenario, the candle that establishes the level could prompt you to plan a trade. For example, upon detecting a resistance, you might prepare a short position the next time the price nears that level — assuming your strategy calls for such a mean-reversion approach. Conversely, a brand-new support might encourage you to place a limit order to buy near that line, anticipating a bounce.

Deepening the Understanding of Market Psychology

By knowing the exact candle that confirmed a new support or resistance, you gain insight into the psychology of the marketplace at that moment. A fractal-based support might mean that the crowd previously pushed the price down, but failed to sustain lower levels, leading to a rebound. Recognizing that turning point can shift your perception: from that candle onward, traders see a “floor” beneath them. Similarly, a newly formed resistance can change the psychological landscape, creating a “ceiling” that must be overcome for the price to advance further.

This is why marking the origin of these levels is so valuable. It doesn’t just show you a static line; it illustrates when collective market sentiment began acknowledging that line’s importance. Understanding this timeline can turn raw technical signals into a richer narrative that guides your trading decisions.

Conclusion and Next Steps

By augmenting our fractal-based approach to detecting support and resistance with visual markers of when these levels first emerged, we enhance both the analytical and interpretive value of our chart. Traders can now see the evolutionary story of the price action: how each new level came to be recognized and how the market subsequently responded.

In real applications, you may further refine this methodology by:

  • Adjusting fractal parameters (e.g., using more than 5 candles for confirmation).

  • Incorporating volume data to confirm the significance of a detected level.

  • Using volatility metrics to adjust the filtering threshold dynamically.

But even with the current setup, you have a strong foundation: a chart that not only tells you where crucial price boundaries lie but also shows you exactly when the market started treating them as significant. That additional piece of temporal context can make all the difference in turning generic technical lines into meaningful strategic insights.