Backtrader transaction Foundation

View account status:

class TestStrategy(bt.Strategy):
    def next(self):
        print('Currently available funds', self.broker.getcash())
        print('Current total assets', self.broker.getvalue())
        print('Current position', self.broker.getposition(self.data).size)
        print('Current position cost', self.broker.getposition(self.data).price)
        # Positions can also be obtained directly
        print('Current position', self.getposition(self.data).size)
        print('Current position cost', self.getposition(self.data).price)
        # Note: getposition() needs to specify the specific target data set

Slide point setting:

# Method 1: through the slip in the BackBroker class_ Perc parameter setting percentage slide point
cerebro.broker = bt.brokers.BackBroker(slip_perc=0.0001)
# Method 2: by calling the set of brokers_ slippage_ The perc method sets the percentage slip point
cerebro.broker.set_slippage_perc(perc=0.0001)

# Method 1: through the slip in the BackBroker class_ The fixed parameter sets the fixed sliding point
cerebro.broker = bt.brokers.BackBroker(slip_fixed=0.001)
# Method 2: by calling the set of brokers_ slippage_ Fixed method to set fixed sliding point
cerebro.broker = cerebro.broker.set_slippage_fixed(fixed=0.001)

Parameter Description:

Additional settings for slip points
slip_open: whether to handle the sliding point of the opening price. This parameter is False by default in the BackBroker() class, and set_slippage_perc and set_ slippage_ The default value in the fixed method is True;
slip_match: whether to match the new transaction price after sliding point processing with the price range low ~ high on the transaction day. If True, the price range will be re matched and adjusted according to the new transaction price to ensure that the order can be executed; If it is False, it will not match the price range, and the order will not be executed, but an empty order will be executed on the next day; The default value is True;
slip_out: if the new transaction price is higher than or lower than the maximum price, whether to deal at the excess price. If True, it is allowed to deal at the excess price; If it is False, the actual transaction price will be limited to the price range {low ~ high; The default value is False;
slip_limit: whether to execute sliding point for price limit order. If True, even slip_match is Fasle, and the price will also be matched to ensure that the order is executed; If False, no price matching will be made; The default value is True.

# Case 1:
set_slippage_fixed(fixed=0.35,
                   slip_open=False,
                   slip_match=True,
                   slip_out=False)
# Due to slip_open=False, the opening price will not be subject to sliding point treatment, so it is still traded at the original opening price of 32.63307367

# Case 2:
set_slippage_fixed(fixed=0.35,
                   slip_open=True,
                   slip_match=True,
                   slip_out=False)

# Case 3:
set_slippage_fixed(fixed=0.35,
                   slip_open=True,
                   slip_match=True,
                   slip_out=True)
# The new transaction price adjusted by the sliding point is 32.63307367 + 0.35 = 32.98307367, exceeding the highest price of 32.94151482 on the day
# Allow price matching slip_match=True, and run to execute slip at the new transaction price beyond the price range_ out=True
# The final transaction price was 32.98307367

# Case 4:
set_slippage_fixed(fixed=0.35,
                   slip_open=True,
                   slip_match=False,
                   slip_out=True)
# The new transaction price adjusted by the sliding point is 32.63307367 + 0.35 = 32.98307367, exceeding the highest price of 32.94151482 on the day
# Due to no price matching, slip_match=False, the new transaction price exceeds the price range and cannot be traded
# The order will not be executed on January 17, 2019, but an empty order will be executed on January 18, 2019
# No orders were executed on July 2, 2019, and empty orders were executed on July 3, 2019 the next day
# Even if open 39.96627412 + 0.35 < high 42.0866713 on July 3, 2019 meets the transaction conditions, the transaction will not be supplemented

Transaction tax management

Stock: at present, the transaction cost of A shares is divided into two parts: Commission and stamp duty,
Among them, the Commission is levied bilaterally. The commission charged by different securities companies is different, generally about 0.02% - 0.03%, and the single Commission is not less than 5 yuan;
Stamp duty is charged only at the time of sale and the tax rate is 0.1%.


Futures: the futures trading fee includes two parts: the handling fee charged by the exchange and the commission charged by the futures company. The handling fee of the exchange is relatively fixed,
The commissions of different futures companies are different, and the collection methods of different futures varieties are different. Some are charged according to a fixed fee, and some are charged according to a fixed percentage of the transaction amount:
Contract current price * contract multiplier * handling fee rate. In addition to the transaction costs, a certain proportion of margin shall be paid during futures trading.

Backtrader also provides a variety of transaction fee setting methods, which can be set simply through parameters or customized fee functions in combination with transaction conditions:

According to different transaction types, Backtrader divides transaction costs into stock like mode and futures like mode;
According to different calculation methods, Backtrader divides the transaction costs into PERC percentage fee mode and FIXED fee mode;

The stock like mode corresponds to the PERC percentage fee mode, and the futures like mode corresponds to the FIXED fee mode;

When setting transaction costs, the following three parameters are most often involved:

commission: handling fee / commission;

mult: multiplier;

Margin: margin / margin ratio.

Bilateral collection: both buying and selling operations charge the same transaction fee.

cerebro.broker.setcommission(
    # Transaction handling fee: it can be divided into percentage handling fee or fixed handling fee according to the value of margin
    commission=0.0,
    # Futures margin, which determines the type of transaction cost, only works when stocklike=False
    margin=None,
    # Multiplier, the profit and loss will be amplified according to this multiplier
    mult=1.0,
    # Transaction cost calculation method, with the following values:
    # 1.CommInfoBase.COMM_PERC percentage fee
    # 2.CommInfoBase.COMM_FIXED fee
    # 3.None determines the type according to the value of margin
    commtype=None,
    # When the transaction fee is in percentage mode, is the commission in% form
    # True indicates that it is not in% and 0 XX form; False indicates that the unit is% and the form is XX%
    percabs=True,
    # Whether it is a stock mode is usually determined by the margin and commtype parameters
    # margin=None or comm_ In perc mode, stocklike=True, corresponding to stock handling fee;
    # margin sets the value or comm_ In fixed mode, stocklike=False, corresponding to Futures Commission
    stocklike=False,
    # Calculate the annualized interest on short positions held
    # days * price * abs(size) * (interest / 365)
    interest=0.0,
    # Calculate annualized interest on long positions held
    interest_long=False,
    # Leverage ratio, which is used to adjust the required cash at the time of transaction
    leverage=1.0,
    # Automatic margin calculation
    # If False, the margin is determined through the margin parameter
    # If automargin < 0, determine the margin through mult*price
    # If automargin > 0, if automargin*price determines the margin
    automargin=False,
    # Data set of transaction cost setting function (i.e. target of function)
    # If the value is None, it will be applied to all data sets by default (that is, to all assets)
    name=None)

From the meanings and functions of the above parameters, we can see that there are two default configuration rules for margin, commtype and stocklike: stock percentage fee and futures fixed fee, as follows:
The comm / margin type is not set to 1_ Perc percentage charge → bottom_ stocklike property will be set to True → corresponding to "stock percentage fee".
Therefore, if you want to set the transaction fee for stocks, make margin = 0 / None / False, or stocklike=True;

Rule 2: set the value for margin → commtype will point to COMM_FIXED fee → bottom_ stocklike property will be set to False → corresponding to "futures fixed fee", because only futures will involve margin.
Therefore, if you want to set the transaction fee for futures, you need to set margin. In addition, you need to make stocklike=True to make the margin parameter work.

Examples of custom transaction costs

# Custom futures percentage fee
class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):
    params = (
      ('stocklike', False), # Designated as futures mode
      ('commtype', bt.CommInfoBase.COMM_PERC), # Usage percentage fee
      ('percabs', False), # commission in%
    )

    def _getcommission(self, size, price, pseudoexec):
        # Calculate transaction costs
        return (abs(size) * price) * (self.p.commission/100) * self.p.mult
    # pseudoexec is used to prompt whether the transaction costs are actually being counted
    # If it's just a trial cost, pseudoexec=False
    # If it is a real statistical cost, pseudoexec=True

