For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/
Bracket Order on Portfolio testing
-
Hello,
I'm trying to backtest a Strategy on a whole portfolio using bracket orders.
I don't get any error or something similar, but the trades are not executed in the right way.When an order is placed, it's allways only for one stock unit instead of a calculated amount.
In my case I want to place an order with a target amount of up to 10% of my portfolio.It works with other methods, but when I use the Bracket order, it's not working.
Here is the code:
import backtrader as bt import pandas as pd import os.path import sys from datetime import datetime, timedelta, date import math import quantstats as qs from backtrader_plotting import Bokeh from backtrader_plotting.schemes import Blackly import warnings from csv import reader import csv from IB_commission import IBCommision period = 2000 icap = 100000 risk = 0.03 pos_percent = 0.03 warnings.simplefilter(action='ignore', category=FutureWarning) benchmark_path = XXX project_path = XXX strategy_name = "Stochastic_V1_PF" data_path = XXX list_path = XXX datalist = [] # load the datalist with open(list_path, 'r') as read_obj: tickers = reader(read_obj) for ticker in tickers: tick = str(ticker[0]) filename = '%s_DAILY_ALPHA_VANTAGE.txt' % tick datapath = os.path.join(data_path, filename) datalist.append((datapath, tick)) class ConstantValue(bt.Indicator): lines = ('constant',) params = (('constant', float('NaN')),) def next(self): self.lines.constant[0] = self.p.constant class RSIStack(bt.Strategy): # Define the parameters of the strategy params = ( ('oneplot', True), ("mtrade", False), ("percents", 0.1) ) def notify_order(self, order): if order.status == order.Completed: pass if not order.alive(): self.order = None # indicate no order is pending def __init__(self): self.log_pnl = [] # Initialize the elements which are needed for the strategy (indicators, etc...) self.inds = dict() for i, d in enumerate(self.datas): # Define the indicators self.inds[d] = dict() self.inds[d]['ema200'] = bt.indicators.EMA(d.close, period=200, plot=False, subplot=False) self.inds[d]['ema300'] = bt.indicators.EMA(d.close, period=300, plot=False, subplot=False) self.inds[d]['stoch'] = bt.indicators.Stochastic(d, period=70, period_dfast=10, period_dslow=10, plot=False, safediv=True) self.inds[d]['percK'] = self.inds[d]['stoch'].percK self.inds[d]['percD'] = self.inds[d]['stoch'].percD self.inds[d]['rsi1'] = bt.indicators.RSI(d, period=2, safediv=True) self.inds[d]['atr'] = bt.indicators.ATR(d, period=14, plot=False, subplot=False) # Define the crossover signals self.inds[d]['stoch_cross'] = bt.indicators.CrossOver(self.inds[d]['percK'], self.inds[d]['percD'], plot=False, subplot=False) self.inds[d]['doji'] = bt.talib.CDLDOJI(d.open, d.high, d.low, d.close, plot=False) self.inds[d]['curpos'] = None self.stoch_oversold = 70.0 self.stoch_overbought = 30.0 if i > 0: # Check we are not on the first loop of data feed: if self.p.oneplot == True: d.plotinfo.plotmaster = self.datas[0] def start(self): self.order = None # sentinel to avoid operations on pending order def prenext(self): self.next() def next(self): for i, d in enumerate(self.datas): pos = self.getposition(d).size # Get the Amount of cash in the Portfolio cash = self.broker.get_cash() if self.order: return # pending order execution if not pos: # check if we already have an open position on the stock long_tp = d.close[0] + 5 * self.inds[d]['atr'] long_stop = d.close[0] - 2 * self.inds[d]['atr'] if self.inds[d]['ema200'] > self.inds[d]['ema300']: if self.inds[d]['stoch'] < self.stoch_oversold and (self.inds[d]['doji'] == 100 or self.inds[d]['doji'] == -100): buy_ord = self.buy_bracket(data=d, limitprice=long_tp, stopprice=long_stop, exectype=bt.Order.Market) self.inds[d]['curpos'] = 'long' def printSQN(analyzer): sqn = round(analyzer.sqn, 2) print('SQN: {}'.format(sqn)) def pretty_print(format, *args): print(format.format(*args)) def exists(object, *properties): for property in properties: if not property in object: return False object = object.get(property) return True def printTradeAnalysis(cerebro, analyzers): format = " {:<24} : {:<24}" NA = '-' print('Backtesting Results') if hasattr(analyzers, 'ta'): ta = analyzers.ta.get_analysis() openTotal = ta.total.open if exists(ta, 'total', 'open') else None closedTotal = ta.total.closed if exists(ta, 'total', 'closed') else None wonTotal = ta.won.total if exists(ta, 'won', 'total') else None lostTotal = ta.lost.total if exists(ta, 'lost', 'total') else None streakWonLongest = ta.streak.won.longest if exists(ta, 'streak', 'won', 'longest') else None streakLostLongest = ta.streak.lost.longest if exists(ta, 'streak', 'lost', 'longest') else None pnlNetTotal = ta.pnl.net.total if exists(ta, 'pnl', 'net', 'total') else None pnlNetAverage = ta.pnl.net.average if exists(ta, 'pnl', 'net', 'average') else None pretty_print(format, 'Open Positions', openTotal or NA) pretty_print(format, 'Closed Trades', closedTotal or NA) pretty_print(format, 'Winning Trades', wonTotal or NA) pretty_print(format, 'Loosing Trades', lostTotal or NA) print('\n') pretty_print(format, 'Longest Winning Streak', streakWonLongest or NA) pretty_print(format, 'Longest Loosing Streak', streakLostLongest or NA) pretty_print(format, 'Strike Rate (Win/closed)', (wonTotal / closedTotal) * 100 if wonTotal and closedTotal else NA) print('\n') pretty_print(format, 'Inital Portfolio Value', '${}'.format(icap)) pretty_print(format, 'Final Portfolio Value', '${}'.format(cerebro.broker.getvalue())) pretty_print(format, 'Net P/L', '${}'.format(round(pnlNetTotal, 2)) if pnlNetTotal else NA) pretty_print(format, 'P/L Average per trade', '${}'.format(round(pnlNetAverage, 2)) if pnlNetAverage else NA) print('\n') if hasattr(analyzers, 'drawdown'): pretty_print(format, 'Drawdown', '${}'.format(analyzers.drawdown.get_analysis()['drawdown'])) if hasattr(analyzers, 'sharpe'): pretty_print(format, 'Sharpe Ratio:', analyzers.sharpe.get_analysis()['sharperatio']) if hasattr(analyzers, 'vwr'): pretty_print(format, 'VRW', analyzers.vwr.get_analysis()['vwr']) if hasattr(analyzers, 'sqn'): pretty_print(format, 'SQN', analyzers.sqn.get_analysis()['sqn']) print('\n') print('Transactions') format = " {:<24} {:<24} {:<16} {:<8} {:<8} {:<16}" pretty_print(format, 'Date', 'Amount', 'Price', 'SID', 'Symbol', 'Value') for key, value in analyzers.txn.get_analysis().items(): pretty_print(format, key.strftime("%Y/%m/%d %H:%M:%S"), value[0][0], value[0][1], value[0][2], value[0][3], value[0][4]) if __name__ == '__main__': NA = '-' analysis_df = pd.DataFrame(columns=['Portfolio', 'Initial portfolio value', 'Final portfolio value', 'Gross P/L', 'Net P/L', 'Open P/L', 'Net P/L average', 'Net total wins', 'Net total lost', 'Net average wins', 'Net average lost', 'Profit factor', 'Payoff ratio', 'Max drawdown', 'total trades', 'Total closed trades', 'Total open trades', 'Total winning trades', 'Total losing trades', 'Strike rate', 'Sharpe ratio', 'VRW', 'SQN', 'Average trade duration', 'Average winning trade duration', 'Average losing trade duration', 'Longest losing Streak', 'Longest winning Streak', 'Total long trades', 'Total long win trades', 'Total long lost trades', 'Total long PnL', 'Long average PnL', 'Max long PnL', 'Total long win trades', 'Long average win', 'Long Max win', 'Total long lost trades', 'Long average lost', 'Long Max lost', 'Average long duration', 'Average long win duration', 'Average long lost duration', 'Total short trades', 'Total short win trades', 'Total short lost trades', 'Total short PnL', 'Short average PnL', 'Max short PnL', 'Total short win trades', 'Short average win', 'Short Max win', 'Total short lost trades', 'Short average lost', 'Short max lost', 'Average short duration', 'Average short win duration', 'Average short lost duration' ]) cerebro = bt.Cerebro(stdstats=False) endDate = date(2021, 1, 17) fromDate = (endDate - timedelta(days=period)) startDate = fromDate.strftime('%Y-%m-%d') for i in range(len(datalist)): data = bt.feeds.GenericCSVData( dataname=datalist[i][0], # Do not pass values before this date fromdate=datetime(fromDate.year, fromDate.month, fromDate.day), # Do not pass values before this date todate=datetime(datetime.today().year, datetime.today().month, datetime.today().day), nullvalue=0.0, dtformat=('%Y-%m-%d'), datetime=0, open=1, high=2, low=3, close=4, volume=5, openinterest=-1 ) cerebro.adddata(data, name=datalist[i][1]) # Set the Cash for the Strategy cerebro.broker.setcash(icap) # Set the comissions comminfo = IBCommision() cerebro.broker.addcommissioninfo(comminfo) # Add the benchmark Observer # cerebro.addobserver(bt.observers.Benchmark, data=data, timeframe=bt.TimeFrame.NoTimeFrame) # Add the analyzers we are interested in cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='ta') cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown') cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.0, annualize=True, timeframe=bt.TimeFrame.Days) cerebro.addanalyzer(bt.analyzers.VWR, _name='vwr') cerebro.addanalyzer(bt.analyzers.SQN, _name='sqn') # The sqn value should be deemed reliable when the number of trades >= 30 ''' 1.6 - 1.9 Below average 2.0 - 2.4 Average 2.5 - 2.9 Good 3.0 - 5.0 Excellent 5.1 - 6.9 Superb 7.0 - Holy Grail? ''' cerebro.addanalyzer(bt.analyzers.Transactions, _name='txn') cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio') cerebro.addstrategy(RSIStack, oneplot=False) # Run over everything strategies = cerebro.run(runonce=False) Sta = strategies[0] # print the analyzers printTradeAnalysis(cerebro, Sta.analyzers) # Generate the chart b = Bokeh(style='bar', scheme=Blackly(), output_mode='show') cerebro.plot(b) # ---- Format the benchmark from SPY.csv ---- with open(benchmark_path, mode='r') as infile: next(infile) for line in infile: reader = csv.reader(infile) mydict = { datetime.strptime(rows[0], "%Y-%m-%d"): float(rows[4]) for rows in reader } benchmark = ( pd.DataFrame.from_dict(mydict, orient="index") ) returns_bm = qs.utils.to_returns(benchmark) returns_bm.index = pd.to_datetime(returns_bm.index) # ------------------------------------------- # Generate the Quantstats HTML Report portfolio_stats = Sta.analyzers.getbyname('pyfolio') returns, positions, transactions, gross_lev = portfolio_stats.get_pf_items() returns.index = returns.index.tz_convert(None) report_name = '%s_%s_%s.html' % (strategy_name, startDate, endDate) qs.extend_pandas() qs.reports.html(returns, returns_bm, output=report_name, title=report_name) # returns.to_csv('returns.csv') # fill the CSV with the strategy KPIs for every stock tested CSV_name = os.path.join(project_path, "Test_report_%s.csv" % (strategy_name)) analysis_df.to_csv(CSV_name, index=False, header=True)
And here are the (transactions) results:
Transactions Date Amount Price SID Symbol Value 2016/10/04 23:59:59 1 7.0 21 AMD -7.0 2016/10/05 23:59:59 1 37.3984 7 ADM -37.3984 2016/10/06 23:59:59 1 79.3908 8 ADP -79.3908 2016/10/07 23:59:59 1 32.4279 13 AFL -32.4279 2016/10/10 23:59:59 1 39.7096 4 ABT -39.7096 2016/10/11 23:59:59 -1 38.20808104887775 4 ABT 38.20808104887775 2016/10/12 23:59:59 1 63.9792 67 CHRW -63.9792 2016/10/13 23:59:59 1 37.7296 4 ABT -37.7296 2016/10/14 23:59:59 1 6.92 21 AMD -6.92 2016/10/17 23:59:59 1 318.671 50 BLK -318.671 2016/10/18 23:59:59 1 57.0478 6 ADI -57.0478 2016/10/19 23:59:59 1 105.2816 24 AMT -105.2816 2016/10/20 23:59:59 1 31.8171 13 AFL -31.8171 2016/10/21 23:59:59 1 43.9882 0 A -43.9882 2016/10/24 23:59:59 1 45.2669 28 AOS -45.2669 2016/10/25 23:59:59 -1 42.949516258290714 0 A 42.949516258290714 2016/10/26 23:59:59 -1 68.3121 37 AVY 68.3121 2016/10/27 23:59:59 1 42.3508 0 A -42.3508 2016/10/28 23:59:59 1 31.8895 13 AFL -31.8895 2016/10/31 23:59:59 -1 36.408354838686556 4 ABT 36.408354838686556 2016/11/01 23:59:59 -1 40.61960193537223 7 ADM 40.61960193537223 2016/11/02 23:59:59 1 104.97 3 ABMD -104.97 2016/11/03 23:59:59 1 31.0932 13 AFL -31.0932 2016/11/04 23:59:59 1 21.49 53 BSX -21.49 2016/11/07 23:59:59 1 79.5919 17 ALB -79.5919 2016/11/08 23:59:59 1 42.4337 28 AOS -42.4337 2016/11/09 23:59:59 -1 75.03601062560966 17 ALB 75.03601062560966 2016/11/10 23:59:59 -1 51.98551251106071 11 AEP 51.98551251106071 2016/11/11 23:59:59 -1 36.3981 51 BLL 36.3981 2016/11/14 23:59:59 1 96.762 24 AMT -96.762 2016/11/15 23:59:59 -1 62.27067399669613 6 ADI 62.27067399669613 2016/11/16 23:59:59 -1 45.76131070490641 0 A 45.76131070490641 2016/11/17 23:59:59 1 46.3494 15 AJG -46.3494 2016/11/18 23:59:59 1 25.9753 1 AAPL -25.9753 2016/11/21 23:59:59 1 8.87 21 AMD -8.87 2016/11/22 23:59:59 1 105.85 5 ADBE -105.85 2016/11/23 23:59:59 1 100.2953 33 ASML -100.2953 2016/11/25 23:59:59 1 78.5715 17 ALB -78.5715 2016/11/28 23:59:59 1 38.2937 7 ADM -38.2937 2016/11/29 23:59:59 1 36.3534 35 ATVI -36.3534 2016/11/30 23:59:59 -1 131.14740123065323 30 APD 131.14740123065323 2016/12/01 23:59:59 -1 101.50838500709875 5 ADBE 101.50838500709875 2016/12/02 23:59:59 -1 34.24753895416149 35 ATVI 34.24753895416149 2016/12/05 23:59:59 -1 61.486834217216085 29 APA 61.486834217216085 2016/12/06 23:59:59 1 31.2918 13 AFL -31.2918 2016/12/07 23:59:59 -1 110.2229904459513 27 AON 110.2229904459513 2016/12/08 23:59:59 -1 279.33752616104323 46 BIIB 279.33752616104323 2016/12/09 23:59:59 1 104.0 5 ADBE -104.0 2016/12/12 23:59:59 1 112.1827 55 BXP -112.1827 2016/12/13 23:59:59 1 44.6046 0 A -44.6046 2016/12/14 23:59:59 1 288.21 46 BIIB -288.21 2016/12/15 23:59:59 1 97.4704 24 AMT -97.4704 2016/12/16 23:59:59 -1 10.967195448987663 21 AMD 10.967195448987663 2016/12/19 23:59:59 1 25.51 63 CDNS -25.51 2016/12/20 23:59:59 1 174.9558 79 COO -174.9558 2016/12/21 23:59:59 -1 64.4191713757161 91 D 64.4191713757161 2016/12/22 23:59:59 -1 31.747025613949187 20 AMAT 31.747025613949187 2016/12/23 23:59:59 -1 78.53153413751777 99 DLTR 78.53153413751777 2016/12/27 23:59:59 1 13.3937 158 HPQ -13.3937 2016/12/28 23:59:59 1 75.93 9 ADSK -75.93 2016/12/29 23:59:59 -1 90.43504287233867 216 MCO 90.43504287233867 2016/12/30 23:59:59 1 44.7989 28 AOS -44.7989 2017/01/03 23:59:59 1 96.5157 116 EOG -96.5157 2017/01/04 23:59:59 1 107.4254 27 AON -107.4254 2017/01/05 23:59:59 1 179.4013 129 FDX -179.4013 2017/01/06 23:59:59 -1 157.6728484681152 36 AVB 157.6728484681152 2017/01/10 23:59:59 -1 22.97554089153823 53 BSX 22.97554089153823 2017/01/11 23:59:59 -1 87.65672724153029 17 ALB 87.65672724153029 2017/01/12 23:59:59 -1 65.84221944952445 69 CINF 65.84221944952445 2017/01/13 23:59:59 1 32.6533 123 EW -32.6533 2017/01/17 23:59:59 1 57.3981 29 APA -57.3981 2017/01/18 23:59:59 -1 96.40142596702519 162 HSY 96.40142596702519 2017/01/19 23:59:59 1 109.2384 86 CTAS -109.2384 2017/01/20 23:59:59 -1 766.5184904139112 39 AZO 766.5184904139112 2017/01/23 23:59:59 -1 19.409222458171516 174 IPG 19.409222458171516 2017/01/24 23:59:59 -1 113.79884365794422 5 ADBE 113.79884365794422 2017/01/25 23:59:59 -1 28.816271955453356 1 AAPL Maybe someone can help ?
-
Can anyone help ?
-
Differences between men's and women's hoodies. Contrary to popular belief Jesus is king hoodie, men's hoodies usually have much larger hoods than women's hoodies.