avatarAlexzap

Summary

The provided content discusses a comprehensive analysis of 15 different Python algorithms aimed at detecting support and resistance levels (SRL) in the BTC-USD price data, integrating various technical indicators and statistical methods to enhance algo-trading strategies.

Abstract

The text outlines an in-depth exploration of semi-automated detection methods for BTC-USD price support and resistance levels, which are crucial for making informed trading decisions. It details 15 distinct algorithms, ranging from global trend lines and local min/max linear regression to advanced techniques like K-Means clustering and volume-based analysis. The article emphasizes the importance of combining these methods, referred to as the SRL15 approach, to identify not just precise points but also support and resistance zones. This integrative methodology is applied to historical BTC-USD data to demonstrate its potential effectiveness in algorithmic trading, aiming to provide a more systematic approach compared to intuition-based trading.

Opinions

  • The author, Cristian Velasquez, stresses the significance of integrating multiple methods to reliably identify support and resistance zones rather than exact points.
  • There is an opinion that the current challenge for BTC-USD is navigating a tight price range, and the Relative Strength Index (RSI) suggests market equilibrium.
  • The text suggests that market momentum may be slowing down, as indicated by the Market Momentum Slows Amid Trading Hesitation headline.
  • The author implies that the SRL algorithms could balance the pros and cons of using ambiguous support and resistance zones in crypto trading.
  • There is a viewpoint that SRL-powered algo-trading might offer a more disciplined approach to BTC trading than one based on instinct or intuition.
  • The content reflects the idea that the SRL15 methodology could provide a more nuanced understanding of price action by considering both global trends and local price behaviors.

Semi-Automated Detection of BTC-USD Price Support & Resistance Levels: Comparing 15 Essential Methods

Making Algo-Trading Decisions using Support & Resistance Levels (image design via Canva).
  • When it comes to stock technical analysis, there is a legion of indicators and SaaS products available. Therefore, it is often useful to combine different tools to get a complete picture of the volatile stock market. Besides, such an integration may offer the proper way to apply the multiple comparison tests.
  • For example, Fibonacci retracement can be a great tool to use in combination with other technical indicators, as it can provide additional confirmation of support and resistance levels (SRL).
  • This post is about semi-automated detection of short/long BTC-USD price SRL by combining, evaluating and comparing the following 15 available algorithms in Python [1–15]:
  1. Global Trend Lines
  2. Local MinMax Linear Regression
  3. Candlestick Low/High SRL
  4. Rolling Window Single SRL
  5. Rolling Window Fibonacci Retracement Levels
  6. IQR-Based Multiple SRL
  7. Rolling Window Swing Highs & Lows
  8. Pivot Points with SRL
  9. K-Means Clustering with SRL
  10. Volume-Based SRL
  11. Linear/Polynomial Regression SRL
  12. Support-Resistance Visualization QC via Seaborn Polynomial Regression
  13. SMA MinMax Support & Resistance Points
  14. Local Low/High SRL
  15. Global Low/High SRL
  • As Cristian Velasquez points out in his article [1], the importance of integration of the aforementioned methods to reliably identify support and resistance zones rather than precise points cannot be overstated.
  • For BTC-USD, the current challenge is the tight price range that it has been navigating in for some time now. On the daily chart, BTC has been oscillating between the $60k and $70k marks, struggling to find a definitive direction. The Relative Strength Index (RSI), situated around the 50% level, implies equilibrium in market momentum.
  • In fact, Market Momentum Slows Amid Trading Hesitation. Is BTC price loosing steam?
  • Can the SRL-powered algo-trading provide a more systematic approach to BTC trading than one based on intuition or instinct?
  • Can the SRL algorithms [1–15] help us to balance pros & cons of using ambiguous support and resistance zones in crypto trading?
  • Let’s try to address these questions by diving into the specifics of the proposed integrated methodology (being referred to as SRL15) to be demonstrated on BTC-USD historical data.

Support & Resistance Basics

  • SRL in crypto trading are two elementary concepts concerning technical analysis. At the core, these are the price levels that act as barriers to price movement.
  • Identifying SRL involves recognizing price thresholds in the market where an asset often stops or changes direction due to a significant amount of buying or selling.
  • Support Level: This is a price below an asset that usually does not fall. When the price approaches this zone, traders expect the asset to become attractive enough to buy.
  • Resistance Level: This is a price zone above which the price usually does not rise. Reaching this zone is often seen by traders as a sell signal, expecting the price to fall due to the rise in supply.
  • Consider bouncing a ball around your house. Your floor and ceiling are barriers limiting the ball’s flight and fall. Support and resistance are similar barriers in trading that limit the movement of price action.
  • The crypto trading strategy based on support and resistance levels is the following: buy slightly above the support in the uptrends and sell near the resistance in downtrends.
  • Remember, there’s no assurance that the support and resistance will hold, take an extra step and wait until the trends confirmation. Combine support and resistance levels with other indicators that confirm the trend.
  • Read more about support and resistance with examples and strategies here.

Reading & Plotting Input Stock Data

  • Importing and installing basic Python libraries
import os
os.chdir('YOURPATH')    # Set working directory
os. getcwd()

import warnings
warnings.filterwarnings('ignore')

import numpy as np
import pandas as pd
from datetime import datetime as dt
import yfinance as yf
import nsepy
from statistics import mean
  • Reading the short-term BTC-USD historical stock data
benchmark_ = ["BTC-USD"]

start_date_ = "2024-01-01"
end_date_  = "2024-06-10"

df = yf.download(benchmark_, start=start_date_, end=end_date_)
df.tail()

           Open          High         Low         Close        Adj Close     Volume
Date      
2024-06-05 70568.351562 71735.414062 70390.710938 71082.820312 71082.820312 32810771409
2024-06-06 71082.843750 71625.734375 70119.125000 70757.164062 70757.164062 25223152007
2024-06-07 70759.187500 71907.851562 68507.257812 69342.585938 69342.585938 36188381096
2024-06-08 69324.179688 69533.320312 69210.742188 69305.773438 69305.773438 14262185861
2024-06-09 69297.492188 69408.937500 69160.843750 69312.343750 69312.343750 12721441792
  • Plotting the short-term BTC-USD historical stock data
symbol=benchmark_
series = df['Close']
series.index = np.arange(series.shape[0])
plt.figure(figsize=(10,6))
plt.title(symbol)
plt.xlabel('Days')
plt.ylabel('Close Prices')
plt.plot(series, label=symbol)
plt.grid()
plt.legend()
The short-term BTC-USD historical stock data
  • Reading the long-term BTC-USD historical stock data
benchmark_ = ["BTC-USD"]

start_date_ = "2021-01-03" #optional 2022-01-03
end_date_  = "2024-06-08"

df = yf.download(benchmark_, start=start_date_, end=end_date_)
df.tail()

           Open          High        Low          Close        Adj Close     Volume
Date      
2024-06-03 67753.898438 70230.820312 67589.835938 68804.781250 68804.781250 32401285324
2024-06-04 68804.570312 71047.406250 68564.640625 70567.765625 70567.765625 33149696545
2024-06-05 70568.351562 71735.414062 70390.710938 71082.820312 71082.820312 32810771409
2024-06-06 71082.843750 71625.734375 70119.125000 70757.164062 70757.164062 25223152007
2024-06-07 70772.414062 71907.828125 70672.617188 70927.195312 70927.195312 30001008640
  • Creating the long-term BTC-USD historical stock data candlestick chart
