Load Data

library( QuantTools )
# load ticks data set
data( ticks )
ticks
##                         time   price volume      id
##       1: 2016-01-19 09:30:00  98.390    100       1
##       2: 2016-01-19 09:30:01  98.350    100       2
##       3: 2016-01-19 09:30:01  98.370    100       3
##       4: 2016-01-19 09:30:01  98.360    100       4
##       5: 2016-01-19 09:30:01  98.370    114       5
##      ---                                           
## 2310006: 2016-09-14 10:42:08 111.160    100 2310006
## 2310007: 2016-09-14 10:42:10 111.150    100 2310007
## 2310008: 2016-09-14 10:42:10 111.150    200 2310008
## 2310009: 2016-09-14 10:42:11 111.130    200 2310009
## 2310010: 2016-09-14 10:42:11 111.135    100 2310010

Note: Wide tables or code blocks can be read with ease by holding Shift and scrolling a mouse wheel.

Define Strategy

Put the following bbands.cpp file into working directory.

// [[Rcpp::plugins(cpp11)]]
// [[Rcpp::depends(QuantTools)]]
#include <Rcpp.h>
#include "BackTest.h"

// [[Rcpp::export]]
Rcpp::List bbands(
    Rcpp::DataFrame ticks,
    Rcpp::List parameters,
    Rcpp::List options,
    bool fast = false
  ) {

  int    n          = parameters["n"         ];
  double k          = parameters["k"         ];
  int    timeFrame  = parameters["timeframe" ];

  // define strategy states
  enum class ProcessingState{ LONG, FLAT, SHORT };
  ProcessingState state = ProcessingState::FLAT;
  int idTrade = 1;

  // initialize indicators
  BBands bbands( n, k );

  // initialize Processor
  Processor bt( timeFrame );
  // set options
  bt.SetOptions( options );
  // if trading hours not set then isTradingHours set true
  bool isTradingHours = not bt.IsTradingHoursSet();

  // define market open/close events
  bt.onMarketOpen  = [&]() {
    // allow trading
    isTradingHours = true;

  };
  bt.onMarketClose = [&]() {
    // forbid trading and close open positions
    isTradingHours = false;
    if( state == ProcessingState::SHORT ) {
      bt.SendOrder(
        new Order( OrderSide::BUY, OrderType::MARKET, NA_REAL, "close short (EOD)", idTrade++ )
      );
    }
    if( state == ProcessingState::LONG ) {
      bt.SendOrder(
        new Order( OrderSide::SELL, OrderType::MARKET, NA_REAL, "close long (EOD)", idTrade++ )
      );
    }
    state = ProcessingState::FLAT;

  };

  // define what to do when new candle is formed
  bt.onCandle = [&]( Candle candle ) {

    // add values to indicators
    bbands.Add( candle.close );

  };

  // define what to do when new tick arrived
  bt.onTick = [&]( Tick tick ) {

    // if bbands not formed yet do nothing
    if( not bbands.IsFormed() ) return;

    if( not bt.CanTrade()  ) return;
    if( not isTradingHours ) return;

    // if strategy has no position
    if( state == ProcessingState::FLAT) {
      // if price below lower band then buy
      if( tick.price < bbands.GetValue().lower ) {

        bt.SendOrder(
          new Order( OrderSide::BUY, OrderType::MARKET, NA_REAL, "long", idTrade )
        );
        state = ProcessingState::LONG;

      }
      // if price above apper band then sell
      if( tick.price > bbands.GetValue().upper ) {

        bt.SendOrder(
          new Order( OrderSide::SELL, OrderType::MARKET, NA_REAL, "short", idTrade )
        );
        state = ProcessingState::SHORT;

      }

    }
    // if strategy is long and price goes above sma then close long
    if( state == ProcessingState::LONG and tick.price > bbands.GetValue().sma ) {

      bt.SendOrder(
        new Order( OrderSide::SELL, OrderType::MARKET, NA_REAL, "close long", idTrade++ )
      );
      state = ProcessingState::FLAT;

    }
    // if strategy is below and price goes below sma then close long
    if( state == ProcessingState::SHORT and tick.price < bbands.GetValue().sma ) {

      bt.SendOrder(
        new Order( OrderSide::BUY, OrderType::MARKET, NA_REAL, "close short", idTrade++ )
      );
      state = ProcessingState::FLAT;

    }

  };

  // run back test on tick data
  bt.Feed( ticks );

  // test summary
  Rcpp::List summary = bt.GetSummary();

  // if fast return only summary
  if( fast ) return summary;

  // combine candles and indicators history
  Rcpp::List indicators = ListBuilder().AsDataTable()
    .Add( bt.GetCandles()                                )
    .Add( bbands.GetHistory()                            )
    .Add( "pnl"     , bt.GetOnCandleMarketValueHistory() )
    .Add( "drawdown", bt.GetOnCandleDrawDownHistory()    );

  // return back test summary, trades, orders and candles/indicators
  return ListBuilder()
    .Add( "summary"          , summary                              )
    .Add( "trades"           , bt.GetTrades()                       )
    .Add( "orders"           , bt.GetOrders()                       )
    .Add( "indicators"       , indicators                           )
    .Add( "daily_performance", bt.GetOnDayClosePerformanceHistory() );

}

