# The Qullamaggie’s OXY Swing Breakouts

Featured Photo by @Nate_Dumlao at @unsplash

This post was inspired by the Qullamaggie’s trading journey and its application to the TSLA swing breakouts. Read more about breakouts here.

Our current goal is to extend the above breakout analysis to the \$OXY stock.

## Motivation

OXY advanced price chart: candlesticks, trading volume, Bollinger bands, Awsome Oscillator (AO), and Cahikin Oscillator.

1D

5D

The summary of Occidental Petroleum Corporation is based on the most popular technical indicators, such as Moving Averages, Oscillators and Pivots.

OXY technical analysis

OSCILLATORS

MOVING AVERAGES

## E2E Workflow

The E2E algorithm is implemented as a sequence of the following steps:

• trend_filter

Take in a pandas series and output a binary array to indicate if a stock
fits the growth criteria (1) or not (0)
Parameters
———-
prices : pd.core.series.Series
The prices we are using to check for growth
growth_4_min : float, optional
The minimum 4 week growth. The default is 25
growth_12_min : float, optional
The minimum 12 week growth. The default is 50
growth_24_min : float, optional
The minimum 24 week growth. The default is 80
Returns
——-
np.array
A binary array showing the positions where the growth criteria is met

• explicit_heat_smooth

Smoothen out a time series using a explicit finite difference method.
Parameters
———-
prices : np.array
The price to smoothen
t_end : float
The time at which to terminate the smootheing (i.e. t = 2)
Returns
——-
P : np.array
The smoothened time-series

Time spacing, must be < 1 for numerical stability

Set up the initial condition

Solve the finite difference scheme for the next time-step

Add the fixed boundary conditions since the above solves the interior points only

• check_consolidation

Smoothen the time-series and check for consolidation, see the
docstring of find_consolidation for the parameters

• find_consolidation

Return a binary array to indicate whether each of the data-points are
classed as consolidating or not
Parameters
———-
prices : np.array
The price time series to check for consolidation
days_to_smooth : int, optional
The length of the time-series to smoothen (days). The default is 50.
perc_change_days : int, optional
The days back to % change compare against (days). The default is 5.
perc_change_thresh : float, optional
The range trading % criteria for consolidation. The default is 0.015.
check_days : int, optional
This says the number of lookback days to check for any consolidation.
If any days in check_days back is consolidating, then the last data
point is said to be consolidating. The default is 5.
Returns
——-
res : np.array
The binary array indicating consolidation (1) or not (0)

• We download the Yahoofinance \$OXY data and call the above functions
• Data visualizations using matplotlib scatter plot – original Close price + Volume size (red) vs breakouts (green).

## Results

Set working directory YOURPATH

import os

os.chdir(‘YOURPATH’)

os. getcwd()

Import libraries

import numpy as np
import pandas as pd
import yfinance as yf

Define functions

def trend_filter(prices: pd.core.series.Series,
growth_4_min: float = 25.,
growth_12_min: float = 50.,
growth_24_min: float = 80.) -> np.array:
”’
Take in a pandas series and output a binary array to indicate if a stock
fits the growth criteria (1) or not (0)
Parameters
———-
prices : pd.core.series.Series
The prices we are using to check for growth
growth_4_min : float, optional
The minimum 4 week growth. The default is 25
growth_12_min : float, optional
The minimum 12 week growth. The default is 50
growth_24_min : float, optional
The minimum 24 week growth. The default is 80
Returns
——-
np.array
A binary array showing the positions where the growth criteria is met
”’

```growth_func = lambda x: 100*(x.values[-1]/x.min() - 1)

growth_4 = df['Close'].rolling(20).apply(growth_func) > growth_4_min
growth_12 = df['Close'].rolling(60).apply(growth_func) > growth_12_min
growth_24 = df['Close'].rolling(120).apply(growth_func) > growth_24_min

return np.where(
growth_4 | growth_12 | growth_24,
1,
0,
)
```

if name == ‘main‘:

```df = yf.download('OXY')
df.loc[:, 'trend_filter'] = trend_filter(df['Close'])
df.dropna()
```
```[*********************100%***********************]  1 of 1 completed
```

df_trending = df[df[‘trend_filter’] == 1]

def explicit_heat_smooth(prices: np.array,
t_end: float = 5.0) -> np.array:
”’
Smoothen out a time series using a explicit finite difference method.
Parameters
———-
prices : np.array
The price to smoothen
t_end : float
The time at which to terminate the smootheing (i.e. t = 2)
Returns
——-
P : np.array
The smoothened time-series
”’

```k = 0.1 # Time spacing, must be < 1 for numerical stability

# Set up the initial condition
P = prices

t = 0
while t < t_end:
# Solve the finite difference scheme for the next time-step
P = k*(P[2:] + P[:-2]) + P[1:-1]*(1-2*k)

# Add the fixed boundary conditions since the above solves the interior
# points only
P = np.hstack((
np.array([prices[0]]),
P,
np.array([prices[-1]]),
))
t += k

return P
```

def check_consolidation(prices: np.array,
perc_change_days: int,
perc_change_thresh: float,
check_days: int) -> int:
”’
Smoothen the time-series and check for consolidation, see the
docstring of find_consolidation for the parameters
”’

```# Find the smoothed representation of the time series
prices = explicit_heat_smooth(prices)

# Perc change of the smoothed time series to perc_change_days days prior
perc_change = prices[perc_change_days:]/prices[:-perc_change_days] - 1

consolidating = np.where(np.abs(perc_change) < perc_change_thresh, 1, 0)

# Provided one entry in the last n days passes the consolidation check,
# we say that the financial instrument is in consolidation on the end day
if np.sum(consolidating[-check_days:]) > 0:
return 1
else:
return 0
```

