Bull Power and Bear Power (Elder-Ray Index)
References
Definition
The Bull Power and Bear Power are the oscillators developed by Dr Alexander Elder. They measure the power of buyers (bulls) and sellers (bears) to push the price in their favor, i.e. above or below a baseline. The two indicators combined are known as Elder-Ray Index. The function of a baseline is often performed by a 13-period Exponential Moving Average (EMA) of closing prices.
The logic is simple: the market situation is constantly changing as bears turn into bulls and vice versa. The indicators help to track this conversion and trade on it.
Read the indicator
You can use the indicators individually but there’s much more sense to use them together as was planned by Elder.
In addition to both indicators, plot the 13 EMA itself on the chart as well. This way you will combine the oscillators with a trend-following tool and increase the quality of the entry signals. The exponential moving average acts as a filter: it shows a trend, so that a trader could pick only those signals which are in the direction of this trend.
- A setup for a buy trade occurs when the following conditions are met:
- EMA is increasing.
- Bears Power is negative but increases.
- There are also other optional cut desirable conditions:
- The last peak of the Bulls Power oscillator is higher than the previous one.
- There’s a bullish divergence between the Bears Power and the price (the price is setting lower lows, but the Bears Power fails to do so).
- It is better not to go long if the Bears Power is positive.
- A setup for a sell occurs when the following conditions are met:
- EMA is decreasing.
- Bulls Power is positive but decreases.
- There are also other optional cut desirable conditions:
- The last low of the Bears Power oscillator is lower than the previous one.
- There’s a bearish divergence between the Bulls Power and the price (the price is setting higher highs, but the Bulls Power only manages to form only lower highs).
- It is better not to go short if the Bulls Power is negative.
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
#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)
#benchmark_tickers = ['^GSPC', '^DJI', '^IXIC', '^RUT', 'CL=F', 'GC=F', '^TNX']
benchmark_tickers = ['^GSPC']
tickers = benchmark_tickers + ['GSK', 'NVO', 'AROC']
#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-08-27 14:41:18.845259 ^GSPC (5701, 7) 1999-12-31 00:00:00 2022-08-26 00:00:00
2022-08-27 14:41:19.190848 GSK (5701, 7) 1999-12-31 00:00:00 2022-08-26 00:00:00
2022-08-27 14:41:19.593140 NVO (5701, 7) 1999-12-31 00:00:00 2022-08-26 00:00:00
2022-08-27 14:41:19.870476 AROC (3782, 7) 2007-08-21 00:00:00 2022-08-26 00:00:00
ticker = 'AROC'
dfs[ticker].tail(5)
| Open | High | Low | Close | Volume | Dividends | Stock Splits | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 2022-08-22 | 7.59 | 7.68 | 7.50 | 7.62 | 753700 | 0.0 | 0 |
| 2022-08-23 | 7.74 | 7.90 | 7.71 | 7.80 | 732200 | 0.0 | 0 |
| 2022-08-24 | 7.78 | 7.95 | 7.74 | 7.92 | 673800 | 0.0 | 0 |
| 2022-08-25 | 7.95 | 8.00 | 7.84 | 7.92 | 857000 | 0.0 | 0 |
| 2022-08-26 | 7.85 | 7.93 | 7.76 | 7.79 | 962900 | 0.0 | 0 |
Define Bull Power and Bear Power (Elder-Ray Index) calculation function
def cal_ebbp(ohlc: pd.DataFrame) -> pd.DataFrame:
"""Bull power and bear power by Dr. Alexander Elder show where today’s high and low lie relative to the a 13-day EMA"""
ohlc = ohlc.copy()
ohlc.columns = [c.lower() for c in ohlc.columns]
bull_power = pd.Series(ohlc["high"] - ohlc["close"].ewm(span=13).mean(), name="EBBP_BULL")
bear_power = pd.Series(ohlc["low"] - ohlc["close"].ewm(span=13).mean(), name="EBBP_BEAR")
return pd.concat([bull_power, bear_power], axis=1)
Calculate Bull Power and Bear Power (Elder-Ray Index)
df = dfs[ticker][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)
cal_ebbp
<function __main__.cal_ebbp(ohlc: pandas.core.frame.DataFrame) -> pandas.core.frame.DataFrame>
df_ta = cal_ebbp(df)
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )
del df_ta
gc.collect()
80
from core.finta import TA
help(TA.EMA)
Help on function EMA in module core.finta:
EMA(ohlc: pandas.core.frame.DataFrame, period: int = 9, column: str = 'close', adjust: bool = True) -> pandas.core.series.Series
Exponential Weighted Moving Average - Like all moving average indicators, they are much better suited for trending markets.
When the market is in a strong and sustained uptrend, the EMA indicator line will also show an uptrend and vice-versa for a down trend.
EMAs are commonly used in conjunction with other indicators to confirm significant market moves and to gauge their validity.
df_ta = TA.EMA(df, period = 13, column = "close")
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )
del df_ta
gc.collect()
42
display(df.head(5))
display(df.tail(5))
| Open | High | Low | Close | Volume | EBBP_BULL | EBBP_BEAR | EMA13 | |
|---|---|---|---|---|---|---|---|---|
| Date | ||||||||
| 2007-08-21 | 50.01 | 50.86 | 49.13 | 49.44 | 1029100 | 1.420000 | -0.310000 | 49.440000 |
| 2007-08-22 | 48.50 | 50.70 | 47.78 | 49.29 | 996500 | 1.340769 | -1.579231 | 49.359231 |
| 2007-08-23 | 49.76 | 49.82 | 47.56 | 48.03 | 742700 | 0.973622 | -1.286378 | 48.846378 |
| 2007-08-24 | 47.93 | 48.77 | 47.87 | 48.58 | 416000 | 0.006308 | -0.893692 | 48.763692 |
| 2007-08-27 | 48.56 | 48.81 | 46.85 | 47.47 | 447000 | 0.390251 | -1.569749 | 48.419749 |
| Open | High | Low | Close | Volume | EBBP_BULL | EBBP_BEAR | EMA13 | |
|---|---|---|---|---|---|---|---|---|
| Date | ||||||||
| 2022-08-22 | 7.59 | 7.68 | 7.50 | 7.62 | 753700 | -0.046064 | -0.226064 | 7.726064 |
| 2022-08-23 | 7.74 | 7.90 | 7.71 | 7.80 | 732200 | 0.163374 | -0.026626 | 7.736626 |
| 2022-08-24 | 7.78 | 7.95 | 7.74 | 7.92 | 673800 | 0.187178 | -0.022822 | 7.762822 |
| 2022-08-25 | 7.95 | 8.00 | 7.84 | 7.92 | 857000 | 0.214724 | 0.054724 | 7.785276 |
| 2022-08-26 | 7.85 | 7.93 | 7.76 | 7.79 | 962900 | 0.144049 | -0.025951 | 7.785951 |
df[['EBBP_BULL', 'EBBP_BEAR']].hist(bins=50)
array([[<AxesSubplot:title={'center':'EBBP_BULL'}>,
<AxesSubplot:title={'center':'EBBP_BEAR'}>]], dtype=object)