Note: It is good practice to have .cpp file per strategy. It is perfect to have a package of your strategies so you don’t need to recompile them every time you restart an R session.

Compile it

# file above can be found in examples directory
strategy_cpp_file = system.file( package = 'QuantTools', 'examples/bbands.cpp' )
Rcpp::sourceCpp( strategy_cpp_file )

Run Backtest

# set strategy parameters
parameters = data.table(
  n         = 100,
  k         = 0.5,
  timeframe = 60
)

# set options, see ?Processor 'Options' section
options = list(
  cost    = list( tradeAbs = -0.01 ),
  latency = 0.1 # 100 milliseconds
)

# see how fast back testing done on over 2 millin ticks
system.time( { test = bbands( ticks, parameters, options ) } )
##    user  system elapsed 
##   0.109   0.009   0.119
# lets limit testing to one date
interval = '2016-09-08'
test = bbands( ticks[ time %bw% interval ], parameters, options )
test
## $summary
##                   from                  to days_tested days_traded n_per_day  n n_long n_short n_win n_loss pct_win pct_loss avg_win avg_loss avg_pnl  win  loss  pnl max_dd        max_dd_start          max_dd_end max_dd_length sharpe sortino r_squared avg_dd
## 1: 2016-09-08 09:30:00 2016-09-08 15:59:59           1           1        17 17     10       7    16      1   94.12     5.88    6.92   -24.09    5.09 1.11 -0.24 0.87   -0.6 2016-09-08 12:24:35 2016-09-08 14:13:25             0    NaN     Inf       NaN  -0.06
## 
## $trades
##     id_trade id_sent id_enter id_exit           time_sent          time_enter           time_exit  side price_enter price_exit    pnl    mtm mtm_min mtm_max  cost     pnl_rel    mtm_rel mtm_min_rel mtm_max_rel   cost_rel  state
##  1:        1    9608     9611    9958 2016-09-08 11:10:00 2016-09-08 11:10:00 2016-09-08 11:15:28  long      105.82    106.060  0.220  0.240   0.000   0.240 -0.02  20.7900208  22.680023   0.0000000   22.680023 -1.8900019 closed
##  2:        2   10340    10356   10994 2016-09-08 11:24:01 2016-09-08 11:24:05 2016-09-08 11:41:37  long      105.91    105.940  0.010  0.030  -0.130   0.030 -0.02   0.9441979   2.832594 -12.2745728    2.832594 -1.8883958 closed
##  3:        3   11069    11072   11156 2016-09-08 11:44:53 2016-09-08 11:45:00 2016-09-08 11:47:51  long      105.86    105.940  0.060  0.090   0.000   0.090 -0.02   5.6678632   8.501795   0.0000000    8.501795 -1.8892877 closed
##  4:        4   11172    11174   11834 2016-09-08 11:48:59 2016-09-08 11:49:00 2016-09-08 12:08:54 short      106.01    105.890  0.100  0.120  -0.115   0.120 -0.02   9.4330724  11.319687 -10.8480332   11.319687 -1.8866145 closed
##  5:        5   12021    12024   12128 2016-09-08 12:14:29 2016-09-08 12:14:32 2016-09-08 12:18:34  long      105.86    105.910  0.030  0.050  -0.020   0.050 -0.02   2.8339316   4.723219  -1.8892877    4.723219 -1.8892877 closed
##  6:        6   12165    12172   15898 2016-09-08 12:20:12 2016-09-08 12:20:15 2016-09-08 14:00:36  long      105.85    105.615 -0.255 -0.235  -0.600   0.030 -0.02 -24.0906944 -22.201228 -56.6839868    2.834199 -1.8894662 closed
##  7:        7   15905    15914   16158 2016-09-08 14:00:37 2016-09-08 14:00:37 2016-09-08 14:01:19 short      105.71    105.600  0.090  0.110  -0.200   0.120 -0.02   8.5138587  10.405827 -18.9196859   11.351812 -1.8919686 closed
##  8:        8   16177    16179   16254 2016-09-08 14:01:35 2016-09-08 14:01:35 2016-09-08 14:02:22  long      105.53    105.610  0.060  0.080  -0.080   0.080 -0.02   5.6855870   7.580783  -7.5807827    7.580783 -1.8951957 closed
##  9:        9   16280    16290   16789 2016-09-08 14:02:28 2016-09-08 14:02:29 2016-09-08 14:09:56 short      105.68    105.590  0.070  0.090  -0.210   0.100 -0.02   6.6237699   8.516276 -19.8713096    9.462528 -1.8925057 closed
## 10:       10   16928    16935   16953 2016-09-08 14:12:47 2016-09-08 14:12:50 2016-09-08 14:13:34 short      105.66    105.570  0.070  0.090   0.000   0.090 -0.02   6.6250237   8.517888   0.0000000    8.517888 -1.8928639 closed
## 11:       11   16976    17007   17259 2016-09-08 14:14:29 2016-09-08 14:14:30 2016-09-08 14:20:15  long      105.49    105.590  0.080  0.090  -0.040   0.110 -0.02   7.5836572   8.531614  -3.7918286   10.427529 -1.8959143 closed
## 12:       12   17300    17315   17921 2016-09-08 14:21:47 2016-09-08 14:21:48 2016-09-08 14:35:09 short      105.70    105.590  0.090  0.100  -0.120   0.120 -0.02   8.5146641   9.460738 -11.3528855   11.352886 -1.8921476 closed
## 13:       13   18044    18049   18104 2016-09-08 14:39:22 2016-09-08 14:39:26 2016-09-08 14:40:25  long      105.52    105.570  0.030  0.040  -0.010   0.060 -0.02   2.8430629   3.790751  -0.9476876    5.686126 -1.8953753 closed
## 14:       14   18226    18233   18830 2016-09-08 14:43:00 2016-09-08 14:43:01 2016-09-08 14:58:05 short      105.66    105.570  0.070  0.070  -0.110   0.095 -0.02   6.6250237   6.625024 -10.4107515    8.991104 -1.8928639 closed
## 15:       15   18869    18874   19199 2016-09-08 14:59:01 2016-09-08 14:59:02 2016-09-08 15:06:45 short      105.66    105.560  0.080  0.100  -0.110   0.100 -0.02   7.5714556   9.464320 -10.4107515    9.464320 -1.8928639 closed
## 16:       16   19355    19364   19431 2016-09-08 15:12:54 2016-09-08 15:12:54 2016-09-08 15:14:41  long      105.51    105.560  0.030  0.050   0.000   0.070 -0.02   2.8433324   4.738887   0.0000000    6.634442 -1.8955549 closed
## 17:       17   19617    19630   19992 2016-09-08 15:21:25 2016-09-08 15:21:25 2016-09-08 15:31:13  long      105.50    105.600  0.080  0.100  -0.050   0.100 -0.02   7.5829384   9.478673  -4.7393365    9.478673 -1.8957346 closed
## 18:       18   20125    20128      NA 2016-09-08 15:32:56 2016-09-08 15:32:56                <NA>  long      105.54         NA     NA -0.030  -0.210   0.030 -0.01          NA  -2.842524 -19.8976691    2.842524 -0.9475081 opened
## 
## $orders
##     id_trade id_sent id_processed           time_sent      time_processed price_init price_exec side   type    state     comment
##  1:        1    9608         9611 2016-09-08 11:10:00 2016-09-08 11:10:00         NA    105.820  buy market executed        long
##  2:        1    9956         9958 2016-09-08 11:15:26 2016-09-08 11:15:28         NA    106.060 sell market executed  close long
##  3:        2   10340        10356 2016-09-08 11:24:01 2016-09-08 11:24:05         NA    105.910  buy market executed        long
##  4:        2   10983        10994 2016-09-08 11:41:35 2016-09-08 11:41:37         NA    105.940 sell market executed  close long
##  5:        3   11069        11072 2016-09-08 11:44:53 2016-09-08 11:45:00         NA    105.860  buy market executed        long
##  6:        3   11138        11156 2016-09-08 11:47:28 2016-09-08 11:47:51         NA    105.940 sell market executed  close long
##  7:        4   11172        11174 2016-09-08 11:48:59 2016-09-08 11:49:00         NA    106.010 sell market executed       short
##  8:        4   11829        11834 2016-09-08 12:08:46 2016-09-08 12:08:54         NA    105.890  buy market executed close short
##  9:        5   12021        12024 2016-09-08 12:14:29 2016-09-08 12:14:32         NA    105.860  buy market executed        long
## 10:        5   12125        12128 2016-09-08 12:18:27 2016-09-08 12:18:34         NA    105.910 sell market executed  close long
## 11:        6   12165        12172 2016-09-08 12:20:12 2016-09-08 12:20:15         NA    105.850  buy market executed        long
## 12:        6   15895        15898 2016-09-08 14:00:36 2016-09-08 14:00:36         NA    105.615 sell market executed  close long
## 13:        7   15905        15914 2016-09-08 14:00:37 2016-09-08 14:00:37         NA    105.710 sell market executed       short
## 14:        7   16154        16158 2016-09-08 14:01:19 2016-09-08 14:01:19         NA    105.600  buy market executed close short
## 15:        8   16177        16179 2016-09-08 14:01:35 2016-09-08 14:01:35         NA    105.530  buy market executed        long
## 16:        8   16250        16254 2016-09-08 14:02:21 2016-09-08 14:02:22         NA    105.610 sell market executed  close long
## 17:        9   16280        16290 2016-09-08 14:02:28 2016-09-08 14:02:29         NA    105.680 sell market executed       short
## 18:        9   16787        16789 2016-09-08 14:09:54 2016-09-08 14:09:56         NA    105.590  buy market executed close short
## 19:       10   16928        16935 2016-09-08 14:12:47 2016-09-08 14:12:50         NA    105.660 sell market executed       short
## 20:       10   16948        16953 2016-09-08 14:13:25 2016-09-08 14:13:34         NA    105.570  buy market executed close short
## 21:       11   16976        17007 2016-09-08 14:14:29 2016-09-08 14:14:30         NA    105.490  buy market executed        long
## 22:       11   17254        17259 2016-09-08 14:20:14 2016-09-08 14:20:15         NA    105.590 sell market executed  close long
## 23:       12   17300        17315 2016-09-08 14:21:47 2016-09-08 14:21:48         NA    105.700 sell market executed       short
## 24:       12   17904        17921 2016-09-08 14:35:04 2016-09-08 14:35:09         NA    105.590  buy market executed close short
## 25:       13   18044        18049 2016-09-08 14:39:22 2016-09-08 14:39:26         NA    105.520  buy market executed        long
## 26:       13   18098        18104 2016-09-08 14:40:21 2016-09-08 14:40:25         NA    105.570 sell market executed  close long
## 27:       14   18226        18233 2016-09-08 14:43:00 2016-09-08 14:43:01         NA    105.660 sell market executed       short
## 28:       14   18809        18830 2016-09-08 14:58:01 2016-09-08 14:58:05         NA    105.570  buy market executed close short
## 29:       15   18869        18874 2016-09-08 14:59:01 2016-09-08 14:59:02         NA    105.660 sell market executed       short
## 30:       15   19185        19199 2016-09-08 15:06:42 2016-09-08 15:06:45         NA    105.560  buy market executed close short
## 31:       16   19355        19364 2016-09-08 15:12:54 2016-09-08 15:12:54         NA    105.510  buy market executed        long
## 32:       16   19429        19431 2016-09-08 15:14:31 2016-09-08 15:14:41         NA    105.560 sell market executed  close long
## 33:       17   19617        19630 2016-09-08 15:21:25 2016-09-08 15:21:25         NA    105.500  buy market executed        long
## 34:       17   19981        19992 2016-09-08 15:31:13 2016-09-08 15:31:13         NA    105.600 sell market executed  close long
## 35:       18   20125        20128 2016-09-08 15:32:56 2016-09-08 15:32:56         NA    105.540  buy market executed        long
##     id_trade id_sent id_processed           time_sent      time_processed price_init price_exec side   type    state     comment
## 
## $indicators
##                     time    open   high     low   close volume    id    lower    upper      sma         pnl     drawdown
##   1: 2016-09-08 09:31:00 107.110 107.26 107.030 107.160  48933   212       NA       NA       NA 0.000000000  0.000000000
##   2: 2016-09-08 09:32:00 107.160 107.18 106.640 106.800 179364   478       NA       NA       NA 0.000000000  0.000000000
##   3: 2016-09-08 09:33:00 106.800 106.96 106.760 106.910  51495   799       NA       NA       NA 0.000000000  0.000000000
##   4: 2016-09-08 09:34:00 106.900 106.98 106.830 106.980  37592   942       NA       NA       NA 0.000000000  0.000000000
##   5: 2016-09-08 09:35:00 106.950 107.06 106.900 106.900  48085  1120       NA       NA       NA 0.000000000  0.000000000
##  ---                                                                                                                    
## 384: 2016-09-08 15:55:00 105.455 105.47 105.370 105.390  25467 21405 105.5256 105.6266 105.5761 0.007237814 -0.001705514
## 385: 2016-09-08 15:56:00 105.390 105.39 105.340 105.345  35934 21530 105.5226 105.6261 105.5743 0.006811436 -0.002131893
## 386: 2016-09-08 15:57:00 105.345 105.41 105.335 105.410  32188 21633 105.5212 105.6257 105.5734 0.007427316 -0.001516013
## 387: 2016-09-08 15:58:00 105.445 105.47 105.420 105.420  16367 21718 105.5201 105.6254 105.5727 0.007522067 -0.001421262
## 388: 2016-09-08 15:59:00 105.430 105.49 105.430 105.460  42548 21895 105.5199 105.6254 105.5726 0.007901070 -0.001042259
## 
## $daily_performance
##          date      return         pnl      drawdown      avg_pnl n_per_day
## 1: 2016-09-08 0.008374824 0.008374824 -0.0005685048 0.0005093574        17

