Mustafa Kutay Yabas - EC581 - 29.10.2016

Assignment #3

We will test two trading strategies based on return runs.

  • Trend Following Strategy
    • Buy after n days of positive return
    • Sell after m days of negative return
  • Mean Reversion Strategy
    • Buy after n days of negative return
    • Sell after m days of positive return

Steps

  1. Apply two different trading algo for these strategies in quantstrat
  2. Apply these strategies to BIST100 index data
  3. For each strategy, optimize n and m using a grid search over set 1,2,...,10
  4. Compare optimized versions of these two strategies
  5. Is it better to be a trend-follower or a contrarian in BIST

Results

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.

  1. To analyze the consecutive days I wrote a custom function. I created signal columns with that function and based the quantstrat rules over those signals.
  2. However I couldn't use the same function (consecutive_days) for grid analysis (add.distribution).
  3. Thats why I looped through variables for optimization process. I didn't save all the variables but just the end_equity for faster execution. So make the performance comparison according to end equity.
  4. Then I repeated the same process for mean reversion strategy.

Comparison of Results

Trend Following Strategy

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.

Mean Reversion Strategy

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.

Buy&Hold Strategy

end equity: 137234.22

Both Trend following and Mean reversion strategy have potential to beat the buy&hold strategy.

Discussion

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.

R Codes


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


'USD'
'BIST'

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


       [,1] [,2]      [,3]
  [1,]    1    1 147782.00
  [2,]    1    2 153579.00
  [3,]    1    3 164743.97
  [4,]    1    4 153019.39
  [5,]    1    5 154354.82
  [6,]    1    6 147417.46
  [7,]    1    7 138993.11
  [8,]    1    8 141846.75
  [9,]    1    9 139613.38
 [10,]    1   10 137559.16
 [11,]    2    1 105064.16
 [12,]    2    2 155410.65
 [13,]    2    3 185773.68
 [14,]    2    4 148293.22
 [15,]    2    5 150977.49
 [16,]    2    6 149849.08
 [17,]    2    7 145654.01
 [18,]    2    8 152944.07
 [19,]    2    9 141582.09
 [20,]    2   10 139527.87
 [21,]    3    1  76974.20
 [22,]    3    2  99279.04
 [23,]    3    3 160367.37
 [24,]    3    4 167518.31
 [25,]    3    5 157007.25
 [26,]    3    6 161471.04
 [27,]    3    7 156192.93
 [28,]    3    8 161317.69
 [29,]    3    9 141737.34
 [30,]    3   10 139683.12
 [31,]    4    1  71062.13
 [32,]    4    2 104499.30
 [33,]    4    3 158430.18
 [34,]    4    4 142629.60
 [35,]    4    5 160760.36
 [36,]    4    6 167730.14
 [37,]    4    7 157372.69
 [38,]    4    8 170392.93
 [39,]    4    9 150967.30
 [40,]    4   10 148913.08
 [41,]    5    1  71579.03
 [42,]    5    2 101281.50
 [43,]    5    3 127862.69
 [44,]    5    4 116073.13
 [45,]    5    5 164028.38
 [46,]    5    6 162384.56
 [47,]    5    7 151338.55
 [48,]    5    8 160230.60
 [49,]    5    9 142160.67
 [50,]    5   10 140106.45
 [51,]    6    1  70084.02
 [52,]    6    2  98101.68
 [53,]    6    3 113691.30
 [54,]    6    4  99230.89
 [55,]    6    5 118381.92
 [56,]    6    6 159678.56
 [57,]    6    7 149974.91
 [58,]    6    8 162533.42
 [59,]    6    9 142976.06
 [60,]    6   10 140921.84
 [61,]    7    1  63386.62
 [62,]    7    2  78014.85
 [63,]    7    3  91886.04
 [64,]    7    4  63358.79
 [65,]    7    5  76272.12
 [66,]    7    6 100028.63
 [67,]    7    7  77744.90
 [68,]    7    8 129244.36
 [69,]    7    9 142577.83
 [70,]    7   10 140523.61
 [71,]    8    1  58273.10
 [72,]    8    2  63206.66
 [73,]    8    3  75983.14
 [74,]    8    4  55509.66
 [75,]    8    5  69895.00
 [76,]    8    6  99597.11
 [77,]    8    7  78199.59
 [78,]    8    8 101471.65
 [79,]    8    9 120062.85
 [80,]    8   10 118008.63
 [81,]    9    1  55257.80
 [82,]    9    2  60222.44
 [83,]    9    3  68471.65
 [84,]    9    4  57871.53
 [85,]    9    5  67290.21
 [86,]    9    6 106852.40
 [87,]    9    7  92541.28
 [88,]    9    8 123216.15
 [89,]    9    9 112173.03
 [90,]    9   10 110118.81
 [91,]   10    1  58274.46
 [92,]   10    2  63239.10
 [93,]   10    3  71488.31
 [94,]   10    4  60888.19
 [95,]   10    5  69997.61
 [96,]   10    6 107877.01
 [97,]   10    7  93565.89
 [98,]   10    8 123910.10
 [99,]   10    9 112866.98
