0

Many buy orders are cancelled in the backtests and I cannot find why. Looking at the Writefile log it seems the buy orders are created at the next day's close. In most cases the buy price is in the day's range but still not executed.

I have tried on different assets, different sizes of the data feed, different strategies, with the same result.

Running on Jupyter notebook. I include the code and the log.

Finally I changed the default parameter ('100') in AllInSizerInt() below 100 and it worked. I do not really understand why, I thought the sizer would get the cash position from the broker and adjust the order.

Here is the fix:

''''
python
#Add the sizer.  We plan to go 'all in' every time  
cerebro.addsizer(bt.sizers.AllInSizerInt, percents = 95)  

''''

And here is the original code:

''''
python
#### Import databases
from datetime import datetime
import backtrader as bt
import backtrader.indicators as btind

abspath = '/mypath/'
logfile = 'abs_momentum.csv'
# Create a Strategy

class Abs_momentum(bt.Strategy):
    alias = ('Abs_momentum',)
    params = (
    # period for momentum calculation
    ('lookback', 252),
    ('range', 100))
    

def __init__(self):
    # keep track of close price in the series
    self.data_close = self.datas[0].close

    # keep track of pending orders/buy price/buy commission
    self.order  = None
    self.price  = None
    self.comm   = None

    # add a momentum indicator
    self.mom    = btind.MomentumOsc(self.data_close, \
                                    period = self.p.lookback, \
                                    band = self.p.range)

    self.buysig = self.mom
    
    
def log(self, txt, dt=None):
    ''' Logging function fot this strategy'''
    dt = dt or self.datas[0].datetime.date(0)
    print('%s, %s' % (dt.isoformat(), txt))


def notify_order(self, order):
    if order.status in [order.Submitted, order.Accepted]:
        # Buy/Sell order submitted/accepted to/by broker - 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, Price: %.2f, Cost: %.2f, Comm %.2f' %
                (order.executed.price,
                 order.executed.value,
                 order.executed.comm))

            self.buyprice = order.executed.price
            self.buycomm = order.executed.comm
        else:  # Sell
            self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                     (order.executed.price,
                      order.executed.value,
                      order.executed.comm))

        #self.bar_executed = len(self)

    elif order.status in [order.Canceled, order.Margin, order.Rejected]:
        self.log('Order Canceled/Margin/Rejected')

    self.order = None

def notify_trade(self, trade):
    if not trade.isclosed:
        return

    self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
             (trade.pnl, trade.pnlcomm))
def next(self):
    
    # do nothing if an order is pending
    if self.order:
        return

    # check if there is already a position
    if not self.position:
        # buy condition
        if self.buysig > 100:
            self.log(f'BUY CREATED --- Price: {self.data_close[0]:.2f}')
            self.order = self.buy(size = None)
    else:
        # sell condition
        if self.buysig < 100:
            self.log(f'SELL CREATED --- Price: {self.data_close[0]:.2f}')
            self.order = self.sell(Size = None)
###
#### Download data from Yahoo Finance
data = bt.feeds.YahooFinanceData(dataname= 'SPY', \
                             fromdate=datetime(2018, 6, 15),\
                             todate=datetime(2021, 3, 17),\
                               reverse = False)
####

# create a Cerebro entity
cerebro = bt.Cerebro(stdstats = False)

### Set up the backtest
# Add the Data Feed to Cerebro
cerebro.adddata(data)

# Set our desired cash start
cerebro.broker.setcash(100000.0)

# Set the commission - 0.1% ... divide by 100 to remove the %
cerebro.broker.setcommission(commission=0.001)

#Add the sizer.  We plan to go 'all in' every time
cerebro.addsizer(bt.sizers.AllInSizerInt)

#Add the strategy
cerebro.addstrategy(Abs_momentum)

#Add the observers to the plot
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)

#Write to a csv file
cerebro.addwriter(bt.WriterFile, out = (abspath + logfile), csv=True,\ data(csv) = False)

# Print out the starting conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything
cerebro.run()

# Print out the final result
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
###

### Plot the results
cerebro.plot(iplot=True, volume=False)
###

''''

Here is the log:

>Starting Portfolio Value: 100000.00
>2019-06-18, BUY CREATED --- Price: 282.95
>2019-06-19, Order Canceled/Margin/Rejected
>2019-06-19, BUY CREATED --- Price: 283.58
>2019-06-20, Order Canceled/Margin/Rejected
>2019-06-20, BUY CREATED --- Price: 286.29
>2019-06-21, Order Canceled/Margin/Rejected
>2019-06-21, BUY CREATED --- Price: 285.88
>2019-06-24, BUY EXECUTED, Price: 286.10, Cost: 99848.90, Comm 99.85
>2020-03-12, SELL CREATED --- Price: 243.56
>2020-03-13, SELL EXECUTED, Price: 258.27, Cost: 99848.90, Comm 90.14
>2020-03-13, OPERATION PROFIT, GROSS -9712.67, NET -9902.66
>2020-04-17, BUY CREATED --- Price: 283.04
>2020-04-20, BUY EXECUTED, Price: 279.06, Cost: 88741.08, Comm 88.74
>2020-04-20, SELL CREATED --- Price: 278.05
>2020-04-21, SELL EXECUTED, Price: 273.25, Cost: 88741.08, Comm 86.89
>2020-04-21, OPERATION PROFIT, GROSS -1847.58, NET -2023.21
>2020-04-29, BUY CREATED --- Price: 289.53
>2020-04-30, Order Canceled/Margin/Rejected
>2020-04-30, BUY CREATED --- Price: 286.83
>2020-05-01, Order Canceled/Margin/Rejected
>2020-05-06, BUY CREATED --- Price: 280.68
>2020-05-07, Order Canceled/Margin/Rejected
>2020-05-07, BUY CREATED --- Price: 284.07
>2020-05-08, Order Canceled/Margin/Rejected
>2020-05-08, BUY CREATED --- Price: 288.77
>2020-05-11, BUY EXECUTED, Price: 286.69, Cost: 87153.76, Comm 87.15
>Final Portfolio Value: 121189.86
Miguel_D
  • 11
  • 2
  • I set the percents argument in AllInSizerInt to less than 100 (the default) and now it seems to work. ''''#Add the sizer. We plan to go 'all in' every time cerebro.addsizer(bt.sizers.AllInSizerInt, percents = 95)'''' – Miguel_D Mar 19 '21 at 12:53

1 Answers1

1

The reason your adjustment from 100% size trade to 95% size trade corrected the problem is that trying to by all in 100% on a portfolio will always result in some margins along the way . This can be seen here in your logs:

>2019-06-19, Order Canceled/Margin/Rejected

The problem is you are calculating the number of shares to trade off the previous close. If the market price gaps up on the next bar, you won't have enough cash to buy. There is an option called cheat-on-open that allows a peak at the next open price to permit sizing of the shares. This helps.

But in reality, it's best to trade below 100%.

run-out
  • 3,114
  • 1
  • 9
  • 25