#https://github.com/matplotlib/mplfinance
#this package help visualize financial data
import mplfinance as mpf
import matplotlib.colors as mcolors
# all_colors = list(mcolors.CSS4_COLORS.keys())#"CSS Colors"
all_colors = list(mcolors.TABLEAU_COLORS.keys()) # "Tableau Palette",
# all_colors = list(mcolors.BASE_COLORS.keys()) #"Base Colors",
#https://github.com/matplotlib/mplfinance/issues/181#issuecomment-667252575
#list of colors: https://matplotlib.org/stable/gallery/color/named_colors.html
#https://github.com/matplotlib/mplfinance/blob/master/examples/styles.ipynb
def plot_ebbp(main_data, add_data=None, mid_panel=None, chart_type='candle', names=None,
figratio=(14,9)):
style = mpf.make_mpf_style(base_mpf_style='yahoo', #charles
base_mpl_style = 'seaborn-whitegrid',
# marketcolors=mpf.make_marketcolors(up="r", down="#0000CC",inherit=True),
gridcolor="whitesmoke",
gridstyle="--", #or None, or - for solid
gridaxis="both",
edgecolor = 'whitesmoke',
facecolor = 'white', #background color within the graph edge
figcolor = 'white', #background color outside of the graph edge
y_on_right = False,
rc = {'legend.fontsize': 'small',#or number
#'figure.figsize': (14, 9),
'axes.labelsize': 'small',
'axes.titlesize':'small',
'xtick.labelsize':'small',#'x-small', 'small','medium','large'
'ytick.labelsize':'small'
},
)
if (chart_type is None) or (chart_type not in ['ohlc', 'line', 'candle', 'hollow_and_filled']):
chart_type = 'candle'
len_dict = {'candle':2, 'ohlc':3, 'line':1, 'hollow_and_filled':2}
kwargs = dict(type=chart_type, figratio=figratio, volume=False,
panel_ratios=(4,2,2), tight_layout=True, style=style, returnfig=True)
if names is None:
names = {'main_title': '', 'sub_tile': ''}
added_plots = {
# 'S': mpf.make_addplot(add_data['S'], panel=0, color='blue', type='scatter', marker=r'${S}$' , markersize=100, secondary_y=False),
# 'B': mpf.make_addplot(add_data['B'], panel=0, color='blue', type='scatter', marker=r'${B}$' , markersize=100, secondary_y=False),
'EMA13': mpf.make_addplot(add_data['EMA13'], panel=0, color='dodgerblue', secondary_y=False),
'EBBP_BULL': mpf.make_addplot(mid_panel['EBBP_BULL'], type='bar',width=0.7, panel=1, color='tan',alpha=0.65, secondary_y=False),
'EBBP_BEAR': mpf.make_addplot(mid_panel['EBBP_BEAR'], type='bar',width=0.7, panel=2, color='tomato',alpha=0.65, secondary_y=False),
# 'AO-SIGNAL': mpf.make_addplot(mid_panel['AO']-mid_panel['SIGNAL'], type='bar',width=0.7,panel=1, color="pink",alpha=0.65,secondary_y=False),
}
fig, axes = mpf.plot(main_data, **kwargs,
addplot=list(added_plots.values()),
)
# add a new suptitle
fig.suptitle(names['main_title'], y=1.05, fontsize=12, x=0.128)
axes[0].set_title(names['sub_tile'], fontsize=10, style='italic', loc='left')
#set legend
for i, l in zip([2, 4], ['BULL Power', 'BEAR Power']):
# axes[i].legend([None]*1)
# handles = axes[i].get_legend().legendHandles
# axes[i].legend(handles=handles,labels=[l])
axes[i].set_title(l, fontsize=9, style='italic', loc='left')
# axes[2].set_ylabel('EBBP')
# axes[0].set_ylabel(names['y_tiles'][0])
return fig, axes
start = -100
end = df.shape[0]
names = {'main_title': f'{ticker}',
'sub_tile': 'EBBP(Elder-Ray Index): EMA is increasing and Bears Power is negative but increases.'}
aa_, bb_ = plot_ebbp(df.iloc[start:end][['Open', 'High', 'Low', 'Close', 'Volume']],
df.iloc[start:end][['EMA13']],
df.iloc[start:end][['EBBP_BEAR', 'EBBP_BULL']],
chart_type='hollow_and_filled',
names = names,
)