comminfo = CommInfo_Fut_Perc_Mult(
    commission=0.1, # 0.1%
    mult=10,
    margin=2000) # instantiation 
cerebro.broker.addcommissioninfo(comminfo)

# The above user-defined functions can also be implemented through setcommission
cerebro.broker.setcommission(commission=0.1, #0.1%
                             mult=10,
                             margin=2000,
                             percabs=False,
                             commtype=bt.CommInfoBase.COMM_PERC,
                             stocklike=False)

The following is the share percentage fee considering Commission and stamp duty:

class StockCommission(bt.CommInfoBase):
    params = (
      ('stocklike', True), # Designated as futures mode
      ('commtype', bt.CommInfoBase.COMM_PERC), # Use percentage charge mode
      ('percabs', True), # commission is not in%
      ('stamp_duty', 0.001),) # Stamp duty is 0.1% by default
    
    # User defined expense calculation formula
      def _getcommission(self, size, price, pseudoexec):
            if size > 0: # When buying, only Commission is considered
                return abs(size) * price * self.p.commission
            elif size < 0: # When selling, both Commission and stamp duty are considered
        return abs(size) * price * (self.p.commission + self.p.stamp_duty)
            else:
                return 0

Volume limit management

Form 1: bt.broker fillers. FixedSize(size) 

