For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

order_target_percent is not working as expected. Please read further; complete code, inputs and outputs are provided



  • Hi,

    I am running the Longshort strategy (provided as an example here: https://www.backtrader.com/blog/posts/2015-09-14-write-it-down/write-it-down/) on BTC USD data (using fractional sizing as explained here: https://www.backtrader.com/blog/posts/2019-08-29-fractional-sizes/fractional-sizes/).

    I'm using order_target_percent(0.5) in my code. I expect that for each buy operation, 50.0% of my cash should get used up. This does happen most of the time. Except when there is a buy immediately after a sell. The amount of cash that is used to make such buy operations is around 50%, but not exactly 50%.

    Check the buy operations on Row Id = 20 and Row Id = 46 in the linked output to see what I'm talking about. For ex, in row 20: I expect my cash value to go from 9908.73 to 4954.36 (which is 50%). Instead it goes from 9908.73 to 4947.38 (which is 49.92%). Buy operations on row 72 and 91 don't have this discrepancy.

    I suspect this could be due to rounding, but not sure. Let me know if any more info is required.

    Input data: https://drive.google.com/file/d/1rPEfRnf1z4DknzhQGSZuL6esK7CWdGGa/view?usp=sharing
    output: https://drive.google.com/file/d/1wjFaNYKJSC_Tez8yCneZlSmFaIxzb-Rd/view?usp=sharing

    Code:

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    
    import pandas as pd
    import numpy as np
    import datetime
    import os
    import seaborn as sns
    import sys
    import copy
    import argparse
    import logging
    
    
    import backtrader as bt
    import backtrader.feeds as btfeeds
    import backtrader.indicators as btind
    from backtrader.analyzers import SQN
    
    
    class CommInfoFractional(bt.CommissionInfo):
        def getsize(self, price, cash):
            '''Returns fractional size for cash operation @price'''
            return self.p.leverage * (cash / price)
    
    
    class act_buy_sell(bt.observer.Observer):
    
    	' For actual buy and sell prices'
        
        alias = ('Act_buy_sell',)
        lines = ('act_buy', 'act_sell', 'order_size')
    
        plotinfo = dict(plot=False, subplot=False)
    
        def next(self):
            for order in self._owner._orderspending:
                
                if order.data is not self.data:
                    continue
    
                if order.isbuy():
                    self.lines.act_buy[0] = order.executed.price
                    self.lines.order_size[0] = order.executed.size
    
                elif order.issell():
                    self.lines.act_sell[0] = order.executed.price
                    self.lines.order_size[0] = order.executed.size          
    
    
    
    class LongShortStrategy(bt.Strategy):
        '''This strategy buys/sells upong the close price crossing
        upwards/downwards a Simple Moving Average.
    
        It can be a long-only strategy by setting the param "onlylong" to True
        '''
        params = dict(
            period=15,
            stake=1,
            printout=False,
            onlylong=False,
            csvcross=False,
        )
    
        def start(self):
            pass
    
        def stop(self):
            pass
    
        def log(self, txt, dt=None):
            if self.p.printout:
                dt = dt or self.data.datetime[0]
                dt = bt.num2date(dt)
                print('%s, %s' % (dt.isoformat(), txt))
    
        def __init__(self):
            # To control operation entries
            self.orderid = None
    
            # Create SMA on 2nd data
            sma = btind.MovAv.SMA(self.data, period=self.p.period)
            # Create a CrossOver Signal from close an moving average
            self.signal = btind.CrossOver(self.data.close, sma)
            self.signal.csv = self.p.csvcross
    
        def next(self):
            if self.orderid:
                return  # if an order is active, no new orders are allowed
    
            if self.signal > 0.0:  # cross upwards
                if self.position:
                    self.log('CLOSE SHORT , %.2f' % self.data.close[0])
                    self.close()
    
                self.log('BUY CREATE , %.2f' % self.data.close[0])
                self.order_target_percent(target=self.p.stake)
    
            elif self.signal < 0.0:
                if self.position:
                    self.log('CLOSE LONG , %.2f' % self.data.close[0])
                    self.close()
    
                if not self.p.onlylong:
                    self.log('SELL CREATE , %.2f' % self.data.close[0])
                    self.order_target_percent(target=-self.p.stake)
    
        def notify_order(self, order):
            if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
                return  # Await further notifications
    
            if order.status == order.Completed:
                if order.isbuy():
                    buytxt = 'BUY COMPLETE, %.2f' % order.executed.price
                    self.log(buytxt, order.executed.dt)
                else:
                    selltxt = 'SELL COMPLETE, %.2f' % order.executed.price
                    self.log(selltxt, order.executed.dt)
    
            elif order.status in [order.Expired, order.Canceled, order.Margin]:
                self.log('%s ,' % order.Status[order.status])
                pass  # Simply log
    
            # Allow new orders
            self.orderid = None
    
        def notify_trade(self, trade):
            if trade.isclosed:
                self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' %
                         (trade.pnl, trade.pnlcomm))
    
            elif trade.justopened:
                self.log('TRADE OPENED, SIZE %2d' % trade.size)
    
    
    def runstrategy(period = 15
                    ,onlylong = True
                    ,csvcross = True
                    ,stake = 1
                    ,comm = 0
                    ,plot = True
                    ,fractional = True):
    
        # Create a cerebro
        cerebro = bt.Cerebro()
    
        # Create the 1st data
        data = btfeeds.GenericCSVData(
        dataname=data #refer to attached data
        dtformat=('%Y-%m-%d'),
        fromdate=datetime.datetime(2017, 1, 1),
        todate=datetime.datetime(2017, 4, 9),
        openinterest=-1
        )
    
        # Add the 1st data to cerebro
        cerebro.adddata(data)
        cerebro.addobserver(act_buy_sell)
    
        if fractional:
            cerebro.broker.addcommissioninfo(CommInfoFractional())
    
        else:        
            # Add the commission - only stocks like a for each operation
            cerebro.broker.setcommission(commission=comm)
    
        # Add the strategy
        cerebro.addstrategy(LongShortStrategy,
                            period=period,
                            onlylong=onlylong,
                            csvcross=csvcross,
                            stake=stake)
    
        # Add the commission - only stocks like a for each operation
        cerebro.broker.setcash(10000)
    
        # cerebro.addanalyzer(SQN)
    
        cerebro.addwriter(bt.WriterFile, csv=True, rounding=2)
    
        # And run it
        cerebro.run()
    
        # Plot if requested
        if plot:
            cerebro.plot(volume=True, zdown=False)
    
    
    runstrategy(plot = False, stake = 0.5)


  • @backtrader @ab_trader @run-out please have a look when you get the chance as this could potentially be a bug. Thanks!


Log in to reply
 

});