# Creating a Candlestick chart for BTC-USD
candlestick = go.Candlestick(x=df.index,
                open=df['Open'],
                high=df['High'],
                low=df['Low'],
                close=df['Adj Close'],
                increasing=dict(line=dict(color='black')),
                decreasing=dict(line=dict(color='red')),
                showlegend=False)

# Layout
layout = go.Layout(
    title='Adjusted BTC-USD Stock Price',
    yaxis=dict(title='Price (USD)'),
    xaxis=dict(title='Date'),
    template = 'ggplot2',
    xaxis_rangeslider_visible=False,
    yaxis_gridcolor='white',
    xaxis_gridcolor='white',
    yaxis_tickfont=dict(color='black'),
    xaxis_tickfont=dict(color='black'),
    margin=dict(t=50,l=50,r=50,b=50)
)

fig = go.Figure(data=[candlestick], layout=layout)

# Plotting annotation
fig.add_annotation(text='BTC-USD',
                    font=dict(color='gray', size=30),
                    xref='paper', yref='paper',
                    x=0.5, y=0.5,
                    showarrow=False,
                    opacity=1.0)


fig.show()
The long-term BTC-USD historical stock data candlestick chart

Common Risk/Return FinTech Analysis

  • Importing stock data analysis libraries
# Importing Libraries

# Data Handling
import pandas as pd
import numpy as np

# Data Visualization
import matplotlib.pyplot as plt
import seaborn as sns 
import plotly.express as px
import plotly.graph_objs as go
import matplotlib.ticker as mtick
from plotly.offline import init_notebook_mode
init_notebook_mode(connected=True)

# Financial Data Analysis
import yfinance as yf
import ta
import quantstats as qs

# Machine Learning 
from sklearn.metrics import roc_auc_score, roc_curve, auc

# Models
from sklearn.linear_model import LogisticRegression
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from catboost import CatBoostClassifier
from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier


# Hiding warnings 
import warnings
warnings.filterwarnings("ignore")
  • Reading the long-term BTC-USD data
benchmark_ = ["BTC-USD"]

start_date_ = "2021-01-03"
end_date_  = "2024-06-08"

df = yf.download(benchmark_, start=start_date_, end=end_date_)
df.tail()

           Open         High         Low          Close        Adj Close     Volume
Date      
2024-06-03 67753.898438 70230.820312 67589.835938 68804.781250 68804.781250 32401285324
2024-06-04 68804.570312 71047.406250 68564.640625 70567.765625 70567.765625 33149696545
2024-06-05 70568.351562 71735.414062 70390.710938 71082.820312 71082.820312 32810771409
2024-06-06 71082.843750 71625.734375 70119.125000 70757.164062 70757.164062 25223152007
2024-06-07 70772.414062 71432.117188 70672.617188 71216.859375 71216.859375 26067939328
  • Printing the basic descriptive statistics of Adj Close price
df['Adj Close'].describe().T

count     1252.000000
mean     37893.080243
std      14711.213854
min      15787.284180
25%      26335.864258
50%      36367.060547
75%      47247.533203
max      73083.500000
Name: Adj Close, dtype: float64
  • Inspecting the index
print(df.index)
DatetimeIndex(['2021-01-03', '2021-01-04', '2021-01-05', '2021-01-06',
               '2021-01-07', '2021-01-08', '2021-01-09', '2021-01-10',
               '2021-01-11', '2021-01-12',
               ...
               '2024-05-29', '2024-05-30', '2024-05-31', '2024-06-01',
               '2024-06-02', '2024-06-03', '2024-06-04', '2024-06-05',
               '2024-06-06', '2024-06-07'],
              dtype='datetime64[ns]', name='Date', length=1252, freq=None)
  • Plotting the BTC-USD Open-Close Price Difference
df['diff'].plot(title="BTC-USD Open-Close Price Difference")
BTC-USD Open-Close Price Difference
  • Plotting Volume
df['Volume'].plot(title="Volume")
BTC-USD Volume
  • Calculating CAGR
# Get the number of days 
days = (df.index[-1] - df.index[0]).days

# Calculate the CAGR 
cagr = ((((df['Adj Close'][-1]) / df['Adj Close'][1])) ** (365.0/days)) - 1

# Print CAGR
print(cagr)

0.25343289151250503
  • Calculating daily and cumulative returns for the time slot 2023–2024
btc = qs.utils.download_returns('BTC-USD')
btc1 = btc.loc['2023-01-01':'2024-06-07']

print('\nBTC-USD Daily Returns Plot:\n')
qs.plots.daily_returns(btc1,benchmark='SPY')

BTC-USD Daily Returns Plot:
BTC-USD Daily Returns
print('\nBTC-USD Cumulative Returns Plot\n')
qs.plots.returns(btc1)

BTC-USD Cumulative Returns Plot:
BTC-USD Cumulative Returns
print('\nBTC-USD Daily Returns Histogram')
qs.plots.histogram(btc1, resample = 'D')

BTC-USD Daily Returns Histogram:
BTC-USD Daily Returns Histogram
  • Calculating kurtosis, skewness, std and the Sharpe ratio for the above dataset
print("BTC-USD's kurtosis: ", qs.stats.kurtosis(btc1).round(2))
BTC-USD's kurtosis:  2.51

print("BTC-USD's skewness: ", qs.stats.skew(btc1).round(2))
BTC-USD's skewness:  0.57

print("BTC-USD's Standard Deviation: ", btc1.std())
BTC-USD's Standard Deviation:  0.025212506481976354

# Calculating Sharpe ratio
print("Sharpe Ratio for BTC-USD: ", qs.stats.sharpe(btc1).round(2))
Sharpe Ratio for BTC-USD:  1.95
  • Calculating alpha and beta coefficients by linear regression vs S&P500
# Loading data from the S&P500
sp500 = qs.utils.download_returns('^GSPC')
sp500 = sp500.loc['2023-01-01':'2024-06-07']

btc2 = btc.loc['2023-01-03':'2024-06-06']
a=btc2.copy()
a_business_days = a[a.index.dayofweek < 5]

#Matching time samples of 2 dataframes by inner merge

dfsp=sp500.to_frame()
dfbtc=btc2.to_frame()
dfspbtc = dfbtc.merge(dfsp, how='inner',right_index = True, left_index=True)