Visualize Results

Static

Here is code to reproduce this plot:

# plot result
layout( matrix( 1:2, ncol = 1 ), height = c( 2, 1 ) )
# 1
par( mar = c( 0, 4, 2, 4 ), family = 'sans', xaxt = 'n' )
# candles
plot_ts( test$indicators[ time %bw% interval ], type = 'candle', main = 'Bollinger Bands' )
# indicators
plot_ts( test$indicators[ ,.( time, upper, sma, lower ) ],
  col = c( 'darkgoldenrod', 'chocolate', 'darkgoldenrod' ), legend = 'topleft', add = TRUE )
# orders
plot_ts( test$orders[ side == 'buy' ,.( time_processed, price_exec ) ],
  col = 'darkolivegreen', type = 'p', pch = 24, legend = 'n', add = TRUE, last_values = FALSE )
plot_ts( test$orders[ side == 'sell',.( time_processed, price_exec ) ],
  col = 'darkred', type = 'p', pch = 25, legend = 'n', add = TRUE, last_values = FALSE )
# 2
par( xaxt = 's', mar = c( 4, 4, 0, 4 ) )
# performance
plot_ts( test$indicators[, .( time, `P&L, %` = pnl * 100, `Draw Down, %` = drawdown * 100 ) ],
  col = c( 'darkolivegreen', 'darkred' ), legend = 'bottomleft' )

