The Breakout Relative Strength Index (BRSI) by Howard Wang
█ OVERVIEW
In his article in September 2015 issue of Technical Analysis of Stocks and Commodities, “The Breakout Relative Strength Index” author Howard Wang introduced a new indicator that he called “the breakout relative strength index” or BRSI. The BRSI was intended to help traders to determine if a breakout was about to happen as well as the relative strength of the breakout.
█ CALCULATION
There are 2 major steps involved in calculting the BRSI. The first step is to construct the “breakout candlestick” using the original candlestick (i.e. high, low, open, close) data. And the second step is using the “breakout candlestick” along with volume data to calculate the BRSI.
- Step 1: Construct “breakout candlestick”: use 2 days candlestick data (today’s and previous day’s data), the “breakout candlestick”’s high, low, open, close are the following
- use High, Low, Open, Close to denote today’s prices
- use Pre_High, Pre_Low, Pre_Open, Pre_Close to denote previous day’s prices
- use break_High, break_Low, break_Open, break_Close to denote breakout prices
- break_High: max(High, Pre_High), namely the higher of the “High” of the 2 days’ prices
- break_Low: min(Low, Pre_Low),namely the lower of the “Low” of the 2 days data
- break_Open and break_Close:
- If there are two up days or two down days, then the open is the pre-day’s open, and the close is today’s close: If (Close>Open AND Pre_Close>Pre_Open) OR (Close<Open AND Pre_Close<Pre_Open) THEN break_Open=Pre_Open, break_Close=Close
- If there is one up day and one down day, then the open is the pre-day’s close and the close is today’s close: If (Close>Open AND Pre_Close<Pre_Open) OR (Close< Open AND Pre_Close >Pre_Open) THEN break_Open=Pre_Close, break_Close=Close
- Step 2: Calculate BRSI using breakout prices
- High, Low, Open, Close to denote the breakout prices
- Pre_Volume, Volume to denote previous day’s and today’s volume
- breakout price = (High + Low + Open + Close)/4
- breakout volume = (Pre_Volume + Volume)
- breakout strength = (Close - Open)/(High - Low)
- breakout power = breakout price * breakout strength * breakout volume
- if today’s breakout power > previous day’s breakout power then it is Positive power: P = sum of positive power in 14 days
- if today’s breakout power < previous day’s breakout power then it is Negative power: N = sum of absolute value of negative power in 14 days
- breakout ratio = P/N
- BRSI = 100 - (100/1 + breakout ratio)
█ EXPLANATION
BRSI ranges between 0 and 100:
- If BRSI is above 80, it indicates the breakout is strong, the stock is overbought, and the price is likely to go down.
- If BRSI is below 20, it indicates that breakout is weak, the stock is oversold, and the price is likely to go up.
- If BRSI 50, it indicates hold the stock.
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)
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', 'AZN', 'BSX', 'NUVA', 'MDT']
#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(f"{datetime.now()}\t {ticker}\t {hist.shape}\t {hist.index.min()}\t {hist.index.max()}")
dfs[ticker] = hist
2023-03-08 12:33:21.731271 ^GSPC (5831, 7) 2000-01-03 00:00:00-05:00 2023-03-07 00:00:00-05:00
2023-03-08 12:33:22.023871 ^VIX (5832, 7) 2000-01-03 00:00:00-05:00 2023-03-08 00:00:00-05:00
2023-03-08 12:33:22.416429 GSK (5831, 7) 2000-01-03 00:00:00-05:00 2023-03-07 00:00:00-05:00
2023-03-08 12:33:22.690798 BST (2102, 7) 2014-10-29 00:00:00-04:00 2023-03-07 00:00:00-05:00
2023-03-08 12:33:23.130656 PFE (5831, 7) 2000-01-03 00:00:00-05:00 2023-03-07 00:00:00-05:00
2023-03-08 12:33:23.519270 AZN (5831, 7) 2000-01-03 00:00:00-05:00 2023-03-07 00:00:00-05:00
2023-03-08 12:33:23.820784 BSX (5831, 7) 2000-01-03 00:00:00-05:00 2023-03-07 00:00:00-05:00
2023-03-08 12:33:24.115289 NUVA (4736, 7) 2004-05-13 00:00:00-04:00 2023-03-07 00:00:00-05:00
2023-03-08 12:33:24.508971 MDT (5831, 7) 2000-01-03 00:00:00-05:00 2023-03-07 00:00:00-05:00
ticker = '^GSPC'
dfs[ticker].tail(5)
| Open | High | Low | Close | Volume | Dividends | Stock Splits | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 2023-03-01 00:00:00-05:00 | 3963.340088 | 3971.729980 | 3939.050049 | 3951.389893 | 4249480000 | 0.0 | 0.0 |
| 2023-03-02 00:00:00-05:00 | 3938.679932 | 3990.840088 | 3928.159912 | 3981.350098 | 4244900000 | 0.0 | 0.0 |
| 2023-03-03 00:00:00-05:00 | 3998.020020 | 4048.290039 | 3995.169922 | 4045.639893 | 4084730000 | 0.0 | 0.0 |
| 2023-03-06 00:00:00-05:00 | 4055.149902 | 4078.489990 | 4044.610107 | 4048.419922 | 4000870000 | 0.0 | 0.0 |
| 2023-03-07 00:00:00-05:00 | 4048.260010 | 4050.000000 | 3980.310059 | 3986.370117 | 3922500000 | 0.0 | 0.0 |
Define Breakout Relative Strength Index (BRSI) calculation function
import sys
sys.path.append(r"/kaggle/input/technical-indicators-core")
#from core.finta import TA
from finta import TA
df = dfs[ticker][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)
def cal_brsi(ohlcv: pd.DataFrame,
period: int = 14,
) -> pd.Series:
"""
// TASC SEP 2015: The Breakout Relative Strength Index by Howard Wang
"""
ohlcv = ohlcv.copy(deep=True)
ohlcv.columns = [c.lower() for c in ohlcv.columns]
#step 1: get the breakout candlesticks
break_high = ohlcv["high"].rolling(window=2).max()
break_low = ohlcv["low"].rolling(window=2).min()
two_signs = np.sign(ohlcv["close"]-ohlcv["open"]).rolling(window=2).apply(np.prod)
break_open = ohlcv["close"].shift(1)
break_open[two_signs>0] = (ohlcv["open"].shift(1))[two_signs>0]
break_close = ohlcv["close"]
break_vol = ohlcv["volume"].rolling(window=2).sum()
df_break = pd.concat([break_open,break_high, break_low, break_close], axis=1)
df_break.columns = ['open', 'high', 'low', 'close']
#step 2: calculate the BRSI
break_price = df_break[['open', 'high', 'low', 'close']].mean(axis=1)
break_strength = (df_break["close"] - df_break["open"])/(df_break["high"] - df_break["low"])
break_power = break_price*break_strength*break_vol
delta = break_power.diff()
## positive gains (up) and negative gains (down) Series
up, down = delta.copy(), delta.copy()
up[up < 0] = 0
down[down > 0] = 0
_gain = up.rolling(window=period).sum()
_loss = down.abs().rolling(window=period).sum()
RS = _gain / _loss
return pd.Series(100 - (100 / (1 + RS)), index=ohlcv.index, name=f"BRSI{period}")
Calculate Breakout Relative Strength Index (BRSI)
#help(TA.RSI)
df['BRSI']=cal_brsi(df)
df['RSI']=TA.RSI(df, period = 14, column="close")
df['EMA50']=TA.EMA(df, period = 50, column="close")
df['EMA200']=TA.EMA(df, period = 200, column="close")
display(df.head(5))
display(df.tail(5))
| Open | High | Low | Close | Volume | BRSI | RSI | EMA50 | EMA200 | |
|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||
| 2000-01-03 00:00:00-05:00 | 1469.25 | 1478.00 | 1438.36 | 1455.22 | 931800000 | NaN | NaN | 1455.220000 | 1455.220000 |
| 2000-01-04 00:00:00-05:00 | 1455.22 | 1455.22 | 1397.43 | 1399.42 | 1009000000 | NaN | 0.000000 | 1426.762000 | 1427.180500 |
| 2000-01-05 00:00:00-05:00 | 1399.42 | 1413.27 | 1377.68 | 1402.11 | 1085500000 | NaN | 4.935392 | 1418.213826 | 1418.739960 |
| 2000-01-06 00:00:00-05:00 | 1402.11 | 1411.90 | 1392.10 | 1403.45 | 1092300000 | NaN | 7.387438 | 1414.298520 | 1414.859942 |
| 2000-01-07 00:00:00-05:00 | 1403.45 | 1441.47 | 1400.73 | 1441.47 | 1225200000 | NaN | 48.207241 | 1420.176074 | 1420.288924 |
| Open | High | Low | Close | Volume | BRSI | RSI | EMA50 | EMA200 | |
|---|---|---|---|---|---|---|---|---|---|
| Date | |||||||||
| 2023-03-01 00:00:00-05:00 | 3963.34 | 3971.73 | 3939.05 | 3951.39 | 4249480000 | 50.335227 | 40.292240 | 4004.483349 | 4007.101141 |
| 2023-03-02 00:00:00-05:00 | 3938.68 | 3990.84 | 3928.16 | 3981.35 | 4244900000 | 57.286866 | 44.519159 | 4003.576159 | 4006.844911 |
| 2023-03-03 00:00:00-05:00 | 3998.02 | 4048.29 | 3995.17 | 4045.64 | 4084730000 | 54.623822 | 52.319603 | 4005.225721 | 4007.230932 |
| 2023-03-06 00:00:00-05:00 | 4055.15 | 4078.49 | 4044.61 | 4048.42 | 4000870000 | 45.664820 | 52.629750 | 4006.919615 | 4007.640773 |
| 2023-03-07 00:00:00-05:00 | 4048.26 | 4050.00 | 3980.31 | 3986.37 | 3922500000 | 43.431024 | 45.513517 | 4006.113747 | 4007.429124 |
Visualize Breakout Relative Strength Index (BRSI)
#from core.visuals import *
from visuals import *
start = -100
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}'}
names = {'main_title': f'{ticker} - Breakout Relative Strength Index (BRSI)'}
lines0, ax_cfg0 = plot_overlay_lines(data = df_sub, overlay_columns = ['EMA50', 'EMA200'])
#lines1, ax_cfg1 = plot_macd(data = df_sub, macd= 'DI_PLUS', macd_signal = 'DI_MINUS', panel =1)
lines1, shadows1, ax_cfg1 = plot_add_lines(data = df_sub, line_columns=['RSI', 'BRSI'],
panel =1, bands = [20, 80])
#b_s_ = plot_buy_sell(data=df_sub, buy_column='DMI_BUY_Close', sell_column='DMI_SELL_Close')
lines_ = dict(**lines0, **lines1)
#lines_.update(lines2)
#lines_.update(b_s_)
shadows_ = shadows1
fig_config_ = dict(figratio=(18,9), volume=True, volume_panel=2,panel_ratios=(4,2,2), tight_layout=True, returnfig=True,)
ax_cfg_ = ax_cfg0
ax_cfg_.update(ax_cfg1)
#ax_cfg_.update(ax_cfg2)
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)

