Moving average bands (MAB) and moving average band width (MABW)

References

█ OVERVIEW

In "Moving Average Bands" (part 1, July 2021 issue) and "Moving Average Band Width" (part 2, August 2021 issue), author Vitali Apirine explains how moving average bands (MAB) can be used as a trend-following indicator by displaying the movement of a shorter-term moving average in relation to the movement of a longer-term moving average. The distance between the bands will widen as volatility increases and will narrow as volatility decreases.

In part 2, the moving average band width (MABW) measures the percentage difference between the bands. Changes in this difference may indicate a forthcoming move or change in the trend.

█ STRATEGY

  • Rules 1:
    • Enter when the 10-day moving average (EMA) breaks above the upper MA band
    • Exit when the EMA crosses below the lower MA band
  • Rules 2:
    • Enter when the 10-day moving average (EMA) breaks above the upper MA band, preceded by the moving average band width (MABW) making a 20-day lowest low at least once within the last 10 days
    • Exit on a trailing stop of 3 ATRs (14-day) from the trade's highest point

png

png

png

Load basic packages
import pandas as pd
import numpy as np
import os
import gc
import copy
from pathlib import Path
from datetime import datetime, timedelta, time, date
#this package is to download equity price data from yahoo finance
#the source code of this package can be found here: https://github.com/ranaroussi/yfinance/blob/main
import yfinance as yf
pd.options.display.max_rows = 100
pd.options.display.max_columns = 100

import warnings
warnings.filterwarnings("ignore")

import pytorch_lightning as pl
random_seed=1234
pl.seed_everything(random_seed)
Global seed set to 1234





1234
Download data
#S&P 500 (^GSPC),  Dow Jones Industrial Average (^DJI), NASDAQ Composite (^IXIC)
#Russell 2000 (^RUT), Crude Oil Nov 21 (CL=F), Gold Dec 21 (GC=F)
#Treasury Yield 10 Years (^TNX)
#CBOE Volatility Index (^VIX) Chicago Options - Chicago Options Delayed Price. Currency in USD

#benchmark_tickers = ['^GSPC', '^DJI', '^IXIC', '^RUT',  'CL=F', 'GC=F', '^TNX']

benchmark_tickers = ['^GSPC', '^VIX']
tickers = benchmark_tickers + ['GSK', 'BST', 'PFE']
#https://github.com/ranaroussi/yfinance/blob/main/yfinance/base.py
#     def history(self, period="1mo", interval="1d",
#                 start=None, end=None, prepost=False, actions=True,
#                 auto_adjust=True, back_adjust=False,
#                 proxy=None, rounding=False, tz=None, timeout=None, **kwargs):

dfs = {}

for ticker in tickers:
    cur_data = yf.Ticker(ticker)
    hist = cur_data.history(period="max", start='2000-01-01')
    print(datetime.now(), ticker, hist.shape, hist.index.min(), hist.index.max())
    dfs[ticker] = hist