Interactive

Here is code to reproduce this plot:

library( plotly ) # install.packages( 'plotly' )
p = plot_ly( symbols = c( 'triangle-up', 'triangle-down' ), 
             colors = c( 'darkseagreen', 'firebrick' ) ) %>% #
  add_data( data = test$indicators ) %>%
  # candle low-high
  add_segments( x = ~time - parameters$timeframe / 2, y = ~low,
    xend = ~time - parameters$timeframe / 2, yend = ~high,
    showlegend = F, name = 'candle', line = list( color = 'cornflowerblue' )
  ) %>%
  # candle open-close
  add_segments( x = ~time - parameters$timeframe, y = ~open,
    xend = ~time, yend = ~close,
    showlegend = F, name = 'candle', line = list( shape = 'hvh', color = 'cornflowerblue' )
  ) %>%
  # add indicators
  add_lines( x = ~time, y = ~lower, name = 'lower', line = list( color = 'goldenrod' ) ) %>%
  add_lines( x = ~time, y = ~sma, name = 'sma', line = list( color = 'darkgreen' ) ) %>%
  add_lines( x = ~time, y = ~upper, name = 'upper', line = list( color = 'darkmagenta' ) ) %>%
  # and orders
  add_data( data = test$orders ) %>%
  add_markers( x = ~time_processed, y = ~price_exec, color = ~side, text = ~comment,
               marker = list( size = 10 ), symbol = ~side
  ) %>%
  layout(
    xaxis = list( title = '', rangeselector = list( visible = FALSE ) ),
    yaxis = list( title = '' )
  ) %>%
  config( scrollZoom = T, autosizable = T, queueLength = 1 )
p

© 2016 Stanislav Kovalevsky. All rights reserved.