Use the FixedSize() method to set the maximum fixed trading volume: size. The trading volume limit rules under this mode are as follows:

Rules for determining the actual trading volume of an order: take the smallest of (size, volume on the day the order is executed, and the number of transactions required in the order);

On the day of order execution, if the transaction quantity required in the order cannot be fully met, only part of the transaction quantity will be completed. We won't make up the bill the next day.

# Set directly through the BackBroker() class
cerebro = Cerebro()
filler = bt.broker.fillers.FixedSize(size=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

# Through set_filler method settings
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=xxx))

# self.order = self.buy(size=2000) # 2000 shares per purchase
# cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=3000)) # Fixed maximum trading volume

Form 2: bt.broker fillers. FixedBarPerc(perc)

Set perc% of the total volume of bar on the day of order execution to the maximum fixed volume through fixedparperc (PERC). The trading volume restriction rules of this mode are as follows:

Rules for determining the actual trading volume of the order: take the minimum of (volume * perc /100, the trading volume required in the order);
On the day of order execution, if the transaction quantity required in the order cannot be fully met, only part of the transaction quantity will be completed.

# Set directly through the BackBroker() class
cerebro = Cerebro()
filler = bt.broker.fillers.FixedBarPerc(perc=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

# Through set_filler method settings
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=xxx))
# perc is in% and the value range is [0.0100.0]

# self.order = self.buy(size=2000) # 2000 shares were purchased at the opening price of the following day
# cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=50))

Form 3: bt.broker fillers. BarPointPerc(minmov=0.01,perc=100.0)

BarPointPerc() determines the trading volume on the basis of considering the price range. On the day of order execution, the trading volume is determined as follows:

The price range low ~ high of bar on that day is divided evenly by minmov to obtain the divided number of shares:

part = (high -low +minmov) / / minmov (round down)

The volume of the total trading volume of bar on that day is also divided into the same number of parts, so that the average trading volume of each part can be obtained:

volume_per = volume // part 

Finally, volume_per * (perc / 100) is the maximum trading volume allowed. When the actual transaction is completed, the final actual trading volume can be obtained by comparing the trading volume required in the order

Actual trading volume = min (volume_per * (perc / 100), the number of transactions required in the order)

# Set directly through the BackBroker() class
cerebro = Cerebro()
filler = bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

# Through set_filler method settings
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0))
# perc is in% and the value range is [0.0100.0]


# self.order = self.buy(size=2000) # 2000 shares were purchased at the opening price of the following day

# cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.1, perc=50)) # Indicates 50%

Transaction timing management
For the generation and execution time of trading orders, Backtrader defaults to "place orders after the closing of the day and close at the opening price the next day". This mode can effectively avoid the use of future data in the back test process.
However, for some special trading scenarios, such as "all_in", the quantity in the order placed on the day is calculated by the closing price of the day (total capital / closing price of the day). When the order is executed at the opening price the next day,
If the opening price is higher than yesterday's closing price, there will be a shortage of available funds.
In order to deal with some special trading scenarios, Backtrader also provides some trading timing modes: cheat on open and cheat on close.

Cheat-On-Open

Cheat on open is the mode of "order on the same day and deal at the opening price on the same day". In this mode, the transaction logic in Strategy is no longer written in the next() method, but in a specific next_open(),nextstart_open() ,prenext_ In the open() function, refer to the following examples for specific settings:

Mode 1: bt.Cerebro(cheat_on_open=True);
Method 2: cerebro broker. set_ coo(True);
Method 3: BackBroker(coo=True).

Cheat-On-Close

Cheat on close is the mode of "order on the same day and close at the closing price on the same day". In this mode, the transaction logic in Strategy is still written in next(). The specific settings are as follows:

Method 1: cerebro broker. set_ coc(True);
Mode 2: BackBroker(coc=True)

class TestStrategy(bt.Strategy):
    ......
    def next(self):
        # Cancel previously unexecuted orders
        if self.order:
            self.cancel(self.order)
        # Check whether there are positions
        if not self.position:
            # Wear the 5-day moving average on the 10-day moving average and buy
            if self.crossover > 0:
                print('{} Send Buy, open {}'.format(self.data.datetime.date(),self.data.open[0]))
                self.order = self.buy(size=100) # Buy 100 shares at the opening price of the following day
        # # Under the 10 day moving average, wear the 5-day moving average and sell
        elif self.crossover < 0:
            self.order = self.close() # Close the position and sell it at the opening price of the following day
    ......

# Instantiated brain
cerebro= bt.Cerebro()
.......
# Place an order on the same day and close the deal at the same day
cerebro.broker.set_coc(True)

Keywords: Python backtrader

Added by cretam on Mon, 21 Feb 2022 13:19:58 +0200