dfspbtc.info()

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 359 entries, 2023-01-03 to 2024-06-06
Data columns (total 2 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   Close_x  359 non-null    float64
 1   Close_y  359 non-null    float64
dtypes: float64(2)
memory usage: 8.4 KB

# Close_y = S&P500, Close_x = BTC-USD

sns.regplot(data=dfspbtc, x="Close_y", y="Close_x")
Linear regression BTC-USD (y) vs S&P500 (x).
# Removing indexes
sp500_no_index = dfspbtc.Close_y.reset_index(drop = True)
a_business_days_no_index = dfspbtc.Close_x.reset_index(drop = True)

# Fitting linear relation among BTC-USD's returns and Benchmark
X = sp500_no_index.values.reshape(-1,1)
y = a_business_days_no_index.values.reshape(-1,1)
linreg = LinearRegression().fit(X, y)
beta = linreg.coef_[0]
alpha = linreg.intercept_
print('\n')
print('BTC-USD beta: ', beta.round(3))
print('\nBTC-USD alpha: ', alpha.round(3))

BTC-USD beta:  [0.721]

BTC-USD alpha:  [0.002]

Method 1: Detecting Global Trend Lines

  • Let’s select the time slot from start=’2022–01–01' to end=’2024–06–09' and draw trend lines connecting significant highs or lows to visualize the simplest global trend direction
import yfinance as yf
import matplotlib.pyplot as plt
data = yf.download('BTC-USD', start='2022-01-01', end='2024-06-09')
# Dropping values with missing/null values
data = data.dropna()

# Drawing a simple trendline
plt.plot(data['Close'])
plt.plot([data.index[0], data.index[-1]], [data['Close'].iloc[0], data['Close'].iloc[-1]], 'r--')

# Rotate x-axis dates
plt.xticks(rotation=45, ha='right')

# Add labels and title
plt.xlabel('Date')
plt.ylabel('Price')
plt.title('Trendlines')
BTC-USD Simplest Global Trendlines
  • Selecting the shorter time slot 2024/01–06 and re-estimating the aforementioned trendlines
import warnings
warnings.filterwarnings('ignore')

import numpy as np
import pandas as pd
from datetime import datetime as dt
import yfinance as yf
import nsepy
from statistics import mean

benchmark_ = ["BTC-USD"]

start_date_ = "2024-01-01"
end_date_  = "2024-06-10"

df = yf.download(benchmark_, start=start_date_, end=end_date_)
#df.tail()

data = df.copy()


# Drawing a simple trendline
plt.figure(figsize=(12, 7))
plt.plot(data['Close'])
plt.plot([data.index[0], data.index[-1]], [data['Close'].iloc[0], data['Close'].iloc[-1]], 'r--')

# Rotate x-axis dates
plt.xticks(rotation=45, ha='right')

# Add labels and title
plt.xlabel('Date')
plt.ylabel('Price')
plt.title('Trendlines')
plt.grid()
BTC-USD Simplest Global Trendlines 2024

Method 2: Local MinMax Linear Regression

  • Applying linear regression and local MinMax search to the smoothed BTC-USD data 2024 (see above)
import numpy as np
import pandas as pd
from math import sqrt
import matplotlib.pyplot as plt
import pandas_datareader as web
from scipy.signal import savgol_filter
from sklearn.linear_model import LinearRegression

def pythag(pt1, pt2):
    a_sq = (pt2[0] - pt1[0]) ** 2
    b_sq = (pt2[1] - pt1[1]) ** 2
    return sqrt(a_sq + b_sq)

def regression_ceof(pts):
    X = np.array([pt[0] for pt in pts]).reshape(-1, 1)
    y = np.array([pt[1] for pt in pts])
    model = LinearRegression()
    model.fit(X, y)
    return model.coef_[0], model.intercept_

def local_min_max(pts):
    local_min = []
    local_max = []
    prev_pts = [(0, pts[0]), (1, pts[1])]
    for i in range(1, len(pts) - 1):
        append_to = ''
        if pts[i-1] > pts[i] < pts[i+1]:
            append_to = 'min'
        elif pts[i-1] < pts[i] > pts[i+1]:
            append_to = 'max'
        if append_to:
            if local_min or local_max:
                prev_distance = pythag(prev_pts[0], prev_pts[1]) * 0.5
                curr_distance = pythag(prev_pts[1], (i, pts[i]))
                if curr_distance >= prev_distance:
                    prev_pts[0] = prev_pts[1]
                    prev_pts[1] = (i, pts[i])
                    if append_to == 'min':
                        local_min.append((i, pts[i]))
                    else:
                        local_max.append((i, pts[i]))
            else:
                prev_pts[0] = prev_pts[1]
                prev_pts[1] = (i, pts[i])
                if append_to == 'min':
                    local_min.append((i, pts[i]))
                else:
                    local_max.append((i, pts[i]))
    return local_min, local_max
  • Plotting the input stock data
symbol=benchmark_
series = df['Close']
series.index = np.arange(series.shape[0])
plt.figure(figsize=(10,6))
plt.title(symbol)
plt.xlabel('Days')
plt.ylabel('Close Prices')
plt.plot(series, label=symbol)
plt.grid()
plt.legend()
BTC-USD Close Price 2024/01–06
  • Calculating the smoothing window length
month_diff = series.shape[0] // 30
if month_diff == 0:
    month_diff = 1
month_diff
5

smooth = int(2 * month_diff + 3)
smooth
13
  • Applying the smoothing filter savgol_filter and plotting the result
pts = savgol_filter(series, smooth, 3)
plt.figure(figsize=(10,6))
plt.title(symbol)
plt.xlabel('Days')
plt.ylabel('Prices')
plt.plot(pts, label=f'Smooth {symbol}')
plt.legend()
plt.grid()
Smoothed BTC-USD Close Price 2024/01–06
  • Comparing smoothed vs original BTC-USD Close Price 2024/01–06
plt.figure(figsize=(10,6))
plt.title(symbol)
plt.xlabel('Days')
plt.ylabel('Prices')
plt.plot(series, label=symbol)
plt.plot(pts, label=f'Smooth {symbol}')
plt.legend()
plt.grid()
Smoothed vs Original BTC-USD Close Price 2024/01–06
local_min, local_max = local_min_max(pts)
plt.figure(figsize=(10,6))
plt.title(symbol)
plt.xlabel('Days')
plt.ylabel('Prices')
plt.plot(pts, label=f'Smooth {symbol}')
for pt in local_min:
    plt.scatter(pt[0], pt[1], c='r',s=150,marker = 'v')
for pt in local_max:
    plt.scatter(pt[0], pt[1], c='g',s=150,marker = '^')
plt.legend()
plt.grid()
Smoothed BTC-USD Close Price 2024/01–06 & Trading Signals as Local MinMax Points
local_min_slope, local_min_int = regression_ceof(local_min)
local_max_slope, local_max_int = regression_ceof(local_max)
support = (local_min_slope * np.array(series.index)) + local_min_int
resistance = (local_max_slope * np.array(series.index)) + local_max_int

plt.figure(figsize=(10,6))
plt.title(symbol)
plt.xlabel('Days')
plt.ylabel('Prices')
plt.plot(pts, label=f'Smooth {symbol}')
plt.plot(support, label='Support', c='r')
plt.plot(resistance, label='Resistance', c='g')
plt.grid()
plt.legend()
Smoothed BTC-USD Close Price 2024/01–06 & Oblique SRL

Method 3: Candlestick Low/High Support & Resistance Lines

  • A good idea is to use the candlestick chart and check the high and low prices of every candle (read more here).
  • Plotting the BTC-USD candlesticks 2024/01–06 (cf. dataframe df above)
import mplfinance as mpf


mpf.plot(df, type='candle', style='charles', title='BTC-USD Candlestick Chart', volume=True)
BTC-USD Candlestick Chart 2024/01–06
  • Preparing the above data for further analysis
import pandas as pd
import numpy as np
import yfinance
from mplfinance.original_flavor import candlestick_ohlc
import matplotlib.dates as mpl_dates
import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = [12, 8]

plt.rc('font', size=14) 

df['Date'] = pd.to_datetime(df.index)
df['Date'] = df['Date'].apply(mpl_dates.date2num)

df = df.loc[:,['Date', 'Open', 'High', 'Low', 'Close']]
df.tail()

         Date      Open           High        Low           Close
Date     
2024-06-05 19879.0 70568.351562 71735.414062 70390.710938 71082.820312
2024-06-06 19880.0 71082.843750 71625.734375 70119.125000 70757.164062
2024-06-07 19881.0 70759.187500 71907.851562 68507.257812 69342.585938
2024-06-08 19882.0 69324.179688 69533.320312 69210.742188 69305.773438
2024-06-09 19883.0 69297.492188 69408.937500 69160.843750 69312.343750
def isSupport(df,i):
  support = df['Low'][i] < df['Low'][i-1]  and df['Low'][i] < df['Low'][i+1] \
  and df['Low'][i+1] < df['Low'][i+2] and df['Low'][i-1] < df['Low'][i-2]

  return support

def isResistance(df,i):
  resistance = df['High'][i] > df['High'][i-1]  and df['High'][i] > df['High'][i+1] \
  and df['High'][i+1] > df['High'][i+2] and df['High'][i-1] > df['High'][i-2] 

  return resistance

levels = []
for i in range(2,df.shape[0]-2):
  if isSupport(df,i):
    levels.append((i,df['Low'][i]))
  elif isResistance(df,i):
    levels.append((i,df['High'][i]))

def plot_all():
  fig, ax = plt.subplots()

  candlestick_ohlc(ax,df.values,width=0.6, \
                   colorup='green', colordown='red', alpha=0.8)

  date_format = mpl_dates.DateFormatter('%d %b %Y')
  ax.xaxis.set_major_formatter(date_format)
  fig.autofmt_xdate()

  fig.tight_layout()

  for level in levels:
    plt.hlines(level[1],xmin=df['Date'][level[0]],\
               xmax=max(df['Date']),colors='blue')
  fig.show()

plot_all()
BTC-USD Candlestick Chart 2024/01–06 with Local Low/High SRL

Method 4: Rolling Window Single Support & Resistance Lines

  • Invoking the Rolling Midpoint Range SRL algorithm [1]
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt

def find_levels(data, window):
    high = data['High'].rolling(window=window).max()
    low = data['Low'].rolling(window=window).min()
    midpoint = (high + low) / 2
    diff = high - low
    resistance = midpoint + (diff / 2)
    support = midpoint - (diff / 2)
    return support, resistance

# Download historical stock prices 2024/01–06 (see above)

data = df.copy()

window = 30

# Calculate support and resistance levels
support, resistance = find_levels(data, window)

# Plot the stock price, support, and resistance lines
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(data.index, data['Close'], label='Stock Price')
ax.plot(data.index, support, label='Support', linestyle='--', color='green')
ax.plot(data.index, resistance, label='Resistance', linestyle='--', color='red')
ax.set_xlabel('Date')
ax.set_ylabel('Price')
ax.set_title(f'{symbol} Stock Price with Support and Resistance Levels')
ax.legend()

# Add annotations for last support and resistance levels
last_support = support.iloc[-1]
last_resistance = resistance.iloc[-1]
ax.annotate(f'Support: {last_support:.2f}', xy=(support.index[-1], last_support),
            xytext=(support.index[-1] - pd.DateOffset(days=30), last_support + 10),
            arrowprops=dict(facecolor='green', arrowstyle='->'))
ax.annotate(f'Resistance: {last_resistance:.2f}', xy=(resistance.index[-1], last_resistance),
            xytext=(resistance.index[-1] - pd.DateOffset(days=30), last_resistance - 10),
            arrowprops=dict(facecolor='red', arrowstyle='->'))
plt.grid()
plt.show()
BTC-USD Price 2024/01–06: the Rolling Midpoint Range SRL

Method 5: Rolling Window Fibonacci Retracement Levels

  • The Fibonacci retracement levels are 23.6%, 38.2%, 61.8%, and 78.6%. While not officially a Fibonacci ratio, 50% is also used. The indicator is useful because it can be drawn between any two significant price points, such as a high and a low.
  • We will notice that when we plot Fibonacci retracement levels on our charts they align beautifully with significant highs and lows. These high-probability areas act as perfect entry or exit points for trades because they have proven over time to show where price has reversed from a new trend.
  • Method 5.1 (Local): Calculating the high and low prices over the lookback period of 15 [1]
stock_data = df.copy()

# Define the lookback period for calculating high and low prices
lookback_period = 15

# Calculate the high and low prices over the lookback period
high_prices = stock_data["High"].rolling(window=lookback_period).max()
low_prices = stock_data["Low"].rolling(window=lookback_period).min()
# Calculate the price difference and Fibonacci levels
price_diff = high_prices - low_prices
levels = np.array([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1])
fib_levels = low_prices.values.reshape(-1, 1) + price_diff.values.reshape(-1, 1) * levels
  • Plotting the stock price with the Fibonacci retracement levels and last prices [1]
# Get the last price for each Fibonacci level
last_prices = fib_levels[-1, :]

# Define a color palette for the Fibonacci levels
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']

# Plot the stock price with the Fibonacci retracement levels and last prices
fig, ax = plt.subplots(figsize=(12,8))
ax.plot(stock_data.index, stock_data["Close"], label="Stock Price")

offsets = [-16, -14, -12, -10, 8, 10, 12] 

for i, level in enumerate(levels):
    if level == 0 or level == 1:
        linestyle = "--"
    else:
        linestyle = "-"
    ax.plot(stock_data.index, fib_levels[:, i], label=f"Fib {level:.3f}", linestyle=linestyle, color=colors[i])
    ax.annotate(f"{last_prices[i]:.2f}", 
                xy=(stock_data.index[-1], fib_levels[-1, i]), 
                xytext=(stock_data.index[-1] + pd.Timedelta(days=5), fib_levels[-1, i] + offsets[i]), 
                ha="left", va="center", fontsize=16, color=colors[i])

ax.set_xlabel("Date")
ax.set_ylabel("Price")
ax.set_title(f"{symbol} with Fibonacci Retracement Levels")
ax.legend(loc="lower right", fontsize=14)
plt.grid()
plt.show()
BTC-USD Price 2024/01–06 with Fibonacci Retracement Levels
  • Method 5.2 (Global): Calculating the global Max/Min of Adj Close [1]
# Download stock data
stock_data = df.copy()

# Calculate price_max, price_min
price_max = stock_data['Adj Close'].max()
price_min = stock_data['Adj Close'].min()
# Define Fibonacci Ratios
fibonacci_levels = [1.0, 0.786, 0.618, 0.5, 0.382, 0.236, 0]

# Calculate and plot Fibonacci Retracement levels
fibonacci_prices = {}
for level in fibonacci_levels:
    fibonacci_price = price_min + level * (price_max - price_min)
    fibonacci_prices[level] = fibonacci_price

# Print results
print("Maximum Price:", price_max)
print("Minimum Price:", price_min)
print("FibonacciRatios and Corresponding Prices:")
for level, price in fibonacci_prices.items():
    print("Fib Ratio:", level, "-> Price:", price)

Maximum Price: 73083.5
Minimum Price: 39507.3671875
FibonacciRatios and Corresponding Prices:
Fib Ratio: 1.0 -> Price: 73083.5
Fib Ratio: 0.786 -> Price: 65898.207578125
Fib Ratio: 0.618 -> Price: 60257.417265625
Fib Ratio: 0.5 -> Price: 56295.43359375
Fib Ratio: 0.382 -> Price: 52333.449921875
Fib Ratio: 0.236 -> Price: 47431.33453125
Fib Ratio: 0 -> Price: 39507.3671875

#Plotting SRL vs Price

# Define Fibonacci Ratios
fibonacci_ratios = [0.236, 0.382, 0.5, 0.618, 0.786]

# Calculate Fibonacci Retracement prices
fib_prices = [price_min + ratio * (price_max - price_min) for ratio in fibonacci_ratios]

# Define colors for each Fibonacci Ratio
colors = ['blue', 'green', 'red', 'orange', 'purple']

# Plot the Fibonacci Retracement levels
plt.figure(figsize=(10, 6))

# Plot the Fibonacci Retracement levels as horizontal lines
for ratio, price, color in zip(fibonacci_ratios, fib_prices, colors):
    plt.axhline(price, color=color, linestyle='-', label=f'Fib Retracement Level: {price:.2f}')
    plt.text(stock_data.index[-1], price, f'FibonacciRatio: {ratio}\nPrice: {price:.2f}', color='black', fontsize=12, ha='right', va='bottom')

# Plot the BTC-USD prices line
plt.plot(stock_data.index[-250:], stock_data['Adj Close'][-250:].values, color='c', linestyle='-', label='BTC-USD Prices')

plt.title('Fibonacci Retracement Levels for BTC-USD')
plt.xlabel('Date')  # Change x-axis label
plt.ylabel('Price')
plt.grid(False)
plt.show()
Fibonacci Retracement Levels for BTC-USD 2024/01–06 using the global Max/Min of Adj Close
  • Method 5.3 (Global): Consider the long-term BTC-USD historical stock dataset (see above) to define the Highest/Lowest Swings and the adjusted time period within these Swings [1]
highest_swing = -1
lowest_swing = -1
for i in range(1,df.shape[0]-1):
  if df['High'][i] > df['High'][i-1] and df['High'][i] > df['High'][i+1] and (highest_swing == -1 or df['High'][i] > df['High'][highest_swing]):
    highest_swing = i

  if df['Low'][i] < df['Low'][i-1] and df['Low'][i] < df['Low'][i+1] and (lowest_swing == -1 or df['Low'][i] < df['Low'][lowest_swing]):
    lowest_swing = i

ratios = [0,0.236, 0.382, 0.5 , 0.618, 0.786,1]
colors = ["black","r","g","b","cyan","magenta","yellow"]
levels = []

max_level = df['High'][highest_swing]
min_level = df['Low'][lowest_swing]

for ratio in ratios:
  if highest_swing > lowest_swing: # Uptrend
    levels.append(max_level - (max_level-min_level)*ratio)
  else: # Downtrend
    levels.append(min_level + (max_level-min_level)*ratio)

plt.rcParams['figure.figsize'] = [18, 8]

plt.rc('font', size=14)


plt.plot(df['Close'])
start_date = df.index[min(highest_swing,lowest_swing)]
end_date = df.index[max(highest_swing,lowest_swing)]
for i in range(len(levels)):

  plt.hlines(levels[i],start_date, end_date,label="{:.1f}%".format(ratios[i]*100),colors=colors[i], linestyles="dashed")


plt.legend()
plt.show()
Fibonacci Retracement Levels for BTC-USD using the Highest/Lowest Swings and the adjusted time frame

Method 6: IQR-Based Global Multiple Support & Resistance Lines

  • The interquartile range (IQR) is the difference between the third quartile and first quartile of a stock price.
  • Using IQR as a statistical measure that helps to determine the spread of prices in quartiles.
  • Assuming we have our data 2024/01–06 in a DataFrame named ‘data’
data=df.copy()
  • Defining 2 main SRL via 25th and 75th percentiles
# Example: using 25th and 75th percentiles
support = data['Close'].quantile(0.25)
resistance = data['Close'].quantile(0.75)
  • Defining 2 additional SRL via 15th and 85th percentiles
support_2 = data['Close'].quantile(0.15)  # Optional: Add additional support levels
resistance_2 = data['Close'].quantile(0.85)  # Optional: Add additional resistance levels
  • Plotting 4 SRL vs Close price
# Plotting
plt.figure(figsize=(12, 7))

# Plotting Close Price
plt.plot(data['Close'], label='Close Price')

# Plotting Support Lines
plt.hlines(support, xmin=data.index[0], xmax=data.index[-1], colors='green', linestyles='dashed', label='Support 1')
plt.hlines(support_2, xmin=data.index[0], xmax=data.index[-1], colors='green', linestyles='dotted', label='Support 2')
# Add more support lines as needed using adjusted variable names

# Plotting Resistance Lines
plt.hlines(resistance, xmin=data.index[0], xmax=data.index[-1], colors='red', linestyles='dashed', label='Resistance 1')
plt.hlines(resistance_2, xmin=data.index[0], xmax=data.index[-1], colors='red', linestyles='dotted', label='Resistance 2')
# Add more resistance lines as needed using adjusted variable names

# Displaying Legends
plt.legend()

# Add labels and title
plt.title('IQR-Based Price Action Analysis with Multiple Support and Resistance Lines')
plt.xlabel('Date')
plt.ylabel('Price')
plt.grid()
plt.show()
BTC-USD Price 2024/01–06: IQR-Based Price Action Analysis with Multiple Support and Resistance Lines

Method 7: Rolling Window Swing Highs & Lows

  • Swing highs and swing lows are earlier market turning points. Hence, they are natural choices for projecting support and resistance levels. Every swing point is a potential support or resistance level.
  • Detecting and plotting local Min/Max (Swings) [1]
#Swing Highs and Lows
import yfinance as yf
import pandas as pd
from scipy.signal import argrelextrema
import matplotlib.pyplot as plt

# Download stock data
stock_data = df.copy()

# Identify local maxima (swing highs)
stock_data['Swing_High'] = stock_data['High'][argrelextrema(stock_data['High'].values, np.greater_equal, order=5)[0]]

# Identify local minima (swing lows)
stock_data['Swing_Low'] = stock_data['Low'][argrelextrema(stock_data['Low'].values, np.less_equal, order=5)[0]]

# Find last two non-NaN values for Swing Highs and Swing Lows
last_two_resistances = stock_data['Swing_High'].dropna().tail(2)
last_two_supports = stock_data['Swing_Low'].dropna().tail(2)

# Plotting
plt.figure(figsize=(12,8))
plt.plot(stock_data['Close'], label="Close Price")
plt.scatter(stock_data.index, stock_data['Swing_High'], color='r', label='Swing Highs', marker='o')
plt.scatter(stock_data.index, stock_data['Swing_Low'], color='g', label='Swing Lows', marker='o')

# Annotate the last two resistance and support prices
for date, price in last_two_resistances.items():
    plt.annotate(f"{price:.2f}", (date, price), textcoords="offset points", xytext=(10,10), ha='center', color='r')

for date, price in last_two_supports.items():
    plt.annotate(f"{price:.2f}", (date, price), textcoords="offset points", xytext=(10,-15), ha='center', color='g')

plt.title(f'{symbol} with Swing Highs & Lows')
plt.legend()
plt.grid()
plt.show()
BTC-USD Price 2024/01–06 with Swing Highs & Lows

Method 8: Pivot Points with Support & Resistance Lines

  • Pivot points SRL are widely accepted as the simplest yet most effective trading strategy. The pivot point is the point in which the market sentiment changes from bearish to bullish.
BTC-USD Price 2024/01–06 with Pivot Points and SRL

Method 9: K-Means Clustering with Support & Resistance Lines

  • Using the K-Means clustering algorithm to detect SRL (cf. [5]).
  • The K-means clustering algorithm finds different sections of the time series data, and groups them into a defined number of groups. This number (K) can be optimized. The highest and lowest value of each group is then defined as the support and resistance values for the cluster.
  • Method 9.1: Global K-Means Price Clustering [1] is implemented as follows:
#K-Means Price Clustering
import yfinance as yf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

# Download BTC-USD stock data 2024

stock_data = df.copy()

# Preparing data for clustering: Normalize time and price to have similar scales
X_time = np.linspace(0, 1, len(stock_data)).reshape(-1, 1)
X_price = (stock_data['Close'].values - np.min(stock_data['Close'])) / (np.max(stock_data['Close']) - np.min(stock_data['Close']))
X_cluster = np.column_stack((X_time, X_price))

# Applying KMeans clustering
num_clusters = 5
kmeans = KMeans(n_clusters=num_clusters)
kmeans.fit(X_cluster)

# Extract cluster centers and rescale back to original price range
cluster_centers = kmeans.cluster_centers_[:, 1] * (np.max(stock_data['Close']) - np.min(stock_data['Close'])) + np.min(stock_data['Close'])

# Plotting
plt.figure(figsize=(12,8))
plt.plot(stock_data['Close'], label="Close Price")
for center in cluster_centers:
    plt.axhline(y=center, color='r', linestyle='--')
    plt.annotate(f"{center:.2f}", xy=(stock_data.index[-1], center * 1.01), xytext=(5,0), textcoords="offset points", fontsize=15, ha='left', va='center', color='r')

plt.title(f'{symbol} Price Data with KMeans Clustering')
plt.legend()
plt.grid()
plt.show()
BTC-USD Price 2024: Global K-Means Clustering SRL
  • Method 9.2: Calculating SRL using Local K-Means Clustering
# Long-term BTC-USD data 2022-2024 (see above)
btc = df.copy()
# Convert adjusted closing price to numpy array
btc_prices = np.array(btc["Adj Close"])
print("BTC Prices:\n", btc_prices)
# Perform cluster analysis
K = 6
kmeans = KMeans(n_clusters=6).fit(btc_prices.reshape(-1, 1))
# predict which cluster each price is in
clusters = kmeans.predict(btc_prices.reshape(-1, 1))
#print("Clusters:\n", clusters)

# Assigns plotly as visualization engine
import plotly.graph_objects as go
pd.options.plotting.backend = 'plotly'
# Arbitrarily 6 colors for our 6 clusters
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo']
# Create Scatter plot, assigning each point a color based
# on it's grouping where group_number == index of color.
fig = btc.plot.scatter(
    x=btc.index,
    y="Adj Close",
    color=[colors[i] for i in clusters],
)
# Configure some styles
layout = go.Layout(
    plot_bgcolor='#efefef',
    showlegend=False,
    # Font Families
    font_family='Monospace',
    font_color='#000000',
    font_size=20,
    xaxis=dict(
        rangeslider=dict(
            visible=False
        ))
)
fig.update_layout(layout)
# Display plot in local browser window
fig.show()
K-Means Clustering of Long-term BTC-USD 2022–2024
  • Calculating Local MinMax price values within K-Means clusters
# Create list to hold values, initialized with infinite values
min_max_values = []
# init for each cluster group
for i in range(6):
    # Add values for which no price could be greater or less
    min_max_values.append([np.inf, -np.inf])
# Print initial values
print(min_max_values)
# Get min/max for each cluster
for i in range(len(btc_prices)):
    # Get cluster assigned to price
    cluster = clusters[i]
    # Compare for min value
    if btc_prices[i] < min_max_values[cluster][0]:
        min_max_values[cluster][0] = btc_prices[i]
    # Compare for max value
    if btc_prices[i] > min_max_values[cluster][1]:
        min_max_values[cluster][1] = btc_prices[i]
# Print resulting values
print(min_max_values)

[[inf, -inf], [inf, -inf], [inf, -inf], [inf, -inf], [inf, -inf], [inf, -inf]]
[[65980.8125, 73083.5], [33086.234375, 40826.21484375], [15787.2841796875, 23957.529296875], [40951.37890625, 52284.875], [24136.97265625, 31792.310546875], [54522.40234375, 65738.7265625]]
  • Plotting horizontal SRL associated with the above clusters
import plotly.graph_objects as go
# Again, assign an arbitrary color to each of the 6 clusters
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo']
# Create Scatter plot, assigning each point a color where
# point group = color index.
fig = btc.plot.scatter(
    x=btc.index,
    y="Adj Close",
    color=[colors[i] for i in clusters],
)
# Add horizontal lines
for cluster_min, cluster_max in min_max_values:
    fig.add_hline(y=cluster_min, line_width=1, line_color="blue")
    fig.add_hline(y=cluster_max, line_width=1, line_color="blue")
# Add a trace of the price for better clarity
fig.add_trace(go.Trace(
    x=btc.index,
    y=btc['Adj Close'],
    line_color="black",
    line_width=1
))
# Make it pretty
layout = go.Layout(
    plot_bgcolor='#efefef',
    showlegend=False,
    # Font Families
    font_family='Monospace',
    font_color='#000000',
    font_size=20,
    xaxis=dict(
        rangeslider=dict(
            visible=False
        ))
)
fig.update_layout(layout)
fig.show()
Horizontal SRL associated with the K-Means clusters
  • Appending more MinMax values within the K-Means clusters
print("Initial Min/Max Values:\n", min_max_values)
# Create container for combined values
output = []
# Sort based on cluster minimum
s = sorted(min_max_values, key=lambda x: x[0])
# For each cluster get average of
for i, (_min, _max) in enumerate(s):
    # Append min from first cluster
    if i == 0:
        output.append(_min)
    # Append max from last cluster
    if i == len(min_max_values) - 1:
        output.append(_max)
    # Append average from cluster and adjacent for all others
    else:
        output.append(sum([_max, s[i+1][0]]) / 2)
print("Sorted Min/Max Values:\n", output)
Initial Min/Max Values:
 [[65980.8125, 73083.5], [33086.234375, 40826.21484375], [15787.2841796875, 23957.529296875], [40951.37890625, 52284.875], [24136.97265625, 31792.310546875], [54522.40234375, 65738.7265625]]
Sorted Min/Max Values:
 [15787.2841796875, 24047.2509765625, 32439.2724609375, 40888.796875, 53403.638671875, 65859.76953125, 73083.5]
  • Checking K-means clusters with K=10
# create a list to contain output values
values = []
# Define a range of cluster values to assess
K = range(1, 10)
# Performa a clustering using each value, save inertia_ value from each
for k in K:
    kmeans_n = KMeans(n_clusters=k)
    kmeans_n.fit(btc_prices.reshape(-1, 1))
    values.append(kmeans_n.inertia_)
  • Examining the Elbow Plot
import plotly.graph_objects as go
# Create initial figure
fig = go.Figure()
# Add line plot of inertia values
fig.add_trace(go.Trace(
    x=list(K),
    y=values,
    line_color="black",
    line_width=1
))
# Make it pretty
layout = go.Layout(
    plot_bgcolor='#efefef',
    showlegend=False,
    # Font Families
    font_family='Monospace',
    font_color='#000000',
    font_size=20,
    xaxis=dict(
        rangeslider=dict(
            visible=False
        ))
)
fig.update_layout(layout)
fig.show()
BTC-USD K-Means Elbow Plot
  • Plotting updated horizontal SRL vs K-Means clusters of BTC-USD Adj Close 2022–2024
import plotly.graph_objects as go
# Again, assign an arbitrary color to each of the 6 clusters
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo']
# Create Scatter plot, assigning each point a color where
# point group = color index.
fig = btc.plot.scatter(
    x=btc.index,
    y="Adj Close",
    color=[colors[i] for i in clusters],
)
# Add horizontal lines
for cluster_min, cluster_max in min_max_values:
    fig.add_hline(y=cluster_min, line_width=1, line_color="blue")
    fig.add_hline(y=cluster_max, line_width=1, line_color="blue")
# Add a trace of the price for better clarity
fig.add_trace(go.Trace(
    x=btc.index,
    y=btc['Adj Close'],
    line_color="black",
    line_width=1
))
# Make it pretty
layout = go.Layout(
    plot_bgcolor='#efefef',
    showlegend=False,
    # Font Families
    font_family='Monospace',
    font_color='#000000',
    font_size=20,
    xaxis=dict(
        rangeslider=dict(
            visible=False
        ))
)
# Add horizontal lines
for cluster_avg in output:
    fig.add_hline(y=cluster_avg, line_width=1, line_color="blue")
fig.update_layout(layout)
fig.show()
Horizontal Local SRL vs K-Means clusters of BTC-USD Adj Close 2022–2024

Method 10: Volume-Based Support & Resistance Lines

  • Remember the importance of trading volume, which can confirm the strength of each level, as changes in volume often signal the reliability of support or resistance.
  • The algorithm [1] consists of the following steps: (1) calculate the volume profile in terms of the 100 price bins Low/High Min/Max; (2) calculate SRL based on np.digitize(current_price, price_bins); (3) plotting price with SRL vs Volume profile, viz.
import yfinance as yf
import numpy as np
import matplotlib.pyplot as plt

# Download stock data

stock_data = df.copy()

# Calculate volume profile
price_bins = np.linspace(stock_data['Low'].min(), stock_data['High'].max(), 100)
volume_profile = []

for i in range(len(price_bins)-1):
    bin_mask = (stock_data['Close'] > price_bins[i]) & (stock_data['Close'] <= price_bins[i+1])
    volume_profile.append(stock_data['Volume'][bin_mask].sum())

# Estimating support and resistance
current_price = stock_data['Close'].iloc[-1]
support_idx = np.argmax(volume_profile[:np.digitize(current_price, price_bins)])
resistance_idx = np.argmax(volume_profile[np.digitize(current_price, price_bins):]) + np.digitize(current_price, price_bins)

support_price = price_bins[support_idx]
resistance_price = price_bins[resistance_idx]

# Plotting
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(12, 8), gridspec_kw={'width_ratios': [3, 1]})
ax1.plot(stock_data['Close'], label="Close Price")
ax1.axhline(y=support_price, color='g', linestyle='--', label='Support')
ax1.axhline(y=resistance_price, color='r', linestyle='--', label='Resistance')
ax1.legend()
ax1.set_title(f'{symbol} Price Data')
ax2.barh(price_bins[:-1], volume_profile, height=(price_bins[1] - price_bins[0]), color='blue', edgecolor='none')
ax2.set_title('Volume Profile')

