I found your results very great and I tried to reproduce the model with the backtrader strategy.
The strategy buys when signal == 1, and sells when signal == - 1.
I tried both long-only with the exit strategy and long/short strategies.
From 10 000 cash, the strategy achieved negative returns.
I gladly share my code for your testing purposes. I hope you can spot the error in my strategy, but I just can't understand why does it perform badly.
I think we cannot simply compare S&P returns in a way that we treat our signal value 1 as if it is earned on that day this return.
The trade must happen and only then we will know if we got the profit the next day.
The attached file SPY_signal.csv has an additional column that I copied from sp500_forecasts_new.csv.
SPY ticker copies S&P index, I used it as an alternative to the buying of the index (which we cannot).
autoencoder_strategy.py
from datetime import datetime
import backtrader as bt
import numpy as np
import yfinance as yf
from helpers.get_feed_for_csv_path import get_feed_for_csv_path
from sizers import MaxRiskSizer
# from strategies.BuyAndHold import BuyAndHold_Buy
class AutoEncoderStrategy(bt.Strategy):
params = (
('sma1', 10),
('sma2', 30),
)
def __init__(self):
pass
def next(self):
prev_signal = self.data.autoencoder_signal.get(ago=-1)
prev_sell_signal = prev_signal[0] == -1 if len(prev_signal) else False
# uncomment to check for at least two sell signals before selling a stock
#sell_signal = prev_sell_signal and self.data.autoencoder_signal == -1
sell_signal = self.data.autoencoder_signal == -1
prev_buy_signal = prev_signal[0] == 1 if len(prev_signal) else False
#buy_signal = prev_buy_signal and self.data.autoencoder_signal == 1
buy_signal = self.data.autoencoder_signal == 1
if buy_signal:
if self.position.size < 0:
self.close()
if self.position.size > 0:
return # will not buy more
self.buy()
elif sell_signal:
if self.position.size > 0:
self.close()
if self.position.size < 0:
return # will not sell more
self.sell()
ticker = 'SPY'
datapathall = 'stocks_for_optimization_archive'
path = os.path.join(datapathall, f'{ticker}.csv')
fromdate = datetime(2002, 1, 1)
todate = datetime(2020, 12, 31)
data0 = get_feed_for_csv_path(path, fromdate=fromdate,
todate=todate)
import pandas as pd
data_csv = pd.read_csv(path, index_col=0)
data_csv.index = pd.to_datetime(data_csv.index)
filtered_period = data_csv.loc[fromdate: todate]['Close']
spy_signal_path = 'SPY_signal.csv'
cerebro = bt.Cerebro(stdstats=False)
if os.path.exists(spy_signal_path):
data0 = get_feed_for_csv_path(spy_signal_path, fromdate=fromdate,
todate=todate,
has_signal=True)
else:
pass
cerebro.broker.set_coc(True)
#cerebro.broker.set_coo(True)
# cerebro.addstrategy(St)
cerebro.addstrategy(AutoEncoderStrategy)
cerebro.adddata(data0)
# cerebro.addsizer(bt.sizers.FixedReverser)
cerebro.addsizer(MaxRiskSizer, risk=0.95)
# cerebro.addsizer(bt.sizers.FixedSize, stake=100)
cerebro.addobserver(bt.observers.Value)
cerebro.addobserver(bt.observers.Trades)
cerebro.addobserver(bt.observers.BuySell,
barplot=True)
from backtrader.analyzers import VWR
from BasicTradeStats import BasicTradeStats
cerebro.addanalyzer(BasicTradeStats, _name="BasicTradeStats")
vwrkwargs = {}
vwrkwargs['_name'] = 'vwr'
vwrkwargs['tau'] = 0.28 # price variability tolerance (risk tolerance)
vwrkwargs['sdev_max'] = 2.2 # maximum stdev of 2 is preferred, variability tolerance.
cerebro.addanalyzer(VWR, **vwrkwargs)
strategy = cerebro.run()[0]
print('Score: {}'.format(cerebro.broker.getvalue()))
vwr = strategy.analyzers.vwr.get_analysis()['vwr'] # returns variability weighted return
print('Score (vwr): {}, broker value: {}'.format(vwr, cerebro.broker.getvalue()))
print(strategy.analyzers.BasicTradeStats.print())
cerebro.plot()
class MaxRiskSizer(bt.Sizer):
params = (('risk', 0.95),)
def __init__(self):
if self.p.risk > 1 or self.p.risk < 0:
raise ValueError('The risk parameter is a percentage which must be'
'entered as a float. e.g. 0.5')
def _getsizing(self, comminfo, cash, data, isbuy):
#return comminfo.getsize(data.close[0], self.broker.get_value())
return round(self.broker.get_value() * self.p.risk / data.close[0])
Hello,
Thank you for the article on Medium https://medium.com/analytics-vidhya/a-step-by-step-implementation-of-a-trading-strategy-in-python-using-arima-garch-models-b622e5b3aa39
I found your results very great and I tried to reproduce the model with the backtrader strategy.
The strategy buys when signal == 1, and sells when signal == - 1. I tried both long-only with the exit strategy and long/short strategies.
From 10 000 cash, the strategy achieved negative returns.
I gladly share my code for your testing purposes. I hope you can spot the error in my strategy, but I just can't understand why does it perform badly.
I think we cannot simply compare S&P returns in a way that we treat our signal value 1 as if it is earned on that day this return. The trade must happen and only then we will know if we got the profit the next day.
The attached file SPY_signal.csv has an additional column that I copied from sp500_forecasts_new.csv.
SPY ticker copies S&P index, I used it as an alternative to the buying of the index (which we cannot).
autoencoder_strategy.py
BasicTradeStats.py https://gist.github.com/iSevenDays/ba44cf853e840559354dc2594eeb1f70
get_feed_for_csv_path.py https://gist.github.com/iSevenDays/378a75e392a92a602cc80ffe8df6ddd7
MaxRiskSizer.py
SPY_signal.csv.zip