2022-09-10 10:27:39.606085 ^GSPC (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 10:27:39.817370 ^VIX (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 10:27:40.192326 GSK (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 10:27:40.423825 BST (1980, 7) 2014-10-29 00:00:00 2022-09-09 00:00:00
2022-09-10 10:27:40.759307 PFE (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
ticker = 'GSK'
dfs[ticker].tail(5)
Open High Low Close Volume Dividends Stock Splits
Date
2022-09-02 31.600000 31.969999 31.469999 31.850000 8152600 0.0 0.0
2022-09-06 31.650000 31.760000 31.370001 31.469999 5613900 0.0 0.0
2022-09-07 31.209999 31.590000 31.160000 31.490000 4822000 0.0 0.0
2022-09-08 30.910000 31.540001 30.830000 31.510000 6620900 0.0 0.0
2022-09-09 31.950001 31.969999 31.730000 31.889999 3556800 0.0 0.0
Calculate
from core.finta import TA
df = dfs[ticker][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)
TA.MABW
<function core.finta.TA.MABW(ohlc: pandas.core.frame.DataFrame, fast_period: int = 10, slow_period: int = 50, multiplier: float = 1.0, column: str = 'close', adjust: bool = True) -> pandas.core.frame.DataFrame>
df_ta = TA.MABW(df, fast_period = 10, slow_period = 50, multiplier = 1.0)
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

del df_ta
gc.collect()
24915
df_ta = TA.EMA(df, period = 20, column="close")
df_ta.name='EMA'
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

del df_ta
gc.collect()
21
  • Rules 1:
    • Enter when the 10-day moving average (EMA) breaks above the upper MA band
    • Exit when the EMA crosses below the lower MA band
  • Rules 2:
    • Enter when the 10-day moving average (EMA) breaks above the upper MA band, preceded by the moving average band width (MABW) making a 20-day lowest low at least once within the last 10 days
    • Exit on a trailing stop of 3 ATRs (14-day) from the trade's highest point
df['SIGNAL'] = ((df['EMA']>=df['MAB_UPPER']) & (df['EMA'].shift(1)<df['MAB_UPPER'].shift(1))).astype(int)
df['B'] = df['SIGNAL']*(df["High"] + df["Low"])/2
df['SIGNAL'].value_counts()
0    5675
1      35
Name: SIGNAL, dtype: int64
df['MAB_WIDTH'].rolling(20).min()
Date
1999-12-31         NaN
2000-01-03         NaN
2000-01-04         NaN
2000-01-05         NaN
2000-01-06         NaN
                ...   
2022-09-02    3.225288
2022-09-06    3.608219
2022-09-07    4.284088
2022-09-08    5.417958
2022-09-09    6.731750
Name: MAB_WIDTH, Length: 5710, dtype: float64
display(df.head(5))
display(df.tail(5))
Open High Low Close Volume MAB_UPPER MAB_MIDDLE MAB_LOWER MAB_WIDTH MAB_LLV EMA SIGNAL B
Date
1999-12-31 19.60 19.67 19.52 19.56 139400 NaN 19.560000 NaN NaN NaN 19.560000 0 0.0
2000-01-03 19.58 19.71 19.25 19.45 556100 NaN 19.499500 NaN NaN NaN 19.502250 0 0.0
2000-01-04 19.45 19.45 18.90 18.95 367200 NaN 19.278605 NaN NaN NaN 19.299467 0 0.0
2000-01-05 19.21 19.58 19.08 19.58 481700 NaN 19.377901 NaN NaN NaN 19.380453 0 0.0
2000-01-06 19.38 19.43 18.90 19.30 853800 NaN 19.355538 NaN NaN NaN 19.360992 0 0.0
Open High Low Close Volume MAB_UPPER MAB_MIDDLE MAB_LOWER MAB_WIDTH MAB_LLV EMA SIGNAL B
Date
2022-09-02 31.60 31.97 31.47 31.85 8152600 41.981303 33.059733 33.259197 23.184605 1.291809 34.599350 0 0.0
2022-09-06 31.65 31.76 31.37 31.47 5613900 41.787135 32.770690 32.970993 23.585774 1.291809 34.301317 0 0.0
2022-09-07 31.21 31.59 31.16 31.49 4822000 41.593738 32.537838 32.702503 23.934548 1.291809 34.033572 0 0.0
2022-09-08 30.91 31.54 30.83 31.51 6620900 41.399202 32.350958 32.454833 24.221746 1.291809 33.793232 0 0.0
2022-09-09 31.95 31.97 31.73 31.89 3556800 41.216672 32.267147 32.242303 24.433689 1.291809 33.611972 0 0.0
df[['MAB_WIDTH', 'MAB_LLV']].hist(bins=50)
array([[<AxesSubplot:title={'center':'MAB_WIDTH'}>,
        <AxesSubplot:title={'center':'MAB_LLV'}>]], dtype=object)

png

from core.visuals import *
start = -1000
end = df.shape[0]
df_sub = df.iloc[start:end]
# df_sub = df[(df.index<='2019-04-01') & (df.index>='2019-01-24')]
names = {'main_title': f'{ticker}'}
lines0 = basic_lines(df_sub[['MAB_UPPER', 'EMA', 'MAB_LOWER']], 
                     colors = [], 
                     **dict(panel=0, width=1.5, secondary_y=False))

lines1 = basic_lines(df_sub[['MAB_WIDTH', 'MAB_LLV']], 
                     colors = ['cadetblue'], 
                     **dict(panel=1, width=1))

lines2 = basic_lines(df_sub[[ 'B']],
                     colors = ['navy'], 
                     **dict(panel=0, type='scatter', marker=r'${B}$' , markersize=100, secondary_y=False))


lines_ = dict(**lines0, **lines1)
lines_.update(lines2)

#shadows_ = basic_shadows(bands=[-0.01, 0.01], nsamples=df.iloc[start:end].shape[0], **dict(panel=1, color="lightskyblue",alpha=0.1,interpolate=True))
shadows_ = []
fig_config_ = dict(figratio=(18,10), volume=True, volume_panel=2,panel_ratios=(4,2, 2), tight_layout=True, returnfig=True,)

ax_cfg_ = {0:dict(basic=[5, 2, ['MAB_UPPER', 'EMA', 'MAB_LOWER']], 
                 title=dict(label = 'MA', fontsize=9, style='italic',  loc='left'), 
                ),
           2:dict(basic=[2, 0, ['MAB_WIDTH', 'MAB_LLV']]
                ),
          }


names = {'main_title': f'{ticker}'}

aa_, bb_ = make_panels(main_data = df_sub[['Open', 'High', 'Low', 'Close', 'Volume']], 
                       added_plots = lines_,
                       fill_betweens = shadows_, 
                       fig_config = fig_config_, 
                       axes_config = ax_cfg_,  
                       names = names)

png