plt.tight_layout()
plt.grid()
plt.show()

print(f"Estimated Support Price: {support_price:.2f}")
print(f"Estimated Resistance Price: {resistance_price:.2f}")

Estimated Support Price: 63786.55
Estimated Resistance Price: 71259.19
BTC-USD Price 2024: SRL vs Volume profile

Method 11: Linear/Polynomial Regression Support & Resistance Lines

#Linear & Polynomial Regression
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import argrelextrema
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# Specify the ticker


# Download stock data
stock_data = df.copy()

# Identify local maxima (swing highs) and minima (swing lows)
swing_highs = argrelextrema(stock_data['High'].values, np.greater_equal, order=5)[0]
swing_lows = argrelextrema(stock_data['Low'].values, np.less_equal, order=5)[0]

# Linear regression for trendlines
upper_m, upper_b = np.polyfit(swing_highs, stock_data['High'].values[swing_highs], 1)
lower_m, lower_b = np.polyfit(swing_lows, stock_data['Low'].values[swing_lows], 1)

stock_data['Upper_Trendline'] = upper_m * np.arange(len(stock_data)) + upper_b
stock_data['Lower_Trendline'] = lower_m * np.arange(len(stock_data)) + lower_b

# Preparing data for polynomial regression
X = np.array(range(len(stock_data))).reshape(-1, 1)
y = stock_data['Close'].values