Call the function from finta.py
#help(TA.BRSI)
df_list = []
for ticker, df in dfs.items():
df = df[['Open', 'High', 'Low', 'Close', 'Volume']].round(2)
#df['BRSI']=cal_brsi(df)
df['BRSI']=TA.BRSI(df, period = 14, )
df['RSI']=TA.RSI(df, period = 14, column="close")
df['EMA50']=TA.EMA(df, period = 50, column="close")
df['EMA200']=TA.EMA(df, period = 200, column="close")
df['ticker'] = ticker
df_list.append(df)
df_all = pd.concat(df_list)
print(df_all.shape)
del df_list
gc.collect()
(47656, 10)
7073
dd = df_all.index
df_all.index = dd.date
df_all.index.name='Date'
df_all.tail(5)
| Open | High | Low | Close | Volume | BRSI | RSI | EMA50 | EMA200 | ticker | |
|---|---|---|---|---|---|---|---|---|---|---|
| Date | ||||||||||
| 2023-03-01 | 82.40 | 82.53 | 81.67 | 82.08 | 5249100 | 46.896985 | 44.022248 | 82.552691 | 86.762242 | MDT |
| 2023-03-02 | 81.49 | 82.56 | 81.20 | 82.26 | 4985100 | 56.823645 | 44.950352 | 82.541213 | 86.717444 | MDT |
| 2023-03-03 | 82.79 | 83.63 | 82.44 | 83.41 | 4771600 | 54.858382 | 50.587132 | 82.575283 | 86.684534 | MDT |
| 2023-03-06 | 83.49 | 83.83 | 81.45 | 81.93 | 6845500 | 42.926467 | 44.300301 | 82.549978 | 86.637225 | MDT |
| 2023-03-07 | 82.09 | 82.15 | 79.51 | 79.74 | 6876000 | 46.116515 | 36.977231 | 82.439782 | 86.568596 | MDT |
df_all.to_csv('b_054_1_brsi_tasc201509.csv', index=True)