rquantstratfinancialinstrument

R: Quantstrat TxnFees Multiplier


I am trying to run a backtesting strategy in R's Quantstrat package. The instrument is Wheat futures and is quoted in US cents. The contract size is 5000 bushels. I have therefore added the following code.

future(symbols, 
      currency = "USD",
      tick_size = 0.25,
      multiplier = 50) 

However, when running the model it seems to draw a loss when the profit is too small, which prompted me to look at how transaction fees are calculated in the blotter package as shown in this code on github.

#' @param ConMult Contract/instrument multiplier for the Symbol if it is not defined in an instrument specification

Does this mean that when I specify .txnfees <- -10, the taxation fee is 50*-10 = -500, in which case I should specify TxnFees to be -0.2. How do I specify a set amount per order?


Solution

  • To directly answer your question, setting .txnfees <- -10 in ruleSignal will make the transaction cost for the trade equal to -10, regardless of the quantity of the trade. The multipliers in the contracts defined by FinancialInstrument do not directly affect the transaction cost calculation. Here is how you could go about achieving what you expect though...

    First a bit of background: The source code for addTxn in blotter is where transaction costs come into play in quantstrat backtests, which you have correctly identified. You can pass in TxnFees as a (non positive) numeric value, or a character string that is the name of a function that defines how the fees are calculated. Look carefully and you'll see that TxnQty, TxnPrice, Symbol are all arguments that are supplied to the TxnFee function. i.e. See this part of the code in addTxn:

    if (is.function(TxnFees)) {
          txnfees <- TxnFees(TxnQty, TxnPrice, Symbol) 
        } else {
          txnfees<- as.numeric(TxnFees)
        }
    

    In quantstrat, the arguments to ruleSignal include transaction costs via the TxnFees argument (and ruleSignal is an argument to add.rule) But you can pass in a custom function (giving its name as a string in the argument to ruleSignal) which will model transaction fees in a way you might like.

    If you look in the same blotter source file you have linked, there is an example of a transaction cost function (look at the blotter unit tests and you'll see examples of how this transaction cost function is used):

    pennyPerShare <- function(TxnQty, ...) {
        return(abs(TxnQty) * -0.01)
    }
    

    Below is another fully reproducible example of how you could model fees that are a function of the quantity traded, and just for demonstration I use the contract multiplier argument from the stock object, instead of future object, but obviously the same kind of logic applies for any instrument type. In the example below, for each transaction, a fee equal to 1.5% of the quantity traded is charged as a transaction cost. You could also possibly make the fees a function of the TxnPrice too, which is another argument to the function.

    #---------------------------------------------------------------
    # Define the transaction cost function
    txnFUN <- function(TxnQty, TxnPrice, Symbol, pct = 0.015) {
      multiStock <- getInstrument(Symbol)$multiplier
      # Do something with multiStock, here it is equal to 1, so it's effectively meaningless but shows how you could go about using it.
    
      fees <- abs(TxnQty) * pct * multiStock
      # Fees are a negative deduction for the trade:
      if (fees > 0) fees <- -fees
    
      fees
    }
    
    #-------------------------------------------------------------------------------------
    
    
    library(quantstrat)
    
    
    suppressWarnings(rm("order_book.RSI",pos=.strategy))
    suppressWarnings(rm("account.RSI","portfolio.RSI",pos=.blotter))
    suppressWarnings(rm("account.st","portfolio.st","stock.str","stratRSI","startDate","initEq",'start_t','end_t'))
    
    
    strategy.st <- "RSI"
    
    stratRSI <- strategy(strategy.st, store = TRUE)
    
    
    add.indicator(strategy = strategy.st, name = "RSI", arguments = list(price = quote(getPrice(mktdata))), label="RSI")
    add.signal(strategy = strategy.st, name="sigThreshold",arguments = list(threshold=70, column="RSI",relationship="gt", cross=TRUE),label="RSI.gt.70")
    
    add.signal(strategy = strategy.st, name="sigThreshold",arguments = list(threshold=30, column="RSI",relationship="lt",cross=TRUE),label="RSI.lt.30")
    
    
    add.rule(strategy = strategy.st, name='ruleSignal', arguments = list(sigcol="RSI.lt.30", sigval=TRUE, orderqty= 100, TxnFees="txnFUN", ordertype='market', orderside='long', pricemethod='market', replace=FALSE, osFUN=osMaxPos), type='enter', path.dep=TRUE)
    add.rule(strategy = strategy.st, name='ruleSignal', arguments = list(sigcol="RSI.gt.70", sigval=TRUE, orderqty='all', TxnFees="txnFUN", ordertype='market', orderside='long', pricemethod='market', replace=FALSE), type='exit', path.dep=TRUE)
    
    
    currency("USD")
    symbols = c("SPY")
    stock.str = symbols
    
        startDate <- "1987-01-01"
        getSymbols(stock.str,from=startDate, to= Sys.Date())
    
    for(symbol in symbols){
        stock(symbol, currency="USD",multiplier=1)
    }
    SPY <- SPY["2015/"]
    
    
    startDate='2005-12-31'
    initEq=100000
    port.st<-'RSI'
    
    initPortf(port.st, symbols=symbols)
    initAcct(port.st, portfolios=port.st, initEq=initEq)
    initOrders(portfolio=port.st)
    for(symbol in symbols){ addPosLimit(port.st, symbol, startDate, 300, 3 ) }
    
    applyStrategy(strategy=strategy.st , portfolios=port.st, parameters=list(n=2) ) 
    
    updatePortf(Portfolio=port.st,Dates=paste('::',as.Date(Sys.time()),sep=''))
    

    Check that the fees are as expected in relation to quantity traded:

    tail(getTxns(port.st, "SPY"), 15)
    #                     Txn.Qty Txn.Price Txn.Fees Txn.Value Txn.Avg.Cost Net.Txn.Realized.PL
    # 2017-03-28 20:00:00    -100  234.3969     -1.5 -23439.69     234.3969            178.6209
    # 2017-04-05 20:00:00     100  234.2974     -1.5  23429.74     234.2974             -1.5000
    # 2017-04-11 20:00:00     100  232.8943     -1.5  23289.43     232.8943             -1.5000
    # 2017-04-20 20:00:00    -200  233.4515     -3.0 -46690.31     233.4515            -31.8605
    # 2017-05-14 20:00:00     100  239.1338     -1.5  23913.38     239.1338             -1.5000
    # 2017-05-15 20:00:00    -100  238.9149     -1.5 -23891.49     238.9149            -23.3933
    # 2017-05-17 20:00:00     100  235.6210     -1.5  23562.10     235.6210             -1.5000
    # 2017-05-22 20:00:00    -100  238.8851     -1.5 -23888.51     238.8851            324.9084
    # 2017-06-12 20:00:00     100  243.3632     -1.5  24336.32     243.3632             -1.5000
    # 2017-06-13 20:00:00    -100  243.0547     -1.5 -24305.47     243.0547            -32.3502
    # 2017-06-27 20:00:00     100  243.4900     -1.5  24349.00     243.4900             -1.5000
    # 2017-06-29 20:00:00     100  241.8000     -1.5  24180.00     241.8000             -1.5000
    # 2017-07-05 20:00:00    -200  240.5500     -3.0 -48110.00     240.5500           -422.0002
    # 2017-07-06 20:00:00     100  242.1100     -1.5  24211.00     242.1100             -1.5000
    # 2017-07-12 20:00:00    -100  244.4200     -1.5 -24442.00     244.4200            229.4997