# Polynomial regression
poly = PolynomialFeatures(degree=5)
X_poly = poly.fit_transform(X)
poly_regressor = LinearRegression()
poly_regressor.fit(X_poly, y)
y_pred = poly_regressor.predict(X_poly)

# Plotting
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12,12))
ax1.plot(stock_data['Close'], label="Close Price")
ax1.plot(stock_data['Upper_Trendline'], label="Upper Trendline", color="orange")
ax1.plot(stock_data['Lower_Trendline'], label="Lower Trendline", color="blue")

# Annotate last prices for Trendlines
ax1.annotate(f"{stock_data['Upper_Trendline'].iloc[-1]:.2f}", 
             xy=(stock_data.index[-1], stock_data['Upper_Trendline'].iloc[-1]), 
             xytext=(stock_data.index[-1], stock_data['Upper_Trendline'].iloc[-1] + 5),
             arrowprops=dict(arrowstyle='->'))

ax1.annotate(f"{stock_data['Lower_Trendline'].iloc[-1]:.2f}", 
             xy=(stock_data.index[-1], stock_data['Lower_Trendline'].iloc[-1]), 
             xytext=(stock_data.index[-1], stock_data['Lower_Trendline'].iloc[-1] - 10),
             arrowprops=dict(arrowstyle='->'))

