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.
Put the following bbands_market_maker.cpp
file into working directory.
// [[Rcpp::plugins(cpp11)]]
// [[Rcpp::depends(QuantTools)]]
#include <Rcpp.h>
#include "BackTest.h"
// [[Rcpp::export]]
Rcpp::List bbands_market_maker(
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{ INIT, LONG, FLAT, SHORT };
ProcessingState state = ProcessingState::INIT;
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();
Order* buy;
Order* sell;
double levelLongEnter;
double levelLongExit;
double levelShortEnter;
double levelShortExit;
// define events logic
// any trade exit event declaration
std::function<void()> onTradeExit;
// long closer logic
std::function<void()> quoteLongExit = [&]() {
// quote sell with current levelLongExit price
sell = new Order( OrderSide::SELL, OrderType::LIMIT, levelLongExit, "close long", idTrade );
// attach events to order:
// when long position closed ( sold ) exit logic triggered
sell->onExecuted = onTradeExit;
// when long closing order cancelled quote long closing order again
sell->onCancelled = quoteLongExit;
// send order to exchange
bt.SendOrder( sell );
};
// long opener logic
std::function<void()> quoteLongEnter = [&]() {
// quote buy with current levelLongEnter price
buy = new Order( OrderSide::BUY, OrderType::LIMIT, levelLongEnter, "long", idTrade );
// attach events to order:
// when long opening order executed
buy->onExecuted = [&]() {
// change state to LONG
state = ProcessingState::LONG;
// change onCancelled event for short opener so the next time when
// short opener cancelled it will trigger long order exiter
sell->onCancelled = quoteLongExit;
// or in case it is accidently executed there is no need in quoting long exiter
// because it is already closed
sell->onExecuted = [&]() {
// so just add relevant comment to it
sell->comment = "close long on cancel";
// and trigger exit logic
onTradeExit();
};
};
// when long opening order cancelled qoute it again
buy->onCancelled = quoteLongEnter;
// send order to exchange
bt.SendOrder( buy );
};
// short order closer logic
std::function<void()> quoteShortExit = [&]() {
// quote buy with current levelShortExit price
buy = new Order( OrderSide::BUY, OrderType::LIMIT, levelShortExit, "close short", idTrade );
// attach events to order:
// when short position closed ( bought ) exit logic triggered
buy->onExecuted = onTradeExit;
// when short closing order cancelled it again
buy->onCancelled = quoteShortExit;
// send order to exchange
bt.SendOrder( buy );
};
// short opener logic
std::function<void()> quoteShortEnter = [&]() {
// quote sell with current levelShortEnter price
sell = new Order( OrderSide::SELL, OrderType::LIMIT, levelShortEnter, "short", idTrade );
// attach events to order:
// when short opening order executed
sell->onExecuted = [&]() {
// change state to SHORT
state = ProcessingState::SHORT;
// change onCancelled event for long opener so the next time when
// long opener cancelled it will trigger short order exiter
buy->onCancelled = quoteShortExit;
// or in case it is accidently executed there is no need in quoting short exiter
// because it is already closed
buy->onExecuted = [&]() {
// so just add relevant comment to it
buy->comment = "close short on cancel";
// and trigger exit logic
onTradeExit();
};
};
// when short opening order cancelled qoute it again
sell->onCancelled = quoteShortEnter;
// send order to exchange
bt.SendOrder( sell );
};
// trade exit logic
onTradeExit = [&]() {
// reset state to FLAT
state = ProcessingState::FLAT;
// increment trade id
idTrade++;
// and start quoting long and short openers
quoteLongEnter ();
quoteShortEnter();
};
// define market open/close events
bt.onMarketOpen = [&]() {
// allow trading
isTradingHours = true;
};
bt.onMarketClose = [&]() {
// forbid trading and close open positions
isTradingHours = false;
// if state was SHORT
if( state == ProcessingState::SHORT ) {
// redefine cancel logic of short closer:
// when cancelled
buy->onCancelled = [&]() {
// create market order to close short position
Order* buy = new Order( OrderSide::BUY, OrderType::MARKET, NA_REAL, "close short (EOD)", idTrade );
// attach events to order:
// when market order executed
buy->onExecuted = [&]() {
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send order to exchange
bt.SendOrder( buy );
};
// when executed
buy->onExecuted = [&]() {
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send cancel request to exchange
buy->Cancel();
}
// if state was LONG
if( state == ProcessingState::LONG ) {
// redefine cancel logic of long closer:
// when cancelled
sell->onCancelled = [&]() {
// create market order to close short position
Order* sell = new Order( OrderSide::SELL, OrderType::MARKET, NA_REAL, "close long (EOD)", idTrade );
// attach events to order:
// when market order executed
sell->onExecuted = [&]() {
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send order to exchange
bt.SendOrder( sell );
};
// when executed
sell->onExecuted = [&]() {
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send cancel request to exchange
sell->Cancel();
}
// if state was FLAT
if( state == ProcessingState::FLAT ) {
// let's try to cancel openers
// redefine short opener logic
// in case short opener executed
sell->onExecuted = [&]() {
// mark it as failed to cancel
sell->comment = "short cancel failed (EOD)";
// send long opener cancel request to exchange
buy->Cancel();
// create market order to close short position
Order* buy = new Order( OrderSide::BUY, OrderType::MARKET, NA_REAL, "close short (EOD)", idTrade );
// attach events to order:
// when market order executed
buy->onExecuted = [&]() {
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send order to exchange
bt.SendOrder( buy );
};
// in case short opener cancelled
sell->onCancelled = [&]() {
// mark it as cancelled
sell->comment = "short cancel (EOD)";
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send cancel request to exchange
sell->Cancel();
// redefine long opener logic
// in case long opener executed
buy->onExecuted = [&]() {
// mark it as failed to cancel
buy->comment = "long cancel failed (EOD)";
// send short opener cancel request to exchange
sell->Cancel();
// create market order to close long position
Order* sell = new Order( OrderSide::SELL, OrderType::MARKET, NA_REAL, "close long (EOD)", idTrade );
// attach events to order:
// when market order executed
sell->onExecuted = [&]() {
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send order to exchange
bt.SendOrder( sell );
};
// in case long opener cancelled
buy->onCancelled = [&]() {
// mark it as cancelled
buy->comment = "long cancel (EOD)";
// reset state to initial
state = ProcessingState::INIT;
// and increment trade id
idTrade++;
};
// send cancel request to exchange
buy->Cancel();
}
};
// define what to do when new candle arrived
bt.onCandle = [&]( Candle candle ) {
// add values to indicators
bbands.Add( candle.close );
// if bbands not formed yet do nothing
if( not bbands.IsFormed() ) return;
if( not bt.CanTrade() ) return;
if( not isTradingHours ) return;
// update current levels
levelLongEnter = bbands.GetValue().lower;
levelLongExit = bbands.GetValue().sma;
levelShortEnter = bbands.GetValue().upper;
levelShortExit = bbands.GetValue().sma;
if( state == ProcessingState::INIT ) {
// in case of initial state
// start quoting long and short openers
quoteLongEnter();
quoteShortEnter();
// and change state to FLAT
state = ProcessingState::FLAT;
} else {
// if not in initial state
// move current orders according to set logic
bt.CancelOrders();
}
};
// 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_market_maker.cpp' )
Rcpp::sourceCpp( strategy_cpp_file )
# set strategy parameters
parameters = data.table(
n = 100,
k = 0.5,
timeframe = 60
)
# set options, see 'Options' section
options = list(
cost = list( tradeAbs = -0.01 ),
latency = 0.1, # 100 milliseconds
allow_limit_to_hit_market = TRUE
)
# see how fast back testing done on over 2 millin ticks
system.time( { test = bbands_market_maker( ticks, parameters, options ) } )
## user system elapsed
## 0.186 0.027 0.214
# lets limit testing to one date
interval = '2016-09-08'
test = bbands_market_maker( 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 5.1 -25.41 3.31 0.82 -0.25 0.56 -0.6 2016-09-08 12:18:34 2016-09-08 14:35:04 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 9957 2016-09-08 11:10:00 2016-09-08 11:10:00 2016-09-08 11:15:26 long 105.8200 106.0459 0.20585000 0.23000000 0.000000000 0.23000000 -0.02 19.452844 21.735022 0.0000000 21.735022 -1.8900019 closed
## 2: 2 9957 10357 10989 2016-09-08 11:15:28 2016-09-08 11:24:08 2016-09-08 11:41:35 long 105.9054 105.9391 0.01374212 0.03464212 -0.125357878 0.03464212 -0.02 1.297585 3.271045 -11.8367834 3.271045 -1.8884786 closed
## 3: 3 10989 11071 11140 2016-09-08 11:41:37 2016-09-08 11:44:53 2016-09-08 11:47:28 long 105.8686 105.9237 0.03515457 0.05640457 -0.008595427 0.05640457 -0.02 3.320586 5.327791 -0.8118958 5.327791 -1.8891343 closed
## 4: 4 11140 11174 11833 2016-09-08 11:47:51 2016-09-08 11:49:00 2016-09-08 12:08:46 short 106.0100 105.9213 0.06865000 0.10000000 -0.115000000 0.10000000 -0.02 6.475804 9.433072 -10.8480332 9.433072 -1.8866145 closed
## 5: 5 11833 12022 12126 2016-09-08 12:08:54 2016-09-08 12:14:29 2016-09-08 12:18:27 long 105.8400 105.9017 0.04175000 0.07000000 0.000000000 0.07000000 -0.02 3.944633 6.613757 0.0000000 6.613757 -1.8896447 closed
## 6: 6 12126 12166 15897 2016-09-08 12:18:34 2016-09-08 12:20:12 2016-09-08 14:00:36 long 105.8537 105.6047 -0.26898677 -0.23373677 -0.603736769 0.02626323 -0.02 -25.411174 -22.081107 -57.0349982 2.481087 -1.8893995 closed
## 7: 7 15897 15908 16155 2016-09-08 14:00:36 2016-09-08 14:00:37 2016-09-08 14:01:19 short 105.6750 105.6025 0.05242455 0.08497455 -0.235025455 0.08497455 -0.02 4.960923 8.041123 -22.2404080 8.041123 -1.8925957 closed
## 8: 8 16155 16178 16253 2016-09-08 14:01:19 2016-09-08 14:01:35 2016-09-08 14:02:21 long 105.5337 105.5993 0.04556528 0.06626528 -0.083734723 0.06626528 -0.02 4.317603 6.279061 -7.9344035 6.279061 -1.8951286 closed
## 9: 9 16253 16289 16788 2016-09-08 14:02:22 2016-09-08 14:02:28 2016-09-08 14:09:54 short 105.6671 105.5934 0.05368542 0.08713542 -0.222864582 0.08713542 -0.02 5.080616 8.246217 -21.0911918 8.246217 -1.8927361 closed
## 10: 10 16788 16933 16952 2016-09-08 14:09:56 2016-09-08 14:12:47 2016-09-08 14:13:25 short 105.6538 105.5902 0.04358466 0.06383466 -0.006165343 0.06383466 -0.02 4.125232 6.041868 -0.5835418 6.041868 -1.8929744 closed
## 11: 11 16952 16978 17255 2016-09-08 14:13:34 2016-09-08 14:14:29 2016-09-08 14:20:14 long 105.5259 105.5814 0.03554829 0.07414829 -0.075851710 0.07414829 -0.02 3.368681 7.026552 -7.1879742 7.026552 -1.8952702 closed
## 12: 12 17255 17305 17906 2016-09-08 14:20:15 2016-09-08 14:21:47 2016-09-08 14:35:04 short 105.6461 105.5835 0.04253268 0.06608268 -0.173917325 0.06608268 -0.02 4.025959 6.255099 -16.4622597 6.255099 -1.8931133 closed
## 13: 13 17906 18046 18099 2016-09-08 14:35:09 2016-09-08 14:39:22 2016-09-08 14:40:21 long 105.5117 105.5733 0.04169042 0.06834042 -0.001659576 0.06834042 -0.02 3.951262 6.477049 -0.1572884 6.477049 -1.8955251 closed
## 14: 14 18099 18227 18810 2016-09-08 14:40:25 2016-09-08 14:43:00 2016-09-08 14:58:01 short 105.6335 105.5705 0.04291653 0.06846653 -0.136533473 0.06846653 -0.02 4.062777 6.481519 -12.9252099 6.481519 -1.8933394 closed
## 15: 15 18810 18872 19189 2016-09-08 14:58:05 2016-09-08 14:59:01 2016-09-08 15:06:42 short 105.6337 105.5772 0.03642021 0.07367021 -0.136329786 0.07367021 -0.02 3.447785 6.974122 -12.9059026 6.974122 -1.8933357 closed
## 16: 16 19189 19356 19430 2016-09-08 15:06:45 2016-09-08 15:12:54 2016-09-08 15:14:31 long 105.5135 105.5783 0.04488294 0.06653294 -0.003467056 0.06653294 -0.02 4.253764 6.305635 -0.3285890 6.305635 -1.8954926 closed
## 17: 17 19430 19621 19983 2016-09-08 15:14:40 2016-09-08 15:21:25 2016-09-08 15:31:13 long 105.5162 105.5951 0.05892356 0.08382356 -0.066176443 0.08382356 -0.02 5.584315 7.944143 -6.2716869 7.944143 -1.8954440 closed
## 18: 18 19983 20127 NA 2016-09-08 15:31:13 2016-09-08 15:32:56 <NA> long 105.5474 NA NA -0.03743160 -0.217431597 0.02256840 -0.01 NA -3.546424 -20.6003684 2.138224 -0.9474413 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 105.9348 105.82 buy limit executed long
## 2: 1 9608 9644 2016-09-08 11:10:00 2016-09-08 11:11:16 106.2540 NA sell limit cancelled short
## 3: 1 9644 9768 2016-09-08 11:11:17 2016-09-08 11:12:00 106.0816 NA sell limit cancelled close long
## 4: 1 9768 9869 2016-09-08 11:12:00 2016-09-08 11:13:01 106.0738 NA sell limit cancelled close long
## 5: 1 9869 9915 2016-09-08 11:13:02 2016-09-08 11:14:00 106.0648 NA sell limit cancelled close long
## ---
## 355: 18 21412 21532 2016-09-08 15:55:03 2016-09-08 15:56:04 105.5761 NA sell limit cancelled close long
## 356: 18 21532 21645 2016-09-08 15:56:05 2016-09-08 15:57:05 105.5743 NA sell limit cancelled close long
## 357: 18 21645 21731 2016-09-08 15:57:08 2016-09-08 15:58:00 105.5734 NA sell limit cancelled close long
## 358: 18 21731 21897 2016-09-08 15:58:00 2016-09-08 15:59:00 105.5727 NA sell limit cancelled close long
## 359: 18 21897 NA 2016-09-08 15:59:00 <NA> 105.5726 NA sell limit registered close long
##
## $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.004134348 -0.001727555
## 385: 2016-09-08 15:56:00 105.390 105.39 105.340 105.345 35934 21530 105.5226 105.6261 105.5743 0.003707999 -0.002153903
## 386: 2016-09-08 15:57:00 105.345 105.41 105.335 105.410 32188 21633 105.5212 105.6257 105.5734 0.004323836 -0.001538067
## 387: 2016-09-08 15:58:00 105.445 105.47 105.420 105.420 16367 21718 105.5201 105.6254 105.5727 0.004418580 -0.001443322
## 388: 2016-09-08 15:59:00 105.430 105.49 105.430 105.460 42548 21895 105.5199 105.6254 105.5726 0.004797557 -0.001064346
##
## $daily_performance
## date return pnl drawdown avg_pnl n_per_day
## 1: 2016-09-08 0.005271277 0.005271277 -0.0005906252 0.0003309365 17
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' )
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.