Mon, 21 Nov 2022 · 8min read

In this tutorial, we are going to write a Python program that detects triangular patterns in market data.
The high-level idea is as follows:

- Load the market data with pandas.
- Find all the pivots.
- Connect the pivots to find triangular patterns.

For this case, we only need the first 5 columns of the CSV file, so we will use

`usecols=range(5)`

and the names of these first 5 columns to avoid loading the full CSV.```
import pandas as pd
path = "BTCUSDT-5m-2022-11-19.csv"
cols = ["time", "open", "high", "low", "close"]
df = pd.read_csv(path, usecols=range(5), names=cols)
df.head()
```

```
def assign_pivot_id(dataframe, l, n_candles):
if l-n_candles < 0 or l+n_candles >= len(dataframe):
return 2
pivot_low = 1
pivot_high = 1
for i in range(l-n_candles, l+n_candles+1):
if dataframe.low[l] > dataframe.low[i]:
pivot_low = 0
if dataframe.high[l] < dataframe.high[i]:
pivot_high = 0
if pivot_low and pivot_high:
return 2
elif pivot_low:
return 0
elif pivot_high:
return 1
else:
return 2
df['pivot'] = df.apply(lambda x: assign_pivot_id(df, x.name, 5), axis=1)
```

`pivot`

with 3 different values:
`(0)`

The row is a pivot low`(1)`

The row is a pivot high`(2)`

The row is not a pivot

`pivot_value`

with the value of the pivot for visualization.
```
import numpy as np
def add_pivot(x):
if x['pivot'] == 1:
return x['high']
elif x['pivot'] == 0:
return x['low']
df['pivot_value'] = df.apply(lambda row: add_pivot(row), axis=1)
```

Now we are going to use plotly to plot the data, first we are going to plot the candles, and then we will add the pivots as data points on the same chart.

```
from plotly import graph_objects
fig = graph_objects.Figure(data=[graph_objects.Candlestick(x=df.index,
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'])])
fig.layout.template = "plotly_dark"
fig.layout.xaxis.rangeslider.visible = False
# Add the pivots
fig.add_scatter(x=df.index, y=df['pivot_value'], mode="markers", marker=dict(size=5, color="orange"),name="pivot")
fig.show()
```

```
from scipy.stats import linregress
BACK_CANDLES = 25
patterns_ids = []
for n in range(0, len(df)):
high_pivots = np.array([])
low_pivots = np.array([])
low_candles = np.array([])
high_candles = np.array([])
for i in range(n-BACK_CANDLES, n+1):
if df.iloc[i].pivot == 1:
high_pivots = np.append(high_pivots, df.iloc[i].high)
high_candles = np.append(high_candles, df.iloc[i].name)
elif df.iloc[i].pivot == 0:
low_pivots = np.append(low_pivots, df.iloc[i].low)
low_candles = np.append(low_candles, df.iloc[i].name)
if high_candles.size == 0 or low_candles.size == 0 or (high_candles.size <= 2 and low_candles.size <= 2):
# If there are no pivots or no resistances/supports (less than 3 pivots)
continue
slope_low, _, correlation_low, _, _ = linregress(low_candles, low_pivots)
slope_high, _, correlation_high, _, _ = linregress(high_candles, high_pivots)
if abs(correlation_high)>=0.7 and abs(correlation_low)>=0.7 and slope_low > 0 and slope_high < 0:
patterns_ids.append(n)
patterns_ids
```

`[50, 51, 77, 123, 124, 125, 126]`

We only need two of the 5 variables returned by `linregress`

: the slope and the Pearson correlation.
The slope is the steepness of the regression line, if there is a triangular pattern, the lower line (support line) will have a positive slope and the upper line (resistances) will have a negative one.
We will also take into account the Pearson correlation, which measures the strength of the linear relationship.
The returned list contains the candlesticks with triangular patterns.
`patterns_ids`

and calculate its regression line as before, only this time, we will store another value returned by `linregress`

:
the `intercept`

, which is the predicted value for y when x is 0. With the intercept we can reconstruct the regression function `y = slope_x*x + intercept`

.
```
for candle_id in patterns_ids:
high_pivots = np.array([])
low_pivots = np.array([])
low_candles = np.array([])
high_candles = np.array([])
for i in range(candle_id-BACK_CANDLES, candle_id+1):
if df.iloc[i].pivot == 1:
high_pivots = np.append(high_pivots, df.iloc[i].high)
high_candles = np.append(high_candles, df.iloc[i].name)
elif df.iloc[i].pivot == 0:
low_pivots = np.append(low_pivots, df.iloc[i].low)
low_candles = np.append(low_candles, df.iloc[i].name)
if high_candles.size == 0 or low_candles.size == 0 or (high_candles.size <= 2 and low_candles.size <= 2):
# If there are no pivots or no resistances/supports (less than 3 pivots)
continue
slope_low, intercept_low, correlation_low, _, _ = linregress(low_candles, low_pivots)
slope_high, intercept_high, correlation_high, _, _ = linregress(high_candles, high_pivots)
fig = graph_objects.Figure(data=[graph_objects.Candlestick(x=df.index,
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'])])
fig.layout.template = "plotly_dark"
fig.layout.xaxis.rangeslider.visible = False
fig.add_scatter(x=df.index, y=df['pivot_value'], mode="markers",
marker=dict(size=4, color="orange"), name="pivot")
low_candles = np.append(low_candles, low_candles[-1]+10)
high_candles = np.append(high_candles, high_candles[-1]+10)
fig.add_trace(graph_objects.Scatter(x=low_candles, y=slope_low*low_candles + intercept_low, mode='lines', name='min slope'))
fig.add_trace(graph_objects.Scatter(x=high_candles, y=slope_high*high_candles + intercept_high, mode='lines', name='max slope'))
fig.show()
```

At Similarcharts we are building an alert system that lets you discover chart patterns at scale before everyone else.

Have a question, feedback or need some guidance? Email us to get in touch!