ax1.set_title(f'{symbol} with Trendlines')
ax1.legend(loc = "lower right")
ax1.grid()
ax2.plot(stock_data['Close'], label="Close Price")
ax2.plot(stock_data.index, y_pred, color='r', label="Polynomial Support/Resistance")

# Annotate last price for Polynomial Regression
ax2.annotate(f"{y_pred[-1]:.2f}", 
             xy=(stock_data.index[-1], y_pred[-1]), 
             xytext=(stock_data.index[-1], y_pred[-1] + 5),
             arrowprops=dict(arrowstyle='->'))

ax2.set_title(f'{symbol} Price Data with Polynomial Regression')
ax2.legend()

plt.tight_layout()
plt.grid()
plt.show()
BTC-USD Price 2024: Global Linear Regression SRL.
BTC-USD Price 2024: Global 5th Order Polynomial Regression SRL.

Method 12: Support-Resistance Visualization QC via Seaborn Polynomial Regression

  • Let’s invoke seaborn.regplot to plot the relationship between two variables in a DataFrame.
  • Preparing the input BTC-USD Close price 2024 for replot
series = df['Close']
series.index = np.arange(series.shape[0])
print(series)
0      47686.812500
1      47345.218750
2      46458.117188
3      45897.574219
4      43569.003906
           ...     
