Strategies
You can initilize a strategy by Python language.
There are many libraries that you can use for defining your trading strategy: you only have to choose what you like best. In these samples, the library used for trading strategy is Backtesting.py.
How to load indicators on your strategy
The best practice is to prepare one column for each indicator that you want to use on your strategy.
[1]:
!pip install pandas_datareader
# initialization
import numpy as np
import pandas as pd
from pandas_datareader import data as pdr
start='2017-10-30'
end='2020-10-08'
amzn = pdr.DataReader('AMZN', 'yahoo', start, end)
# simple indicators
amzn['SMA20'] = amzn['Close'].rolling(20).mean()
amzn['SMA50'] = amzn['Close'].rolling(50).mean()
amzn['EMA20'] = amzn['Close'].ewm(span=20, adjust=False).mean()
amzn['EMA50'] = amzn['Close'].ewm(span=50, adjust=False).mean()
amzn['STD20'] = amzn['Close'].rolling(20).std()
amzn['Upper Band'] = amzn['SMA20'] + (amzn['STD20'] * 2)
amzn['Lower Band'] = amzn['SMA20'] - (amzn['STD20'] * 2)
#amzn['Upper Band'] = amzn['EMA20'] + (amzn['STD20'] * 2)
#amzn['Lower Band'] = amzn['EMA20'] - (amzn['STD20'] * 2)
def RSI(data, time_window):
diff = data.diff(1).dropna()
up_chg = 0 * diff
down_chg = 0 * diff
up_chg[diff > 0] = diff[ diff>0 ]
down_chg[diff < 0] = diff[ diff < 0 ]
up_chg_avg = up_chg.ewm(com=time_window-1 , min_periods=time_window).mean()
down_chg_avg = down_chg.ewm(com=time_window-1 , min_periods=time_window).mean()
rs = abs(up_chg_avg/down_chg_avg)
rsi = 100 - 100/(1+rs)
return rsi
amzn['RSI'] = RSI(amzn['Close'], 14)
amzn['RSI70'] = 70.0
amzn['RSI50'] = 50.0
# custom indicator
def RSIaverage(data, n1, n2):
RSI_1 = RSI(data, n1)
RSI_2 = RSI(data, n2)
return (RSI_1 + RSI_2) / 2
amzn['RSIaverage'] = RSIaverage(amzn['Close'], 2, 14)
# data ranges
piece_d = pd.date_range(start='2017-10-30', end='2020-10-01')
#piece_d = pd.date_range(start='2020-01-01', end='2020-10-01')
amzn_piece_d = amzn.reindex(piece_d)
data = pd.DataFrame(amzn_piece_d.dropna())
# sample of divergence signal
divergence = [[('2020-07-22',3250),('2020-09-01',3550)]]
data['divergence'] = np.where((data.index == '2020-07-10') | (data.index == '2020-09-01'), data['RSI'], None)
data['divergence'] = data['divergence'].dropna()
Requirement already satisfied: pandas_datareader in /opt/conda/lib/python3.8/site-packages (0.9.0)
Requirement already satisfied: pandas>=0.23 in /opt/conda/lib/python3.8/site-packages (from pandas_datareader) (1.1.5)
Requirement already satisfied: requests>=2.19.0 in /opt/conda/lib/python3.8/site-packages (from pandas_datareader) (2.25.1)
Requirement already satisfied: lxml in /opt/conda/lib/python3.8/site-packages (from pandas_datareader) (4.6.2)
Requirement already satisfied: python-dateutil>=2.7.3 in /opt/conda/lib/python3.8/site-packages (from pandas>=0.23->pandas_datareader) (2.8.1)
Requirement already satisfied: pytz>=2017.2 in /opt/conda/lib/python3.8/site-packages (from pandas>=0.23->pandas_datareader) (2020.5)
Requirement already satisfied: numpy>=1.15.4 in /opt/conda/lib/python3.8/site-packages (from pandas>=0.23->pandas_datareader) (1.19.4)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.8/site-packages (from python-dateutil>=2.7.3->pandas>=0.23->pandas_datareader) (1.15.0)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in /opt/conda/lib/python3.8/site-packages (from requests>=2.19.0->pandas_datareader) (1.26.2)
Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.8/site-packages (from requests>=2.19.0->pandas_datareader) (2020.12.5)
Requirement already satisfied: idna<3,>=2.5 in /opt/conda/lib/python3.8/site-packages (from requests>=2.19.0->pandas_datareader) (2.10)
Requirement already satisfied: chardet<5,>=3.0.2 in /opt/conda/lib/python3.8/site-packages (from requests>=2.19.0->pandas_datareader) (4.0.0)
You can have to see your strategy and the plot is your solution. If you have not defined which indicators you want to use, you can see them all together.
[2]:
!pip install mplfinance
# plot with candle daily, sma 20, sma 50, bb, RSI and yahoo style
import mplfinance as mpf
kwargs = dict(type='candle',volume=True,figratio=(16,9),figscale=2)
aps = [
mpf.make_addplot(data['SMA20'],color='C0'), # blue
mpf.make_addplot(data['SMA50'],color='C1'), # orange
# mpf.make_addplot(data['EMA20'],color='C0'), # blue
# mpf.make_addplot(data['EMA50'],color='C1'), # orange
mpf.make_addplot(data['Upper Band'],linestyle='-.',color='g'),
mpf.make_addplot(data['Lower Band'],linestyle='-.',color='g'),
mpf.make_addplot(data['RSI'],color='C4',panel=2,ylabel='RSI'),
mpf.make_addplot(data['RSI70'],color='g',panel=2,type='line',linestyle='-.',alpha=0.5),
mpf.make_addplot(data['RSI50'],color='r',panel=2,type='line',linestyle='-.',alpha=0.5),
mpf.make_addplot(data['divergence'],color='C0',panel=2,type='scatter',markersize=78,marker='o'),
mpf.make_addplot(data['RSIaverage'],color='C2',panel=3,ylabel='RSIaverage'),
]
mpf.plot(data,**kwargs,style='yahoo',title='AMZN',addplot=aps,alines=divergence,fill_between=dict(y1=data['Lower Band'].values,y2=data['Upper Band'].values,alpha=0.1,color='g'))
#mpf.plot(data,**kwargs,style='yahoo',title='AMZN',addplot=aps,alines=divergence,fill_between=dict(y1=data['Lower Band'].values,y2=data['Upper Band'].values,alpha=0.1,color='g'),savefig=dict(fname='plot.with.candle.daily.sma.20.sma.50.bb.RSI.yahoo.style.png'))
Requirement already satisfied: mplfinance in /opt/conda/lib/python3.8/site-packages (0.12.7a4)
Requirement already satisfied: matplotlib in /opt/conda/lib/python3.8/site-packages (from mplfinance) (3.3.3)
Requirement already satisfied: pandas in /opt/conda/lib/python3.8/site-packages (from mplfinance) (1.1.5)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in /opt/conda/lib/python3.8/site-packages (from matplotlib->mplfinance) (2.4.7)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.8/site-packages (from matplotlib->mplfinance) (8.0.1)
Requirement already satisfied: numpy>=1.15 in /opt/conda/lib/python3.8/site-packages (from matplotlib->mplfinance) (1.19.4)
Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/lib/python3.8/site-packages (from matplotlib->mplfinance) (2.8.1)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.8/site-packages (from matplotlib->mplfinance) (0.10.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.8/site-packages (from matplotlib->mplfinance) (1.3.1)
Requirement already satisfied: six in /opt/conda/lib/python3.8/site-packages (from cycler>=0.10->matplotlib->mplfinance) (1.15.0)
Requirement already satisfied: pytz>=2017.2 in /opt/conda/lib/python3.8/site-packages (from pandas->mplfinance) (2020.5)
How to load signals on your strategy
The best practice is to prepare one column for each signal that you want to use on your strategy. The strategy below use the moving averages that they are SMA and EMA. You can use one or the other.
Disclaimer
The strategies below are some simple samples for having an idea how to use the libraries: those strategies are for the educational purpose only. All investments and trading in the stock market involve risk: any decisions related to buying/selling of stocks or other financial instruments should only be made after a thorough research, backtesting, running in demo and seeking a professional assistance if required.
Moving Average Crossover Strategy - Sample 1
when the price value crosses the MA value from below, it will close any existing short position and go long (buy) one unit of the asset
when the price value crosses the MA value from above, it will close any existing long position and go short (sell) one unit of the asset
Reference: https://www.learndatasci.com/tutorials/python-finance-part-3-moving-average-trading-strategy/
[3]:
# Moving Average Crossover Strategy - Sample 1
#ma = 'SMA20'
ma = 'EMA20'
# Taking the difference between the prices and the MA timeseries
data['price_ma_diff'] = data['Close'] - data[ma]
# Taking the sign of the difference to determine whether the price or the EMA is greater
data['signal1'] = data['price_ma_diff'].apply(np.sign)
data['position1'] = data['signal1'].diff()
data['buy'] = np.where(data['position1'] == 2, data[ma], np.nan)
data['sell'] = np.where(data['position1'] == -2, data[ma], np.nan)
[4]:
# plot with candle daily, sma 20, signal and yahoo style
import mplfinance as mpf
kwargs = dict(type='candle',figratio=(16,9),figscale=2)
aps = [
mpf.make_addplot(data[ma],color='C0'), # blue
mpf.make_addplot(data['buy'],color='g',type='scatter',markersize=78,marker='^'),
mpf.make_addplot(data['sell'],color='r',type='scatter',markersize=78,marker='v'),
mpf.make_addplot(data['signal1'],color='C1',panel=1,ylabel='signal')
]
mpf.plot(data,**kwargs,style='yahoo',title='AMZN',addplot=aps)
#mpf.plot(data,**kwargs,style='yahoo',title='AMZN',addplot=aps,savefig=dict(fname='plot.with.candle.daily.sma.20.signal.yahoo.style.png'))
[5]:
!pip install backtesting
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
class PositionSign(Strategy):
def init(self):
self.close = self.data.Close
def next(self):
i = len(self.data.Close) - 1
if self.data.position1[-1] == 2:
self.position.close()
self.buy()
elif self.data.position1[-1] == -2:
self.position.close()
self.sell()
bt = Backtest(data, PositionSign, cash=10000, commission=.002, exclusive_orders=True)
bt.run()
Requirement already satisfied: backtesting in /opt/conda/lib/python3.8/site-packages (0.3.0)
Requirement already satisfied: bokeh>=1.4.0 in /opt/conda/lib/python3.8/site-packages (from backtesting) (2.2.3)
Requirement already satisfied: numpy in /opt/conda/lib/python3.8/site-packages (from backtesting) (1.19.4)
Requirement already satisfied: pandas!=0.25.0,>=0.25.0 in /opt/conda/lib/python3.8/site-packages (from backtesting) (1.1.5)
Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/lib/python3.8/site-packages (from bokeh>=1.4.0->backtesting) (2.8.1)
Requirement already satisfied: Jinja2>=2.7 in /opt/conda/lib/python3.8/site-packages (from bokeh>=1.4.0->backtesting) (2.11.2)
Requirement already satisfied: packaging>=16.8 in /opt/conda/lib/python3.8/site-packages (from bokeh>=1.4.0->backtesting) (20.8)
Requirement already satisfied: pillow>=7.1.0 in /opt/conda/lib/python3.8/site-packages (from bokeh>=1.4.0->backtesting) (8.0.1)
Requirement already satisfied: PyYAML>=3.10 in /opt/conda/lib/python3.8/site-packages (from bokeh>=1.4.0->backtesting) (5.3.1)
Requirement already satisfied: typing-extensions>=3.7.4 in /opt/conda/lib/python3.8/site-packages (from bokeh>=1.4.0->backtesting) (3.7.4.3)
Requirement already satisfied: tornado>=5.1 in /opt/conda/lib/python3.8/site-packages (from bokeh>=1.4.0->backtesting) (6.1)
Requirement already satisfied: MarkupSafe>=0.23 in /opt/conda/lib/python3.8/site-packages (from Jinja2>=2.7->bokeh>=1.4.0->backtesting) (1.1.1)
Requirement already satisfied: pyparsing>=2.0.2 in /opt/conda/lib/python3.8/site-packages (from packaging>=16.8->bokeh>=1.4.0->backtesting) (2.4.7)
Requirement already satisfied: pytz>=2017.2 in /opt/conda/lib/python3.8/site-packages (from pandas!=0.25.0,>=0.25.0->backtesting) (2020.5)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.8/site-packages (from python-dateutil>=2.1->bokeh>=1.4.0->backtesting) (1.15.0)
/opt/conda/lib/python3.8/site-packages/backtesting/_plotting.py:45: UserWarning: Jupyter Notebook detected. Setting Bokeh output to notebook. This may not work in Jupyter clients without JavaScript support (e.g. PyCharm, Spyder IDE). Reset with `backtesting.set_bokeh_output(notebook=False)`.
warnings.warn('Jupyter Notebook detected. '
[5]:
Start 2018-01-10 00:00:00
End 2020-10-01 00:00:00
Duration 995 days 00:00:00
Exposure Time [%] 96.9432
Equity Final [$] 6449.39
Equity Peak [$] 11361.5
Return [%] -35.5061
Buy & Hold Return [%] 156.811
Return (Ann.) [%] -14.8609
Volatility (Ann.) [%] 25.1543
Sharpe Ratio 0
Sortino Ratio 0
Calmar Ratio 0
Max. Drawdown [%] -62.3199
Avg. Drawdown [%] -17.0591
Max. Drawdown Duration 934 days 00:00:00
Avg. Drawdown Duration 238 days 00:00:00
# Trades 68
Win Rate [%] 25
Best Trade [%] 58.9442
Worst Trade [%] -12.944
Avg. Trade [%] -0.750267
Max. Trade Duration 141 days 00:00:00
Avg. Trade Duration 15 days 00:00:00
Profit Factor 0.790866
Expectancy [%] -0.473583
SQN -1.05025
_strategy PositionSign
_equity_curve ...
_trades Size EntryB...
dtype: object
[6]:
bt.plot()
Moving Average Crossover Strategy - Sample 2
when the short term moving average crosses above the long term moving average, this indicates a buy signal
when the short term moving average crosses below the long term moving average, it may be a good moment to sell
[7]:
# Moving Average Crossover Strategy - Sample 2
ma20 = 'SMA20'
ma50 = 'SMA50'
#ma20 = 'EMA20'
#ma50 = 'EMA50'
data['signal2'] = 0.0
data['signal2'] = np.where(data[ma20] > data[ma50], 1.0, 0.0)
data['position2'] = data['signal2'].diff()
data['buy'] = np.where(data['position2'] == 1, data[ma20], np.nan)
data['sell'] = np.where(data['position2'] == -1, data[ma20], np.nan)
[8]:
# plot with candle daily, sma 20, sma 50, signal and yahoo style
import mplfinance as mpf
kwargs = dict(type='candle',figratio=(16,9),figscale=2)
aps = [
mpf.make_addplot(data[ma20],color='C0'), # blue
mpf.make_addplot(data[ma50],color='C1'), # orange
mpf.make_addplot(data['buy'],color='g',type='scatter',markersize=78,marker='^'),
mpf.make_addplot(data['sell'],color='r',type='scatter',markersize=78,marker='v'),
mpf.make_addplot(data['signal2'],color='C1',panel=1,ylabel='signal')
]
mpf.plot(data,**kwargs,style='yahoo',title='AMZN',addplot=aps)
#mpf.plot(data,**kwargs,style='yahoo',title='AMZN',addplot=aps,savefig=dict(fname='plot.with.candle.daily.sma.20.sma.50.signal.yahoo.style.png'))
[9]:
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
class SmaCross(Strategy):
n1 = 20
n2 = 50
def init(self):
close = self.data.Close
self.sma1 = self.I(SMA, close, self.n1)
self.sma2 = self.I(SMA, close, self.n2)
def next(self):
if crossover(self.sma1, self.sma2):
self.position.close()
self.buy()
elif crossover(self.sma2, self.sma1):
self.position.close()
self.sell()
bt = Backtest(data, SmaCross, cash=10000, commission=.002, exclusive_orders=True)
bt.run()
[9]:
Start 2018-01-10 00:00:00
End 2020-10-01 00:00:00
Duration 995 days 00:00:00
Exposure Time [%] 90.5386
Equity Final [$] 7712.69
Equity Peak [$] 12532.8
Return [%] -22.8731
Buy & Hold Return [%] 156.811
Return (Ann.) [%] -9.08703
Volatility (Ann.) [%] 24.431
Sharpe Ratio 0
Sortino Ratio 0
Calmar Ratio 0
Max. Drawdown [%] -52.0307
Avg. Drawdown [%] -11.5153
Max. Drawdown Duration 517 days 00:00:00
Avg. Drawdown Duration 80 days 00:00:00
# Trades 13
Win Rate [%] 38.4615
Best Trade [%] 35.0832
Worst Trade [%] -32.3988
Avg. Trade [%] -2.23977
Max. Trade Duration 166 days 00:00:00
Avg. Trade Duration 70 days 00:00:00
Profit Factor 0.79924
Expectancy [%] -1.11657
SQN -0.620588
_strategy SmaCross
_equity_curve ...
_trades Size EntryB...
dtype: object
[10]:
bt.plot()