We will test two trading strategies based on return runs.
You may find the R codes below. At the top of the document I want to talk about the problems I have encountered with quantsrat and how I have overcame those.
positive days: 2
negative days: 3
end equity: 185773.68
Trend following strategy goes long after 2 positive days and close the trade after 3 negative days or goes short after 3 negative days and close the trade after 2 positive days.
Trend following is better than mean reversion and buy&hold strategies.
positive days: 9
negative days: 1
end equity: 144742.20
Mean reversion strategy goes short after 9 positive days and close the trade after 1 negative day or goes long after 1 negative day and close the trade after 9 positive days.
end equity: 137234.22
Both Trend following and Mean reversion strategy have potential to beat the buy&hold strategy.
In this optimization process positive day and negative day variables kept same for both short and long positions. Thus this demonstration is not a flawless one. A better optimization strategy should seperate long and short positions.
As I can't use the custom signal function with quantstrat's add.distribution function, I don't have an extensive comparison of the optimized strategies. Drawdown, sharpe ratio etc. should also be considered for a better benchmark. It could also have been done by returning the order book from the test_strategy function but it would take a lot more effort.
Money management should be considered for real world trading. This backtest just buys or sells 1 lot of stock for each trade. Different rules to exit/close the trades may be used. Preset limits or trailing stops may yield better end equities than closing the trade after n days of a trend.
The optimization process most probably affected by sharp market movements. For better optimization market data should be divided in to two as control and treatment groups.
Despite all the drawbacks, both trend following and mean reversion strategies are promising over buy&hold strategy.
In [2]:
# load libraries
library(quantstrat)
library(Quandl)
In [3]:
# define instruments
currency("USD")
stock("BIST", currency="USD", multiplier=1)
# get data
date_from = "2005-08-01"
date_to = "2016-05-25"
BIST<-Quandl("GOOG/INDEXIST_XU100", type="xts", start_date = date_from, end_date = date_to)
BIST<-na.omit(BIST)
BIST<-xts(coredata(BIST), as.POSIXct(time(BIST)))
BIST_back<-BIST #backup
In [39]:
consecutive_days<-function(days_pos,days_neg, stock, posneg = TRUE) {
#days_pos <- 4
#days_neg <- 4
#n_day_signals <- data.frame(positive = logical(length(time(stock))), negative = logical(length(time(stock))))
n_day_signals <- data.frame(sigcol = logical(length(time(stock))))
n_day_signals <- xts( n_day_signals, as.POSIXct(time(stock)) )
n_day_signals[1,1] <- NA
#Signal <- xts(c("Positive", "Negative"), as.POSIXct(time(BIST)))
sign_counter <- 1
sign_last <- -1
for (i in 2:length(time(stock))) {
sign_temp <- sign( as.numeric ( as.numeric( stock[i,4]) - as.numeric( stock[i-1,4]) ) )
if (sign_temp == sign_last) {
sign_counter <- sign_counter + 1
} else {
sign_counter <- 1
sign_last <- sign_temp
}
if (posneg) {
if (sign_counter == days_pos && sign_last == 1) {
n_day_signals[i,1] <- TRUE
} else {
n_day_signals[i,1] <- NA
}
} else {
if (sign_counter == days_neg && sign_last == -1) {
n_day_signals[i,1] <- TRUE
} else {
n_day_signals[i,1] <- NA
}
}
}
if (posneg == TRUE) {
return( n_day_signals$sigcol)
} else {
return(n_day_signals$sigcol)
}
}
In [40]:
test_strategy<-function(days_pos, days_neg) {
BIST <- BIST_back
# define strategy component names
portfolio_name = "investiphi"
#strategy_trend = "trend_following"
strategy.st = "consecutive_days"
#account_trend = "account_trend"
account_name = "account_name"
# remove if defined before
rm.strat(portfolio_name)
#rm.strat(strategy_trend)
rm.strat(strategy.st)
#rm.strat(account_trend)
rm.strat(account_name)
# create .blotter and .strategy environments
.blotter<-new.env()
.strategy<-new.env()
# init portfolio and accoiunt in .blotter
init_eq <- 100000 # 100k
init_date <- as.character(as.Date(date_from) - 1)
initPortf(portfolio_name, symbols="BIST", initDate=init_date, currency="USD")
#initAcct(account_trend, portfolios=portfolio_name, initDate=init_date, currency="USD", initEq = init_eq)
initAcct(account_name, portfolios=portfolio_name, initDate=init_date, currency="USD", initEq = init_eq)
initOrders(portfolio_name, initDate=init_date)
# init strategies
#strategy(strategy_trend, store=TRUE)
strategy(strategy.st, store=TRUE)
add.signal(strategy.st, name="consecutive_days",
arguments = list(days_pos = days_pos, days_neg = days_neg, stock=BIST, posneg=TRUE),
label="bull"
)
add.signal(strategy.st, name="consecutive_days",
arguments = list(days_pos = days_pos, days_neg = days_neg, stock=BIST, posneg=FALSE),
label="bear"
)
order_qty = 1
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bear',
sigval=1,
orderside='short',
ordertype='market',
orderqty=-order_qty,
TxnFees=0,
replace=FALSE),
type='enter',
label='EnterShort'
)
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bull',
sigval=1,
orderside='long',
ordertype='market',
orderqty='all',
TxnFees=0,
replace=TRUE),
type='exit',
label='Exit2Long'
)
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bull',
sigval=TRUE,
orderside='long',
ordertype='market',
orderqty=order_qty,
TxnFees=0,
replace=FALSE),
type='enter',
label='EnterLong'
)
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bear',
sigval=TRUE,
orderside='short',
ordertype='market',
orderqty='all',
TxnFees=0,
replace=TRUE),
type='exit',
label='Exit2Short'
)
applyStrategy(strategy.st, portfolio_name)
updatePortf(portfolio_name)
updateAcct(account_name)
updateEndEq(account_name)
return(getEndEq(account_name, date_to))
}
In [ ]:
results = matrix(nrow=100, ncol=3)
for (days_pos in 1:10) {
for(days_neg in 1:10) {
i = (days_pos-1)*10 + days_neg
results[i,1] = days_pos
results[i,2] = days_neg
results[i,3] = test_strategy(days_pos, days_neg)
}
}
In [43]:
print(results)
# positive days, negative days, end balance
In [60]:
#calculate buy&hold end equity
100000*(as.numeric(BIST[length(time(BIST))]$Close) - as.numeric(BIST[1]$Close))/as.numeric(BIST[1]$Close)
In [61]:
# just copy and paste the function for adapting to mean reversion strategy
# and change the rule variables accordingly
test_strategy_mean<-function(days_pos, days_neg) { #mean reversion
BIST <- BIST_back
# define strategy component names
portfolio_name = "investiphi"
#strategy_trend = "trend_following"
strategy.st = "consecutive_days"
#account_trend = "account_trend"
account_name = "account_name"
# remove if defined before
rm.strat(portfolio_name)
#rm.strat(strategy_trend)
rm.strat(strategy.st)
#rm.strat(account_trend)
rm.strat(account_name)
# create .blotter and .strategy environments
.blotter<-new.env()
.strategy<-new.env()
# init portfolio and accoiunt in .blotter
init_eq <- 100000 # 100k
init_date <- as.character(as.Date(date_from) - 1)
initPortf(portfolio_name, symbols="BIST", initDate=init_date, currency="USD")
#initAcct(account_trend, portfolios=portfolio_name, initDate=init_date, currency="USD", initEq = init_eq)
initAcct(account_name, portfolios=portfolio_name, initDate=init_date, currency="USD", initEq = init_eq)
initOrders(portfolio_name, initDate=init_date)
# init strategies
#strategy(strategy_trend, store=TRUE)
strategy(strategy.st, store=TRUE)
add.signal(strategy.st, name="consecutive_days",
arguments = list(days_pos = days_pos, days_neg = days_neg, stock=BIST, posneg=TRUE),
label="bull"
)
add.signal(strategy.st, name="consecutive_days",
arguments = list(days_pos = days_pos, days_neg = days_neg, stock=BIST, posneg=FALSE),
label="bear"
)
order_qty = 1
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bear',
sigval=1,
orderside='long',
ordertype='market',
orderqty=order_qty,
TxnFees=0,
replace=FALSE),
type='enter',
label='EnterLong'
)
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bull',
sigval=1,
orderside='short',
ordertype='market',
orderqty='all',
TxnFees=0,
replace=TRUE),
type='exit',
label='Exit2Short'
)
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bull',
sigval=TRUE,
orderside='short',
ordertype='market',
orderqty=-order_qty,
TxnFees=0,
replace=FALSE),
type='enter',
label='EnterShort'
)
add.rule(strategy.st, name='ruleSignal',
arguments=list(sigcol='sigcol.bear',
sigval=TRUE,
orderside='long',
ordertype='market',
orderqty='all',
TxnFees=0,
replace=TRUE),
type='exit',
label='Exit2Long'
)
applyStrategy(strategy.st, portfolio_name)
updatePortf(portfolio_name)
updateAcct(account_name)
updateEndEq(account_name)
return(getEndEq(account_name, date_to))
}
In [ ]:
results_mean = matrix(nrow=100, ncol=3)
for (days_pos in 1:10) {
for(days_neg in 1:10) {
i = (days_pos-1)*10 + days_neg
results[i,1] = days_pos
results[i,2] = days_neg
results[i,3] = test_strategy_mean(days_pos, days_neg)
}
}
In [65]:
print(results_mean)
# positive days, negative days, end balance