885    70567.765625
886    71082.820312
887    70757.164062
888    69342.585938
889    69373.531250
Name: Close, Length: 890, dtype: float64

frame = {'Time': series.index,
         'Close': series}
  • 1-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close")
plt.title("1-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
1-Order Polyfit
  • 2-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=2)
plt.title("2-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
2-Order Polyfit
  • 3-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=3)
plt.title("3-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
3-Order Polyfit
  • 4-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=4)
plt.title("4-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
4-Order Polyfit
  • 5-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=5)
plt.title("5-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
5-Order Polyfit
  • 6-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=6)
plt.title("6-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
6-Order Polyfit
  • 7-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=7)
plt.title("7-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
7-Order Polyfit
  • 8-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=8)
plt.title("8-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
8-Order Polyfit
  • 9-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=9)
plt.title("9-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
9-Order Polyfit
  • 10-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=10)
plt.title("10-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
10-Order Polyfit
  • 11-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=11)
plt.title("11-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
11-Order Polyfit
  • 12-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=12)
plt.title("12-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
12-Order Polyfit
  • 13-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=13)
plt.title("13-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
13-Order Polyfit
  • 14-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=14)
plt.title("14-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
14-Order Polyfit
  • 15-Order Polynomial
import seaborn as sns
sns.set_theme(rc={'figure.figsize':(10,6)})
sns.regplot(data=frame, x="Time", y="Close",order=15)
plt.title("15-Order Polyfit",fontsize=14)
plt.xlabel('Day',fontsize=14)
plt.grid(color='gray')
15-Order Polyfit

Method 13: SMA MinMax Support & Resistance Points

  • Moving averages are a popular technical indicator that can be used in combination with Fibonacci retracement. Moving averages can help identify the trend of the market, while Fibonacci retracement can provide key support and resistance levels.
  • For example, if the price is above the 200-day moving average, and the retracement level is at the 50% level, traders can confirm that the trend is bullish and enter a buy position.
  • But what is the optimal (or statistically significant) SMA window length? Here is the answer:
result = []
train_size = 0.6

n_forward = 40

data=df.copy()


data['Forward Close'] = data['Close'].shift(-n_forward)

data['Forward Return'] = (data['Forward Close'] - data['Close'])/data['Close']

for sma_length in range(20,500):
  
  data['SMA'] = data['Close'].rolling(sma_length).mean()
  data['input'] = [int(x) for x in data['Close'] > data['SMA']]
  
  df = data.dropna()

  training = df.head(int(train_size * df.shape[0]))
  test = df.tail(int((1 - train_size) * df.shape[0]))
  
  tr_returns = training[training['input'] == 1]['Forward Return']
  test_returns = test[test['input'] == 1]['Forward Return']

  mean_forward_return_training = tr_returns.mean()
  mean_forward_return_test = test_returns.mean()

  pvalue = ttest_ind(tr_returns,test_returns,equal_var=False)[1]
 
  result.append({
      'sma_length':sma_length,
      'training_forward_return': mean_forward_return_training,
      'test_forward_return': mean_forward_return_test,
      'p-value':pvalue
  })

result.sort(key = lambda x : -x['training_forward_return'])

result[0]

{'sma_length': 20,
 'training_forward_return': 0.24811903929378637,
 'test_forward_return': -0.06870073406495417,
 'p-value': 1.393936604950164e-11}

plt.plot(data['Close'],label='Close')

plt.plot(data['SMA'],label = "{} periods SMA".format(best_sma))

plt.legend()
plt.grid(color='grey')
plt.show()
BTC-USD Price 2024/01–06 vs SMA-20

Method 14: Local Low/High Support & Resistance Lines

  • Invoking argrelextrema to calculate local Low/High SRL within WINDOW = 10 [1]
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import argrelextrema

WINDOW = 10 

df['min'] = df.iloc[argrelextrema(df['Close'].values, np.less_equal, order=WINDOW)[0]]['Close']
df['max'] = df.iloc[argrelextrema(df['Close'].values, np.greater_equal, order=WINDOW)[0]]['Close']

plt.figure(figsize=(12, 8))
plt.plot(df.index, df['Close'], label='Price')
plt.scatter(df.index, df['min'], color='green', label='Support', marker='^', alpha=0.7,s=150)
plt.scatter(df.index, df['max'], color='red', label='Resistance', marker='v', alpha=0.7,s=150)
plt.title('Support and Resistance Levels')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.show()
BTC-USD Price 2024/01–06 with Local Low/High SRL using argrelextrema

Method 15: Global Low/High Support & Resistance Lines

  • Let’s invoke the long-term BTC-USD stock data (see above) and identify the global MinMax SRL in 2024 as follows
import trendln
import yfinance as yf
import numpy as np
import matplotlib.pyplot as plt
import altair as alt
import pandas as pd

hist=df.copy()

price = hist["Close"]
low52 = min(price[-52 * 5 :])
high52 = max(price[-52 * 5 :])

plot_price = alt.Chart(hist.reset_index()).mark_line().encode(x="Date", y="Close")

overlay = pd.DataFrame({"Close": [low52]})
plot_low52 = (
    alt.Chart(overlay).mark_rule(color="gray", strokeWidth=2).encode(y="Close:Q")
)

overlay = pd.DataFrame({"Close": [high52]})
plot_high52 = (
    alt.Chart(overlay).mark_rule(color="gray", strokeWidth=2).encode(y="Close:Q")
)

alt.layer(plot_price, plot_low52 + plot_high52).properties(
    width=800,
    height=300
).configure_title().configure_axis(
    labelFontSize = 14,
    titleFontSize = 16
)
Global MinMax SRL for long-term BTC-USD data
  • These 2 levels are denoted by multiple touches of price without a breakthrough of the level in 2024.

Conclusions

  • In this comprehensive case study, we have explored various types of SRL. This includes pivot-based, Fibonacci, global/local MinMax, trend lines, rolling window, low/high, swing highs & lows, SMA, IQR, K-Means clustering, horizontal, and diagonal single/multiple levels.
  • Specifically, we have utilized the BTC-USD historical data to evaluate 15 essential SRL methods within the context of automated algo-trading technical analysis discussed earlier [1–15].
  • The resulting integrated SRL15 approach represents our guiding light in the world of crypto trading. It helps us manage the BTC’s volatility and risks effectively.
  • For quant traders: placing stops and limits below support and above resistance zones detected by SRL15 is strongly recommended.
  • Another strategy used in SRL trading is the breakout strategy, whereby traders wait for the stock price to move outside either level.
  • Pros: Traders can aim to sell a particular asset when the price reaches a resistance level zone. This indicates a potential reversal of the uptrend and a good opportunity to sell at a higher price.
  • Cons: SRL are generally unreliable indicators of future price movements. SRL zones can be broken if there is a significant shift in market sentiment or other factors that influence crypto markets.
  • Our results have shown that support and resistance are crucial risk management concepts necessary to make informed decisions in crypto trading and maximize profits.
  • Using these essential technical analysis tools, cryptocurrency traders can identify potential entry and exit points.
  • Disclaimer: Although SRL can provide crucial insights into price movements, traders should incorporate other technical analysis tools and fundamental analysis to develop a well-rounded trading strategy. These methods can reduce the risk of missed opportunities or false signals.

Explore More

References

  1. Algorithmically Identifying Stock Price Support & Resistance in Python
  2. How to Detect Support & Resistance Levels and Breakout using Python
  3. Detection of price support and resistance levels in Python
  4. machinelearning/Support_and_resistance.ipynb
  5. Calculating Support & Resistance in Python using K-Means Clustering
  6. Implementing a Support/Resistance Breakout Strategy (Python Tutorial)
  7. Python Trading Guide: Support & Resistance
  8. Support and Resistance Basics
  9. Bitcoin KeyZones (Support & Resistance Levels)
  10. Identify support & resistance trendlines
  11. Support and Resistance Trading Bot: Boost Your Strategy
  12. A Simple Python Function to Detect Support/Resistance Levels
  13. Support and Resistance Levels | Python
  14. Showcase: Finding Support and Resistance Levels using Python.ipynb
  15. PriceLevels

Disclaimer

  • The following disclaimer clarifies that the information provided in this article is for educational use only and should not be considered financial or investment advice.
  • The information provided does not take into account your individual financial situation, objectives, or risk tolerance.
  • Any investment decisions or actions you undertake are solely your responsibility.
  • You should independently evaluate the suitability of any investment based on your financial objectives, risk tolerance, and investment timeframe.
  • It is recommended to seek advice from a certified financial professional who can provide personalized guidance tailored to your specific needs.
  • The tools, data, content, and information offered are impersonal and not customized to meet the investment needs of any individual. As such, the tools, data, content, and information are provided solely for informational and educational purposes only.

Contacts

Python
Algorithmic Trading
Crypto
Regression
Technical Analysis
Recommended from ReadMedium