def find_consolidation(prices: np.array,
days_to_smooth: int = 50,
perc_change_days: int = 5,
perc_change_thresh: float = 0.015,
check_days: int = 5) -> np.array:
”’
Return a binary array to indicate whether each of the data-points are
classed as consolidating or not
Parameters
———-
prices : np.array
The price time series to check for consolidation
days_to_smooth : int, optional
The length of the time-series to smoothen (days). The default is 50.
perc_change_days : int, optional
The days back to % change compare against (days). The default is 5.
perc_change_thresh : float, optional
The range trading % criteria for consolidation. The default is 0.015.
check_days : int, optional
This says the number of lookback days to check for any consolidation.
If any days in check_days back is consolidating, then the last data
point is said to be consolidating. The default is 5.
Returns
——-
res : np.array
The binary array indicating consolidation (1) or not (0)
”’

```res = np.full(prices.shape, np.nan)

for idx in range(days_to_smooth, prices.shape[0]):
res[idx] = check_consolidation(
prices = prices[idx-days_to_smooth:idx],
perc_change_days = perc_change_days,
perc_change_thresh = perc_change_thresh,
check_days = check_days,
)

return res
```

Let’s proceed with main

if name == ‘main‘:

```df = yf.download('TSLA')
df.loc[:, 'consolidating'] = find_consolidation(df['Close'].values)
df.dropna()
```
```[*********************100%***********************]  1 of 1 completed
```

df.loc[:, ‘consolidating’] = find_consolidation(df[‘Close’].values)
df.loc[:, ‘trend_filter’] = trend_filter(df[‘Close’])
df.loc[:, ‘filtered’] = np.where(
df[‘consolidating’] + df[‘trend_filter’] == 2,
True,
False,
)

`[*********************100%***********************]  1 of 1 completed`

Our dataframe df looks as follows

```<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 3051 entries, 2010-06-29 to 2022-08-10
Data columns (total 9 columns):
#   Column         Non-Null Count  Dtype
---  ------         --------------  -----
0   Open           3051 non-null   float64
1   High           3051 non-null   float64
2   Low            3051 non-null   float64
3   Close          3051 non-null   float64
4   Adj Close      3051 non-null   float64
5   Volume         3051 non-null   int64
6   consolidating  3001 non-null   float64
7   trend_filter   3051 non-null   int32
8   filtered       3051 non-null   bool
dtypes: bool(1), float64(6), int32(1), int64(1)
memory usage: 205.6 KB```

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df.index = pd.DatetimeIndex(data=df.index, tz=’US/Eastern’)

import matplotlib
plt.figure(figsize=(12,10))
matplotlib.rcParams.update({‘font.size’: 18})
import matplotlib.pyplot as plt
plt.plot(dft.DateTime,df.Close, ‘r’)
plt.xlabel(“Date”)
plt.ylabel(“Price \$”)
plt.savefig(‘tslaprice.png’)

plt.figure(figsize=(12,10))
scal=5e5
plt.scatter(df0.index, df0[“Close”],color=’red’,s=df0[“Volume”]/scal,alpha=0.4)
plt.scatter(df1.index, df1[“Close”],color=’green’,s=df1[“Volume”]/scal,alpha=0.4)
plt.xlabel(“Date”)
plt.ylabel(“Price \$”)

plt.legend([“Close” , “Filtered”], facecolor=’bisque’,
loc=’upper center’, bbox_to_anchor=(0.5, -0.08),
ncol=2)
plt.grid()
plt.show()
plt.savefig(‘oxypriceswingfilter.png’)

## Conclusions

print(df.loc[‘2021-01-01 00:00:00-04:00′:’2022-08-22 00:00:00-04:00’])

```                    Adj Close    Volume  consolidating  trend_filter  \
Date
2021-01-04 00:00:00-05:00  17.351271  18497800            0.0             1
2021-01-05 00:00:00-05:00  19.101311  37293800            1.0             1
2021-01-06 00:00:00-05:00  19.886841  37156400            1.0             1
2021-01-07 00:00:00-05:00  20.453615  24299300            1.0             1
2021-01-08 00:00:00-05:00  19.966389  18277900            0.0             1
...                              ...       ...            ...           ...
2022-08-16 00:00:00-04:00  63.509998  16662200            1.0             0
2022-08-17 00:00:00-04:00  62.970001  14889800            1.0             0
2022-08-18 00:00:00-04:00  64.879997  16818000            1.0             0
2022-08-19 00:00:00-04:00  71.290001  79840900            0.0             0
2022-08-22 00:00:00-04:00  69.029999  47888500            0.0             0

filtered
Date
2021-01-04 00:00:00-05:00     False
2021-01-05 00:00:00-05:00      True
2021-01-06 00:00:00-05:00      True
2021-01-07 00:00:00-05:00      True
2021-01-08 00:00:00-05:00     False
...                             ...
2022-08-16 00:00:00-04:00     False
2022-08-17 00:00:00-04:00     False
2022-08-18 00:00:00-04:00     False
2022-08-19 00:00:00-04:00     False
2022-08-22 00:00:00-04:00     False  ```

We focus on the “True” trading signals and ignore “False”.

The above scatter plot and the table help identify the setups. You need to have a watchlist ready before the market open. You should also probably have alerts set, and know how many shares you want to buy.

A swing trader can use the daily chart to find these setups, but it also works on the weekly chart and the intraday (1- and 5-minute) charts.