forked from timtrice/backtesting-strategies
-
Notifications
You must be signed in to change notification settings - Fork 1
/
search_index.json
18 lines (18 loc) · 114 KB
/
search_index.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
["index.html", "Backtesting Strategies with R Chapter 1 Introduction 1.1 R Resources 1.2 Libraries 1.3 SessionInfo", " Backtesting Strategies with R Tim Trice 2016-05-06 Chapter 1 Introduction This book is designed to not only produce statistics on many of the most common technical patterns in the stock market, but to show actual trades in such scenarios. Test a strategy; reject if results are not promising Apply a range of parameters to strategies for optimization Attempt to kill any strategy that looks promising. Let me explain that last one a bit. Just because you may find a strategy that seems to outperform the market, have good profit and low drawdown this doesn’t mean you’ve found a strategy to put to work. On the contrary, you must work to disprove it. Nothing is worse than putting a non-profitable strategy to work because it wasn’t rigurously tested. We’ll address that later. 1.1 R Resources This book assumes you have at least a basic working knowledge of the R platform. If you are new to R or need a refresher, the following site should be beneficial: Advanced R In addition, the packages used in this book can be found under the TradeAnalytics projected on R-Forge. You will find forums and source code that have helped inspire this book. I also recommend you read Guy Yollin’s presentations on backtesting as well as the Using Quantstrat presentation by Jan Humme and Brian Peterson. This book is not intended to replace any of the existing resources on backtesting strategies in R. Rather, the intent is to enhance and streamline those resources. If something is not addressed in this book read the presentations above. Also, this book is open-source. Anyone is welcome to contribute. You can find the source code available on my Github account. 1.2 Libraries The only required library needed to run backtesting strategies is quantstrat. quantstrat will load all additionally required libraries. quantstrat 0.9.1739 This version of quantstrat includes the following packages, among others: blotter 0.9.1741 quantmod 0.4-5 TTR 0.23-1 With these libraries we will have all we need to fully-test strategies and measure performance. See 1.3 SessionInfo for more details. library(quantstrat) ## Loading required package: quantmod ## Loading required package: xts ## Loading required package: zoo ## ## Attaching package: 'zoo' ## The following objects are masked from 'package:base': ## ## as.Date, as.Date.numeric ## Loading required package: TTR ## Version 0.4-0 included new data defaults. See ?getSymbols. ## Loading required package: blotter ## Loading required package: FinancialInstrument ## Loading required package: PerformanceAnalytics ## ## Attaching package: 'PerformanceAnalytics' ## The following object is masked from 'package:graphics': ## ## legend ## Loading required package: foreach Additional libraries we may use for analysis or book presentation: ggplot2 2.0.0 dplyr 0.4.3 tidyr 0.4.1 library(data.table) ## ## Attaching package: 'data.table' ## The following object is masked from 'package:xts': ## ## last library(dplyr) ## ## Attaching package: 'dplyr' ## The following objects are masked from 'package:data.table': ## ## between, last ## The following objects are masked from 'package:xts': ## ## first, last ## The following objects are masked from 'package:stats': ## ## filter, lag ## The following objects are masked from 'package:base': ## ## intersect, setdiff, setequal, union library(DT) library(ggplot2) library(htmltools) library(htmlwidgets) library(knitr) library(lattice) library(pander) ## ## Attaching package: 'pander' ## The following object is masked from 'package:htmltools': ## ## p library(tidyr) ## ## Attaching package: 'tidyr' ## The following object is masked from 'package:FinancialInstrument': ## ## spread library(webshot) 1.3 SessionInfo sessionInfo() ## R version 3.2.3 (2015-12-10) ## Platform: x86_64-pc-linux-gnu (64-bit) ## Running under: Ubuntu 14.04.4 LTS ## ## locale: ## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C ## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 ## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 ## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C ## [9] LC_ADDRESS=C LC_TELEPHONE=C ## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C ## ## attached base packages: ## [1] stats graphics grDevices utils datasets methods base ## ## other attached packages: ## [1] webshot_0.3 tidyr_0.4.1 ## [3] pander_0.6.0 lattice_0.20-33 ## [5] knitr_1.12.3 htmlwidgets_0.6 ## [7] htmltools_0.3.5 ggplot2_2.0.0 ## [9] DT_0.1 dplyr_0.4.3 ## [11] data.table_1.9.6 quantstrat_0.9.1739 ## [13] foreach_1.4.3 blotter_0.9.1741 ## [15] PerformanceAnalytics_1.4.4000 FinancialInstrument_1.2.0 ## [17] quantmod_0.4-5 TTR_0.23-1 ## [19] xts_0.9.874 zoo_1.7-13 ## ## loaded via a namespace (and not attached): ## [1] Rcpp_0.12.4 plyr_1.8.3 formatR_1.3 ## [4] iterators_1.0.8 tools_3.2.3 digest_0.6.9 ## [7] gtable_0.1.2 evaluate_0.8.3 shiny_0.13.2 ## [10] DBI_0.3.1 yaml_2.1.13 parallel_3.2.3 ## [13] stringr_1.0.0 grid_3.2.3 R6_2.1.2 ## [16] rmarkdown_0.9.5.9 bookdown_0.0.62 magrittr_1.5 ## [19] scales_0.3.0 codetools_0.2-14 assertthat_0.1 ## [22] colorspace_1.2-6 mime_0.4 xtable_1.8-2 ## [25] httpuv_1.3.3 stringi_1.0-1 miniUI_0.1.1 ## [28] munsell_0.4.2 chron_2.3-47 "],
["terminology.html", "Chapter 2 Terminology", " Chapter 2 Terminology BTO: Buy to Open (open long positions) BTC: Buy to close (close short positions) SL: Stop-limit order STO: Sell to open (open short positions) STC: Sell to close (close long positions) TS: Trailing-stop order "],
["using-quantstrat.html", "Chapter 3 Using Quantsrat 3.1 Settings and Variables 3.2 Symbols 3.3 checkBlotterUpdate()", " Chapter 3 Using Quantsrat In this book we use the quantstrat library version 0.9.1739. quantstrat provides the base functions we will use to build our strategies; adding indicators, signals and creating the rules of when to buy and when to sell. quantstrat is for signal-based trading strategies, not time-based. However, you can create functions that add signals based on time frames and implement those functions as indicators. We’ll get to that later. quantstrat also allows us to test a strategy on one or many symbols. The downside to using many symbols is that it can be resource-intensive. We can also test strategies with a range of parameters. Say, for example, you want to test a simple SMA strategy but want to find the best-performing SMA parameter; quantstrat allows for this. Again, though, it can be resource-intensive. 3.1 Settings and Variables Settings listed here will be used in all of our backtests. They are required; you will get errors if you run any of the strategies without including the below settings and variables. Some of these may change depending on the strategy which will be noted. First we use Sys.setenv() to set our timezone to UTC. Sys.setenv(TZ = "UTC") Next, since we’ll be working with stocks in the U.S. market we need to set our currency object to USD. currency('USD') ## [1] "USD" When backtesting strategies you should always include periods of market turmoil. After all, you don’t want to just see how your strategy performs when the market is strong but also when it is weak. For this book we’ll use the years 2008 and 2009. init_date: The date we will initialize our account and portfolio objects. This date should be the day prior to start_date. start_date: First date of data to retrieve. end_date: Last date of data to retrieve. init_equity: Initial account equity. adjustment: Boolean - TRUE if we should adjust the prices for dividend payouts, stock splits, etc; otherwise, FALSE. You should always work with adjusted pricing when possible to give you the truest results. init_date <- "2007-12-31" start_date <- "2008-01-01" end_date <- "2009-12-31" init_equity <- 1e4 # $10,000 adjustment <- TRUE 3.2 Symbols Most our strategies will use three ETF’s: IWM, QQQ and SPY. This is only for demonstration purposes. They are loaded into basic_symbols(). basic_symbols <- function() { symbols <- c( "IWM", # iShares Russell 2000 Index ETF "QQQ", # PowerShares QQQ TRust, Series 1 ETF "SPY" # SPDR S&P 500 ETF Trust ) } Where we may want to test strategies on a slightly broader scale we’ll use enhanced_symbols() which adds basic_symbols(), TLT and Sector SPDR ETF’s XLB, XLE, XLF, XLI, XLK, XLP, XLU, XLV, and XLY. enhanced_symbols <- function() { symbols <- c( basic_symbols(), "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF "XLB", # Materials Select Sector SPDR ETF "XLE", # Energy Select Sector SPDR ETF "XLF", # Financial Select Sector SPDR ETF "XLI", # Industrials Select Sector SPDR ETF "XLK", # Technology Select Sector SPDR ETF "XLP", # Consumer Staples Select Sector SPDR ETF "XLU", # Utilities Select Sector SPDR ETF "XLV", # Health Care Select Sector SPDR ETF "XLY" # Consumer Discretionary Select Sector SPDR ETF ) } Lastly, we may use global_symbols() for better insight into a strategy. However, the purposes of this book is to show how to backtest strategies, not to find profitable strategies. global_symbols <- function() { symbols <- c( enhanced_symbols(), "EFA", # iShares EAFE "EPP", # iShares Pacific Ex Japan "EWA", # iShares Australia "EWC", # iShares Canada "EWG", # iShares Germany "EWH", # iShares Hong Kong "EWJ", # iShares Japan "EWS", # iShares Singapore "EWT", # iShares Taiwan "EWU", # iShares UK "EWY", # iShares South Korea "EWZ", # iShares Brazil "EZU", # iShares MSCI EMU ETF "IGE", # iShares North American Natural Resources "IYR", # iShares U.S. Real Estate "IYZ", # iShares U.S. Telecom "LQD", # iShares Investment Grade Corporate Bonds "SHY" # iShares 42372 year TBonds ) } 3.3 checkBlotterUpdate() The checkBlotterUpdate() function comes courtesy of Guy Yollin. The purpose of this function is to check for discrepancies between the account object and portfolio object. If the function returns FALSE we must examine why (perhaps we didn’t clear our objects before running the strategy?). # Guy Yollin, 2014 # http://www.r-programming.org/papers checkBlotterUpdate <- function(port.st = portfolio.st, account.st = account.st, verbose = TRUE) { ok <- TRUE p <- getPortfolio(port.st) a <- getAccount(account.st) syms <- names(p$symbols) port.tot <- sum( sapply( syms, FUN = function(x) eval( parse( text = paste("sum(p$symbols", x, "posPL.USD$Net.Trading.PL)", sep = "$"))))) port.sum.tot <- sum(p$summary$Net.Trading.PL) if(!isTRUE(all.equal(port.tot, port.sum.tot))) { ok <- FALSE if(verbose) print("portfolio P&L doesn't match sum of symbols P&L") } initEq <- as.numeric(first(a$summary$End.Eq)) endEq <- as.numeric(last(a$summary$End.Eq)) if(!isTRUE(all.equal(port.tot, endEq - initEq)) ) { ok <- FALSE if(verbose) print("portfolio P&L doesn't match account P&L") } if(sum(duplicated(index(p$summary)))) { ok <- FALSE if(verbose)print("duplicate timestamps in portfolio summary") } if(sum(duplicated(index(a$summary)))) { ok <- FALSE if(verbose) print("duplicate timestamps in account summary") } return(ok) } http://r-forge.r-project.org/projects/blotter/ http://www.r-programming.org/papers "],
["get-symbols.html", "Chapter 4 Get Symbols 4.1 Yahoo! 4.2 Google 4.3 MySQL 4.4 FRED 4.5 OANDA", " Chapter 4 Get Symbols We start off by loading basic_symbols() into the symbols variable: print(basic_symbols()) ## [1] "IWM" "QQQ" "SPY" symbols <- basic_symbols() getSymbols() is part of the quantmod package which was automatically loaded when we loaded quantstrat. It should have been installed automatically when you install quantstrat. We send a request through getSymbols() to download data. We can use any of the following sources: Yahoo! Google MySQL RData CSV For data other than stocks we can use: FRED (Federal Reserve Economic Data) source; FRED contains data such as unemployment, GDP, treasury rates and more. OANDA is a subscription-service for forex and currency data. 4.1 Yahoo! getSymbols(Symbols = symbols, src = "yahoo", index.class = "POSIXct", from = start_date, to = end_date, adjust = adjustment) ## As of 0.4-0, 'getSymbols' uses env=parent.frame() and ## auto.assign=TRUE by default. ## ## This behavior will be phased out in 0.5-0 when the call will ## default to use auto.assign=FALSE. getOption("getSymbols.env") and ## getOptions("getSymbols.auto.assign") are now checked for alternate defaults ## ## This message is shown once per session and may be disabled by setting ## options("getSymbols.warning4.0"=FALSE). See ?getSymbols for more details. ## [1] "IWM" "QQQ" "SPY" The first thing you notice is the warning message. As it states, it will only appear the first time you run getSymbols() in a new session. Symbols: one or a vector of multiple symbols. src: string, our source. In this case, Yahoo! index.class: POSIXct. This sets the class of our xts object index. from: string, first date of data we want to retrieve end: string, last date of data we want to retrieve adjust: boolean, whether to adjust our data or not. Suggested to set as TRUE. getSymbols will load xts objects for each of the symbols we passed into our Global Environment. We can view the data as we would any other dataset. head(IWM) ## IWM.Open IWM.High IWM.Low IWM.Close IWM.Volume IWM.Adjusted ## 2008-01-02 74.03150 74.37176 72.51490 73.06905 94259600 66.70117 ## 2008-01-03 73.17598 73.51625 71.97048 72.26213 78036800 65.96457 ## 2008-01-04 71.20245 71.28996 69.61780 70.08445 123498600 63.97667 ## 2008-01-07 70.26916 70.82330 69.09283 70.24000 139672900 64.11867 ## 2008-01-08 70.30805 71.23162 68.07203 68.07203 157222700 62.13964 ## 2008-01-09 67.98454 69.02477 66.71099 68.76229 140191600 62.76974 tail(IWM) ## IWM.Open IWM.High IWM.Low IWM.Close IWM.Volume IWM.Adjusted ## 2009-12-23 62.70 63.16 62.28 63.12 49503600 57.61916 ## 2009-12-24 63.29 63.39 63.13 63.36 16521900 57.83825 ## 2009-12-28 63.59 63.61 62.97 63.26 35784500 57.74696 ## 2009-12-29 63.42 63.53 63.18 63.23 24085300 57.71958 ## 2009-12-30 63.04 63.49 62.65 63.19 45268900 57.68306 ## 2009-12-31 63.23 63.57 62.26 62.44 34407900 56.99842 summary(IWM) ## Index IWM.Open IWM.High ## Min. :2008-01-02 00:00:00 Min. :34.28 Min. :35.17 ## 1st Qu.:2008-07-02 00:00:00 1st Qu.:48.31 1st Qu.:49.42 ## Median :2008-12-31 00:00:00 Median :58.88 Median :59.42 ## Mean :2008-12-31 20:46:05 Mean :57.83 Mean :58.65 ## 3rd Qu.:2009-07-02 00:00:00 3rd Qu.:68.03 3rd Qu.:69.02 ## Max. :2009-12-31 00:00:00 Max. :77.14 Max. :84.11 ## IWM.Low IWM.Close IWM.Volume IWM.Adjusted ## Min. :33.81 Min. :33.94 Min. : 50600 Min. :30.98 ## 1st Qu.:47.48 1st Qu.:48.44 1st Qu.: 51197500 1st Qu.:44.22 ## Median :58.13 Median :58.72 Median : 69790800 Median :53.60 ## Mean :56.94 Mean :57.81 Mean : 77115884 Mean :52.77 ## 3rd Qu.:67.24 3rd Qu.:68.14 3rd Qu.: 96282200 3rd Qu.:62.21 ## Max. :72.80 Max. :74.20 Max. :355117000 Max. :67.73 # Clear symbols rm(list=basic_symbols()) 4.2 Google getSymbols(Symbols = symbols, src = "google", index.class = "POSIXct", from = start_date, to = end_date, adjust = adjustment) ## [1] "IWM" "QQQ" "SPY" We access data from Google same as Yahoo! only changing the src parameter. head(IWM) ## IWM.Open IWM.High IWM.Low IWM.Close IWM.Volume ## 2008-01-02 76.15 76.50 74.59 75.16 90414700 ## 2008-01-03 75.27 75.62 74.03 74.33 72688600 ## 2008-01-04 73.24 73.33 71.61 72.09 6932200 ## 2008-01-07 72.28 72.85 71.07 72.25 36734900 ## 2008-01-08 72.32 73.27 70.02 70.02 47046500 ## 2008-01-09 69.93 71.00 68.62 70.73 23320600 tail(IWM) ## IWM.Open IWM.High IWM.Low IWM.Close IWM.Volume ## 2009-12-23 62.70 63.16 62.28 63.12 49507902 ## 2009-12-24 63.29 63.39 63.13 63.36 16522449 ## 2009-12-28 63.59 63.61 62.97 63.26 35791114 ## 2009-12-29 63.42 63.53 63.18 63.23 24085986 ## 2009-12-30 63.04 63.49 62.65 63.19 45268923 ## 2009-12-31 NA NA NA 62.44 0 summary(IWM) ## Index IWM.Open IWM.High IWM.Low ## Min. :2008-01-02 Min. :34.73 Min. : 35.64 Min. :34.26 ## 1st Qu.:2008-07-01 1st Qu.:48.98 1st Qu.: 49.92 1st Qu.:48.10 ## Median :2008-12-30 Median :59.12 Median : 59.73 Median :58.37 ## Mean :2008-12-31 Mean :58.83 Mean : 59.81 Mean :57.92 ## 3rd Qu.:2009-07-01 3rd Qu.:69.92 3rd Qu.: 70.90 3rd Qu.:69.05 ## Max. :2009-12-31 Max. :78.92 Max. :159.46 Max. :74.59 ## NA's :1 NA's :1 NA's :1 ## IWM.Close IWM.Volume ## Min. :34.39 Min. : 0 ## 1st Qu.:49.06 1st Qu.: 40761287 ## Median :59.02 Median : 57575055 ## Mean :58.81 Mean : 57593828 ## 3rd Qu.:69.96 3rd Qu.: 74991155 ## Max. :76.20 Max. :160640196 ## # Clear symbols rm(list=basic_symbols()) 4.3 MySQL getSymbols(Symbols = symbols, src = "MySQL", dbname = db, user = user, password = pw, host = host, index.class = "POSIXct", from = start_date, to = end_date, adjust = adjustment) ## [1] "IWM" "QQQ" "SPY" To load data via MySQL database we need to supply additional parameters: dbname, user, password and host. user needs to have SELECT privileges but nothing more for our purposes. getSymbols() does not currently permit passing database values via a my.cnf file. The parameters are checked early in the function so either you need to include them in the call or include them in a hidden R file outside of your project directory and source the file. Your database should contain a table named by symbol, one for each symbol. Each table should have the following fields: date, o, h, l, c, v and a. If your table has different field names you must add them to the db.fields parameter as a character vector. You can also change the names of the fields as they are imported into R by using the field.names parameter. However, none of that is required as long as db.fields meets the default criteria. The data in my database is a duplicate of Yahoo!. The output below is to show the success of the getSymbols(src="MySQL") call. head(IWM) ## IWM.Open IWM.High IWM.Low IWM.Close IWM.Volume IWM.Adjusted ## 2008-01-02 74.03150 74.37176 72.51490 73.06905 94259600 66.70117 ## 2008-01-03 73.17598 73.51625 71.97048 72.26213 78036800 65.96457 ## 2008-01-04 71.20245 71.28996 69.61780 70.08445 123498600 63.97667 ## 2008-01-07 70.26916 70.82330 69.09283 70.24000 139672900 64.11867 ## 2008-01-08 70.30805 71.23162 68.07203 68.07203 157222700 62.13964 ## 2008-01-09 67.98454 69.02477 66.71099 68.76229 140191600 62.76974 tail(IWM) ## IWM.Open IWM.High IWM.Low IWM.Close IWM.Volume IWM.Adjusted ## 2009-12-23 62.70 63.16 62.28 63.12 49503600 57.61916 ## 2009-12-24 63.29 63.39 63.13 63.36 16521900 57.83825 ## 2009-12-28 63.59 63.61 62.97 63.26 35784500 57.74696 ## 2009-12-29 63.42 63.53 63.18 63.23 24085300 57.71958 ## 2009-12-30 63.04 63.49 62.65 63.19 45268900 57.68306 ## 2009-12-31 63.23 63.57 62.26 62.44 34407900 56.99842 summary(IWM) ## Index IWM.Open IWM.High IWM.Low ## Min. :2008-01-02 Min. :34.28 Min. :35.17 Min. :33.81 ## 1st Qu.:2008-07-02 1st Qu.:48.31 1st Qu.:49.42 1st Qu.:47.48 ## Median :2008-12-31 Median :58.88 Median :59.42 Median :58.13 ## Mean :2008-12-31 Mean :57.83 Mean :58.65 Mean :56.94 ## 3rd Qu.:2009-07-02 3rd Qu.:68.03 3rd Qu.:69.02 3rd Qu.:67.24 ## Max. :2009-12-31 Max. :77.14 Max. :84.11 Max. :72.80 ## IWM.Close IWM.Volume IWM.Adjusted ## Min. :33.94 Min. : 50600 Min. :30.98 ## 1st Qu.:48.44 1st Qu.: 51197500 1st Qu.:44.22 ## Median :58.72 Median : 69790800 Median :53.60 ## Mean :57.81 Mean : 77115884 Mean :52.77 ## 3rd Qu.:68.14 3rd Qu.: 96282200 3rd Qu.:62.21 ## Max. :74.20 Max. :355117000 Max. :67.73 # Clear symbols rm(list=basic_symbols()) 4.4 FRED For this example we’ll bail on the symbols we have been using prior. FRED contains over 11,000 econonomic datasets at no cost. Let’s look at the 10-Year Treasury Rate: https://research.stlouisfed.org/fred2/series/DGS10 Notice the tail of the url has the alphanumeric string DGS10 which we will be using in our getSymbols() call. getSymbols(Symbols = "DGS10", src = "FRED") ## [1] "DGS10" Notice our call is shorter than previous; we do not need to classify index nor do we need to adjust any data. In addition, passing data parameters doesn’t seem to do anything; the entire dataset is returned. I’ll use quantmod::chartSeries() to plot the data: chartSeries(DGS10) rm(DGS10) 4.5 OANDA Contributions needed. "],
["basic-strategy.html", "Chapter 5 Basic Strategy 5.1 Strategy Setup 5.2 Add Indicators 5.3 Add Signals 5.4 Add Rules 5.5 Apply Strategy", " Chapter 5 Basic Strategy Let’s kick things off with a variation of the Luxor trading strategy. This strategy uses two SMA indicators: SMA(10) and SMA(30). If the SMA(10) indicator is greater than or equal to the SMA(30) indicator we will submit a stoplimit long order to open and close any short positions that may be open. If the SMA(10) is less than the SMA(30) we will submit a stoplimit short order to open and close any open long positions. If SMA(10) >= SMA(30): BTC short, BTO long Else if SMA(10) < SMA(30): STC long, STO short Note: Remember we have already set some variables earlier in the book. If you copy and paste the code below by itself you will get errors. There will be complete tutorials listed later in the book. 5.1 Strategy Setup We load our symbols into symbols. symbols <- basic_symbols() getSymbols(Symbols = symbols, src = "yahoo", index.class = "POSIXct", from = start_date, to = end_date, adjust = adjustment) ## [1] "IWM" "QQQ" "SPY" After we’ve loaded our symbols we use FinancialInstrument::stock() to define the meta-data for our symbols. In this case we’re defining the currency in USD (US Dollars) with a multiplier of 1. Multiplier is applied to price. This will vary depending on the financial instrument you are working on but for stocks it should always be 1. stock(symbols, currency = "USD", multiplier = 1) ## [1] "IWM" "QQQ" "SPY" Next we’ll assign proper names for our portfolio, account and strategy objects. These can be any name you want and should be based on how you intend to log the data later on. portfolio.st <- "Port.Luxor" account.st <- "Acct.Luxor" strategy.st <- "Strat.Luxor" We remove any residuals from previous runs by clearing out the portfolio and account values. At this point for what we have done so far this is unnecessary. However, it’s a good habit to include this with all of your scripts as data stored in memory can affect results or generate errors. rm.strat(portfolio.st) rm.strat(account.st) Now we initialize our portfolio, account and orders. We will also store our strategy to save for later. initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) ## [1] "Port.Luxor" initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, initEq = init_equity) ## [1] "Acct.Luxor" initOrders(portfolio = portfolio.st, symbols = symbols, initDate = init_date) strategy(strategy.st, store = TRUE) 5.2 Add Indicators Indicators are functions used to measure a variable. A SMA is just an average of the previous n prices; typically closing price. So SMA(10) is just an average of the last 10 closing prices. This is where the TTR library comes in; short for Technical Trading Rules. SMA() is a function of TTR as are many other indicators. If you want MACD, RSI, Bollinger Bands, etc., you will use the TTR library. add.indicator(strategy = strategy.st, name = "SMA", arguments = list(x = quote(Cl(mktdata)), n = 10), label = "nFast") ## [1] "Strat.Luxor" add.indicator(strategy = strategy.st, name = "SMA", arguments = list(x = quote(Cl(mktdata)), n = 30), label = "nSlow") ## [1] "Strat.Luxor" add.indicator is a function of quantstrat and adds our indicators to our strategy object. For now we’ll use the following parameters: strategy: As we stored our strategy name in the strategy.st variable all we need to do is pass that variable. Otherwise we would provide a string. Use variables when this will become redundant as we move along. name: Indicator function; for this example SMA. We only pass the name of the function as a character string. Parameters for the function are passed into the arguments parameter… arguments: If we look at ?SMA we see required parameters are x and n with the default n being 10. x is the price object. In our example we are using closing prices. label: Label of the variable that will be added to our dataset. This must be unique for each indicator we add. Let’s pause for a moment and examine arguments. Notice we’re passing a series of functions to x. If you wanted to access the Close variable of the IWM dataset you would normally do so by calling IWM$Close or IWM[,4]. Here we’re accessing a mktdata data object mktdata is a special dataset created for each symbol that will store all of our indicators and signals. When the strategy is ran you will see the mktdata object in your environment. It will only exist for the last symbol the strategy executed. The add.indicator() function (along with add.signal and add.rules which we’ll discuss momentarily) is not evaluated until we run our strategy. All it does is add our specs to the strategy object. When we run our strategy the mktdata object is created for each symbol iteration where our data will be added. Cl is actually short-hand for Close as you may have guessed. In fact, we have several short-hand functions for our variables: Op(): Open Hi(): High Lo(): Low Cl(): Close Vo(): Volume Ad(): Adjusted OpCl(): Open and Close (n x 2 dataset) HLC(): High, Low and Close (n x 3 dataset) See the help for any of those symbols above for a more detailed listing. quote() is a R function that simply wraps the supplied parameter in quotes. So we’ve added two indicators to our mktdata object, nFast (SMA(10)) and nSlow (SMA(30)). Let’s now add signals. 5.3 Add Signals Signals are a value given when conditions are met by our indicators. For example, in this strategy we want a signal whenever nFast is greater than or equal to nSlow. We also want another signal where nFast is less than nSlow. We’ll name these signals long and short, respectively. add.signal(strategy = strategy.st, name="sigCrossover", arguments = list(columns = c("nFast", "nSlow"), relationship = "gte"), label = "long") ## [1] "Strat.Luxor" add.signal(strategy = strategy.st, name="sigCrossover", arguments = list(columns = c("nFast", "nSlow"), relationship = "lt"), label = "short") ## [1] "Strat.Luxor" Again, we’re passing strategy.st to the strategy parameter. name takes a function just as it did in add.indicator. Here we’ll use some built-in quantstrat functions. Let’s take a quick look at what’s available: sigComparison: boolean, compare two variables by relationship gt greater than lt less than eq equal to gte greater than or equal to lte less than or equal to sigCrossover: boolean, TRUE when one signal crosses another. Uses the same relationships as sigComparison sigFormula: apply a formula to multiple variables. sigPeak: identify local minima or maxima of an indicator sigThreshold: boolean, when an indicator crosses a value. Uses relationships as identified above. sigTimestamp: generates a signal based on a timestamp. We’ll attempt to use each of these signals throughout the book when possible. 5.4 Add Rules We’ve now constructed our nFast and nSlow indicators and generated signals based on those indicators. Now we have to add rules for those signals. add.rules will determine the positions we take depending on our signals, what type of order we’ll place and how many shares we will buy. Whenever our long variable (sigcol) is TRUE (sigval) we want to place a stoplimit order (ordertype). Our preference is at the High (prefer) plus threshold. We want to buy 100 shares (orderqty). A new variable EnterLONG will be added to mktdata. When we enter (type) a position EnterLONG will be TRUE, otherwise FALSE. This order will not replace any other open orders. add.rule(strategy = strategy.st, name = "ruleSignal", arguments = list(sigcol = "long", sigval = TRUE, orderqty = 100, ordertype = "stoplimit", orderside = "long", threshold = 0.0005, prefer = "High", TxnFees = -10, replace = FALSE), type = "enter", label = "EnterLONG") ## [1] "Strat.Luxor" If our short variable (sigcol) is TRUE (sigval) we will place another stoplimit order (ordertype) with a preference on the Low (prefer). We will sell 100 shares (orderqty). This order will not replace any open orders (replace). add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, orderqty = -100, ordertype = "stoplimit", threshold = -0.005, orderside = "short", replace = FALSE, TxnFees = -10, prefer = "Low"), type = "enter", label = "EnterSHORT") ## [1] "Strat.Luxor" We now have rules set up to enter positions based on our signals. However, we do not have rules to exit open positions. We’ll create those now. Our next rule, Exit2SHORT, is a simple market order to exit (type) when short is TRUE (sigcol, sigval). This closes out all long positions (orderside, orderqty). This order will replace (replace) any open orders. add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, orderside = "long", ordertype = "market", orderqty = "all", TxnFees = -10, replace = TRUE), type = "exit", label = "Exit2SHORT") ## [1] "Strat.Luxor" Lastly, we close out any short positions (orderside) when long is TRUE (sigcol, sigval). We will exit (type) at market price (ordertype) all open positions (orderqty). This order will replace any open orders we have (replace). add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "long", sigval = TRUE, orderside = "short", ordertype = "market", orderqty = "all", TxnFees = -10, replace = TRUE), type = "exit", label = "Exit2LONG") ## [1] "Strat.Luxor" TxnFees are transaction fees associated with an order. This can be any value you choose but should accurately reflect the fees charged by your selected broker. In addition, we only show them here on exits. Some brokers charge fees on entry positions as well. TxnFees can be added to any rule set. If you’re not sure what fees your selected broker charges - what’s wrong with you? Go find out now. Some retail brokers (TD Ameritrade, ETrade) will charge under $10 per position on unlimited shares; some such as Interactive Brokers or TradeStation will charge even less depending on the number of shares. $10 is a good starting point. 5.5 Apply Strategy Now we get to the fun part! Do or die. Here we’ll find out if we built our strategy correctly or if we have any errors in our code. Cross your fingers. Let’s go! cwd <- getwd() setwd("./_data/") results_file <- paste("results", strategy.st, "RData", sep = ".") if( file.exists(results_file) ) { load(results_file) } else { results <- applyStrategy(strategy.st, portfolios = portfolio.st) updatePortf(portfolio.st) updateAcct(account.st) updateEndEq(account.st) if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { save(list = "results", file = results_file) save.strategy(strategy.st) } } setwd(cwd) Awesome! We know that at least our code is good. applyStrategy() is the function we will run when we have a straight strategy. What I mean by that is a strategy that doesn’t test different parameters. We’ll get to that type of testing later. You can see it’s a pretty simple call; we just pass our strategy.st variable as the first parameter and our portfolio as the second parameter. There is no need to get into additional parameters at the moment. We won’t show the results of any more applyStrategy runs to save space. Just know that if you get trade output you should be good. Next we update our portfolio and account objects. We do this with the updatePortf(), updateAcct() and updateEndEq() functions. updatePortf calculates the P&L for each symbol in symbols. updateAcct calculcates the equity from the portfolio data. And updateEndEq updates the ending equity for the account. They must be called in order. We also use the checkBlotterUpdate() mentioned in 3.3. We’re looking for a TRUE value to be returned. Anything FALSE will need to be researched. (If you forgot to clear our your portfolio or strategy with the rm.strat() call mentioned earlier this can result in a FALSE value). If checkBlotterUpdate returns true we save the results and our strategy (save.strategy) as a RData file into our _data directory. We’ll use them for analysis later. "],
["data-quality.html", "Chapter 6 Data Quality 6.1 Yahoo! vs. Google 6.2 Examining Trades", " Chapter 6 Data Quality Before doing any analysis you must always check the data to ensure quality. Do not assume that because you are getting it from a source such as Yahoo! or Google that it is clean. I’ll show you why. 6.1 Yahoo! vs. Google I’ll use dplyr 0.4.3, ggplot2 2.0.0 and tidyr 0.4.1 to help with analysis. getSymbols("SPY", src = "yahoo", index.class = c("POSIXt", "POSIXct"), from = "2010-01-01", to = "2011-01-01", adjust = TRUE) ## [1] "SPY" yahoo.SPY <- SPY summary(yahoo.SPY) ## Index SPY.Open SPY.High ## Min. :2010-01-04 00:00:00 Min. :102.0 Min. :102.3 ## 1st Qu.:2010-04-05 18:00:00 1st Qu.:108.0 1st Qu.:108.9 ## Median :2010-07-04 00:00:00 Median :112.0 Median :112.7 ## Mean :2010-07-03 16:28:34 Mean :112.8 Mean :113.5 ## 3rd Qu.:2010-10-01 18:00:00 3rd Qu.:117.4 3rd Qu.:118.1 ## Max. :2010-12-31 00:00:00 Max. :126.0 Max. :126.2 ## SPY.Low SPY.Close SPY.Volume SPY.Adjusted ## Min. :100.1 Min. :101.1 Min. : 55309100 Min. : 90.81 ## 1st Qu.:107.3 1st Qu.:108.2 1st Qu.:156667725 1st Qu.: 97.19 ## Median :111.2 Median :112.0 Median :192116250 Median :100.59 ## Mean :112.0 Mean :112.9 Mean :209692210 Mean :101.36 ## 3rd Qu.:116.7 3rd Qu.:117.5 3rd Qu.:240310650 3rd Qu.:105.56 ## Max. :125.9 Max. :125.9 Max. :647356600 Max. :113.07 Above is a summary for the SPY data we received from Yahoo!. Examining each of the variables does not show anything out of the ordinary. rm(SPY) getSymbols("SPY", src = "google", index.class = c("POSIXt", "POSIXct"), from = "2010-01-01", to = "2011-01-01", adjust = TRUE) ## [1] "SPY" google.SPY <- SPY summary(google.SPY) ## Index SPY.Open SPY.High SPY.Low ## Min. :2010-01-04 Min. :103.1 Min. :103.5 Min. :101.1 ## 1st Qu.:2010-04-05 1st Qu.:109.4 1st Qu.:110.4 1st Qu.:108.9 ## Median :2010-07-04 Median :113.9 Median :114.5 Median :113.1 ## Mean :2010-07-03 Mean :114.2 Mean :114.9 Mean :113.4 ## 3rd Qu.:2010-10-01 3rd Qu.:118.4 3rd Qu.:119.0 3rd Qu.:117.8 ## Max. :2010-12-31 Max. :126.0 Max. :126.2 Max. :125.9 ## NA's :8 NA's :8 NA's :8 ## SPY.Close SPY.Volume ## Min. :102.2 Min. : 0 ## 1st Qu.:109.7 1st Qu.:151055510 ## Median :113.8 Median :188064051 ## Mean :114.2 Mean :203756549 ## 3rd Qu.:118.5 3rd Qu.:239588977 ## Max. :125.9 Max. :647356524 ## Now we have a dataset from Google. it’s for the same symbol and same time frame. But now we have NA values - 8, in fact. In addition, our percentiles do not match up for any of the variables (with the exception of Date). bind_rows(as.data.frame(yahoo.SPY) %>% mutate(Src = "Yahoo"), as.data.frame(google.SPY) %>% mutate(Src = "Google")) %>% gather(key, value, 1:4, na.rm = TRUE) %>% ggplot(aes(x = key, y = value, fill = Src)) + geom_boxplot() + theme_bw() + theme(legend.title = element_blank(), legend.position = "bottom") + ggtitle("Google vs. Yahoo! (non-NA)") We can see above clearly we have a mismatch of data between Google and Yahoo!. For one reason, Google does not supply a full day of data for holidays and early sessions. Let’s look at the NA values: as.data.frame(google.SPY) %>% mutate(Date = index(google.SPY)) %>% select(Date, starts_with("SPY"), -SPY.Volume) %>% filter(is.na(SPY.Open)) ## Date SPY.Open SPY.High SPY.Low SPY.Close ## 1 2010-01-15 NA NA NA 113.64 ## 2 2010-02-12 NA NA NA 108.04 ## 3 2010-04-01 NA NA NA 117.80 ## 4 2010-05-28 NA NA NA 109.37 ## 5 2010-07-02 NA NA NA 102.20 ## 6 2010-09-03 NA NA NA 110.89 ## 7 2010-11-24 NA NA NA 120.20 ## 8 2010-12-23 NA NA NA 125.60 We can see many of these dates correspond closely to national holidays; 2010-11-24 would be Thanksgiving, 2010-12-23 would be Christimas. So where Yahoo! does give OHLC values for these dates, Google just provides the Close. This won’t affect most indicators that typically use closing data (moving averages, Bollinger Bands, etc.). However, if you are working on a strategy that triggers a day prior to one of these holidays, and you issue a buy order for the next morning, this may cause some integrity loss. This doesn’t mean you should only use Yahoo!. At this point we don’t know the quality of Yahoo!’s data - we only know it seems complete. And this may be enough depending on what you want to do. However, it’s up to you to ensure your data is top quality. Garbage in, garbage out 6.2 Examining Trades It’s not just data that we want to QA against but also our trades. After all, how disappointing would it be to think you have a winning strategy only to learn you were buying on tomorrow’s close instead of today (look-ahead bias). Or that you wrote your rules incorrectly? Every backtest must be picked apart from beginning to end. Checking our data was the first step. Checking our trades is next. We’ll reload our Luxor strategy and examine some of the trades for SPY. rm.strat(portfolio.st) rm.strat(account.st) symbols <- basic_symbols() getSymbols(Symbols = symbols, src = "yahoo", index.class = "POSIXct", from = start_date, to = end_date, adjust = adjustment) initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, initEq = init_equity) initOrders(portfolio = portfolio.st, symbols = symbols, initDate = init_date) applyStrategy(strategy.st, portfolios = portfolio.st) checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) updatePortf(portfolio.st) updateAcct(account.st) updateEndEq(account.st) chart.Posn(portfolio.st, Symbol = "SPY", Dates="2008-01-01::2008-07-01", TA="add_SMA(n = 10, col = 2); add_SMA(n = 30, col = 4)") Figure 6.1: SPY Trades for Jan 1, 2008 to July 1, 2008 Our strategy called for a long entry when SMA(10) was greater than or equal to SMA(30). It seems we got a cross on February 25 but the trade didn’t trigger until two days later. Let’s take a look. le <- as.data.frame(mktdata["2008-02-25::2008-03-07", c(1:4, 7:10)]) DT::datatable(le, rownames = TRUE, extensions = c("Scroller", "FixedColumns"), options = list(pageLength = 5, autoWidth = TRUE, deferRender = TRUE, scrollX = 200, scroller = TRUE, fixedColumns = TRUE), caption = htmltools::tags$caption( "Table 6.1: mktdata object for Feb. 25, 2008 to Mar. 7, 2008")) {\"x\":{\"data\":[[\"2008-02-25\",\"2008-02-26\",\"2008-02-27\",\"2008-02-28\",\"2008-02-29\",\"2008-03-03\",\"2008-03-04\",\"2008-03-05\",\"2008-03-06\",\"2008-03-07\"],[129.359620662662,130.514453587282,131.287517180531,130.982115267941,129.41689718115,127.069061938547,126.20055338785,127.336293503373,126.91635477864,123.852732461276],[131.373409529819,132.614134803726,132.795473064863,131.669286511902,129.493236922276,127.708510226584,127.317201648678,128.624741733205,127.145415995702,125.732905068818],[128.63428097974,130.275853123686,131.144362628784,130.323576079611,126.725477270968,126.210105996011,125.017103678028,126.305537591833,124.597164953295,122.716992345753],[131.06800857163,132.051041527246,131.917425267632,130.628977037799,127.718062834744,127.412647560528,126.9259073868,127.727602081279,125.083905127022,123.795471213218],[129.283274145282,129.723253495594,130.0315249128,130.07924500552,129.950401614139,129.79387980546,129.552416231741,129.352946625935,128.99695521145,128.43290486079],[129.298863504239,129.241917749621,129.1445688241,129.10321131497,129.002681336255,129.004908496609,129.034495176789,129.133434867507,129.044357265658,128.876382602913],[null,1,null,null,null,null,null,null,null,null],[null,null,null,null,null,null,null,null,1,null]],\"container\":\"\\n \\n \\n \\u003c/th>\\n SPY.Open\\u003c/th>\\n SPY.High\\u003c/th>\\n SPY.Low\\u003c/th>\\n SPY.Close\\u003c/th>\\n SMA.nFast\\u003c/th>\\n SMA.nSlow\\u003c/th>\\n long\\u003c/th>\\n short\\u003c/th>\\n \\u003c/tr>\\n \\u003c/thead>\\n\\u003c/table>\",\"options\":{\"pageLength\":5,\"autoWidth\":true,\"deferRender\":true,\"scrollX\":200,\"scroller\":true,\"fixedColumns\":true,\"columnDefs\":[{\"className\":\"dt-right\",\"targets\":[1,2,3,4,5,6,7,8]},{\"orderable\":false,\"targets\":0}],\"order\":[],\"orderClasses\":false,\"lengthMenu\":[5,10,25,50,100]},\"callback\":null,\"caption\":\"Table 6.1: mktdata object for Feb. 25, 2008 to Mar. 7, 2008\\u003c/caption>\",\"filter\":\"none\",\"extensions\":[\"Scroller\",\"FixedColumns\"],\"extOptions\":{\"FixedColumns\":null},\"selection\":\"multiple\"},\"evals\":[],\"jsHooks\":[]} The 2008-02-25T00:00:00Z bar shows nFast just fractions of a penny lower than nSlow. We get the cross on 2008-02-26T00:00:00Z which gives a TRUE long signal. Our high on that bar is $132.61 which would be our stoplimit. On the 2008-02-27T00:00:00Z bar we get a higher high which means our stoplimit order gets filled at $132.61. This is reflected by the faint green arrow at the top of the candles upper shadow. ob <- as.data.table(getOrderBook(portfolio.st)$Quantstrat$SPY) DT::datatable(ob, rownames = FALSE, filter = "top", extensions = c("Scroller", "FixedColumns"), options = list(pageLength = 5, autoWidth = TRUE, deferRender = TRUE, scrollX = 200, scroller = TRUE, fixedColumns = TRUE), caption = htmltools::tags$caption( "Table 6.2: Order book for SPY")) ## Warning in min(d, na.rm = TRUE): no non-missing arguments to min; returning ## Inf ## Warning in max(d, na.rm = TRUE): no non-missing arguments to max; returning ## -Inf {\"x\":null,\"evals\":[],\"jsHooks\":[]} When we look at the order book (Table 6.2) we get confirmation of our order. index reflects the date the order was submitted. Order.StatusTime reflects when the order was filled. (Regarding the time stamp, ignore it. No time was provided so by default it falls to midnight Zulu time which is four to five hours ahead of EST/EDT (depending on time of year) which technically would be the previous day. To avoid confusion, just note the dates.) If we look at Rule we see the value of EnterLONG. These are the labels of the rules we set up in our strategy. Now you can see how all these labels we assigned earlier start coming together. On 2008-03-06T00:00:00Z we get a market order to vacate all long positions and take a short positions. We see this charted in Fig. 6.1 identified with a red arrow on the same candle one bar after the cross. We stay in that position until 2008-04-01T00:00:00Z when we flip back long. If you flip to page 5 of Table 6.2, on 2009-11-03T00:00:00Z you will see we had an order replaced (Order.Status). Let’s plot this time frame and see what was going on. chart.Posn(portfolio.st, Symbol = "SPY", Dates="2009-08-01::2009-12-31", TA="add_SMA(n = 10, col = 2); add_SMA(n = 30, col = 4)") Figure 6.2: SPY Trades for Jan 1, 2008 to July 1, 2008 We got a bearish SMA cross on November 2 which submitted the short order. However, our stoplimit was with a preference of the Low and a threshold of $0.0005 or $102.98. So the order would only fill if we broke below that price. As you see, that never happened. The order stayed open until we got the bullish SMA cross on Nov. 11. At that point our short order was replaced with our long order to buy; a stoplimit at $109.50. Nov. 12 saw an inside day; the high wasn’t breached therefore the order wasn’t filled. However, on Nov. 13 we rallied past the high triggering the long order (green arrow). This is the last position taken in our order book. So it seems the orders are triggering as expected. On a side note, when I was originally writing the code I realized my short order was for +100 shares instead of -100 shares; actually, orderqty = 100 which meant I wasn’t really taking short positions. This is why you really need to examine your strategies as soon as you create them. Before noticing the error the profit to drawdown ratio was poor. After correcting the error, it was worse. It only takes a minor barely recognizable typo to ruin results. Finally, we’ll get to the chart.Posn() function later in the analysis chapters. For now I want to point out one flaw (in my opinion) with the function. You may have noticed our indicators and positions didn’t show up immediately on the chart. Our indicators didn’t appear until the 10-bar and 30-bar periods had passed. And our positions didn’t show up until a new trade was made. You may also notice our CumPL and Drawdown graphs started at 0 on the last chart posted. chart.Posn() doesn’t “zoom in” as you may think. Rather it just operates on a subset of data when using the Dates parameter. Effectively, it’s adjusting your strategy to the Dates parameter that is passed. chart.Posn(portfolio.st, Symbol = "SPY", TA="add_SMA(n = 10, col = 2); add_SMA(n = 30, col = 4)") Figure 6.3: SPY Trades for Jan 1, 2008 to July 1, 2008 Also, note the CumPL value of $2251.20741 and Drawdown value of -$1231.29476 are the final values. It does not show max profit or max drawdown. Notice the values are different from figure 6.3 and figure 6.2. Going by that alone it may seem the strategy overall is profitable. But when you realize max drawdown was near -$3,000 and max profit was up to $3,000, it doesn’t seem so promising. Again, we’ll get into all of this later. Just something to keep in mind when you start doing analysis. "],
["parameter-optimization.html", "Chapter 7 Parameter Optimization 7.1 Add Distribution 7.2 Add Distribution Constraint 7.3 Running Parallel 7.4 Apply Paramset", " Chapter 7 Parameter Optimization One of the important aspects of backtesting is being able to test out various parameters. After all, what if you’re Luxor strategy doesn’t do well with 10/30 SMA indicators but does spectacular with 17/28 SMA indicators? quantstrat helps us do this by adding distributions to our parameters. We can create a range of SMA values for our nFast and nSlow variables. We then examine the results to find the combination that gives us the best results. We’ll assign the range for each of our SMA’s to two new variables: .fastSMA and .slowSMA. Both are just simple integer vectors. You can make them as narrow or wide as you want. However, this is an intensive process. The wider your parameters, the longer it will run. I’ll address this later. We also introduce .nsamples. .nsamples will be our value for the input parameter nsamples in apply.paramset(). This tells quantstrat that of the x-number of combinations of .fastSMA and .slowSMA to test to only take a random sample of those combinations. By default apply.paramset(nsamples = 0) which means all combinations will be tested. This can be fine provided you have the computational resources to do so - it can be a very intensive process (we’ll deal with resources later). .fastSMA <- (1:30) .slowSMA <- (20:80) .nsamples <- 5 portfolio.st <- "Port.Luxor.MA.Opt" account.st <- "Acct.Luxor.MA.Opt" strategy.st <- "Strat.Luxor.MA.Opt" rm.strat(portfolio.st) rm.strat(account.st) initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) ## [1] "Port.Luxor.MA.Opt" initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, initEq = init_equity) ## [1] "Acct.Luxor.MA.Opt" initOrders(portfolio = portfolio.st, symbols = symbols, initDate = init_date) strategy(strategy.st, store = TRUE) Next we’ll go through and re-initiate our portfolio and account objects as we did prior. rm.strat(portfolio.st) rm.strat(account.st) initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) ## [1] "Port.Luxor.MA.Opt" initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date) ## [1] "Acct.Luxor.MA.Opt" initOrders(portfolio = portfolio.st, initDate = init_date) 7.1 Add Distribution We already saved our indicators, signals and rules - strategy.st - and loaded the strategy; we do not need to rewrite that code. We use add.distribution to distribute our range of values across the two indicators. Again, our first parameter the name of our strategy strategy.st. paramset.label: name of the function to which the parameter range will be applied; in this case TTR:SMA(). component.type: indicator component.label: label of the indicator when we added it (nFast and nSlow) variable: the parameter of SMA() to which our integer vectors (.fastSMA and .slowSMA) will be applied; n. label: unique identifier for the distribution. This ties our distribution parameters to our indicators. When we run the strategy, each possible value for .fastSMA will be applied to nFAST and .slowSMA to nSLOW. add.distribution(strategy.st, paramset.label = "SMA", component.type = "indicator", component.label = "nFast", variable = list(n = .fastSMA), label = "nFAST") ## [1] "Strat.Luxor.MA.Opt" add.distribution(strategy.st, paramset.label = "SMA", component.type = "indicator", component.label = "nSlow", variable = list(n = .slowSMA), label = "nSLOW") ## [1] "Strat.Luxor.MA.Opt" 7.2 Add Distribution Constraint What we do not want is to abandon our initial rules which were to buy only when SMA(10) was greater than or equal to SMA(30), otherwise short. In other words, go long when our faster SMA is greater than or equal to our slower SMA and go short when the faster SMA was less than the slower SMA. We keep this in check by using add.distribution.constraint. We pass in the paramset.label as we did in add.distribution. We assign nFAST to distribution.label.1 and nSLOW to distribution.label.2. Our operator will be one of c("<", ">", "<=", ">=", "="). Here, we’re issuing a constraint to always keep nFAST less than nSLOW. We’ll name this constraint SMA.Con by applying it to the label parameter. add.distribution.constraint(strategy.st, paramset.label = "SMA", distribution.label.1 = "nFAST", distribution.label.2 = "nSLOW", operator = "<", label = "SMA.Constraint") ## [1] "Strat.Luxor.MA.Opt" 7.3 Running Parallel quantstrat includes the foreach library for purposes such as this. foreach allows us to execute our strategy in parallel on multicore processors which can save some time. On my current setup it is using one virtual core which doesn’t help much for large tasks such as this. However, if you are running on a system with more than one core processor you can use the follinwg if/else statement courtesy of Guy Yollin. It requires the parallel library and doParallel for Windows users and doMC for non-Windows users. library(parallel) if( Sys.info()['sysname'] == "Windows") { library(doParallel) registerDoParallel(cores=detectCores()) } else { library(doMC) registerDoMC(cores=detectCores()) } ## Loading required package: iterators 7.4 Apply Paramset When we ran our original strategy we used applyStrategy(). When running distributions we use apply.paramset(). For our current strategy we only need to pass in our portfolio and account objects. I’ve also used an if/else statement to avoid running this strategy repeatedly when making updates to the book which, again, is time-consuming. The results are saved to a RData file we’ll analyze later. cwd <- getwd() setwd("./_data/") results_file <- paste("results", strategy.st, "RData", sep = ".") if( file.exists(results_file) ) { load(results_file) } else { results <- apply.paramset(strategy.st, paramset.label = "SMA", portfolio.st = portfolio.st, account.st = account.st, nsamples = .nsamples) if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { save(list = "results", file = results_file) save.strategy(strategy.st) } } setwd(cwd) "],
["stop-loss.html", "Chapter 8 Stop-Loss Orders 8.1 Add Indicators 8.2 Add Signals 8.3 Add Rules 8.4 Add Position Limit 8.5 Enable Rules 8.6 Apply Strategy", " Chapter 8 Stop-Loss Orders We’ll continue using a variation of the Luxor strategy. This time we’re going to implement stop-loss orders. We’re also going to keep all of our settings in variables so as to make the code easier to work with from here forward. .fast <- 10 .slow <- 30 .threshold <- 0.0005 .orderqty <- 100 .txnfees <- -10 .stoploss <- 3e-3 # 0.003 or 0.3% portfolio.st <- "Port.Luxor.Stop.Loss" account.st <- "Acct.Luxor.Stop.Loss" strategy.st <- "Strat.Luxor.Stop.Loss" rm.strat(portfolio.st) rm.strat(account.st) initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) ## [1] "Port.Luxor.Stop.Loss" initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, initEq = init_equity) ## [1] "Acct.Luxor.Stop.Loss" initOrders(portfolio = portfolio.st, symbols = symbols, initDate = init_date) strategy(strategy.st, store = TRUE) 8.1 Add Indicators add.indicator(strategy.st, name = "SMA", arguments = list(x = quote(Cl(mktdata)), n = .fast), label = "nFast") ## [1] "Strat.Luxor.Stop.Loss" add.indicator(strategy.st, name = "SMA", arguments = list(x = quote(Cl(mktdata)), n = .slow), label = "nSlow") ## [1] "Strat.Luxor.Stop.Loss" 8.2 Add Signals add.signal(strategy.st, name = "sigCrossover", arguments = list(columns = c("nFast", "nSlow"), relationship = "gte"), label = "long" ) ## [1] "Strat.Luxor.Stop.Loss" add.signal(strategy.st, name = "sigCrossover", arguments = list(columns = c("nFast", "nSlow"), relationship = "lt"), label = "short") ## [1] "Strat.Luxor.Stop.Loss" 8.3 Add Rules Our rules are largely the same as they were in our original Luxor strategy. However, we have added some slight modifications. Let’s start off with osFUN which is abbreviated for order size function. It is defined as: function or text descriptor of function to use for order sizing. The default value for this parameter is osNoOp which is an ordering function that performs no operation. In other words, if you pass 100 as orderqty that is what is purchased. In the EnterLong rule below we pass a different function, osMaxPos(). osMaxPos() works with addPosLimit() (next section) to set a maximum position per symbol. This will keep us from executing the same orders repeatedly. We’ve also included the orderset parameter with a value of “ocolong”. This will help group our long and short orders together. add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "long" , sigval = TRUE, replace = FALSE, orderside = "long" , ordertype = "stoplimit", prefer = "High", threshold = .threshold, TxnFees = .txnfees, orderqty = +.orderqty, osFUN = osMaxPos, orderset = "ocolong"), type = "enter", label = "EnterLONG") ## [1] "Strat.Luxor.Stop.Loss" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, replace = FALSE, orderside = "short", ordertype = "stoplimit", prefer = "Low", threshold = .threshold, TxnFees = .txnfees, orderqty = -.orderqty, osFUN = osMaxPos, orderset = "ocoshort"), type = "enter", label = "EnterSHORT") ## [1] "Strat.Luxor.Stop.Loss" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, replace = TRUE, orderside = "long" , ordertype = "market", TxnFees = .txnfees, orderqty = "all", orderset = "ocolong"), type = "exit", label = "Exit2SHORT") ## [1] "Strat.Luxor.Stop.Loss" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "long", sigval = TRUE, replace = TRUE, orderside = "short", ordertype = "market", TxnFees = .txnfees, orderqty = "all", orderset = "ocoshort"), type = "exit", label = "Exit2LONG") ## [1] "Strat.Luxor.Stop.Loss" Up to this point our Luxor.Stop.Loss strategy has been the same as our original Luxor strategy. When we take a long position we stay in it until we get a short signal, rinse and repeat. However, now we’re going to put stops in place. From the onset there isn’t much different from the previous rules we have added. Many of the parameters are similar. We do have some new ones though. First, we’ve created rule StopLossLONG as a child rule of the parent rule EnterLONG, part of the orderset ocolong. Currently it is not enabled. The critical portion of StopLossLONG is the tmult and threshold parameter. When a long order is filled threshold and tmult work together to determine the stoplimit price (ordertype). .stoploss is multiplied (tmult) against the price of the filled long order. That price serves as the stop-loss price. For example, \\[ \\text{StopLossLONG} = \\text{fill price } - \\left( \\text{.stoploss } * \\text{fill price}\\right) \\] \\[ \\text{StopLossLONG} = 134.39 - \\left(0.003 * 134.39\\right) \\] \\[ \\text{StopLossLONG} = $133.9868 \\] If market price moves below $ $133.9868 $ the StopLossLONG order becomes a market order and the Exit2SHORT order is cancelled (OCO). The same applies to StopLossSHORT which is a child of EnterSHORT except .stoploss is added to the fill price. add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "long" , sigval = TRUE, replace = FALSE, orderside = "long", ordertype = "stoplimit", tmult = TRUE, threshold = quote(.stoploss), TxnFees = .txnfees, orderqty = "all", orderset = "ocolong"), type = "chain", parent = "EnterLONG", label = "StopLossLONG", enabled = FALSE) ## [1] "Strat.Luxor.Stop.Loss" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, replace = FALSE, orderside = "short", ordertype = "stoplimit", tmult = TRUE, threshold = quote(.stoploss), TxnFees = .txnfees, orderqty = "all", orderset = "ocoshort"), type = "chain", parent = "EnterSHORT", label = "StopLossSHORT", enabled = FALSE) ## [1] "Strat.Luxor.Stop.Loss" 8.4 Add Position Limit As mentioned previously when using osMaxPos() we must supply a position limit to each symbol our strategy is working. We do this with addPosLimit. For now the only parameter we apply is maxpos which we set to .orderqty. for(symbol in symbols){ addPosLimit(portfolio = portfolio.st, symbol = symbol, timestamp = init_date, maxpos = .orderqty) } 8.5 Enable Rules When we wrote StopLossLONG and StopLossSHORT we disabled them by assigning enabled = FALSE. Now we enable both rules set. This is very beneficial when you want to test a strategy versus different rulesets (rather than rewriting code). label can apply to a specific rule or by matching the value to all rules with a similar value (grep). By supply “StopLoss” to label we are instructing quantstrat to enable all of our rules with the string “StopLoss” in the label, StopLossLONG and StopLossSHORT. enable.rule(strategy.st, type = "chain", label = "StopLoss") ## [1] "Strat.Luxor.Stop.Loss" 8.6 Apply Strategy cwd <- getwd() setwd("./_data/") results_file <- paste("results", strategy.st, "RData", sep = ".") if( file.exists(results_file) ) { load(results_file) } else { results <- applyStrategy(strategy.st, portfolios = portfolio.st) if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { save(list = "results", file = results_file) save.strategy(strategy.st) } } setwd(cwd) "],
["stop-loss-optimization.html", "Chapter 9 Stop Loss Optimization 9.1 Add Indicators 9.2 Add Signals 9.3 Add Rules 9.4 Add Position Limit 9.5 Add Distribution 9.6 Add Distribution Constraint 9.7 Enable Rules 9.8 Apply Paramset", " Chapter 9 Stop Loss Optimization We take the code from 8 and reuse it here this time to test parameters for our stop-loss just as we did in 7. This time though instead of optimizing indicators we’re going to optimize .stopLoss in a new integer vector .StopLoss. .StopLoss = seq(0.05, 0.6, length.out = 48)/100 We’ll name this strategy Luxor.Stop.Loss.Opt. strategy.st <- "Luxor.Stop.Loss.Opt" rm.strat(portfolio.st) rm.strat(account.st) initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) ## [1] "Port.Luxor.Stop.Loss" initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date) ## [1] "Acct.Luxor.Stop.Loss" initOrders(portfolio = portfolio.st, initDate = init_date) strategy(strategy.st, store = TRUE) 9.1 Add Indicators add.indicator(strategy.st, name = "SMA", arguments = list(x = quote(Cl(mktdata)), n = .fast), label = "nFast") ## [1] "Luxor.Stop.Loss.Opt" add.indicator(strategy.st, name = "SMA", arguments = list(x = quote(Cl(mktdata)), n = .slow), label = "nSlow") ## [1] "Luxor.Stop.Loss.Opt" 9.2 Add Signals add.signal(strategy.st, name = "sigCrossover", arguments = list(columns = c("nFast", "nSlow"), relationship = "gte"), label = "long" ) ## [1] "Luxor.Stop.Loss.Opt" add.signal(strategy.st, name = "sigCrossover", arguments = list(columns = c("nFast", "nSlow"), relationship = "lt"), label = "short") ## [1] "Luxor.Stop.Loss.Opt" 9.3 Add Rules add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "long" , sigval = TRUE, replace = FALSE, orderside = "long" , ordertype = "stoplimit", prefer = "High", threshold = .threshold, TxnFees = .txnfees, orderqty = +.orderqty, osFUN = osMaxPos, orderset = "ocolong"), type = "enter", label = "EnterLONG") ## [1] "Luxor.Stop.Loss.Opt" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, replace = FALSE, orderside = "short", ordertype = "stoplimit", prefer = "Low", threshold = .threshold, TxnFees = .txnfees, orderqty = -.orderqty, osFUN = osMaxPos, orderset = "ocoshort"), type = "enter", label = "EnterSHORT") ## [1] "Luxor.Stop.Loss.Opt" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, replace = TRUE, orderside = "long" , ordertype = "market", TxnFees = .txnfees, orderqty = "all", orderset = "ocolong"), type = "exit", label = "Exit2SHORT") ## [1] "Luxor.Stop.Loss.Opt" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "long", sigval = TRUE, replace = TRUE, orderside = "short", ordertype = "market", TxnFees = .txnfees, orderqty = "all", orderset = "ocoshort"), type = "exit", label = "Exit2LONG") ## [1] "Luxor.Stop.Loss.Opt" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "long" , sigval = TRUE, replace = FALSE, orderside = "long", ordertype = "stoplimit", tmult = TRUE, threshold = quote(.stoploss), TxnFees = .txnfees, orderqty = "all", orderset = "ocolong"), type = "chain", parent = "EnterLONG", label = "StopLossLONG", enabled = FALSE) ## [1] "Luxor.Stop.Loss.Opt" add.rule(strategy.st, name = "ruleSignal", arguments = list(sigcol = "short", sigval = TRUE, replace = FALSE, orderside = "short", ordertype = "stoplimit", tmult = TRUE, threshold = quote(.stoploss), TxnFees = .txnfees, orderqty = "all", orderset = "ocoshort"), type = "chain", parent = "EnterSHORT", label = "StopLossSHORT", enabled = FALSE) ## [1] "Luxor.Stop.Loss.Opt" 9.4 Add Position Limit for(symbol in symbols){ addPosLimit(portfolio = portfolio.st, symbol = symbol, timestamp = init_date, maxpos = .orderqty) } 9.5 Add Distribution We use add.distribution again to assign our .StopLoss vector as values for the StopLossLONG and StopLossSHORT rule chains. add.distribution(strategy.st, paramset.label = "StopLoss", component.type = "chain", component.label = "StopLossLONG", variable = list(threshold = .StopLoss), label = "StopLossLONG") ## [1] "Luxor.Stop.Loss.Opt" add.distribution(strategy.st, paramset.label = "StopLoss", component.type = "chain", component.label = "StopLossSHORT", variable = list(threshold = .StopLoss), label = "StopLossSHORT") ## [1] "Luxor.Stop.Loss.Opt" 9.6 Add Distribution Constraint We set a distribution constraint to keep the StopLossLONG and StopLossSHORT parameters the same. add.distribution.constraint(strategy.st, paramset.label = "StopLoss", distribution.label.1 = "StopLossLONG", distribution.label.2 = "StopLossSHORT", operator = "==", label = "StopLoss") ## [1] "Luxor.Stop.Loss.Opt" 9.7 Enable Rules enable.rule(strategy.st, 'chain', 'StopLoss') ## [1] "Luxor.Stop.Loss.Opt" 9.8 Apply Paramset cwd <- getwd() setwd("./_data/") results_file <- paste("results", strategy.st, "RData", sep = ".") if( file.exists(results_file) ) { load(results_file) } else { results <- apply.paramset(strategy.st, paramset.label = "StopLoss", portfolio.st = portfolio.st, account.st = account.st, nsamples = .nsamples, verbose = TRUE) if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { save(list = "results", file = results_file) save.strategy(strategy.st) } } ## Warning in data.frame(..., check.names = FALSE): row names were found from ## a short variable and have been discarded ## Warning in data.frame(..., check.names = FALSE): row names were found from ## a short variable and have been discarded ## Warning in data.frame(..., check.names = FALSE): row names were found from ## a short variable and have been discarded ## Warning in data.frame(..., check.names = FALSE): row names were found from ## a short variable and have been discarded ## Warning in data.frame(..., check.names = FALSE): row names were found from ## a short variable and have been discarded setwd(cwd) "],
["trailing-stops.html", "Chapter 10 Trailing Stops 10.1 osFixedDollar 10.2 Add Indicators 10.3 Add Signals 10.4 Add Rules 10.5 Enable Rules 10.6 Save Strategy", " Chapter 10 Trailing Stops If Signal Line crosses above 0: BTO Else If Signal Line crosses below 0: STC trailingStopPercent <- 0.07 trade_size <- init_equity/length(symbols) portfolio.st <- "Quantstrat" account.st <- "Strategies" strategy.st <- "MACD.TS" rm.strat(portfolio.st) rm.strat(account.st) initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) ## [1] "Quantstrat" initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, initEq = init_equity) ## [1] "Strategies" initOrders(portfolio = portfolio.st, symbols = symbols, initDate = init_date) strategy(strategy.st, store = TRUE) 10.1 osFixedDollar quantstratIII pg. 11/66 \\[ \\text{orderqty} = \\frac{\\text{trade_size}}{\\text{Cl}} \\] osFixedDollar <- function(timestamp, orderqty, portfolio, symbol, ruletype, ...) { if(!exists("trade_size")) stop("You must set trade_size") ClosePrice <- as.numeric(Cl(mktdata[timestamp,])) orderqty <- round(trade_size/ClosePrice,-2) return(orderqty) } 10.2 Add Indicators add.indicator(strategy = strategy.st, name = "MACD", arguments = list(x = quote(Cl(mktdata))), label = "osc") ## [1] "MACD.TS" 10.3 Add Signals add.signal(strategy = strategy.st, name="sigThreshold", arguments = list(column ="signal.osc", relationshipo = "gt", threshold = 0, cross = TRUE), label = "signal.gt.zero") ## [1] "MACD.TS" add.signal(strategy = strategy.st, name="sigThreshold", arguments = list(column = "signal.osc", relationship = "lt", threshold = 0, cross = TRUE), label = "signal.lt.zero") ## [1] "MACD.TS" 10.4 Add Rules add.rule(strategy = strategy.st, name = "ruleSignal", arguments = list(sigcol = "signal.gt.zero", sigval = TRUE, orderqty = 100, orderside = "long", ordertype = "market", osFUN = "osFixedDollar", orderset = "ocolong"), type = "enter", label = "LE") ## [1] "MACD.TS" add.rule(strategy = strategy.st, name = "ruleSignal", arguments = list(sigcol = "signal.lt.zero", sigval = TRUE, replace = TRUE, orderside = "long", ordertype = "market", orderqty = "all", orderset = "ocolong"), type = "exit", label = "LX") ## [1] "MACD.TS" add.rule(strategy = strategy.st, name = "ruleSignal", arguments = list(sigcol = "signal.gt.zero", sigval = TRUE, replace = FALSE, orderside = "long", ordertype = "stoptrailing", tmult = TRUE, threshold = quote(trailingStopPercent), orderqty = "all", orderset = "ocolong"), type = "chain", parent = "LE", label = "StopTrailingLong", enabled = FALSE) ## [1] "MACD.TS" 10.5 Enable Rules enable.rule(strategy.st, type = "chain", label = "StopTrailingLong") ## [1] "MACD.TS" 10.6 Save Strategy cwd <- getwd() setwd("./_data/") save.strategy(strategy.st) setwd(cwd) "],
["obtaining-resources.html", "Chapter 11 Obtaining Resources 11.1 Amazon Web Services 11.2 Getting Started 11.3 Testing Resources 11.4 Changing Instances 11.5 Stop the Server 11.6 Reading Resources", " Chapter 11 Obtaining Resources Some of you may not have the resources needed to run complex strategies. Some of you may have dual or quad core processors (even more) but still find some strategies taking a bit of time to run. Nothing is worse than running a strategy for ten or thirty minutes or even longer to find something’s not right. This can get more irritated if you’re running on a large number of symbols. 11.1 Amazon Web Services Amazon Web Services is a cloud-computing service that allows us to use resources that may not normally be available to us. They’re are literally dozens of services available depending on your needs. The one we will focus on here is EC2 or Elastic Compute Cloud. EC2 offers virtual server space in the Linux and Windows platforms. With EC2 we can get a very minimal setup for as little as $0.006 per hour. You do not need to have this service running 24 hours a day. One of the huge benefits of the service is we only need to fire it up when we’re ready to do something. It takes just a few minutes to boot up. We only pay for uptime. Another advantage is we can use multiple instances. We can have one server testing a script or running an analysis and work on the same project on another server. In addition, if you’ve never used Amazon Web Services before you are likely available for a one year free trial. There may be some restrictions. With the free trial you can get the t2.micro service which gives 1GB of memory and a 1 core 2.40Ghz processor, leave it running 24 hours a day, 7 days a week for a full year and not pay a dime. This is plenty to get the basic foundation layed. When you see this block it means we are going to perform some operations that may incur a charge depending on your AWS account. It is up to you to know the associated costs for your account. We’re also going to take advantage of a tremendous service offered by Louis Aslett. Louis has taken the time to create and maintain dozens of Linux images set up with RStudio. Don’t worry if you’re not familiar with Linux. After the initial setup you’ll rarely need it. 11.2 Getting Started If you have not used AWS before go create an account. Registration takes just a few minutes. When finished, we’ll go to Louis Aslett’s website to grab an image. You’ll see the RStudio/R images listed down the left side and server locations across the header. You want to find the server location closest to you but for our purposes it shouldn’t matter much. The latest release as of this writing that is RStudio 0.99.491 with R 3.2.3 (This book is written in RStudio 0.99.893 using R 3.2.3). Click on the corresponding ami hyperlink. This will take you to choose an instance type. If you’re eligible for the free-tier service you’ll see the green font accent beneath the t2.micro service. Click “Next: Configure Instance Details” and keep clicking Next until you get to Step 6: Configure Security Group. By default port 22 is open for SSH from any IP address. You can close this if you like. However, I like to install my R libraries to the root user through SSH. I’ll demonstrate this later. If you choose to keep SSH change Source to Custom IP and your IP address range should pop up in the text field to the right. You also want to click “Add Rule” to open port 80. Select the “Custom TCP Rule” under Type and change it to HTTP. This will add the value 80 under Port Range. As with SSH change Source to Custom IP and again your IP address range will automatically fill in. You can also create your own Security group name or leave the default. I used rstudio-0-99-491. Do not leave Source open to “Anywhere” as this will allow anyone to potentially attempt to access your virtual server. This may seem harmless but if you’re working solo there’s no sense having it all open unless you’re comfortable with Linux security. If you’re working in groups, use Github. Next, click “Review and Launch”. This will take you to a summary page reiterating your selections. If all looks good click “Launch”. If you left SSH open you’re not quite done yet. You’ll see a pop-up window asking you to create or use an existing key pair. A private key is used to SSH into your remote server. Select “Create a new key pair” from the drop down menu and give a Key pair name; I named mine after my security group for simplicity. Click “Download Key Pair” and save the pem file to a safe location. When you’ve saved the pem file click “Launch Instances”. If you’re service is running but you are unable to log in via SSH or HTTP it is likely because your IP address has been changed by your DNS provider. See Adding Rules to a Security Group on changing the IP rules. Once you launch an instance you are now on the billing clock. If you were eligible for the free-tier and selected the t2.micro instance you should not be incurring charges during your trial. While you are waiting, if you are on a Windows system and left SSH enabled please take a moment to review Connecting to Your Linux Instance from Windows Using PuTTY. You will need to convert the pem file to a ppk file in order to log in. As we wait you’ll notice Instance State is listed as “running” but Status Checks shows as “initializing”. Once Status Checks displays “2/2 checks…” you are now ready to log in to your new server. Take a look at the frame in the bottom of your browser. You’ll see your instance ID followed by Public DNS. Copy the Public DNS and paste it into a new browser window or tab and hit enter. You should now be greeted by a RStudio login page. Congratulations! We’re not done yet. The default username and login to the AMI is rstudio for both Username and Password. So go ahead and log into your system. When RStudio loads you will see a commented script in the editor. Read over it carefully and follow the instructions particularly in regards to changing your password. I would suggest getting a new password from Secure Password Generator. The defaults should suffice. After running passwd() you should get a confirmation message: (current) UNIX password: Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully You are now set up to use RStudio as you have been. 11.2.1 Installing Quantstrat If you attempt to install quantstrat from the RStudio package window or from Cran you will get an error that it is not available for R 3.2.3. You can install it from R-Forge. install.packages("quantstrat", repos="http://R-Forge.R-project.org") This will install the latest version, 0.9.1739, the same as used in this book along with the additional libraries needed. 11.3 Testing Resources We’ll use a minor variation of the MACD demo in quantstrat to test some speeds. The strategy will buy when our signal line crosses over 0 and sell when it crosses under 0. We’ll use a range of 1:20 for fastMA and 30:80 for slowMA. The original demo called for a random sample of 10 but we’ll remove that to test all 1,020 iterations. We will execute the strategy on one stock, AAPL, from 2007 to Jun. 1, 2014. These tests are performed using Ubuntu 14.04.3 LTS, x86 64 bit. The unevaluated code is as follows: start_t <- Sys.time() library(quantstrat) # For Linux library(doMC) registerDoMC(cores = parallel::detectCores()) stock.str <- "AAPL" # what are we trying it on #MA parameters for MACD fastMA <- 12 slowMA <- 26 signalMA <- 9 maType <- "EMA" .FastMA <- (1:20) .SlowMA <- (30:80) currency("USD") stock(stock.str, currency = "USD", multiplier = 1) start_date <- "2006-12-31" initEq <- 1000000 portfolio.st <- "macd" account.st <- "macd" rm.strat(portfolio.st) rm.strat(account.st) initPortf(portfolio.st, symbols = stock.str) initAcct(account.st, portfolios = portfolio.st) initOrders(portfolio = portfolio.st) strat.st <- portfolio.st # define the strategy strategy(strat.st, store = TRUE) #one indicator add.indicator(strat.st, name = "MACD", arguments = list(x = quote(Cl(mktdata)), nFast = fastMA, nSlow = slowMA), label = "_") #two signals add.signal(strat.st, name = "sigThreshold", arguments = list(column = "signal._", relationship = "gt", threshold = 0, cross = TRUE), label = "signal.gt.zero") add.signal(strat.st, name = "sigThreshold", arguments = list(column = "signal._", relationship = "lt", threshold = 0, cross = TRUE), label = "signal.lt.zero") # add rules # entry add.rule(strat.st, name = "ruleSignal", arguments = list(sigcol = "signal.gt.zero", sigval = TRUE, orderqty = 100, ordertype = "market", orderside = "long", threshold = NULL), type = "enter", label = "enter", storefun = FALSE) # exit add.rule(strat.st, name = "ruleSignal", arguments = list(sigcol = "signal.lt.zero", sigval = TRUE, orderqty = "all", ordertype = "market", orderside = "long", threshold = NULL, orderset = "exit2"), type = "exit", label = "exit") ### MA paramset add.distribution(strat.st, paramset.label = "MA", component.type = "indicator", component.label = "_", #this is the label given to the indicator in the strat variable = list(n = .FastMA), label = "nFAST") add.distribution(strat.st, paramset.label = "MA", component.type = "indicator", component.label = "_", #this is the label given to the indicator in the strat variable = list(n = .SlowMA), label = "nSLOW") add.distribution.constraint(strat.st, paramset.label = "MA", distribution.label.1 = "nFAST", distribution.label.2 = "nSLOW", operator = "<", label = "MA") getSymbols(stock.str, from = start_date, to = "2014-06-01") results <- apply.paramset(strat.st, paramset.label = "MA", portfolio.st = portfolio.st, account.st = account.st, nsamples = 0, verbose = TRUE) updatePortf(Portfolio = portfolio.st,Dates = paste("::",as.Date(Sys.time()),sep = "")) end_t <- Sys.time() print(end_t-start_t) servers <- c("t2.micro", "t2.medium", "m4.xlarge", "m4.2xlarge", "m4.4xlarge") aws <- data.frame("Server" = factor(servers, levels = servers), "Processor" = c("Intel Xeon E5-2676 v3, 2.40 GHz", "Intel Xeon E5-2670 v2, 2.50 GHz", "Intel Xeon E5-2676 v3, 2.40Ghz", "Intel Xeon E5-2676 v3, 2.40Ghz", "Intel Xeon E5-2676 v3, 2.40Ghz"), "VirtualCores" = c(1, 2, 4, 8, 16), "Memory" = c(1, 4, 16, 32, 64), "Seconds" = c(749.841, 293.6499, 118.1366, 62.24196, 34.68986), "Price" = c(0.013, 0.052, 0.239, 0.479, 0.958)) # knitr::kable(aws, caption = "Test Results for AWS", booktabs = TRUE) aws %>% ggplot(aes(x = Server, y = Seconds, fill = Server)) + geom_bar(stat = "identity") + theme_bw() + labs(title = "Execution Time per Server") aws_ppe <- aws %>% mutate(ppe = (Price/60) * Seconds) aws_ppe %>% ggplot(aes(x = Server, y = ppe, fill = Server)) + geom_bar(stat = "identity") + theme_bw() + labs(title = "Cost per Second of Execution (ppe)", y = "Cost (USD)") # knitr::kable(aws_ppe, caption = "Cost per Second of Execution (ppe)", # y = "Cost (USD)", booktabs = TRUE) We can get a general idea of costs by comparing execution times versus the cost of the server. Keep in mind this may vary by script and using other servers available. Obviously we’re paying a premium for speed though all things considered there isn’t much of a difference going from m4.xlarge to m4.4xlarge. Prices are not prorated for hourly usage so if you’re going to pay a premium for the faster servers it may not be a bad idea to have several backtests ready to run. 11.4 Changing Instances Regardless if you’re free-tier eligible or not, using any server instance beyond the t2.micro will incur charges. See Amazon EC2 Pricing for more details. To change your instance types go to your EC2 Dashboard then click the Instances link under Instances. Check the box next to your instance then click on the Actions dropdown. If your instance is already running stop it by selecting Instance State > Stop. With the instance stopped under Instance State go back to Actions and select Instance Settings > Change Instance Types. Then select your instance from the select field and Apply. You can restart the instance right away by going back to Actions > Instance State > Start. It will take a few minutes but will be ready to go when Status Checks reads “2/2 checks…”. 11.5 Stop the Server Do not terminate the server else you will lose all of your data. There is no charge for stopping an instance and it only takes minutes to fire it back up when you’re ready to work. When you finish your workload be sure to log off RStudio. You can stop the instance by going to Actions > Instance State > Stop. All of your data will be saved for the next time you are ready to work. When you stop/start or restart a server you will receive a new Public DNS. If you have stored your original Public DNS as a bookmark or in PuTTY, be sure to update it to the new DNS assigned by AWS when the server is started. You can use Elastic IPs to keep a consistent IP address. However, this is only free if you leave your instance running 24/7. After you have stopped the server you are no longer incurring charges regardless of the instance type. However, you may want to downgrade the Instance Type to be safe the next time you start the server. 11.6 Reading Resources If you are interested in further utilizing AWS for your backtesting I recommend the book Amazon Web Services in Action (affiliate link) by Andreas and Michael Wittig. The book details virtual hosting and storage services as well as proper security. If you intend to develop complex strategies in R but lack the resources you may find AWS can be a great option. If you’re going to use Ubuntu I would also recommend The Official Ubuntu Server Book (affiliate link) by Kyle Rankin and Benjamin Mako Hill. "],
["analyzing-results.html", "Chapter 12 Analyzing Results 12.1 Apply Strategy 12.2 Chart Positions 12.3 Trade Statistics 12.4 Account Summary", " Chapter 12 Analyzing Results portfolio.st <- "Port.Luxor" account.st <- "Acct.Luxor" strategy.st <- "Strat.Luxor" cwd <- getwd() setwd("./_data/") load.strategy(strategy.st) setwd(cwd) rm.strat(portfolio.st) rm.strat(account.st) initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) ## [1] "Port.Luxor" initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, initEq = init_equity) ## [1] "Acct.Luxor" initOrders(portfolio = portfolio.st, initDate = init_date) 12.1 Apply Strategy # Results hidden to save space applyStrategy(strategy.st, portfolios = portfolio.st) checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) ## [1] TRUE updatePortf(portfolio.st) ## [1] "Port.Luxor" updateAcct(account.st) ## [1] "Acct.Luxor" updateEndEq(account.st) ## [1] "Acct.Luxor" 12.2 Chart Positions for(symbol in symbols) { chart.Posn(portfolio.st, Symbol = symbol, TA = "add_SMA(n = 10, col = 4); add_SMA(n = 30, col = 2)") } 12.3 Trade Statistics tstats <- tradeStats(portfolio.st) kable(t(tstats)) IWM QQQ SPY Portfolio Port.Luxor Port.Luxor Port.Luxor Symbol IWM QQQ SPY Num.Txns 23 21 23 Num.Trades 11 10 11 Net.Trading.PL 2501.460 1070.748 2251.207 Avg.Trade.PL 211.1204 101.8385 200.5545 Med.Trade.PL 51.52684 -45.13684 -120.63947 Largest.Winner 2109.166 1287.017 2927.862 Largest.Loser -428.1350 -355.5896 -891.9164 Gross.Profits 3724.335 2463.680 5705.846 Gross.Losses -1402.011 -1445.295 -3499.746 Std.Dev.Trade.PL 732.3649 515.1777 1149.6862 Percent.Positive 63.63636 50.00000 45.45455 Percent.Negative 36.36364 50.00000 54.54545 Profit.Factor 2.656424 1.704621 1.630360 Avg.Win.Trade 532.0479 492.7360 1141.1692 Med.Win.Trade 152.6189 351.7958 909.5703 Avg.Losing.Trade -350.5026 -289.0589 -583.2910 Med.Losing.Trade -352.4492 -300.4438 -726.2925 Avg.Daily.PL 211.1204 101.8385 200.5545 Med.Daily.PL 51.52684 -45.13684 -120.63947 Std.Dev.Daily.PL 732.3649 515.1777 1149.6862 Ann.Sharpe 4.576179 3.138017 2.769194 Max.Drawdown -1691.308 -1343.709 -2942.752 Profit.To.Max.Draw 1.4790094 0.7968602 0.7650008 Avg.WinLoss.Ratio 1.517957 1.704621 1.956432 Med.WinLoss.Ratio 0.4330239 1.1709206 1.2523471 Max.Equity 2961.741 1893.009 3482.502 Min.Equity -678.91818 -52.69256 -2053.06787 End.Equity 2501.460 1070.748 2251.207 12.3.1 Trade Related tab.trades <- tstats %>% mutate(Trades = Num.Trades, Win.Percent = Percent.Positive, Loss.Percent = Percent.Negative, WL.Ratio = Percent.Positive/Percent.Negative) %>% select(Trades, Win.Percent, Loss.Percent, WL.Ratio) kable(t(tab.trades)) Trades 11.00000 10 11.0000000 Win.Percent 63.63636 50 45.4545455 Loss.Percent 36.36364 50 54.5454545 WL.Ratio 1.75000 1 0.8333333 12.3.2 Profit Related tab.profit <- tstats %>% select(Net.Trading.PL, Gross.Profits, Gross.Losses, Profit.Factor) kable(t(tab.profit)) IWM QQQ SPY Net.Trading.PL 2501.459861 1070.748428 2251.20741 Gross.Profits 3724.334958 2463.679871 5705.84581 Gross.Losses -1402.010577 -1445.294647 -3499.74600 Profit.Factor 2.656424 1.704621 1.63036 12.3.3 Averages tab.wins <- tstats %>% select(Avg.Trade.PL, Avg.Win.Trade, Avg.Losing.Trade, Avg.WinLoss.Ratio) kable(t(tab.wins)) IWM QQQ SPY Avg.Trade.PL 211.120398 101.838522 200.554528 Avg.Win.Trade 532.047851 492.735974 1141.169162 Avg.Losing.Trade -350.502644 -289.058929 -583.291000 Avg.WinLoss.Ratio 1.517957 1.704621 1.956432 12.3.4 Performance Summary rets <- PortfReturns(Account = account.st) rownames(rets) <- NULL charts.PerformanceSummary(rets, colorset = bluefocus) 12.3.5 Per Trade Statistics for(symbol in symbols) { pts <- perTradeStats(portfolio.st, Symbol = symbol) kable(pts, booktabs = TRUE, caption = symbol) } kable(pts) Start End Init.Pos Max.Pos Num.Txns Max.Notional.Cost Net.Trading.PL MAE MFE Pct.Net.Trading.PL Pct.MAE Pct.MFE tick.Net.Trading.PL tick.MAE tick.MFE 2008-02-27 2008-03-07 100 100 2 13261.463 -881.91636 -881.91636 0.00000 -0.0665022 -0.0665022 0.0000000 -881.91636 -881.91636 0.00000 2008-03-07 2008-04-02 -100 -100 2 -12385.273 -725.99029 -725.99029 168.92951 -0.0586172 -0.0586172 0.0136395 -725.99029 -725.99029 168.92951 2008-04-02 2008-06-03 100 100 2 13144.834 99.74852 -395.16121 575.47610 0.0075884 -0.0300621 0.0437796 99.74852 -395.16121 575.47610 2008-06-03 2008-08-06 -100 -100 2 -13235.450 807.57595 -267.13716 1572.93130 0.0610161 -0.0201835 0.1188423 807.57595 -267.13716 1572.93130 2008-08-06 2008-09-08 100 100 2 12392.260 -151.38610 -436.70758 207.19458 -0.0122162 -0.0352404 0.0167197 -151.38610 -436.70758 207.19458 2008-09-11 2008-12-17 -100 -100 2 -11759.376 2937.86161 -394.74462 4444.47045 0.2498314 -0.0335685 0.3779512 2937.86161 -394.74462 4444.47045 2008-12-17 2009-01-21 100 100 2 8921.423 -706.59467 -1046.72109 214.09136 -0.0792020 -0.1173267 0.0239974 -706.59467 -1046.72109 214.09136 2009-02-05 2009-03-24 -100 -100 2 -7823.378 -110.63947 -689.54812 1166.48465 -0.0141422 -0.0881394 0.1491024 -110.63947 -689.54812 1166.48465 2009-03-24 2009-06-26 100 100 2 8100.427 991.08945 -344.57932 1258.95869 0.1223503 -0.0425384 0.1554188 991.08945 -344.57932 1258.95869 2009-07-06 2009-07-23 -100 -100 2 -8804.437 -863.21910 -863.21910 97.01343 -0.0980436 -0.0980436 0.0110187 -863.21910 -863.21910 97.01343 2009-07-23 2009-11-04 100 100 2 9516.246 919.57028 0.00000 1403.96276 0.0966316 0.0000000 0.1475333 919.57028 0.00000 1403.96276 2009-11-16 2009-12-31 100 100 1 10978.893 165.10760 -94.49099 293.10750 0.0150386 -0.0086066 0.0266974 165.10760 -94.49099 293.10750 12.3.6 Performance Statistics tab.perf <- table.Arbitrary(rets, metrics=c( "Return.cumulative", "Return.annualized", "SharpeRatio.annualized", "CalmarRatio"), metricsNames=c( "Cumulative Return", "Annualized Return", "Annualized Sharpe Ratio", "Calmar Ratio")) kable(tab.perf) IWM.DailyEndEq QQQ.DailyEndEq SPY.DailyEndEq Cumulative Return 0.2317202 0.1002049 0.1306882 Annualized Return 0.1095999 0.0488073 0.0632089 Annualized Sharpe Ratio 0.5363315 0.4537899 0.1979763 Calmar Ratio 0.6821284 0.3825050 0.2385090 12.3.7 Risk Statistics tab.risk <- table.Arbitrary(rets, metrics=c( "StdDev.annualized", "maxDrawdown", "VaR", "ES"), metricsNames=c( "Annualized StdDev", "Max DrawDown", "Value-at-Risk", "Conditional VaR")) kable(tab.risk) IWM.DailyEndEq QQQ.DailyEndEq SPY.DailyEndEq Annualized StdDev 0.2043511 0.1075549 0.3192751 Max DrawDown 0.1606735 0.1275992 0.2650170 Value-at-Risk -0.0194849 -0.0103564 -0.0310188 Conditional VaR -0.0266399 -0.0163183 -0.0522518 12.3.8 Buy and Hold Performance rm.strat("buyHold") # initialize portfolio and account initPortf("buyHold", "SPY", initDate = init_date) ## [1] "buyHold" initAcct("buyHold", portfolios = "buyHold", initDate = init_date, initEq = init_equity) ## [1] "buyHold" # place an entry order CurrentDate <- time(getTxns(Portfolio = portfolio.st, Symbol = "SPY"))[2] equity = getEndEq("buyHold", CurrentDate) ClosePrice <- as.numeric(Cl(SPY[CurrentDate,])) UnitSize = as.numeric(trunc(equity/ClosePrice)) addTxn("buyHold", Symbol = "SPY", TxnDate = CurrentDate, TxnPrice = ClosePrice, TxnQty = UnitSize, TxnFees = 0) ## [1] "2008-02-27 00:00:00 SPY 75 @ 131.917425267632" # place an exit order LastDate <- last(time(SPY)) LastPrice <- as.numeric(Cl(SPY[LastDate,])) addTxn("buyHold", Symbol = "SPY", TxnDate = LastDate, TxnPrice = LastPrice, TxnQty = -UnitSize , TxnFees = 0) ## [1] "2009-12-31 00:00:00 SPY -75 @ 111.440002" # update portfolio and account updatePortf(Portfolio = "buyHold") ## [1] "buyHold" updateAcct(name = "buyHold") ## [1] "buyHold" updateEndEq(Account = "buyHold") ## [1] "buyHold" chart.Posn("buyHold", Symbol = "SPY") 12.3.9 Strategy vs. Market rets <- PortfReturns(Account = account.st) rets.bh <- PortfReturns(Account = "buyHold") returns <- cbind(rets, rets.bh) charts.PerformanceSummary(returns, geometric = FALSE, wealth.index = TRUE, main = "Strategy vs. Market") 12.3.10 Risk/Return Scatterplot chart.RiskReturnScatter(returns, Rf = 0, add.sharpe = c(1, 2), main = "Return vs. Risk", colorset = c("red", "blue")) 12.3.11 Relative Performance for(n in 1:(ncol(returns) - 1)) { chart.RelativePerformance(returns[, n], returns[, ncol(returns)], colorset = c("red", "blue"), lwd = 2, legend.loc = "topleft") } 12.3.12 Portfolio Summary #' Error pf <- getPortfolio(portfolio.st) xyplot(pf$summary, type = "h", col = 4) 12.3.13 Order Book ob <- getOrderBook(portfolio.st) 12.3.14 Maximum Adverse Excursion for(symbol in symbols) { chart.ME(Portfolio = portfolio.st, Symbol = symbol, type = "MAE", scale = "percent") } 12.3.15 Maximum Favorable Excursion for(symbol in symbols) { chart.ME(Portfolio = portfolio.st, Symbol = symbol, type = "MFE", scale = "percent") } 12.4 Account Summary a <- getAccount(account.st) xyplot(a$summary, type = "h", col = 4) 12.4.1 Equity Curve equity <- a$summary$End.Eq plot(equity, main = "Equity Curve") 12.4.2 Account Performance Summary ret <- Return.calculate(equity, method = "log") charts.PerformanceSummary(ret, colorset = bluefocus, main = "Strategy Performance") 12.4.3 Cumulative Returns rets <- PortfReturns(Account = account.st) chart.CumReturns(rets, colorset = rich10equal, legend.loc = "topleft", main="SPDR Cumulative Returns") 12.4.4 Distribution Analysis chart.Boxplot(rets, main = "SPDR Returns", colorset= rich10equal) 12.4.5 Annualized Returns (ar.tab <- table.AnnualizedReturns(rets)) ## IWM.DailyEndEq QQQ.DailyEndEq SPY.DailyEndEq ## Annualized Return 0.1096 0.0488 0.0632 ## Annualized Std Dev 0.2044 0.1076 0.3193 ## Annualized Sharpe (Rf=0%) 0.5363 0.4538 0.1980 12.4.6 Performance Scatter Plot max.risk <- max(ar.tab["Annualized Std Dev",]) max.return <- max(ar.tab["Annualized Return",]) chart.RiskReturnScatter(rets, main = "SPDR Performance", colorset = rich10equal, xlim = c(0, max.risk * 1.1), ylim = c(0, max.return)) 12.4.7 Notional Costs #quantstratII pp. 67/69 mnc <- pts$Max.Notional.Cost pe <- sapply(pts$Start,getEndEq, Account = account.st)/3 barplot(rbind(pe,mnc),beside=T,col=c(2,4),names.arg=format(pts$Start,"%m/%d/%y"), ylim=c(0,1.5e5),ylab="$",xlab="Trade Date") legend(x="topleft",legend=c("(Portfolio Equity)/9","Order Size"), pch=15,col=c(2,4),bty="n") title("Percent of Portfolio Equity versus Trade Size for XLU") "],
["walk-forward-analysis.html", "Chapter 13 Walk Forward Analysis", " Chapter 13 Walk Forward Analysis "],
["monte-carlo-analysis.html", "Chapter 14 Monte Carlo Analysis", " Chapter 14 Monte Carlo Analysis "],
["warnings-and-errors.html", "Chapter 15 Warnings and Errors 15.1 Must use auto.assign=TRUE for multiple Symbols requests 15.2 Missing in call to function add.distribution", " Chapter 15 Warnings and Errors It is very frustrating to hammer out tons of code only to find an error on execution. Often the errors and warnings do not provide much input - especially for beginners. Messages below are not intended to state specifically where the issue may be. Hopefully they will point you in the right direction. 15.1 Must use auto.assign=TRUE for multiple Symbols requests Error in getSymbols(Symbols = symbols, reload.Symbols = FALSE, verbose = FALSE, : must use auto.assign=TRUE for multiple Symbols requests You intentionally set auto.assign to false in getSymbols() while also calling multiple symbols. If you are loading getSymbols() with multiple symbols, set auto.assign = FALSE or remove the parameter as FALSE is the default setting. See ?getSymbols for more information on auto.assign. 15.2 Missing in call to function add.distribution Error in must.have.args(match.call(), c(“strategy”, “paramset.label”, : label: argument(s) missing in call to function add.distribution You’re missing a label argument in your add.distribution call. "],
["about-backtesting-strategies-with-r.html", "Chapter 16 About “Backtesting Strategies with R” 16.1 Tim Trice 16.2 To You, the Reader", " Chapter 16 About “Backtesting Strategies with R” This book is intended to help you do your own homework. This book is in no way, shape or form to be misconstrued as investment or trading guidance. It is for backtesting strategies only. What you do with your money on your time is your problem. There are many resources available for you to ask questions if you do not understand something. Use them. 16.1 Tim Trice My first foray into the stock market came in late 2007 and early 2008. It should go without saying this was a bad time to learn a new craft. I took a few years off before gradually working my way back into the market in 2011. I concentrate more on technical analysis. I had read many books discussing this-and-that pattern and strategy and how it worked and so forth but found in the real world things weren’t always what they seemed. In 2014 I began using my programming background to backtest strategies. Initially this was confined to downloading daily data and using Excel to test ideas. Shortly after I moved my backtesting to R and Python. I am, by no means, a quantitative trading expert. I have never worked for a large trading firm. I trade with my own money. I do not offer advice nor will I ever. I like solving puzzles. I like challenges. My intent with this book is to help bring together the vast resources available to test ideas ad nauseam so that hopefully some of you won’t make the same costly mistakes I made. If you can’t simulate it, don’t trade it. 16.2 To You, the Reader The source code for this book is available on Github. All contributors will be acknolwedged on this page. Comments are included throughout the source code on things I would like to expand on or questions I have to address later. I will attempt to list them all on the project issues page. If you don’t understand something, let me know. I will attempt to address it the best I can. If not, I will find the answer. "]
]