Any algorithmic trading strategy should entail the following:
It should be a model based on an underlying market theory since only then can you find its predictive power. Fitting a model to data with great backtesting results is simple, but usually does not provide sound predictions.
It should be as simple as possible – the more complex the strategy, the less likely it is to perform well in the long term (overfitting).
It should restrict the strategy for a well-defined set of financial assets (trading universe) based on the following:
a) Their returns profile.
b) Their returns not being correlated.
c) Their trading patterns – you do not want to trade an illiquid asset; you restrict yourself just to significantly traded assets.
It should define the relevant financial data:
a) Frequency: Daily, monthly, intraday, and suchlike
b) Data source
It should define the model’s parameters.
It should define their timing, entry, exit rules, and position sizing strategy – for example, we cannot trade more than 10% of the average daily volume; usually, the decision to enter/exit is made by a composition of several indicators.
It should define the risk levels – how much of a risk a single asset can bear.
It should define the benchmark used to compare performance against.
It should define its rebalancing policy – as the markets evolve, the position sizes and risk levels will deviate from their target levels and then it is necessary to adjust the portfolios.
Finding a portfolio of stocks to consider for trading
The options are as follows:
Use ETF/index components – for example, the members of the Dow Jones Industrial Average.
Use all listed stocks and then restrict the list to the following:
a) Those stocks that are traded the most
b) Just non-correlated stocks
c) Those stocks that are underperforming or overperforming using a returns model, such as the Fama-French three-factor model
Classify each stock into as many categories as possible:
a) Value/growth stocks
b) By industry
Each trading strategy depends on a number of parameters. Finding the best values, the possible approaches are as follows:
- A parameter sweep by trying each possible value within the range of possible values for each parameter, but this would require an enormous amount of computing resources.
- Very often, a parameter sweep that involves testing many random samples, instead of all values, from the range of possible values provides a reasonable approximation.
To build a large library of algorithmic strategies,
- Subscribe to financial trading blogs.
- Read financial trading books.
The key algorithmic trading strategies can be classified as follows:
- Momentum-based/trend-following strategies
- Mean-reversion strategies
- Mathematical model-based strategies
- Arbitrage strategies
- Market-making strategies
- Index fund rebalancing strategies
- Trading timing optimisation strategies (VWAP, TWAP, POV, and so on)
Following the strategies:
- Identify the signal formula of the strategy and consider it for an entry/exit rule for your own strategy or for a combination with other strategies – some of the most profitable strategies are combinations of existing strategies.
- Consider the frequency of trading – daily trading may not be suitable for all strategies due to the transaction costs.
- Each strategy works for different types of stocks and their market – some work only for trending stocks, some work only for high-volatility stocks, and so on.
Momentum-based/Trend-following strategies
Rolling window mean strategy
This strategy is to own a financial asset if its latest stock price is above the average price over the last X days.
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('AAPL')
context.rolling_window = 90
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, "close", context.rolling_window, "1d")
order_target_percent(context.stock, 1.0 if price_hist[-1] > price_hist.mean() else 0.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2000-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Simple moving averages strategy
This strategy follows a simple rule: buy the stock if the short-term moving average rises above the long-term moving averages:
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrad
import pandas as pd
import pyfolio as pf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('AAPL')
context.rolling_window = 90
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, "close", context.rolling_window, "1d")
rolling_mean_short_term = price_hist.rolling(window=45, center=False).mean()
rolling_mean_long_term = price_hist.rolling(window=90, center=False).mean()
if rolling_mean_short_term[-1] > rolling_mean_long_term[-1]:
order_target_percent(context.stock, 1.0)
elif rolling_mean_short_term[-1] < rolling_mean_long_term[-1]:
order_target_percent(context.stock, 0.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2000-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Exponentially weighted moving averages strategy
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('AAPL')
context.rolling_window = 90
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, "close", context.rolling_window, "1d")
rolling_mean_short_term = price_hist.ewm(span=5, adjust=True, ignore_na=True).mean()
rolling_mean_long_term = price_hist.ewm(span=30, adjust=True, ignore_na=True).mean()
if rolling_mean_short_term[-1] > rolling_mean_long_term[-1]:
order_target_percent(context.stock, 1.0)
elif rolling_mean_short_term[-1] < rolling_mean_long_term[-1]:
order_target_percent(context.stock, 0.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2000-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
RSI strategy
This strategy depends on the stockstats package.
pip install stockstats
The RSI indicator measures the velocity and magnitude of price movements and provides an indicator when a financial asset is oversold or overbought. It is a leading indicator.
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
from stockstats import StockDataFrame as sdf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('AAPL')
context.rolling_window = 20
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, ["open", "high", "low","close"], context.rolling_window, "1d")
stock=sdf.retype(price_hist)
rsi = stock.get('rsi_12')
if rsi[-1] > 90:
order_target_percent(context.stock, 0.0)
elif rsi[-1] < 10:
order_target_percent(context.stock, 1.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2015-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
MACD crossover strategy
Moving Average Convergence Divergence (MACD) is a lagging, trend-following momentum indicator reflecting the relationship between two moving averages of stock prices.
The strategy depends on two statistics, the MACD and the MACD signal line:
- The MACD is defined as the difference between the 12-day exponential moving average and the 26-day exponential moving average.
- The MACD signal line is then defined as the 9-day exponential moving average of the MACD.
The MACD crossover strategy is defined as follows:
- A bullish crossover happens when the MACD line turns upward and crosses beyond the MACD signal line.
- A bearish crossover happens when the MACD line turns downward and crosses under the MACD signal line.
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
from stockstats import StockDataFrame as sdf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('AAPL')
context.rolling_window = 20
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, ["open","high", "low","close"], context.rolling_window, "1d")
stock=sdf.retype(price_hist)
signal = stock['macds']
macd = stock['macd']
if macd[-1] > signal[-1] and macd[-2] <= signal[-2]:
order_target_percent(context.stock, 1.0)
elif macd[-1] < signal[-1] and macd[-2] >= signal[-2]:
order_target_percent(context.stock, 0.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2015-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize,
analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
RSI and MACD strategies
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
from stockstats import StockDataFrame as sdf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('MSFT')
context.rolling_window = 20
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, ["open", "high", "low","close"], context.rolling_window, "1d")
stock=sdf.retype(price_hist)
rsi = stock.get('rsi_12')
signal = stock['macds']
macd = stock['macd']
if rsi[-1] < 50 and macd[-1] > signal[-1] and macd[-2] <= signal[-2]:
order_target_percent(context.stock, 1.0)
elif rsi[-1] > 50 and macd[-1] < signal[-1] and macd[-2] >= signal[-2]:
order_target_percent(context.stock, 0.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2015-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Triple exponential average strategy
The Triple Exponential Average (TRIX) indicator is an oscillator oscillating around the zero line. A positive value indicates an overbought market, whereas a negative value is indicative of an oversold market:
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
from stockstats import StockDataFrame as sdf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('MSFT')
context.rolling_window = 20
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, ["open","high","low","close"], context.rolling_window, "1d")
stock=sdf.retype(price_hist)
trix = stock.get('trix')
if trix[-1] > 0 and trix[-2] < 0:
order_target_percent(context.stock, 0.0)
elif trix[-1] < 0 and trix[-2] > 0:
order_target_percent(context.stock, 1.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2015-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Williams R% strategy
The William R% oscillates from 0 to -100. The stockstats library has implemented the values from 0 to +100.
The values above -20 indicate that the security has been overbought, while values below -80 indicate that the security has been oversold.
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
from stockstats import StockDataFrame as sdf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('MSFT')
context.rolling_window = 20
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, ["open", "high","low","close"], context.rolling_window, "1d")
stock=sdf.retype(price_hist)
wr = stock.get('wr_6')
if wr[-1] < 10:
order_target_percent(context.stock, 0.0)
elif wr[-1] > 90:
order_target_percent(context.stock, 1.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2015-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Mean-reversion strategies
Mean-reversion strategies are based on the assumption that some statistics will revert to their long-term mean values.
Bollinger band strategy
The Bollinger band strategy is based on identifying periods of short-term volatility.
It depends on three lines:
- The middle band line is the simple moving average, usually 20-50 days.
- The upper band is the 2 standard deviations above the middle base line.
- The lower band is the 2 standard deviations below the middle base line.
One way of creating trading signals from Bollinger bands is to define the overbought and oversold market state:
- The market is overbought when the price of the financial asset rises above the upper band and so is due for a pullback.
- The market is oversold when the price of the financial asset drops below the lower band and so is due to bounce back.
This is a mean-reversion strategy, meaning that long term, the price should remain within the lower and upper bands. It works best for low-volatility stocks:
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('DG')
context.rolling_window = 20
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, "close", context.rolling_window, "1d")
middle_base_line = price_hist.mean()
std_line = price_hist.std()
lower_band = middle_base_line - 2 * std_line
upper_band = middle_base_line + 2 * std_line
if price_hist[-1] < lower_band:
order_target_percent(context.stock, 1.0)
elif price_hist[-1] > upper_band:
order_target_percent(context.stock, 0.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2000-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Pairs trading strategy
This strategy became very popular some time ago and ever since, has been overused, so is barely profitable nowadays.
This strategy involves finding pairs of stocks that are moving closely together, or are highly co-integrated. Then, at the same time, we place a BUY order for one stock and a SELL order for the other stock, assuming their relationship will revert back. There are a wide range of varieties of tweaks in terms of how this algorithm is implemented – are the prices log prices? Do we trade only if the relationships are very strong?
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
import numpy as np
import statsmodels.api as sm
from statsmodels.tsa.stattools import coint
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock_x = symbol('PEP')
context.stock_y = symbol('KO')
context.rolling_window = 500
set_commission(PerTrade(cost=5))
context.i = 0
def handle_data(context, data):
context.i += 1
if context.i < context.rolling_window:
return
try:
x_price = data.history(context.stock_x, "close", context.rolling_window,"1d")
x = np.log(x_price)
y_price = data.history(context.stock_y, "close", context.rolling_window,"1d")
y = np.log(y_price)
_, p_value, _ = coint(x, y)
if p_value < .9:
return
slope, intercept = sm.OLS(y, sm.add_constant(x, prepend=True)).fit().params
spread = y - (slope * x + intercept)
zscore = (spread[-1] - spread.mean()) / spread.std()
if -1 < zscore < 1:
return
side = np.copysign(0.5, zscore)
order_target_percent(context.stock_y, -side * 100 / y_price[-1])
order_target_percent(context.stock_x, side * slope*100/x_price[-1])
except:
pass
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2015-1-1', utc=True)
end_date = pd.to_datetime('2018-01-01', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Mathematical model-based strategies
Minimisation of the portfolio volatility strategy with monthly trading
The key success factors of the strategy are the following:
- The stock universe – perhaps a portfolio of global index ETFs would fare better.
- The rolling window – we go back 200 days.
- The frequency of trades – the following algorithm uses monthly trading – notice the construct.
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission, schedule_function, date_rules, time_rules
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
from scipy.optimize import minimize
import numpy as np
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stocks = [symbol('DIS'), symbol('WMT'), symbol('DOW'), symbol('CRM'), symbol('NKE'), symbol('HD'), symbol('V'), symbol('MSFT'), symbol('MMM'), symbol('CSCO'), symbol('KO'), symbol('AAPL'), symbol('HON'), symbol('JNJ'), symbol('TRV'), symbol('PG'), symbol('CVX'), symbol('VZ'), symbol('CAT'), symbol('BA'), symbol('AMGN'), symbol('IBM'), symbol('AXP'), symbol('JPM'), symbol('WBA'), symbol('MCD'), symbol('MRK'), symbol('GS'), symbol('UNH'), symbol('INTC')]
context.rolling_window = 200
set_commission(PerTrade(cost=5))
schedule_function(handle_data, date_rules.month_end(), time_rules.market_open(hours=1))
def minimum_vol_obj(wo, cov):
w = wo.reshape(-1, 1)
sig_p = np.sqrt(np.matmul(w.T, np.matmul(cov, w)))[0, 0]
return sig_p
def handle_data(context, data):
n_stocks = len(context.stocks)
prices = None
for i in range(n_stocks):
price_history = data.history(context.stocks[i], "close", context.rolling_window, "1d")
price = np.array(price_history)
if prices is None:
prices = price
else:
prices = np.c_[prices, price]
rets = prices[1:,:]/prices[0:-1, :]-1.0
mu = np.mean(rets, axis=0)
cov = np.cov(rets.T)
w0 = np.ones(n_stocks) / n_stocks
cons = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0}, {'type': 'ineq', 'fun': lambda w: w})
TOL = 1e-12
res = minimize(minimum_vol_obj, w0, args=cov, method='SLSQP', constraints=cons,
tol=TOL, options={'disp': False})
if not res.success:
return;
w = res.x
for i in range(n_stocks):
order_target_percent(context.stocks[i], w[i])
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2010-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize,
analyze = analyze, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Maximum Sharpe ratio strategy with monthly trading
In this strategy, for the given stocks, we choose their weights so that they maximise the portfolio’s expected Sharpe ratio – such a portfolio lies on the efficient frontier.
We use the PyPortfolioOpt Python library.
pip install PyPortfolioOpt
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbols, set_commission, schedule_function, date_rules, time_rules
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
import numpy as np
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stocks = symbols('DIS','WMT','DOW','CRM','NKE','HD','V','MSFT', 'MMM','CSCO','KO','AAPL','HON','JNJ','TRV', 'PG','CVX','VZ','CAT','BA','AMGN','IBM','AXP',
'JPM','WBA','MCD','MRK','GS','UNH','INTC')
context.rolling_window = 252
set_commission(PerTrade(cost=5))
schedule_function(handle_data, date_rules.month_end(), time_rules.market_open(hours=1))
def handle_data(context, data):
prices_history = data.history(context.stocks, "close", context.rolling_window,
"1d")
avg_returns = expected_returns.mean_historical_return(prices_history)
cov_mat = risk_models.sample_cov(prices_history)
efficient_frontier = EfficientFrontier(avg_returns, cov_mat)
weights = efficient_frontier.max_sharpe()
cleaned_weights = efficient_frontier.clean_weights()
for stock in context.stocks:
order_target_percent(stock, cleaned_weights[stock])
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns,benchmark_rets = None)
start_date = pd.to_datetime('2010-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Time series prediction-based strategies
SARIMAX strategy
This strategy is based on the most elementary rule: own the stock if the current price is lower than the predicted price in 7 days:
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
import pmdarima as pm
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('AAPL')
context.rolling_window = 90
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, "close", context.rolling_window, "1d")
try:
model = pm.auto_arima(price_hist, seasonal=True)
forecasts = model.predict(7)
order_target_percent(context.stock, 1.0 if price_hist[-1] < forecasts[-1] else 0.0)
except:
pass
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns,benchmark_rets = None)
start_date = pd.to_datetime('2017-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')
Prophet strategy
This strategy is based on the prediction confidence intervals.
Prophet predictions are more robust to frequent changes than SARIMAX. The backtesting results are all identical, but the prediction algorithms are significantly better.
We only buy the stock if the last price is below the lower value of the confidence interval (we anticipate that the stock price will go up) and sell the stock if the last price is above the upper value of the predicted confidence interval (we anticipate that the stock price will go down):
%matplotlib inline
from zipline import run_algorithm
from zipline.api import order_target_percent, symbol, set_commission
from zipline.finance.commission import PerTrade
import pandas as pd
import pyfolio as pf
from fbprophet import Prophet
import logging
logging.getLogger('fbprophet').setLevel(logging.WARNING)
import warnings
warnings.filterwarnings('ignore')
def initialize(context):
context.stock = symbol('AAPL')
context.rolling_window = 90
set_commission(PerTrade(cost=5))
def handle_data(context, data):
price_hist = data.history(context.stock, "close", context.rolling_window, "1d")
price_df = pd.DataFrame({'y' : price_hist}).rename_axis('ds').reset_index()
price_df['ds'] = price_df['ds'].dt.tz_convert(None)
model = Prophet()
model.fit(price_df)
df_forecast = model.make_future_dataframe(periods=7, freq='D')
df_forecast = model.predict(df_forecast)
last_price=price_hist[-1]
forecast_lower=df_forecast['yhat_lower'].iloc[-1]
forecast_upper=df_forecast['yhat_upper'].iloc[-1]
if last_price < forecast_lower:
order_target_percent(context.stock, 1.0)
elif last_price > forecast_upper:
order_target_percent(context.stock, 0.0)
def analyze(context, perf):
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(perf)
pf.create_returns_tear_sheet(returns, benchmark_rets = None)
start_date = pd.to_datetime('2017-1-1', utc=True)
end_date = pd.to_datetime('2018-1-1', utc=True)
results = run_algorithm(start = start_date, end = end_date, initialize = initialize, analyze = analyze, handle_data = handle_data, capital_base = 10000, data_frequency = 'daily', bundle ='quandl')









