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?
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