Hi there,
While backtesting works well for me, I run into problems with optimizing with respect to sharpe ratio when using my below code, see the below output.
It seems that the final_results_list does get only NoneType values for the Sharpe Rations. Any idea what went wrong? Thanks!
Output:
File "C:\Users\User\Desktop\Python for Finance\MACD_Strategy\BackTrader_MACD_Example\btmain.py", line 174, in <module>
sort_by_sharpe = sorted(final_results_list, key=lambda x: x[3], reverse=True)
TypeError: '<' not supported between instances of 'NoneType' and 'NoneType'
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
# Import the backtrader platform
import backtrader as bt
import backtrader.indicators as btind
import pandas as pd
import numpy as np
from strategies import *
###--------------------------------
###--------------------------------
###--------------------------------
### FOR OPTIMIZATION
#Instantiate Cerebro engine
cerebro = bt.Cerebro(optreturn=False, optdatas=True)
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, 'C:/Users/User/Desktop/Python for Finance/MACD_Strategy/BackTrader_MACD_Example/ohlc.csv')
data = bt.feeds.GenericCSVData(
dataname=datapath,
#add or remove do in-sample and out-of-sample tests (i.e. optimize in one and backtest in next years)
#settings for in-sample optimization
#fromdate=datetime.datetime(2020, 5, 4, 0, 0, 0),
#todate=datetime.datetime(2020, 6, 4, 0, 0, 0),
#settings for out-of-sample backtest
#fromdate=datetime.datetime(2020, 6, 4, 0, 0, 0),
#todate=datetime.datetime(2020, 6, 23, 0, 0, 0),
nullvalue=0.0,
dtformat=('%Y-%m-%d %H:%M:%S'),
datetime=0,
time=-1,
high=2,
low=3,
open=1,
close=4,
volume=5,
openinterest=-1,
timeframe=bt.TimeFrame.Minutes)
cerebro.adddata(data)
#Add strategy to Cerebro
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe_ratio')
cerebro.optstrategy(MAcrossover, pfast=range(5, 20), pslow=range(50, 100))
#Default position size / 1 = 1 underlying share
cerebro.addsizer(bt.sizers.SizerFix, stake=1)
if __name__ == '__main__':
optimized_runs = cerebro.run()
final_results_list = []
for run in optimized_runs:
for strategy in run:
PnL = round(strategy.broker.get_value() - 10000,2)
sharpe = strategy.analyzers.sharpe_ratio.get_analysis()
final_results_list.append([strategy.params.pfast,
strategy.params.pslow, PnL, sharpe['sharperatio']])
sort_by_sharpe = sorted(final_results_list, key=lambda x: x[3], reverse=True)
for line in sort_by_sharpe[:5]:
print(line)
Strategy:
class MAcrossover(bt.Strategy):
#Moving average parameters
params = (('pfast',20),('pslow',50),)
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.datetime(0)
#print('%s, %s' % (dt.isoformat(), txt))
print("%s, %s" % (dt, txt))
def __init__(self):
self.dataclose = self.datas[0].close
# Order variable will contain ongoing order details/status
self.order = None
# Instantiate moving averages
self.slow_sma = bt.indicators.MovingAverageSimple(self.datas[0],
period=self.params.pslow)
self.fast_sma = bt.indicators.MovingAverageSimple(self.datas[0],
period=self.params.pfast)
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
#Active Buy/Sell order submitted/accepted - Nothing to do
return
#Check if an order has been completed
#Attention: broker could reject order if not enough cash
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, %.2f' % order.executed.price)
elif order.issell():
self.log('SELL EXECUTED, %.2f' % order.executed.price)
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
#Reset orders
self.order = None
def next(self):
if self.order:
return
#Check if we are in the market
if not self.position:
#We are not in the market, look for a signal to OPEN trades
#If the 20 SMA is above the 50 SMA
if self.fast_sma[0] > self.slow_sma[0] and self.fast_sma[-1] < self.slow_sma[-1]:
self.log('BUY CREATE, %.2f' % self.dataclose[0])
#Keep track of the created order to avoid a 2nd order
self.order = self.buy()
#Otherwise if the 20 SMA is below the 50 SMA
elif self.fast_sma[0] < self.slow_sma[0] and self.fast_sma[-1] > self.slow_sma[-1]:
self.log('SELL CREATE, %.2f' % self.dataclose[0])
#Keep track of the created order to avoid a 2nd order
self.order = self.sell()
else:
# We are already in the market, look for a signal to CLOSE trades
if len(self) >= (self.bar_executed + 5):
self.log('CLOSE CREATE, %.2f' % self.dataclose[0])
self.order = self.close()