[100,]   10   10 110812.76

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)


137234.225832354

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


       [,1] [,2]      [,3]
  [1,]    1    1  52218.00
  [2,]    1    2  46421.00
  [3,]    1    3  35256.03
  [4,]    1    4  46980.61
  [5,]    1    5  45645.18
  [6,]    1    6  52582.54
  [7,]    1    7  61006.89
  [8,]    1    8  58153.25
  [9,]    1    9  60386.62
 [10,]    1   10  62440.84
 [11,]    2    1  94935.84
 [12,]    2    2  44589.35
 [13,]    2    3  14226.32
 [14,]    2    4  51706.78
 [15,]    2    5  49022.51
 [16,]    2    6  50150.92
 [17,]    2    7  54345.99
 [18,]    2    8  47055.93
 [19,]    2    9  58417.91
 [20,]    2   10  60472.13
 [21,]    3    1 123025.80
 [22,]    3    2 100720.96
 [23,]    3    3  39632.63
 [24,]    3    4  32481.69
 [25,]    3    5  42992.75
 [26,]    3    6  38528.96
 [27,]    3    7  43807.07
 [28,]    3    8  38682.31
 [29,]    3    9  58262.66
 [30,]    3   10  60316.88
 [31,]    4    1 128937.87
 [32,]    4    2  95500.70
 [33,]    4    3  41569.82
 [34,]    4    4  57370.40
 [35,]    4    5  39239.64
 [36,]    4    6  32269.86
 [37,]    4    7  42627.31
 [38,]    4    8  29607.07
 [39,]    4    9  49032.70
 [40,]    4   10  51086.92
 [41,]    5    1 128420.97
 [42,]    5    2  98718.50
 [43,]    5    3  72137.31
 [44,]    5    4  83926.87
 [45,]    5    5  35971.62
 [46,]    5    6  37615.44
 [47,]    5    7  48661.45
 [48,]    5    8  39769.40
 [49,]    5    9  57839.33
 [50,]    5   10  59893.55
 [51,]    6    1 129915.98
 [52,]    6    2 101898.32
 [53,]    6    3  86308.70
 [54,]    6    4 100769.11
 [55,]    6    5  81618.08
 [56,]    6    6  40321.44
 [57,]    6    7  50025.09
 [58,]    6    8  37466.58
 [59,]    6    9  57023.94
 [60,]    6   10  59078.16
 [61,]    7    1 136613.38
 [62,]    7    2 121985.15
 [63,]    7    3 108113.96
 [64,]    7    4 136641.21
 [65,]    7    5 123727.88
 [66,]    7    6  99971.37
 [67,]    7    7 122255.10
 [68,]    7    8  70755.64
 [69,]    7    9  57422.17
 [70,]    7   10  59476.39
 [71,]    8    1 141726.90
 [72,]    8    2 136793.34
 [73,]    8    3 124016.86
 [74,]    8    4 144490.34
 [75,]    8    5 130105.00
 [76,]    8    6 100402.89
 [77,]    8    7 121800.41
 [78,]    8    8  98528.35
 [79,]    8    9  79937.15
 [80,]    8   10  81991.37
 [81,]    9    1 144742.20
 [82,]    9    2 139777.56
 [83,]    9    3 131528.35
 [84,]    9    4 142128.47
 [85,]    9    5 132709.79
 [86,]    9    6  93147.60
 [87,]    9    7 107458.72
 [88,]    9    8  76783.85
 [89,]    9    9  87826.97
 [90,]    9   10  89881.19
 [91,]   10    1 141725.54
 [92,]   10    2 136760.90
 [93,]   10    3 128511.69
 [94,]   10    4 139111.81
 [95,]   10    5 130002.39
 [96,]   10    6  92122.99
 [97,]   10    7 106434.11
 [98,]   10    8  76089.90
 [99,]   10    9  87133.02
[100,]   10   10  89187.24