├── .gitignore ├── Cl-SMA20-Crossover.Rmd ├── R ├── basic-strategy.R ├── functions.R ├── load.R ├── parallel-test.R ├── parameter-optimization.R ├── stop-loss-optimization.R ├── stop-loss.R ├── symbols.R └── trailing-stop.R ├── _001-quantstrat.Rmd ├── _002-quantstrat-ii.Rmd ├── _02-basic-strategy.Rmd ├── _03-basic-strategy-ii.Rmd ├── _Cl-SMA20-Crossover.Rmd ├── _bollinger.bands.Rmd ├── _bookdown.yml ├── _connors.rsi.2.Rmd ├── _output.yml ├── about.Rmd ├── analyzing-results.Rmd ├── basic-strategy.Rmd ├── custom_styles.css ├── data-quality.Rmd ├── errors-warnings.Rmd ├── get-symbols.Rmd ├── index.Rmd ├── introduction.Rmd ├── monte-carlo-analysis.Rmd ├── obtaining-resources.Rmd ├── parameter-optimization.Rmd ├── simple-sma.Rmd ├── stock-book.Rproj ├── stop-loss-optimization.Rmd ├── stop-loss.Rmd ├── trailing-stop.Rmd └── walk-forward-analysis.Rmd /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .my.conf.R 5 | /_data 6 | -------------------------------------------------------------------------------- /Cl-SMA20-Crossover.Rmd: -------------------------------------------------------------------------------- 1 | ```{r libraries} 2 | library(dplyr) 3 | library(ggplot2) 4 | library(lattice) 5 | library(tidyr) 6 | library(TTR) 7 | ``` 8 | 9 | # Close/SMA(20) Crossover 10 | 11 | $$ Signal = 12 | \begin{cases} 13 | Cl >= SMA(20), BTO \\ 14 | Cl < SMA(20), STC 15 | \end{cases} 16 | $$ 17 | 18 | ```{r 2-1-set-symbols} 19 | symbols <- basic_symbols() 20 | ``` 21 | 22 | ```{r 2-1-settings} 23 | strategy_title <- "Close > SMA(20)" 24 | ``` 25 | 26 | ```{r 2-1-initialize-portfolio} 27 | Cl.SMA20.Crossover <- "Cl.Sma20.Crossover" 28 | rm.strat(Cl.SMA20.Crossover) 29 | initPortf(Cl.SMA20.Crossover, symbols = basic_symbols(), initDate = pv$init_date) 30 | initAcct(Cl.SMA20.Crossover, portfolios = Cl.SMA20.Crossover, 31 | initDate = pv$init_date, initEq = pv$account_equity) 32 | initOrders(portfolio = Cl.SMA20.Crossover, initDate = pv$init_date) 33 | strategy(Cl.SMA20.Crossover, store = TRUE) 34 | strat <- getStrategy(Cl.SMA20.Crossover) 35 | ``` 36 | 37 | ```{r 2-1-add-indicators} 38 | add.indicator(strategy = Cl.SMA20.Crossover, name = "SMA", 39 | arguments = list(x = quote(Cl(mktdata)), n = 20), label = "SMA20") 40 | ``` 41 | 42 | ```{r 2-1-add-signals} 43 | add.signal(Cl.SMA20.Crossover, name="sigCrossover", 44 | arguments = list(columns = c("Close", "SMA20"), relationship = "gte"), 45 | label="Cl.gte.SMA") 46 | add.signal(Cl.SMA20.Crossover, name = "sigCrossover", 47 | arguments = list(columns = c("Close", "SMA20"), relationship = "lt"), 48 | label = "Cl.lt.SMA") 49 | ``` 50 | 51 | ```{r 2-1-add-rules} 52 | # BTO when Cl crosses above SMA(20) 53 | add.rule(Cl.SMA20.Crossover, name = 'ruleSignal', 54 | arguments = list(sigcol = "Cl.gte.SMA", sigval = TRUE, orderqty = 100, 55 | ordertype = 'market', orderside = 'long'), 56 | type = 'enter') 57 | 58 | # STC when Cl crosses under SMA(20) 59 | add.rule(Cl.SMA20.Crossover, name = 'ruleSignal', 60 | arguments = list(sigcol = "Cl.lt.SMA", sigval = TRUE, orderqty = 'all', 61 | ordertype = 'market', orderside = 'long'), 62 | type = 'exit') 63 | ``` 64 | 65 | ```{r 2-1-apply-strategy, include = FALSE} 66 | applyStrategy(strategy = Cl.SMA20.Crossover, portfolios = Cl.SMA20.Crossover) 67 | ``` 68 | 69 | ```{r 2-1-update-portfolio} 70 | updatePortf(Cl.SMA20.Crossover) 71 | updateAcct(Cl.SMA20.Crossover) 72 | updateEndEq(Cl.SMA20.Crossover) 73 | checkBlotterUpdate(Cl.SMA20.Crossover, Cl.SMA20.Crossover) 74 | ``` 75 | 76 | ```{r 2-1-account-summary} 77 | a <- getAccount(Cl.SMA20.Crossover) 78 | p <- getPortfolio(Cl.SMA20.Crossover) 79 | ``` 80 | 81 | ### Per Trade Stats 82 | 83 | ```{r 2-1-trade-stats, include = TRUE} 84 | knitr::kable(t(tradeStats(Cl.SMA20.Crossover))[-c(1:2),], 85 | caption = "Trade Stats per Symbol") 86 | ``` 87 | 88 | ### Maximum Adverse Excursion 89 | 90 | ```{r 2-1-mae, include = TRUE, fig.caption = "Maximum Adverse Excursion by Symbol"} 91 | par(mfrow = c(2,2)) 92 | for(symbol in symbols) { 93 | chart.ME(Portfolio = Cl.SMA20.Crossover, Symbol = symbol, type = "MAE", 94 | scale = "percent") 95 | } 96 | par(mfrow = c(1,1)) 97 | ``` 98 | 99 | ### Maximum Favorable Excursion 100 | 101 | ```{r 2-1-mfe, include = TRUE, fig.caption = "Maximum Favorable Excursion by Symbol"} 102 | par(mfrow = c(2,2)) 103 | for(symbol in symbols) { 104 | chart.ME(Portfolio = Cl.SMA20.Crossover, Symbol = symbol, type = "MFE", 105 | scale = "percent", legend.loc = "none") 106 | } 107 | par(mfrow = c(1,1)) 108 | ``` 109 | 110 | ```{r 2-1-individual-asset-returns} 111 | rets.multi <- PortfReturns(Cl.SMA20.Crossover) 112 | colnames(rets.multi) <- symbols 113 | rets.multi <- na.omit(cbind(rets.multi, Return.calculate(a$summary$End.Eq))) 114 | names(rets.multi)[length(names(rets.multi))] <- "TOTAL" 115 | rets.multi <- rets.multi[,c("TOTAL", symbols)] 116 | ``` 117 | 118 | ### Cumulative Returns 119 | 120 | ```{r 2-1-return-distribution-analysis, include = TRUE, fig.cap = "Return Distribution Analysis"} 121 | as.data.frame(rets.multi) %>% 122 | mutate(Date = index(rets.multi)) %>% 123 | gather(key, value, 1:ncol(rets.multi)) %>% 124 | filter(value != 0) %>% 125 | ggplot(aes(x = key, y = value, fill = key)) + 126 | geom_boxplot() + 127 | coord_flip() + 128 | theme(legend.title = element_blank()) + 129 | theme_bw() + 130 | scale_x_discrete(name = NULL) + 131 | scale_y_continuous(name = NULL) + 132 | scale_fill_discrete(name = "Symbol") + 133 | ggtitle("Return Distribution Analysis") 134 | ``` 135 | 136 | ### Annualized Returns 137 | 138 | ```{r 2-1-annualized-risk-return, include = TRUE} 139 | ar.tab <- table.AnnualizedReturns(rets.multi) 140 | max.risk <- max(ar.tab["Annualized Std Dev",]) 141 | max.return <- max(ar.tab["Annualized Return",]) 142 | knitr::kable(data.frame("Max Risk" = max.risk, 143 | "Max Return" = max.return, 144 | "Ratio" = max.risk/max.return), 145 | booktabs = TRUE, caption = "Max Risk, Max Reward, Ratio") 146 | 147 | knitr::kable(t(ar.tab), booktabs = TRUE, caption = "Annualized Risk and Return") 148 | ``` 149 | 150 | ```{r 2-1-annualized-risk-return-chart, include = TRUE, fig.cap = "Annualized Risk and Return"} 151 | chart.RiskReturnScatter(rets.multi, main = "Performance", 152 | colorset = rich10equal, xlim = c(0, max.risk * 1.1), 153 | ylim = c(0, max.return)) 154 | ``` 155 | 156 | ```{r 2-1-consolidated-equity-curve, include = TRUE, fig.cap = "Consolidated Equity Curve"} 157 | equity <- a$summary$End.Eq 158 | plot(equity, main = "Consolidated Equity Curve") 159 | ``` 160 | 161 | ```{r 2-1-asummary, include = TRUE, fig.cap = "Account Summary"} 162 | a <- getAccount(Cl.SMA20.Crossover) 163 | xyplot(a$summary, type = "h", col = 4) 164 | ``` 165 | 166 | ```{r 2-1-performance-summary, include = TRUE} 167 | ret <- Return.calculate(equity, method = "log") 168 | charts.PerformanceSummary(ret, colorset = bluefocus, 169 | main = strategy_title) 170 | ``` 171 | -------------------------------------------------------------------------------- /R/basic-strategy.R: -------------------------------------------------------------------------------- 1 | library(quantstrat) 2 | 3 | source("R/symbols.R") 4 | source("R/functions.R") 5 | 6 | portfolio.st <- "Port.Luxor" 7 | account.st <- "Acct.Luxor" 8 | strategy.st <- "Strat.Luxor" 9 | init_date <- "2007-12-31" 10 | start_date <- "2008-01-01" 11 | end_date <- "2009-12-31" 12 | adjustment <- TRUE 13 | init_equity <- 1e4 # $10,000 14 | 15 | Sys.setenv(TZ = "UTC") 16 | 17 | currency('USD') 18 | 19 | symbols <- basic_symbols() 20 | 21 | getSymbols(Symbols = symbols, 22 | src = "yahoo", 23 | index.class = "POSIXct", 24 | from = start_date, 25 | to = end_date, 26 | adjust = adjustment) 27 | 28 | stock(symbols, 29 | currency = "USD", 30 | multiplier = 1) 31 | 32 | rm.strat(portfolio.st) 33 | rm.strat(account.st) 34 | 35 | initPortf(name = portfolio.st, 36 | symbols = symbols, 37 | initDate = init_date) 38 | 39 | initAcct(name = account.st, 40 | portfolios = portfolio.st, 41 | initDate = init_date, 42 | initEq = init_equity) 43 | 44 | initOrders(portfolio = portfolio.st, 45 | symbols = symbols, 46 | initDate = init_date) 47 | 48 | strategy(strategy.st, store = TRUE) 49 | 50 | add.indicator(strategy = strategy.st, 51 | name = "SMA", 52 | arguments = list(x = quote(Cl(mktdata)), 53 | n = 10), 54 | label = "nFast") 55 | 56 | add.indicator(strategy = strategy.st, 57 | name = "SMA", 58 | arguments = list(x = quote(Cl(mktdata)), 59 | n = 30), 60 | label = "nSlow") 61 | 62 | add.signal(strategy = strategy.st, 63 | name="sigCrossover", 64 | arguments = list(columns = c("nFast", "nSlow"), 65 | relationship = "gte"), 66 | label = "long") 67 | 68 | add.signal(strategy = strategy.st, 69 | name="sigCrossover", 70 | arguments = list(columns = c("nFast", "nSlow"), 71 | relationship = "lt"), 72 | label = "short") 73 | 74 | add.rule(strategy = strategy.st, 75 | name = "ruleSignal", 76 | arguments = list(sigcol = "long", 77 | sigval = TRUE, 78 | orderqty = 100, 79 | ordertype = "stoplimit", 80 | orderside = "long", 81 | threshold = 0.0005, 82 | prefer = "High", 83 | TxnFees = -10, 84 | replace = FALSE), 85 | type = "enter", 86 | label = "EnterLONG") 87 | 88 | add.rule(strategy.st, 89 | name = "ruleSignal", 90 | arguments = list(sigcol = "short", 91 | sigval = TRUE, 92 | orderqty = -100, 93 | ordertype = "stoplimit", 94 | threshold = -0.005, 95 | orderside = "short", 96 | replace = FALSE, 97 | TxnFees = -10, 98 | prefer = "Low"), 99 | type = "enter", 100 | label = "EnterSHORT") 101 | 102 | add.rule(strategy.st, 103 | name = "ruleSignal", 104 | arguments = list(sigcol = "short", 105 | sigval = TRUE, 106 | orderside = "long", 107 | ordertype = "market", 108 | orderqty = "all", 109 | TxnFees = -10, 110 | replace = TRUE), 111 | type = "exit", 112 | label = "Exit2SHORT") 113 | 114 | add.rule(strategy.st, 115 | name = "ruleSignal", 116 | arguments = list(sigcol = "long", 117 | sigval = TRUE, 118 | orderside = "short", 119 | ordertype = "market", 120 | orderqty = "all", 121 | TxnFees = -10, 122 | replace = TRUE), 123 | type = "exit", 124 | label = "Exit2LONG") 125 | 126 | cwd <- getwd() 127 | setwd("./_data/") 128 | results_file <- paste("results", strategy.st, "RData", sep = ".") 129 | if( file.exists(results_file) ) { 130 | load(results_file) 131 | } else { 132 | results <- applyStrategy(strategy.st, portfolios = portfolio.st, verbose = TRUE) 133 | updatePortf(portfolio.st) 134 | updateAcct(account.st) 135 | updateEndEq(account.st) 136 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 137 | save.strategy(strategy.st) 138 | } 139 | } 140 | setwd(cwd) 141 | -------------------------------------------------------------------------------- /R/functions.R: -------------------------------------------------------------------------------- 1 | #' Copyright (C) 2011-2014 Guy Yollin 2 | #' License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 3 | #' http://www.r-programming.org/papers 4 | checkBlotterUpdate <- function(port.st = portfolio.st, 5 | account.st = account.st, 6 | verbose = TRUE) { 7 | 8 | ok <- TRUE 9 | p <- getPortfolio(port.st) 10 | a <- getAccount(account.st) 11 | syms <- names(p$symbols) 12 | port.tot <- sum( 13 | sapply( 14 | syms, 15 | FUN = function(x) eval( 16 | parse( 17 | text = paste("sum(p$symbols", 18 | x, 19 | "posPL.USD$Net.Trading.PL)", 20 | sep = "$"))))) 21 | 22 | port.sum.tot <- sum(p$summary$Net.Trading.PL) 23 | 24 | if(!isTRUE(all.equal(port.tot, port.sum.tot))) { 25 | ok <- FALSE 26 | if(verbose) print("portfolio P&L doesn't match sum of symbols P&L") 27 | } 28 | 29 | initEq <- as.numeric(first(a$summary$End.Eq)) 30 | endEq <- as.numeric(last(a$summary$End.Eq)) 31 | 32 | if(!isTRUE(all.equal(port.tot, endEq - initEq)) ) { 33 | ok <- FALSE 34 | if(verbose) print("portfolio P&L doesn't match account P&L") 35 | } 36 | 37 | if(sum(duplicated(index(p$summary)))) { 38 | ok <- FALSE 39 | if(verbose)print("duplicate timestamps in portfolio summary") 40 | 41 | } 42 | 43 | if(sum(duplicated(index(a$summary)))) { 44 | ok <- FALSE 45 | if(verbose) print("duplicate timestamps in account summary") 46 | } 47 | return(ok) 48 | } 49 | 50 | #' Copyright (C) 2011-2014 Guy Yollin 51 | #' License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher 52 | #' http://www.r-programming.org/papers 53 | osFixedDollar <- function(timestamp, orderqty, portfolio, symbol, ruletype, ...) { 54 | if(!exists("trade_size")) stop("You must set trade_size") 55 | ClosePrice <- as.numeric(Cl(mktdata[timestamp,])) 56 | orderqty <- round(trade_size/ClosePrice,-2) 57 | return(orderqty) 58 | } 59 | -------------------------------------------------------------------------------- /R/load.R: -------------------------------------------------------------------------------- 1 | library(quantmod) 2 | library(quantstrat) 3 | 4 | knitr::opts_chunk$set(echo = FALSE, message = FALSE, warning = TRUE, 5 | include = FALSE, cache = FALSE, fig.align = "center") 6 | 7 | pv <- list( 8 | source = "yahoo", 9 | account_equity = 10000, 10 | transaction_fees = -50, 11 | init_date = as.POSIXct(Sys.Date()), 12 | start_date = "1900-01-01", 13 | end_date = "2015-12-31", 14 | adjust = TRUE 15 | ) 16 | 17 | initEq <- pv$account_equity 18 | 19 | Sys.setenv(TZ = "UTC") 20 | 21 | currency('USD') 22 | 23 | #' Basic Symbols 24 | #' 25 | #' IWM, QQQ, SPY and TLT for basic analysis 26 | #' 27 | #' @return a list of basic stock symbols 28 | #' @export 29 | #' 30 | basic_symbols <- function() { 31 | symbols <- c( 32 | "IWM", # iShares Russell 2000 Index ETF 33 | "QQQ", # PowerShares QQQ TRust, Series 1 ETF 34 | "SPY", # SPDR S&P 500 ETF Trust 35 | "TLT" # iShares Barclays 20+ Yr Treas. Bond ETF 36 | ) 37 | } 38 | 39 | #' Enhanced Symbols 40 | #' 41 | #' Includes SPDR ETFs 42 | #' 43 | #' @return a list of stock symbols including basic_symbols() and sector SPDRs 44 | #' @export 45 | #' 46 | enhanced_symbols <- function() { 47 | symbols <- c( 48 | "IWM", # iShares Russell 2000 Index ETF 49 | "QQQ", # PowerShares QQQ TRust, Series 1 ETF 50 | "SPY", # SPDR S&P 500 ETF Trust 51 | "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF 52 | "XLB", # Materials Select Sector SPDR ETF 53 | "XLE", # Energy Select Sector SPDR ETF 54 | "XLF", # Financial Select Sector SPDR ETF 55 | "XLI", # Industrials Select Sector SPDR ETF 56 | "XLK", # Technology Select Sector SPDR ETF 57 | "XLP", # Consumer Staples Select Sector SPDR ETF 58 | "XLU", # Utilities Select Sector SPDR ETF 59 | "XLV", # Health Care Select Sector SPDR ETF 60 | "XLY" # Consumer Discretionary Select Sector SPDR ETF 61 | ) 62 | } 63 | 64 | #' Global Symbols 65 | #' 66 | #' @return a list of global stock symbols 67 | #' @export 68 | #' 69 | global_symbols <- function() { 70 | symbols <- c( 71 | "EFA", # iShares EAFE 72 | "EPP", # iShares Pacific Ex Japan 73 | "EWA", # iShares Australia 74 | "EWC", # iShares Canada 75 | "EWG", # iShares Germany 76 | "EWH", # iShares Hong Kong 77 | "EWJ", # iShares Japan 78 | "EWS", # iShares Singapore 79 | "EWT", # iShares Taiwan 80 | "EWU", # iShares UK 81 | "EWY", # iShares South Korea 82 | "EWZ", # iShares Brazil 83 | "EZU", # iShares MSCI EMU ETF 84 | "IGE", # iShares North American Natural Resources 85 | "IWM", # iShares Russell 2000 Index ETF 86 | "IYR", # iShares U.S. Real Estate 87 | "IYZ", # iShares U.S. Telecom 88 | "LQD", # iShares Investment Grade Corporate Bonds 89 | "QQQ", # PowerShares QQQ TRust, Series 1 ETF 90 | "SHY", # iShares 42372 year TBonds 91 | "SPY", # SPDR S&P 500 ETF Trust 92 | "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF 93 | "XLB", # Materials Select Sector SPDR ETF 94 | "XLE", # Energy Select Sector SPDR ETF 95 | "XLF", # Financial Select Sector SPDR ETF 96 | "XLI", # Industrials Select Sector SPDR ETF 97 | "XLK", # Technology Select Sector SPDR ETF 98 | "XLP", # Consumer Staples Select Sector SPDR ETF 99 | "XLU", # Utilities Select Sector SPDR ETF 100 | "XLV", # Health Care Select Sector SPDR ETF 101 | "XLY" # Consumer Discretionary Select Sector SPDR ETF 102 | ) 103 | } 104 | 105 | symbols <- enhanced_symbols() 106 | 107 | getSymbols(symbols, src = pv$source, index.class = c("POSIXt", "POSIXct"), 108 | from = pv$start_date, to = pv$end_date, adjust = pv$adjust) 109 | 110 | for(symbol in symbols) { 111 | stock(symbol, currency = "USD", multiplier = 1) 112 | x <- get(symbol) 113 | indexFormat(x) <- "%Y-%m-%d" 114 | colnames(x) <- gsub("x", symbol, colnames(x)) 115 | assign(symbol, x) 116 | # Set pv$init_date to one day prior to earliest date in all of symbols 117 | if(pv$init_date > min(index(x))) pv$init_date = min(index(x)) - 86400 118 | rm(x) 119 | } 120 | -------------------------------------------------------------------------------- /R/parallel-test.R: -------------------------------------------------------------------------------- 1 | start_t <- Sys.time() 2 | 3 | library(quantstrat) 4 | library(doMC) 5 | 6 | stock.str <- "AAPL" # what are we trying it on 7 | 8 | #MA parameters for MACD 9 | fastMA <- 12 10 | slowMA <- 26 11 | signalMA <- 9 12 | maType <- "EMA" 13 | .FastMA <- (1:20) 14 | .SlowMA <- (30:80) 15 | 16 | currency("USD") 17 | stock(stock.str, currency = "USD", multiplier = 1) 18 | 19 | start_date <- "2006-12-31" 20 | initEq <- 1000000 21 | portfolio.st <- "macd" 22 | account.st <- "macd" 23 | 24 | rm.strat(portfolio.st) 25 | rm.strat(account.st) 26 | 27 | initPortf(portfolio.st, symbols = stock.str) 28 | initAcct(account.st, portfolios = portfolio.st) 29 | initOrders(portfolio = portfolio.st) 30 | 31 | strat.st <- portfolio.st 32 | # define the strategy 33 | strategy(strat.st, store = TRUE) 34 | 35 | #one indicator 36 | add.indicator(strat.st, 37 | name = "MACD", 38 | arguments = list(x = quote(Cl(mktdata)), 39 | nFast = fastMA, 40 | nSlow = slowMA), 41 | label = "_") 42 | 43 | #two signals 44 | add.signal(strat.st, 45 | name = "sigThreshold", 46 | arguments = list(column = "signal._", 47 | relationship = "gt", 48 | threshold = 0, 49 | cross = TRUE), 50 | label = "signal.gt.zero") 51 | 52 | add.signal(strat.st, 53 | name = "sigThreshold", 54 | arguments = list(column = "signal._", 55 | relationship = "lt", 56 | threshold = 0, 57 | cross = TRUE), 58 | label = "signal.lt.zero") 59 | 60 | # add rules 61 | 62 | # entry 63 | add.rule(strat.st, 64 | name = "ruleSignal", 65 | arguments = list(sigcol = "signal.gt.zero", 66 | sigval = TRUE, 67 | orderqty = 100, 68 | ordertype = "market", 69 | orderside = "long", 70 | threshold = NULL), 71 | type = "enter", 72 | label = "enter", 73 | storefun = FALSE) 74 | 75 | # exit 76 | add.rule(strat.st, 77 | name = "ruleSignal", 78 | arguments = list(sigcol = "signal.lt.zero", 79 | sigval = TRUE, 80 | orderqty = "all", 81 | ordertype = "market", 82 | orderside = "long", 83 | threshold = NULL, 84 | orderset = "exit2"), 85 | type = "exit", 86 | label = "exit") 87 | 88 | ### MA paramset 89 | 90 | add.distribution(strat.st, 91 | paramset.label = "MA", 92 | component.type = "indicator", 93 | component.label = "_", #this is the label given to the indicator in the strat 94 | variable = list(n = .FastMA), 95 | label = "nFAST") 96 | 97 | add.distribution(strat.st, 98 | paramset.label = "MA", 99 | component.type = "indicator", 100 | component.label = "_", #this is the label given to the indicator in the strat 101 | variable = list(n = .SlowMA), 102 | label = "nSLOW") 103 | 104 | add.distribution.constraint(strat.st, 105 | paramset.label = "MA", 106 | distribution.label.1 = "nFAST", 107 | distribution.label.2 = "nSLOW", 108 | operator = "<", 109 | label = "MA") 110 | 111 | if( Sys.info()['sysname'] == "Windows") { 112 | library(doParallel) 113 | registerDoParallel(cores = parallel::detectCores()) 114 | } else { 115 | library(doMC) 116 | registerDoMC(cores = parallel::detectCores()) 117 | } 118 | 119 | getSymbols(stock.str, from = start_date, to = "2014-06-01") 120 | 121 | results <- apply.paramset(strat.st, 122 | paramset.label = "MA", 123 | portfolio.st = portfolio.st, 124 | account.st = account.st, 125 | nsamples = 0, 126 | verbose = TRUE) 127 | 128 | updatePortf(Portfolio = portfolio.st,Dates = paste("::",as.Date(Sys.time()),sep = "")) 129 | 130 | end_t <- Sys.time() 131 | print(end_t-start_t) 132 | -------------------------------------------------------------------------------- /R/parameter-optimization.R: -------------------------------------------------------------------------------- 1 | library(quantstrat) 2 | 3 | source("R/symbols.R") 4 | source("R/functions.R") 5 | 6 | portfolio.st <- "Port.Luxor.MA.Opt" 7 | account.st <- "Acct.Luxor.MA.Opt" 8 | strategy.st <- "Strat.Luxor.MA.Opt" 9 | init_date <- "2007-12-31" 10 | start_date <- "2008-01-01" 11 | end_date <- "2009-12-31" 12 | adjustment <- TRUE 13 | init_equity <- 1e4 # $10,000 14 | .fast <- 10 15 | .slow <- 30 16 | # .fastSMA <- (1:30) 17 | # .slowSMA <- (20:80) 18 | .fastSMA <- (1:30) 19 | .slowSMA <- (20:80) 20 | .threshold <- 0.0005 21 | .txnfees <- -10 22 | .orderqty <- 100 23 | .nsamples <- 5 24 | 25 | Sys.setenv(TZ = "UTC") 26 | 27 | currency('USD') 28 | 29 | symbols <- basic_symbols() 30 | 31 | getSymbols(Symbols = symbols, 32 | src = "yahoo", 33 | index.class = "POSIXct", 34 | from = start_date, 35 | to = end_date, 36 | adjust = adjustment) 37 | 38 | stock(symbols, 39 | currency = "USD", 40 | multiplier = 1) 41 | 42 | rm.strat(portfolio.st) 43 | rm.strat(account.st) 44 | 45 | initPortf(name = portfolio.st, 46 | symbols = symbols, 47 | initDate = init_date) 48 | 49 | initAcct(name = account.st, 50 | portfolios = portfolio.st, 51 | initDate = init_date, 52 | initEq = init_equity) 53 | 54 | initOrders(portfolio = portfolio.st, 55 | symbols = symbols, 56 | initDate = init_date) 57 | 58 | strategy(strategy.st, store = TRUE) 59 | 60 | add.indicator(strategy = strategy.st, 61 | name = "SMA", 62 | arguments = list(x = quote(Cl(mktdata)[,1]), 63 | n = .fast), 64 | label = "nFast") 65 | 66 | add.indicator(strategy = strategy.st, 67 | name = "SMA", 68 | arguments = list(x = quote(Cl(mktdata)[,1]), 69 | n = .slow), 70 | label = "nSlow") 71 | 72 | add.signal(strategy = strategy.st, 73 | name="sigCrossover", 74 | arguments = list(columns = c("nFast", "nSlow"), 75 | relationship = "gte"), 76 | label = "long") 77 | 78 | add.signal(strategy = strategy.st, 79 | name="sigCrossover", 80 | arguments = list(columns = c("nFast", "nSlow"), 81 | relationship = "lt"), 82 | label = "short") 83 | 84 | add.rule(strategy = strategy.st, 85 | name = "ruleSignal", 86 | arguments = list(sigcol = "long", 87 | sigval = TRUE, 88 | orderqty = .orderqty, 89 | ordertype = "stoplimit", 90 | orderside = "long", 91 | threshold = -.threshold, 92 | prefer = "High", 93 | TxnFees = .txnfees, 94 | replace = FALSE), 95 | type = "enter", 96 | label = "EnterLONG") 97 | 98 | add.rule(strategy.st, 99 | name = "ruleSignal", 100 | arguments = list(sigcol = "short", 101 | sigval = TRUE, 102 | orderqty = -.orderqty, 103 | ordertype = "stoplimit", 104 | threshold = -.threshold, 105 | orderside = "short", 106 | replace = FALSE, 107 | TxnFees = .txnfees, 108 | prefer = "Low"), 109 | type = "enter", 110 | label = "EnterSHORT") 111 | 112 | add.rule(strategy.st, 113 | name = "ruleSignal", 114 | arguments = list(sigcol = "short", 115 | sigval = TRUE, 116 | orderside = "long", 117 | ordertype = "market", 118 | orderqty = "all", 119 | TxnFees = .txnfees, 120 | replace = TRUE), 121 | type = "exit", 122 | label = "Exit2SHORT") 123 | 124 | add.rule(strategy.st, 125 | name = "ruleSignal", 126 | arguments = list(sigcol = "long", 127 | sigval = TRUE, 128 | orderside = "short", 129 | ordertype = "market", 130 | orderqty = "all", 131 | TxnFees = .txnfees, 132 | replace = TRUE), 133 | type = "exit", 134 | label = "Exit2LONG") 135 | 136 | add.distribution(strategy.st, 137 | paramset.label = "SMA", 138 | component.type = "indicator", 139 | component.label = "nFast", 140 | variable = list(n = .fastSMA), 141 | label = "nFAST") 142 | 143 | add.distribution(strategy.st, 144 | paramset.label = "SMA", 145 | component.type = "indicator", 146 | component.label = "nSlow", 147 | variable = list(n = .slowSMA), 148 | label = "nSLOW") 149 | 150 | add.distribution.constraint(strategy.st, 151 | paramset.label = "SMA", 152 | distribution.label.1 = "nFAST", 153 | distribution.label.2 = "nSLOW", 154 | operator = "<", 155 | label = "SMA.Constraint") 156 | 157 | library(parallel) 158 | 159 | if( Sys.info()['sysname'] == "Windows") { 160 | library(doParallel) 161 | registerDoParallel(cores = detectCores()) 162 | } else { 163 | library(doMC) 164 | registerDoMC(cores = detectCores()) 165 | } 166 | 167 | cwd <- getwd() 168 | setwd("./_data/") 169 | results_file <- paste("results", strategy.st, "RData", sep = ".") 170 | if( file.exists(results_file) ) { 171 | load(results_file) 172 | } else { 173 | results <- apply.paramset(strategy.st, 174 | paramset.label = "SMA", 175 | portfolio.st = portfolio.st, 176 | account.st = account.st, 177 | nsamples = .nsamples) 178 | updatePortf(portfolio.st) 179 | updateAcct(account.st) 180 | updateEndEq(account.st) 181 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 182 | save(list = "results", file = results_file) 183 | save.strategy(strategy.st) 184 | } 185 | } 186 | setwd(cwd) 187 | -------------------------------------------------------------------------------- /R/stop-loss-optimization.R: -------------------------------------------------------------------------------- 1 | library(quantstrat) 2 | library(doMC) 3 | registerDoMC(cores = parallel::detectCores()) 4 | 5 | portfolio.st <- "Port.Luxor.Stop.Loss.Opt" 6 | account.st <- "Acct.Luxor.Stop.Loss.Opt" 7 | strategy.st <- "Strat.Luxor.Stop.Loss.Opt" 8 | init_date <- "2007-12-31" 9 | start_date <- "2008-01-01" 10 | end_date <- "2009-12-31" 11 | init_equity <- 1e4 # $10,000 12 | adjustment <- TRUE 13 | 14 | .fast <- 10 15 | .slow <- 30 16 | .FastSMA <- (1:30) 17 | .SlowSMA <- (20:80) 18 | .threshold <- 0.0005 19 | .orderqty <- 1000 20 | .txnfees <- -10 21 | .stoploss <- 0.003 22 | .StopLoss <- seq(0.05, 0.6, length.out = 48)/100 23 | .nsamples = 10 24 | 25 | source("R/symbols.R") 26 | source("R/functions.R") 27 | 28 | Sys.setenv(TZ = "UTC") 29 | 30 | currency('USD') 31 | 32 | symbols <- basic_symbols() 33 | 34 | getSymbols(Symbols = symbols, 35 | src = "yahoo", 36 | index.class = "POSIXct", 37 | from = start_date, 38 | to = end_date, 39 | adjust = adjustment) 40 | 41 | stock(symbols, currency = "USD", multiplier = 1) 42 | 43 | rm.strat(portfolio.st) 44 | rm.strat(account.st) 45 | 46 | initPortf(name = portfolio.st, 47 | symbols = symbols, 48 | initDate = init_date) 49 | 50 | initAcct(name = account.st, 51 | portfolios = portfolio.st, 52 | initDate = init_date) 53 | 54 | initOrders(portfolio = portfolio.st, 55 | initDate = init_date) 56 | 57 | strategy(strategy.st, store = TRUE) 58 | 59 | add.indicator(strategy.st, 60 | name = "SMA", 61 | arguments = list(x = quote(Cl(mktdata)[,1]), 62 | n = .fast), 63 | label = "nFast") 64 | 65 | add.indicator(strategy.st, 66 | name = "SMA", 67 | arguments = list(x = quote(Cl(mktdata)[,1]), 68 | n = .slow), 69 | label = "nSlow") 70 | 71 | add.signal(strategy.st, 72 | name = "sigCrossover", 73 | arguments = list(columns = c("nFast", "nSlow"), 74 | relationship = "gte"), 75 | label = "long" 76 | ) 77 | add.signal(strategy.st, 78 | name = "sigCrossover", 79 | arguments = list(columns = c("nFast", "nSlow"), 80 | relationship = "lt"), 81 | label = "short") 82 | 83 | add.rule(strategy.st, 84 | name = "ruleSignal", 85 | arguments = list(sigcol = "long" , 86 | sigval = TRUE, 87 | replace = FALSE, 88 | orderside = "long" , 89 | ordertype = "stoplimit", 90 | prefer = "High", 91 | threshold = .threshold, 92 | TxnFees = .txnfees, 93 | orderqty = +.orderqty, 94 | osFUN = osMaxPos, 95 | orderset = "ocolong"), 96 | type = "enter", 97 | label = "EnterLONG") 98 | 99 | add.rule(strategy.st, 100 | name = "ruleSignal", 101 | arguments = list(sigcol = "short", 102 | sigval = TRUE, 103 | replace = FALSE, 104 | orderside = "short", 105 | ordertype = "stoplimit", 106 | prefer = "Low", 107 | threshold = .threshold, 108 | TxnFees = .txnfees, 109 | orderqty = -.orderqty, 110 | osFUN = osMaxPos, 111 | orderset = "ocoshort"), 112 | type = "enter", 113 | label = "EnterSHORT") 114 | 115 | add.rule(strategy.st, 116 | name = "ruleSignal", 117 | arguments = list(sigcol = "short", 118 | sigval = TRUE, 119 | replace = TRUE, 120 | orderside = "long" , 121 | ordertype = "market", 122 | TxnFees = .txnfees, 123 | orderqty = "all", 124 | orderset = "ocolong"), 125 | type = "exit", 126 | label = "Exit2SHORT") 127 | 128 | add.rule(strategy.st, 129 | name = "ruleSignal", 130 | arguments = list(sigcol = "long", 131 | sigval = TRUE, 132 | replace = TRUE, 133 | orderside = "short", 134 | ordertype = "market", 135 | TxnFees = .txnfees, 136 | orderqty = "all", 137 | orderset = "ocoshort"), 138 | type = "exit", 139 | label = "Exit2LONG") 140 | 141 | add.rule(strategy.st, 142 | name = "ruleSignal", 143 | arguments = list(sigcol = "long" , 144 | sigval = TRUE, 145 | replace = FALSE, 146 | orderside = "long", 147 | ordertype = "stoplimit", 148 | tmult = TRUE, 149 | threshold = quote(.stoploss), 150 | TxnFees = .txnfees, 151 | orderqty = "all", 152 | orderset = "ocolong"), 153 | type = "chain", 154 | parent = "EnterLONG", 155 | label = "StopLossLONG", 156 | enabled = FALSE) 157 | 158 | add.rule(strategy.st, 159 | name = "ruleSignal", 160 | arguments = list(sigcol = "short", 161 | sigval = TRUE, 162 | replace = FALSE, 163 | orderside = "short", 164 | ordertype = "stoplimit", 165 | tmult = TRUE, 166 | threshold = quote(.stoploss), 167 | TxnFees = .txnfees, 168 | orderqty = "all", 169 | orderset = "ocoshort"), 170 | type = "chain", 171 | parent = "EnterSHORT", 172 | label = "StopLossSHORT", 173 | enabled = FALSE) 174 | 175 | for(symbol in symbols){ 176 | addPosLimit(portfolio = portfolio.st, 177 | symbol = symbol, 178 | timestamp = init_date, 179 | maxpos = .orderqty) 180 | } 181 | 182 | add.distribution(strategy.st, 183 | paramset.label = "StopLoss", 184 | component.type = "chain", 185 | component.label = "StopLossLONG", 186 | variable = list(threshold = .StopLoss), 187 | label = "StopLossLONG") 188 | 189 | add.distribution(strategy.st, 190 | paramset.label = "StopLoss", 191 | component.type = "chain", 192 | component.label = "StopLossSHORT", 193 | variable = list(threshold = .StopLoss), 194 | label = "StopLossSHORT") 195 | 196 | add.distribution.constraint(strategy.st, 197 | paramset.label = "StopLoss", 198 | distribution.label.1 = "StopLossLONG", 199 | distribution.label.2 = "StopLossSHORT", 200 | operator = "==", 201 | label = "StopLoss") 202 | 203 | enable.rule(strategy.st, "chain", "StopLoss") 204 | 205 | if( Sys.info()['sysname'] == "Windows") { 206 | library(doParallel) 207 | registerDoParallel(cores = parallel::detectCores()) 208 | } else { 209 | library(doMC) 210 | registerDoMC(cores = parallel::detectCores()) 211 | } 212 | 213 | cwd <- getwd() 214 | setwd("./_data/") 215 | results_file <- paste("results", strategy.st, "RData", sep = ".") 216 | if( file.exists(results_file) ) { 217 | load(results_file) 218 | } else { 219 | results <- apply.paramset(strategy.st, 220 | paramset.label = "StopLoss", 221 | portfolio.st = portfolio.st, 222 | account.st = account.st, 223 | nsamples = .nsamples, 224 | verbose = TRUE) 225 | updatePortf(portfolio.st) 226 | updateAcct(account.st) 227 | updateEndEq(account.st) 228 | 229 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 230 | save(list = "results", file = results_file) 231 | save.strategy(strategy.st) 232 | } 233 | } 234 | setwd(cwd) 235 | -------------------------------------------------------------------------------- /R/stop-loss.R: -------------------------------------------------------------------------------- 1 | library(quantstrat) 2 | 3 | source("R/symbols.R") 4 | source("R/functions.R") 5 | 6 | portfolio.st <- "Port.Luxor.Stop.Loss" 7 | account.st <- "Acct.Luxor.Stop.Loss" 8 | strategy.st <- "Strat.Luxor.Stop.Loss" 9 | init_date <- "2007-12-31" 10 | start_date <- "2008-01-01" 11 | end_date <- "2009-12-31" 12 | init_equity <- 1e4 # $10,000 13 | adjustment <- TRUE 14 | .fast <- 10 15 | .slow <- 30 16 | .threshold <- 0.0005 17 | .orderqty <- 100 18 | .txnfees <- -10 19 | .stoploss <- 3e-3 # 0.003 or 0.3% 20 | 21 | Sys.setenv(TZ = "UTC") 22 | 23 | currency('USD') 24 | 25 | symbols <- basic_symbols() 26 | 27 | getSymbols(Symbols = symbols, 28 | src = "yahoo", 29 | index.class = "POSIXct", 30 | from = start_date, 31 | to = end_date, 32 | adjust = adjustment) 33 | 34 | stock(symbols, 35 | currency = "USD", 36 | multiplier = 1) 37 | 38 | rm.strat(portfolio.st) 39 | rm.strat(account.st) 40 | 41 | initPortf(name = portfolio.st, 42 | symbols = symbols, 43 | initDate = init_date) 44 | 45 | initAcct(name = account.st, 46 | portfolios = portfolio.st, 47 | initDate = init_date, 48 | initEq = init_equity) 49 | 50 | initOrders(portfolio = portfolio.st, 51 | symbols = symbols, 52 | initDate = init_date) 53 | 54 | strategy(strategy.st, store = TRUE) 55 | 56 | add.indicator(strategy.st, 57 | name = "SMA", 58 | arguments = list(x = quote(Cl(mktdata)), 59 | n = .fast), 60 | label = "nFast") 61 | 62 | add.indicator(strategy.st, 63 | name = "SMA", 64 | arguments = list(x = quote(Cl(mktdata)), 65 | n = .slow), 66 | label = "nSlow") 67 | 68 | add.signal(strategy.st, 69 | name = "sigCrossover", 70 | arguments = list(columns = c("nFast", "nSlow"), 71 | relationship = "gte"), 72 | label = "long") 73 | 74 | add.signal(strategy.st, 75 | name = "sigCrossover", 76 | arguments = list(columns = c("nFast", "nSlow"), 77 | relationship = "lt"), 78 | label = "short") 79 | 80 | add.rule(strategy.st, 81 | name = "ruleSignal", 82 | arguments = list(sigcol = "long" , 83 | sigval = TRUE, 84 | replace = FALSE, 85 | orderside = "long" , 86 | ordertype = "stoplimit", 87 | prefer = "High", 88 | threshold = .threshold, 89 | TxnFees = .txnfees, 90 | orderqty = +.orderqty, 91 | osFUN = osMaxPos, 92 | orderset = "ocolong"), 93 | type = "enter", 94 | label = "EnterLONG") 95 | 96 | add.rule(strategy.st, 97 | name = "ruleSignal", 98 | arguments = list(sigcol = "short", 99 | sigval = TRUE, 100 | replace = FALSE, 101 | orderside = "short", 102 | ordertype = "stoplimit", 103 | prefer = "Low", 104 | threshold = .threshold, 105 | TxnFees = .txnfees, 106 | orderqty = -.orderqty, 107 | osFUN = osMaxPos, 108 | orderset = "ocoshort"), 109 | type = "enter", 110 | label = "EnterSHORT") 111 | 112 | add.rule(strategy.st, 113 | name = "ruleSignal", 114 | arguments = list(sigcol = "short", 115 | sigval = TRUE, 116 | replace = TRUE, 117 | orderside = "long" , 118 | ordertype = "market", 119 | TxnFees = .txnfees, 120 | orderqty = "all", 121 | orderset = "ocolong"), 122 | type = "exit", 123 | label = "Exit2SHORT") 124 | 125 | add.rule(strategy.st, 126 | name = "ruleSignal", 127 | arguments = list(sigcol = "long", 128 | sigval = TRUE, 129 | replace = TRUE, 130 | orderside = "short", 131 | ordertype = "market", 132 | TxnFees = .txnfees, 133 | orderqty = "all", 134 | orderset = "ocoshort"), 135 | type = "exit", 136 | label = "Exit2LONG") 137 | 138 | add.rule(strategy.st, 139 | name = "ruleSignal", 140 | arguments = list(sigcol = "long" , 141 | sigval = TRUE, 142 | replace = FALSE, 143 | orderside = "long", 144 | ordertype = "stoplimit", 145 | tmult = TRUE, 146 | threshold = quote(.stoploss), 147 | TxnFees = .txnfees, 148 | orderqty = "all", 149 | orderset = "ocolong"), 150 | type = "chain", 151 | parent = "EnterLONG", 152 | label = "StopLossLONG", 153 | enabled = FALSE) 154 | 155 | add.rule(strategy.st, 156 | name = "ruleSignal", 157 | arguments = list(sigcol = "short", 158 | sigval = TRUE, 159 | replace = FALSE, 160 | orderside = "short", 161 | ordertype = "stoplimit", 162 | tmult = TRUE, 163 | threshold = quote(.stoploss), 164 | TxnFees = .txnfees, 165 | orderqty = "all", 166 | orderset = "ocoshort"), 167 | type = "chain", 168 | parent = "EnterSHORT", 169 | label = "StopLossSHORT", 170 | enabled = FALSE) 171 | 172 | for(symbol in symbols){ 173 | addPosLimit(portfolio = portfolio.st, 174 | symbol = symbol, 175 | timestamp = init_date, 176 | maxpos = .orderqty) 177 | } 178 | 179 | enable.rule(strategy.st, type = "chain", label = "StopLoss") 180 | 181 | cwd <- getwd() 182 | setwd("./_data/") 183 | results_file <- paste("results", strategy.st, "RData", sep = ".") 184 | if( file.exists(results_file) ) { 185 | load(results_file) 186 | } else { 187 | results <- applyStrategy(strategy.st, portfolios = portfolio.st) 188 | updatePortf(portfolio.st) 189 | updateAcct(account.st) 190 | updateEndEq(account.st) 191 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 192 | save.strategy(strategy.st) 193 | } 194 | } 195 | setwd(cwd) 196 | -------------------------------------------------------------------------------- /R/symbols.R: -------------------------------------------------------------------------------- 1 | #' Basic Symbols 2 | #' 3 | #' IWM, QQQ, SPY and TLT for basic analysis 4 | #' 5 | #' @return a list of basic stock symbols 6 | #' @export 7 | #' 8 | basic_symbols <- function() { 9 | symbols <- c( 10 | "IWM", # iShares Russell 2000 Index ETF 11 | "QQQ", # PowerShares QQQ TRust, Series 1 ETF 12 | "SPY", # SPDR S&P 500 ETF Trust 13 | "TLT" # iShares Barclays 20+ Yr Treas. Bond ETF 14 | ) 15 | } 16 | 17 | #' Enhanced Symbols 18 | #' 19 | #' Includes SPDR ETFs 20 | #' 21 | #' @return a list of stock symbols including basic_symbols() and sector SPDRs 22 | #' @export 23 | #' 24 | enhanced_symbols <- function() { 25 | symbols <- c( 26 | "IWM", # iShares Russell 2000 Index ETF 27 | "QQQ", # PowerShares QQQ TRust, Series 1 ETF 28 | "SPY", # SPDR S&P 500 ETF Trust 29 | "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF 30 | "XLB", # Materials Select Sector SPDR ETF 31 | "XLE", # Energy Select Sector SPDR ETF 32 | "XLF", # Financial Select Sector SPDR ETF 33 | "XLI", # Industrials Select Sector SPDR ETF 34 | "XLK", # Technology Select Sector SPDR ETF 35 | "XLP", # Consumer Staples Select Sector SPDR ETF 36 | "XLU", # Utilities Select Sector SPDR ETF 37 | "XLV", # Health Care Select Sector SPDR ETF 38 | "XLY" # Consumer Discretionary Select Sector SPDR ETF 39 | ) 40 | } 41 | 42 | #' Global Symbols 43 | #' 44 | #' @return a list of global stock symbols 45 | #' @export 46 | #' 47 | global_symbols <- function() { 48 | symbols <- c( 49 | "EFA", # iShares EAFE 50 | "EPP", # iShares Pacific Ex Japan 51 | "EWA", # iShares Australia 52 | "EWC", # iShares Canada 53 | "EWG", # iShares Germany 54 | "EWH", # iShares Hong Kong 55 | "EWJ", # iShares Japan 56 | "EWS", # iShares Singapore 57 | "EWT", # iShares Taiwan 58 | "EWU", # iShares UK 59 | "EWY", # iShares South Korea 60 | "EWZ", # iShares Brazil 61 | "EZU", # iShares MSCI EMU ETF 62 | "IGE", # iShares North American Natural Resources 63 | "IWM", # iShares Russell 2000 Index ETF 64 | "IYR", # iShares U.S. Real Estate 65 | "IYZ", # iShares U.S. Telecom 66 | "LQD", # iShares Investment Grade Corporate Bonds 67 | "QQQ", # PowerShares QQQ TRust, Series 1 ETF 68 | "SHY", # iShares 42372 year TBonds 69 | "SPY", # SPDR S&P 500 ETF Trust 70 | "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF 71 | "XLB", # Materials Select Sector SPDR ETF 72 | "XLE", # Energy Select Sector SPDR ETF 73 | "XLF", # Financial Select Sector SPDR ETF 74 | "XLI", # Industrials Select Sector SPDR ETF 75 | "XLK", # Technology Select Sector SPDR ETF 76 | "XLP", # Consumer Staples Select Sector SPDR ETF 77 | "XLU", # Utilities Select Sector SPDR ETF 78 | "XLV", # Health Care Select Sector SPDR ETF 79 | "XLY" # Consumer Discretionary Select Sector SPDR ETF 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /R/trailing-stop.R: -------------------------------------------------------------------------------- 1 | library(quantstrat) 2 | 3 | source("R/symbols.R") 4 | source("R/functions.R") 5 | 6 | portfolio.st <- "Port.MACD.TS" 7 | account.st <- "Acct.MACD.TS" 8 | strategy.st <- "Strat.MACD.TS" 9 | init_date <- "2007-12-31" 10 | start_date <- "2008-01-01" 11 | end_date <- "2009-12-31" 12 | init_equity <- 1e5 # $100,000 13 | trade_size <- init_equity/3 # Using 3 symbols 14 | adjustment <- TRUE 15 | trailingStopPercent <- 0.07 16 | 17 | Sys.setenv(TZ = "UTC") 18 | 19 | currency('USD') 20 | 21 | symbols <- basic_symbols() 22 | 23 | getSymbols(Symbols = symbols, 24 | src = "yahoo", 25 | index.class = "POSIXct", 26 | from = start_date, 27 | to = end_date, 28 | adjust = adjustment) 29 | 30 | stock(symbols, 31 | currency = "USD", 32 | multiplier = 1) 33 | 34 | rm.strat(portfolio.st) 35 | rm.strat(account.st) 36 | 37 | initPortf(name = portfolio.st, 38 | symbols = symbols, 39 | initDate = init_date) 40 | 41 | initAcct(name = account.st, 42 | portfolios = portfolio.st, 43 | initDate = init_date, 44 | initEq = init_equity) 45 | 46 | initOrders(portfolio = portfolio.st, 47 | symbols = symbols, 48 | initDate = init_date) 49 | 50 | strategy(strategy.st, store = TRUE) 51 | 52 | add.indicator(strategy = strategy.st, 53 | name = "MACD", 54 | arguments = list(x = quote(Cl(mktdata))), 55 | label = "osc") 56 | 57 | add.signal(strategy = strategy.st, 58 | name="sigThreshold", 59 | arguments = list(column ="signal.osc", 60 | relationshipo = "gt", 61 | threshold = 0, 62 | cross = TRUE), 63 | label = "signal.gt.zero") 64 | 65 | add.signal(strategy = strategy.st, 66 | name="sigThreshold", 67 | arguments = list(column = "signal.osc", 68 | relationship = "lt", 69 | threshold = 0, 70 | cross = TRUE), 71 | label = "signal.lt.zero") 72 | 73 | add.rule(strategy = strategy.st, 74 | name = "ruleSignal", 75 | arguments = list(sigcol = "signal.gt.zero", 76 | sigval = TRUE, 77 | orderqty = 100, 78 | orderside = "long", 79 | ordertype = "market", 80 | osFUN = "osFixedDollar", 81 | orderset = "ocolong"), 82 | type = "enter", 83 | label = "LE") 84 | 85 | add.rule(strategy = strategy.st, 86 | name = "ruleSignal", 87 | arguments = list(sigcol = "signal.lt.zero", 88 | sigval = TRUE, 89 | replace = TRUE, 90 | orderside = "long", 91 | ordertype = "market", 92 | orderqty = "all", 93 | orderset = "ocolong"), 94 | type = "exit", 95 | label = "LX") 96 | 97 | add.rule(strategy = strategy.st, 98 | name = "ruleSignal", 99 | arguments = list(sigcol = "signal.gt.zero", 100 | sigval = TRUE, 101 | replace = FALSE, 102 | orderside = "long", 103 | ordertype = "stoptrailing", 104 | tmult = TRUE, 105 | threshold = quote(trailingStopPercent), 106 | orderqty = "all", 107 | orderset = "ocolong"), 108 | type = "chain", 109 | parent = "LE", 110 | label = "StopTrailingLong", 111 | enabled = FALSE) 112 | 113 | enable.rule(strategy.st, type = "chain", label = "StopTrailingLong") 114 | 115 | cwd <- getwd() 116 | setwd("./_data/") 117 | results_file <- paste("results", strategy.st, "RData", sep = ".") 118 | if( file.exists(results_file) ) { 119 | load(results_file) 120 | } else { 121 | results <- applyStrategy(strategy.st, portfolios = portfolio.st) 122 | updatePortf(portfolio.st) 123 | updateAcct(account.st) 124 | updateEndEq(account.st) 125 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 126 | save(list = "results", file = results_file) 127 | save.strategy(strategy.st) 128 | } 129 | } 130 | setwd(cwd) 131 | -------------------------------------------------------------------------------- /_001-quantstrat.Rmd: -------------------------------------------------------------------------------- 1 | # Long Tutorial 2 | 3 | ```{r 001-comment-1, eval = FALSE} 4 | # http://www.r-programming.org/papers 5 | ``` 6 | 7 | ```{r 001-symbols} 8 | symbols <- basic_symbols() 9 | ``` 10 | 11 | ## Quantstrat 12 | 13 | ```{r 001-getSymbols} 14 | getSymbols(Symbols = symbols, 15 | src = "yahoo", 16 | index.class = "POSIXct", 17 | from = start_date, 18 | to = end_date, 19 | adjust = adjustment) 20 | ``` 21 | 22 | ```{r 001-stock} 23 | stock(symbols, currency = "USD", multiplier = 1) 24 | ``` 25 | 26 | ```{r 001-init} 27 | portfolio.st <- "qsFaber" 28 | account.st <- "qsFaber" 29 | strategy.st <- "qsFaber" 30 | ``` 31 | 32 | ```{r 001-rm-strat} 33 | rm.strat(portfolio.st) 34 | rm.strat(account.st) 35 | ``` 36 | 37 | ```{r 001-init-portf} 38 | initPortf(name = portfolio.st, 39 | symbols = symbols, 40 | initDate = init_date) 41 | ``` 42 | 43 | ```{r 001-init-acct} 44 | initAcct(name = account.st, 45 | portfolios = portfolio.st, 46 | initDate = init_date, 47 | initEq = init_equity) 48 | ``` 49 | 50 | ```{r 001-init-orders} 51 | initOrders(portfolio = portfolio.st, 52 | symbols = symbols, 53 | initDate = init_date) 54 | ``` 55 | 56 | ```{r 001-strategy} 57 | strategy(strategy.st, store = TRUE) 58 | ``` 59 | 60 | ### Add Indicator 61 | 62 | ```{r 001-add-indicator} 63 | add.indicator(strategy = strategy.st, 64 | name = "SMA", 65 | arguments = list(x = quote(Cl(mktdata)), 66 | n = 20), 67 | label = "SMA20") 68 | ``` 69 | 70 | ### Add Signal 71 | 72 | ```{r 001-add-signal} 73 | add.signal(strategy = strategy.st, 74 | name="sigCrossover", 75 | arguments = list(columns = c("Close", "SMA20"), 76 | relationship = "gte"), 77 | label = "Cl.gte.SMA20") 78 | 79 | add.signal(strategy = strategy.st, 80 | name="sigCrossover", 81 | arguments = list(columns = c("Close", "SMA20"), 82 | relationship = "lt"), 83 | label = "Cl.lt.SMA20") 84 | ``` 85 | 86 | ### Add Rule 87 | 88 | ```{r 001-add-rule} 89 | add.rule(strategy = strategy.st, 90 | name = "ruleSignal", 91 | arguments = list(sigcol = "Cl.gte.SMA20", 92 | sigval = TRUE, 93 | orderqty = 100, 94 | ordertype = "market", 95 | orderside = "long"), 96 | type = "enter", 97 | label = "Long.Entry") 98 | 99 | add.rule(strategy.st, 100 | name = "ruleSignal", 101 | arguments = list(sigcol = "Cl.lt.SMA20", 102 | sigval = TRUE, 103 | orderqty = "all", 104 | ordertype = "market", 105 | orderside = "long"), 106 | type = "exit", 107 | label = "Long.Exit") 108 | ``` 109 | 110 | ### Apply Strategy 111 | 112 | ```{r 001-applyStrategy, results = "hide"} 113 | applyStrategy(strategy.st, portfolio.st, debug = TRUE) 114 | ``` 115 | 116 | ```{r 001-checkBlotterUpdate} 117 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 118 | ``` 119 | 120 | ### Update Objects 121 | 122 | ```{r 001-update} 123 | # Update Objects 124 | updatePortf(Portfolio = portfolio.st) 125 | updateAcct(name = account.st) 126 | updateEndEq(Account = account.st) 127 | ``` 128 | 129 | ### Equity Curve 130 | ```{r 001-equity-curve} 131 | a <- getAccount(account.st) 132 | equity <- a$summary$End.Eq 133 | plot(equity, main = "Consolidated Equity Curve") 134 | ``` 135 | 136 | ### Plot Performance 137 | 138 | ```{r 001-plot-performance} 139 | for(symbol in symbols) { 140 | chart.Posn(strategy.st, Symbol = symbol, 141 | TA = "add_SMA(n = 20, col = 4, on = 1, lwd = 2)") 142 | } 143 | ``` 144 | 145 | ### Trade Stats 146 | 147 | ```{r 001-trade-stats} 148 | trade_stats <- t(tradeStats(strategy.st)) 149 | as.data.frame(trade_stats) 150 | ``` 151 | 152 | ### Order Book 153 | 154 | ```{r 001-order-book, results = "asis"} 155 | ob <- getOrderBook(strategy.st) 156 | ob$qsFaber$SPY[,1:5] 157 | ob$qsFaber$SPY[,6:11] 158 | ``` 159 | 160 | ### Per Trade Statistics 161 | 162 | ```{r 001-per-trade-stats} 163 | perTradeStats(strategy.st) 164 | ``` 165 | 166 | ### MAE 167 | 168 | ```{r 001-mae} 169 | for(symbol in symbols) { 170 | chart.ME(Portfolio = portfolio.st, Symbol = symbol, type = "MAE", 171 | scale = "percent") 172 | } 173 | ``` 174 | 175 | ### MFE 176 | 177 | ```{r 001-mfe} 178 | for(symbol in symbols) { 179 | chart.ME(Portfolio = portfolio.st, Symbol = symbol, type = "MFE", 180 | scale = "percent") 181 | } 182 | ``` 183 | 184 | ### Get Account 185 | 186 | ```{r 001-get-account} 187 | a <- getAccount(strategy.st) 188 | last(a$summary, 5) 189 | ``` 190 | 191 | ### Account Summary 192 | 193 | ```{r 001-account-summary} 194 | xyplot(a$summary, type = "h", col = 4) 195 | ``` 196 | 197 | ### Strategy Performance 198 | 199 | ```{r 001-strat-perf} 200 | ret <- Return.calculate(equity, method = "log") 201 | charts.PerformanceSummary(ret, colorset = bluefocus, 202 | main = "Strategy Performance") 203 | ``` 204 | 205 | ### Cumulative Returns 206 | 207 | ```{r 001-cumulative-returns} 208 | rets.multi <- PortfReturns(account.st) 209 | colnames(rets.multi) <- symbols 210 | rets.multi <- na.omit(cbind(rets.multi, Return.calculate(a$summary$End.Eq))) 211 | names(rets.multi)[length(names(rets.multi))] <- "TOTAL" 212 | rets.multi <- rets.multi[,c("TOTAL", symbols)] 213 | round(tail(rets.multi, 5), 6) 214 | chart.CumReturns(rets.multi, colorset = rich10equal, legend.loc = "topleft", 215 | main = "Strategy Cumulative Returns") 216 | ``` 217 | 218 | ```{r 001-returns-boxplot} 219 | chart.Boxplot(rets.multi, main = "Strategy Returns", colorset = rich10equal) 220 | ``` 221 | 222 | ### Annualized Returns 223 | 224 | ```{r 001-annualized-returns} 225 | (ar.tab <- table.AnnualizedReturns(rets.multi)) 226 | max.risk <- max(ar.tab["Annualized Std Dev",]) 227 | min.risk <- min(ar.tab["Annualized Std Dev",]) 228 | max.return <- max(ar.tab["Annualized Return",]) 229 | min.return <- min(ar.tab["Annualized Return",]) 230 | chart.RiskReturnScatter(rets.multi, 231 | main = "Strategy Performance", colorset = rich10equal, 232 | xlim = c(min.risk, max.risk), ylim = c(min.return, max.return)) 233 | ``` 234 | 235 | ### Chart Series 236 | 237 | ```{r 002-chart-series} 238 | chart_Series(SPY["2009"], TA = "add_BBands(lwd = 2)", name = "SPY") 239 | ``` 240 | -------------------------------------------------------------------------------- /_002-quantstrat-ii.Rmd: -------------------------------------------------------------------------------- 1 | ## Quantstrat II 2 | 3 | ```{r 002a-comment-1, eval = FALSE} 4 | # http://www.r-programming.org/papers 5 | ``` 6 | 7 | ```{r 002a-symbols} 8 | symbols <- basic_symbols() 9 | ``` 10 | 11 | ```{r 002a-getSymbols} 12 | getSymbols(Symbols = symbols, 13 | src = "yahoo", 14 | index.class = "POSIXct", 15 | from = start_date, 16 | to = end_date, 17 | adjust = adjustment) 18 | ``` 19 | 20 | ```{r 002a-stock} 21 | stock(symbols, currency = "USD", multiplier = 1) 22 | ``` 23 | 24 | ### multiAsset.bb1 25 | 26 | ```{r 002a-strat-vars} 27 | SD <- 2 28 | N <- 20 29 | ``` 30 | 31 | ```{r 002a-init} 32 | strategy.st <- "bbands" 33 | portfolio.st <- "multiAsset.bb1" 34 | account.st <- "multiAsset.bb1" 35 | ``` 36 | 37 | ```{r 002a-rm-strat} 38 | rm.strat(portfolio.st) 39 | rm.strat(account.st) 40 | ``` 41 | 42 | ```{r 002a-init-portf} 43 | initPortf(name = portfolio.st, 44 | symbols = symbols, 45 | initDate = init_date) 46 | ``` 47 | 48 | ```{r 002a-init-acct} 49 | initAcct(name = account.st, 50 | portfolios = portfolio.st, 51 | initDate = init_date, 52 | initEq = init_equity) 53 | ``` 54 | 55 | ```{r 002a-init-orders} 56 | initOrders(portfolio = portfolio.st, 57 | symbols = symbols, 58 | initDate = init_date) 59 | ``` 60 | 61 | ```{r 002a-strategy} 62 | strategy(strategy.st, store = TRUE) 63 | ``` 64 | 65 | ```{r 002a-add-indicators} 66 | add.indicator(strategy.st, 67 | name = "BBands", 68 | arguments = list(HLC = quote(HLC(mktdata)), 69 | maType = "SMA"), 70 | label = "BBands") 71 | ``` 72 | 73 | ```{r 002a-add-signals} 74 | add.signal(strategy.st, 75 | name = "sigCrossover", 76 | arguments = list(columns = c("Close", "up"), 77 | relationship = "gt"), 78 | label = "Cl.gt.UpperBand") 79 | 80 | add.signal(strategy.st, 81 | name = "sigCrossover", 82 | arguments = list(columns = c("Close", "dn"), 83 | relationship = "lt"), 84 | label = "Cl.lt.LowerBand") 85 | 86 | add.signal(strategy.st, 87 | name = "sigCrossover", 88 | arguments = list(columns = c("High", "Low", "mavg"), 89 | relationship = "op"), 90 | label = "Cross.Mid") 91 | ``` 92 | 93 | ```{r 002a-add-rules} 94 | add.rule(strategy.st, 95 | name = "ruleSignal", 96 | arguments = list(sigcol = "Cl.gt.UpperBand", 97 | sigval = TRUE, 98 | orderqty = -100, 99 | ordertype = "market", 100 | orderside = NULL), 101 | type = "enter") 102 | 103 | add.rule(strategy.st, 104 | name = "ruleSignal", 105 | arguments = list(sigcol = "Cl.lt.LowerBand", 106 | sigval = TRUE, 107 | orderqty = 100, 108 | ordertype = "market", 109 | orderside = NULL), 110 | type = "enter") 111 | 112 | add.rule(strategy.st, 113 | name = "ruleSignal", 114 | arguments = list(sigcol = "Cross.Mid", sigval = TRUE, 115 | orderqty = "all", 116 | ordertype = "market", 117 | orderside = NULL), 118 | type = "exit") 119 | ``` 120 | 121 | ```{r 002a-apply-strategy, results = "hide"} 122 | out <- applyStrategy(strategy.st, 123 | portfolios = portfolio.st, 124 | parameters = list(sd = SD, n = N)) 125 | ``` 126 | 127 | ```{r 002a-update} 128 | updatePortf(portfolio.st) 129 | updateAcct(account.st) 130 | updateEndEq(account.st) 131 | ``` 132 | 133 | ```{r 002a-checkBlotterUpdate} 134 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 135 | ``` 136 | 137 | ```{r 002a-chart-posn} 138 | for(symbol in symbols) { 139 | chart.Posn(portfolio.st, Symbol = symbol, 140 | TA = "add_BBands(n = 20, sd = 2)") 141 | } 142 | ``` 143 | 144 | ### multiAsset.bb2 145 | 146 | ```{r 002b-init} 147 | strategy.st <- "bbands" 148 | portfolio.st <- "multiAsset.bb2" 149 | account.st <- "multiAsset.bb2" 150 | ``` 151 | 152 | ```{r 002b-rm-strat} 153 | rm.strat(portfolio.st) 154 | ``` 155 | 156 | ```{r 002b-init-portf} 157 | initPortf(name = portfolio.st, 158 | symbols, 159 | initDate = init_date) 160 | ``` 161 | 162 | ```{r 002b-init-account} 163 | initAcct(name = account.st, 164 | portfolios = portfolio.st, 165 | initDate = init_date, 166 | initEq = init_equity) 167 | ``` 168 | 169 | ```{r 002b-init-orders} 170 | initOrders(portfolio = portfolio.st, 171 | initDate = init_date) 172 | ``` 173 | 174 | ```{r 002b-strat-vars} 175 | SD <- 3 176 | ``` 177 | 178 | ```{r 002b-apply-strategy} 179 | out <- applyStrategy(strategy.st, 180 | portfolios = portfolio.st, 181 | parameters = list(sd = SD, n = N)) 182 | ``` 183 | 184 | ```{r 002b-update} 185 | updatePortf(portfolio.st) 186 | updateAcct(account.st) 187 | updateEndEq(account.st) 188 | ``` 189 | 190 | ```{r 002b-checkBlotterUpdate} 191 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 192 | ``` 193 | 194 | ```{r 002b-chart-posn} 195 | for(symbol in symbols) { 196 | chart.Posn(portfolio.st, Symbol = symbol, 197 | TA = "add_BBands(n = 20, sd = 2)") 198 | } 199 | ``` 200 | 201 | ### Compare Strategies 202 | 203 | ```{r} 204 | eq1 <- getAccount("multiAsset.bb1")$summary$End.Eq 205 | rt1 <- Return.calculate(eq1, "log") 206 | eq2 <- getAccount("multiAsset.bb2")$summary$End.Eq 207 | rt2 <- Return.calculate(eq2, "log") 208 | returns <- cbind(rt1, rt2) 209 | colnames(returns) <- c("SD = 2","SD = 3") 210 | chart.CumReturns(returns, colorset = c(2, 4), legend.loc = "topleft", 211 | main = "BBand SD Parameter Comparison", ylab = "cum return", 212 | xlab = "", minor.ticks = FALSE) 213 | ``` 214 | 215 | ### Order Sizing 216 | 217 | #### osFixedDollar() 218 | 219 | This order sizing function adjusts the share quantity such that the transaction value is approximately equal to a pre-defined tradesize 220 | 221 | $$ \text{orderqty } = \frac{\text{tradeSize}}{\text{ClosePrice}} $$ 222 | 223 | ```{r} 224 | osFixedDollar <- function(timestamp, orderqty, portfolio, symbol, ruletype, ...) { 225 | ClosePrice <- as.numeric(Cl(mktdata[timestamp,])) 226 | orderqty <- round(tradeSize/ClosePrice, -2) 227 | return(orderqty) 228 | } 229 | ``` 230 | 231 | ### Fixed-Dollar Order Sizing 232 | 233 | #### Strategy Variables 234 | 235 | ```{r 002c-strategy-vars} 236 | fastMA <- 12 237 | slowMA <- 26 238 | signalMA <- 9 239 | maType = "EMA" 240 | tradeSize <- init_equity/10 241 | ``` 242 | 243 | ```{r 002c-init} 244 | strategy.st <- "macd" 245 | portfolio.st <- "multi.macd" 246 | account.st <- "multi.macd" 247 | ``` 248 | 249 | ```{r 002c-rm-strat} 250 | rm.strat(portfolio.st) 251 | ``` 252 | 253 | ```{r 002c-init-portf} 254 | initPortf(name = portfolio.st, 255 | symbols, 256 | initDate = init_date) 257 | ``` 258 | 259 | ```{r 002c-init-account} 260 | initAcct(name = account.st, 261 | portfolios = portfolio.st, 262 | initDate = init_date, 263 | initEq = init_equity) 264 | ``` 265 | 266 | ```{r 002c-init-orders} 267 | initOrders(portfolio = portfolio.st, 268 | initDate = init_date) 269 | ``` 270 | 271 | ```{r 002c-strategy} 272 | strategy(strategy.st, store = TRUE) 273 | ``` 274 | 275 | #### Add Indicators 276 | 277 | ```{r 002c-add-indicators} 278 | add.indicator(strategy.st, 279 | name = "MACD", 280 | arguments = list(x = quote(Cl(mktdata))), 281 | label = "osc") 282 | ``` 283 | 284 | #### Add Signals 285 | 286 | ```{r 002c-add-signals} 287 | add.signal(strategy.st, 288 | name = "sigThreshold", 289 | arguments = list(column = "signal.osc", 290 | relationship = "gt", 291 | threshold = 0, 292 | cross = TRUE), 293 | label = "signal.gt.zero") 294 | 295 | add.signal(strategy.st, 296 | name = "sigThreshold", 297 | arguments = list(column = "signal.osc", 298 | relationship = "lt", 299 | threshold = 0, 300 | cross = TRUE), 301 | label = "signal.lt.zero") 302 | ``` 303 | 304 | #### Add Rules 305 | 306 | ```{r 002c-add-rules} 307 | add.rule(strategy.st, 308 | name = "ruleSignal", 309 | arguments = list(sigcol = "signal.gt.zero", 310 | sigval = TRUE, 311 | orderqty = 100, 312 | ordertype = "market", 313 | orderside = "long", 314 | osFUN = "osFixedDollar"), 315 | type = "enter", 316 | label = "enter", 317 | storefun = FALSE) 318 | 319 | add.rule(strategy.st, 320 | name = "ruleSignal", 321 | arguments = list(sigcol = "signal.lt.zero", 322 | sigval = TRUE, 323 | orderqty = "all", 324 | ordertype = "market", 325 | orderside = "long"), 326 | type = "exit", 327 | label = "exit") 328 | ``` 329 | 330 | #### Apply Strategy 331 | 332 | ```{r} 333 | out <- applyStrategy(strategy.st, 334 | portfolios = portfolio.st, 335 | parameters = list(nFast = fastMA, nSlow = slowMA, 336 | nSig = signalMA, maType = maType), 337 | verbose = TRUE) 338 | ``` 339 | 340 | ```{r 002c-update} 341 | updatePortf(portfolio.st) 342 | updateAcct(account.st) 343 | updateEndEq(account.st) 344 | ``` 345 | 346 | ```{r 002c-checkBlotterUpdate} 347 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 348 | ``` 349 | 350 | #### Chart Positions 351 | 352 | ```{r 002c-chart-posn} 353 | for(symbol in symbols) { 354 | chart.Posn(Portfolio = portfolio.st, 355 | Symbol = symbol) 356 | add_MACD() 357 | add_EMA(12, col = "red") 358 | add_EMA(26, col = "blue") 359 | } 360 | ``` 361 | 362 | ### Max Position Order Sizing 363 | 364 | ```{r 002d-strategy-vars} 365 | strategy.st <- "bb.lim" 366 | SD <- 2 367 | N <- 20 368 | ``` 369 | 370 | ```{r 002d-strategy} 371 | strategy(strategy.st, store = TRUE) 372 | ``` 373 | 374 | ```{r 002d-add-indicators} 375 | add.indicator(strategy.st, 376 | name = "BBands", 377 | arguments = list(HLC = quote(HLC(mktdata)), 378 | maType = "SMA"), 379 | label = "BBands") 380 | ``` 381 | 382 | ```{r 002d-add-signals} 383 | add.signal(strategy.st, 384 | name = "sigCrossover", 385 | arguments = list(columns = c("Close", "up"), 386 | relationship = "gt"), 387 | label = "Cl.gt.UpperBand") 388 | 389 | add.signal(strategy.st, 390 | name = "sigCrossover", 391 | arguments = list(columns = c("Close", "dn"), 392 | relationship = "lt"), 393 | label = "Cl.lt.LowerBand") 394 | 395 | add.signal(strategy.st, 396 | name = "sigCrossover", 397 | arguments = list(columns = c("High", "Low", "mavg"), 398 | relationship = "op"), 399 | label = "Cross.Mid") 400 | ``` 401 | 402 | ```{r 002d-add-rules} 403 | add.rule(strategy.st, 404 | name = "ruleSignal", 405 | arguments = list(sigcol = "Cl.gt.UpperBand", 406 | sigval = TRUE, 407 | orderqty = -1000, 408 | ordertype = "market", 409 | orderside = NULL, 410 | osFUN = "osMaxPos"), 411 | type = "enter") 412 | 413 | add.rule(strategy.st, name = "ruleSignal", 414 | 415 | arguments = list(sigcol = "Cl.lt.LowerBand", 416 | sigval = TRUE, 417 | orderqty = 1000, 418 | ordertype = "market", 419 | orderside = NULL, 420 | osFUN = "osMaxPos"), 421 | type = "enter") 422 | 423 | add.rule(strategy.st, 424 | name = "ruleSignal", 425 | arguments = list(sigcol = "Cross.Mid", 426 | sigval = TRUE, 427 | orderqty = "all", 428 | ordertype = "market", 429 | orderside = NULL), 430 | type = "exit") 431 | ``` 432 | 433 | #### Position Limits 434 | 435 | ```{r 002d-remove-portf} 436 | rm.strat("multi.bb.limit") # remove portfolio, account, orderbook if re-run 437 | ``` 438 | 439 | ```{r 002d-init-objects} 440 | initPortf(name = "multi.bb.limit", 441 | symbols, 442 | initDate = init_date) 443 | initAcct(name = "multi.bb.limit", 444 | portfolios = "multi.bb.limit", 445 | initDate = init_date, 446 | initEq = init_equity) 447 | initOrders(portfolio = "multi.bb.limit", 448 | initDate = init_date) 449 | ``` 450 | 451 | #### addPosLimit() 452 | 453 | ```{r 002d-addPosLimit} 454 | for(symbol in symbols) { 455 | addPosLimit("multi.bb.limit", 456 | symbol = symbol, 457 | timestamp = init_date, 458 | maxpos = 200, 459 | longlevels = 2 ) 460 | } 461 | ``` 462 | 463 | #### Apply Strategy 464 | 465 | ```{r 002d-apply-strategy} 466 | out <- applyStrategy(strategy.st, 467 | portfolios = "multi.bb.limit", 468 | parameters = list(sd = SD, n = N)) 469 | ``` 470 | 471 | ```{r 002d-update} 472 | updatePortf("multi.bb.limit") 473 | updateAcct("multi.bb.limit") 474 | updateEndEq("multi.bb.limit") 475 | ``` 476 | 477 | ```{r 002d-checkBlotterUpdate} 478 | checkBlotterUpdate("multi.bb.limit", "multi.bb.limit") 479 | ``` 480 | 481 | -------------------------------------------------------------------------------- /_02-basic-strategy.Rmd: -------------------------------------------------------------------------------- 1 | # Basic Strategy {#basic-strategy} 2 | 3 | Let's start by using a basic strategy with SMA(20). Whenever `Cl >= SMA20` we'll issue an order to buy. When `Cl < SMA20` we'll exit the position. We will create this strategy on **SPY**. 4 | 5 | $$ Signal = 6 | \begin{cases} 7 | \text{Cl } >= \text{ SMA(20)}, \text{ BTO} \\ 8 | \text{Cl } < \text{ SMA(20)}, \text{ STC} 9 | \end{cases} 10 | $$ 11 | 12 | ```{r} 13 | symbols <- c("SPY") 14 | ``` 15 | 16 | There are several sources we can use depending on the instrument we want to access. For stocks, I'll use Yahoo! 17 | 18 | We also set our index.class parameter to a vector of `POSIXt` and `POSIXct`. Set the `from` parameter to the first date of data you want to retrieve and the `to` parameter to the last date of data. Lastly, we set `adjust` to `TRUE`. This adjusts the prices to accomodate stock splits, dividends, etc. 19 | 20 | ```{r 1-getsymbols} 21 | getSymbols(Symbols = symbols, 22 | src = "yahoo", 23 | index.class = "POSIXct", 24 | from = start_date, 25 | to = end_date, 26 | adjust = adjustment) 27 | ``` 28 | 29 | ```{r 1-stock} 30 | stock(symbols, 31 | currency = "USD", 32 | multiplier = 1) 33 | ``` 34 | 35 | ## Initialize Account, Portfolio and Strategy 36 | 37 | Next, we name our strategy. This will come in handy later when saving them and accessing them later on. It will also keep our "accounts" seperated. 38 | 39 | ```{r 2-create-objects} 40 | portfolio.st = "Basic.Portfolio" 41 | account.st = "Basic.Account" 42 | strategy.st = "Basic.Strategy" 43 | ``` 44 | 45 | We then run the `rm.strat` command to clean out any residuals from any previous runs. This is useless on a first-run but if we make a parameter change to the script this will ensure we're not holding onto old data. 46 | 47 | ```{r 2-rm-strat} 48 | rm.strat(portfolio.st) 49 | rm.strat(account.st) 50 | ``` 51 | 52 | With `initPortf`, we create a new portfolio object that will hold all of our transactions, positions and more. 53 | 54 | We pass three parameters here: 55 | 56 | * `name`: for simplicity we can use our `strat.name` variable to keep our data organized. 57 | 58 | * `symbols`: Pretty self-explantory 59 | 60 | * `initDate`: This is a new parameter we havent seen before but will use often on other function calls. This is simply an "initilization date". 61 | 62 | `initDate` should be the date prior to our first record. So, in this example we're accessing all of 2010 data for **SPY** starting at 2010-01-01. So, our `initDate` should be 2009-12-31. 63 | 64 | Do not set an `initDate` beyond one day prior to your first observation. When running reports and charts you will get a lot of empty data/space. 65 | 66 | ```{r 2-init-portfolio} 67 | initPortf(name = portfolio.st, 68 | symbols = symbols, 69 | initDate = init_date) 70 | ``` 71 | 72 | Next we'll initialize our account with `initAcct`. This will hold multiple portfolios and our account details. Notice two of the parameters are the same as `initPortf` and two differences: 73 | 74 | * `portfolios`: The name of our current portfolio 75 | 76 | * `initEq`: This is the balance we want to start our portfolio against. 77 | 78 | There is also a `currency` parameter we can pass but because we set that earlier, we do not need it here. 79 | 80 | ```{r 2-init-account} 81 | initAcct(name = account.st, 82 | portfolios = portfolio.st, 83 | initDate = init_date, 84 | initEq = init_equity) 85 | ``` 86 | 87 | `initOrders` will create an object to hold all of the orders for our portfolio. 88 | 89 | ```{r 2init-orders} 90 | initOrders(portfolio = portfolio.st, 91 | symbols = symbols, 92 | initDate = init_date) 93 | ``` 94 | 95 | `strategy` will construct our strategy object. `store` will hold the strategy settings for later use. 96 | 97 | ```{r 2-build-strategy-object} 98 | strategy(strategy.st, store = TRUE) 99 | ``` 100 | 101 | ## Indicators 102 | 103 | For our current strategy we only have one indicator to add: SMA(20). We'll add this indicator to our strategy with the `add.indicator` function. 104 | 105 | * `name` parameter is the name of a function; do not misinterpret it as a label (an additinoal parameter). This allows you to create your own functions as you get comfortable with backtesting. For now we'll use the `TTR:SMA` function. 106 | 107 | * `arguments` is a list of parameters passed to the function called in `name`. `SMA` requires only two parameters, `x`, our data object, and `n`, the number of periods to calculate. 108 | 109 | For this strategy we are basing our SMA(20) on closing prices. But the keen observer will recognize we're not passing an object as we normally would. For example, we might think to use: 110 | 111 | ```{r 2-example1, eval = FALSE} 112 | arguments = list(SPY$Close, n = 20) 113 | ``` 114 | 115 | Instead we're passing a new object called `mktdata` wrapped inside a `Cl` function wrapped inside a `quote` function. This seems messy and may be a bit confusing. So let's start from the beginning. 116 | 117 | `mktdata` is a new object that will be created when our strategy runs. It will copy our symbol object then be manipulated as we will instruct. In this example, a new variable called *SMA20* (our `label` parameter) will be added. 118 | 119 | The `Cl()` function simply references the `Close` variable of `mktdata`. This is a shortcut function created in the `quantmod` package. Note that it will only call the first variable that begins with "Cl". 120 | 121 | For example, if we have a xts object with the column names `c("Open", "High", "Low", "Close", "Close.gt.SMA20")` then `Cl()` will reference our `Close` variable. 122 | 123 | If, however, we have ended up with a xts object where the column names are `c("Close.gt.SMA20", "Open", "High", "Low", "Close")` then `Cl()` will reference the `Close.gt.SMA20` variable. 124 | 125 | As long as you don't manipulate the original symbol object this shouldn't be an issue. 126 | 127 | We can use similar functions to represent the other variables in our object: `Op()` for Open, `Hi()` for High, `Lo()` for Low and `Ad()` for Adjusted. Run a help query on any of those calls to get more details. 128 | 129 | Lastly, we wrap our call in the `quote()` function which essentially wraps quotes around our arguments during the call. 130 | 131 | ```{r 2-comment1, include = FALSE} 132 | #' Would like to have more information here as to why all of that is required and what will happen if we did just attempt to reference, say by mktdata$Close 133 | ``` 134 | 135 | ```{r 2-add-indicators} 136 | add.indicator(strategy = strategy.st, 137 | name = "SMA", 138 | arguments = list(x = quote(Cl(mktdata)), 139 | n = 20), 140 | label = "SMA20") 141 | ``` 142 | 143 | An example of our `mktdata` object would look like this: 144 | 145 | ```{r 2-example2} 146 | knitr::kable(data.frame("n" = c(1:6), 147 | "Close" = c(99, 101.50, 102, 101, 103, 102), 148 | "SMA20" = c(100, 101, 101.50, 100.50, 102.50, 102.50)), 149 | caption = "Sample mktdata with indicators") 150 | ``` 151 | 152 | ## Signals 153 | 154 | Now that we've added our indicator it's time to instruct our strategy on when to buy and sell. We do this by setting up signals. 155 | 156 | Signals are simply boolean values on if a given condition is met. In our current strategy, we want to buy when Close crosses over SMA(20) and sell when Close crosses under SMA(20). Each of these will be a signal. 157 | 158 | We build our signals similar to how we built our indicators with some distinction. In `add.signal()`, one parameter we pass, `name` is similar to what we did in `add.indicator`; it is the name of a function call. We can use technically any function we want but the `quantstrat` library already has some essential ones built in for us. 159 | 160 | * `sigComparison`: compare one value to another. For example, if High is higher than Close or SMA(20) is greather than SMA(50). 161 | 162 | * `sigCrossover`: If one variable has crossed another. 163 | 164 | * `sigFormula`: Use a formula to calculate the signal based on other observations. 165 | 166 | * `sigPeak`: Use to find a local minima or maxima. 167 | 168 | * `sigThreshold`: Use when an indicator or price object has crossed a certain value. 169 | 170 | * `sigTimestamp`: Signal based on date or time (Sell in May, go away?) 171 | 172 | All of the `name` parameters above should cover just about any strategy you want to run. For our current strategy, we'll use the `sigCrossover`. 173 | 174 | In our `arguments` list, we'll pass the two `columns` we are looking at for the crossover: `Close` and `SMA20` (the latter added in our `add.indicator` call above). We need to also pass the relationship we are looking for; in our example, `gte` and `lt`: 175 | 176 | * `gt`: greather than 177 | 178 | * `lt`: less than 179 | 180 | * `eq`: equal to 181 | 182 | * `gte`: greather than or equal to 183 | 184 | * `lte`: less than or equal to 185 | 186 | We also assign a label which can be any descriptive title we want to identify our variable. 187 | 188 | Also note that you cannot have consistent TRUE values for either one of the variables. Therefore, you cannot have two consecutive signals. 189 | 190 | ```{r 2-add-signals} 191 | add.signal(strategy = strategy.st, 192 | name="sigCrossover", 193 | arguments = list(columns = c("Close", "SMA20"), 194 | relationship = "gte"), 195 | label = "Cl.gte.SMA20") 196 | 197 | add.signal(strategy = strategy.st, 198 | name="sigCrossover", 199 | arguments = list(columns = c("Close", "SMA20"), 200 | relationship = "lt"), 201 | label = "Cl.lt.SMA20") 202 | ``` 203 | 204 | An updated sample of our `mktdata` object would look like this: 205 | 206 | ```{r 2-example3} 207 | knitr::kable(data.frame("n" = c(1:6), 208 | "Close" = c(99, 101.50, 102, 101, 103, 102), 209 | "SMA20" = c(100, 101, 101.50, 100.50, 102.50, 102.50), 210 | "Cl.gte.SMA20" = c(0, 1, 0, 0, 0, 0), 211 | "Cl.lt.SMA20" = c(0, 0, 0, 0, 0, 1)), 212 | caption = "Sample mktdata with indicators and signals") 213 | ``` 214 | 215 | In the example above, we see on row 2 that `Close` "crosses over" `SMA20`. This generates a TRUE value for `Cl.gte.SMA20`. Our next signal comes on row 6 when `Close` crosses back under `SMA20` (`Cl.lt.SMA20 == TRUE`). 216 | 217 | However, this does not create any trades. All we've done now is added indicators and signals. Now, we must tell our strategy what to do given the signals. We add rules. 218 | 219 | ## Adding Rules 220 | 221 | Rules are where we will instruct R to make our transactions. When I first began working with these libraries this is where I often struggled. We'll keep it simple for the time being and go over a few basics. 222 | 223 | As with `add.indicator()` and `add.signal()`, `add.rule()` expects the first parameter to be the name of your strategy. As with before, `name` will be the function we want to call. For now we'll stick with `ruleSignal`. 224 | 225 | You'll see below we have two rule sets; one for BTO orders and one for STC orders. We also use pretty much the same parameters in our `arguments` list: 226 | 227 | * `sigcol`: This is the signal column for which the rule references. 228 | 229 | * `sigval`: The value of our `sigcol` when the rule should be applied. 230 | 231 | * `orderqty`: numeric or "all" for the number of shares to be executed. 232 | 233 | * `ordertype`: c("market", "limit", "stoplimit", "stoptrailing", "iceberg") 234 | 235 | * `orderside`: long or short 236 | 237 | Our last parameter, `type` is the type of order we are placing. There are several options we'll get into later but for now our rules will simply be enter or exit. 238 | 239 | ```{r 2-add-rules} 240 | # BTO when Cl crosses above SMA(20) 241 | add.rule(strategy = strategy.st, 242 | name = "ruleSignal", 243 | arguments = list(sigcol = "Cl.gte.SMA20", 244 | sigval = TRUE, 245 | orderqty = 100, 246 | ordertype = "market", 247 | orderside = "long"), 248 | type = "enter", 249 | label = "BTO") 250 | 251 | # STC when Cl crosses under SMA(20) 252 | add.rule(strategy.st, 253 | name = "ruleSignal", 254 | arguments = list(sigcol = "Cl.lt.SMA20", 255 | sigval = TRUE, 256 | orderqty = "all", 257 | ordertype = "market", 258 | orderside = "long"), 259 | type = "exit", 260 | label = "STC") 261 | ``` 262 | 263 | Simply put, whenever our `Cl.gte.SMA` variable is TRUE we will submit a market order for 100 shares long. When `Cl.lt.SMA == TRUE` we will exit all long positions. 264 | 265 | ## Apply Strategy 266 | 267 | Up until this point if you've tried to execute certain blocks of code at a time you may have been disappointed. Nothing happens other than some little chunks of output. Unfortunately, we don't know how all of this works until we apply our strategy. 268 | 269 | `applyStrategy()` will execute trades based on the conditions we've specified. Notice we have two parameters which we assign to our strategy name. When we execute this next block we either get trades or we get errors. 270 | 271 | ```{r 2-apply-strategy, results = "hide"} 272 | # Results hidden to save space 273 | results <- applyStrategy(strategy = strategy.st, 274 | portfolios = portfolio.st) 275 | ``` 276 | 277 | ```{r 2-update} 278 | updatePortf(portfolio.st) 279 | updateAcct(account.st) 280 | updateEndEq(account.st) 281 | ``` 282 | 283 | ```{r 2-checkBlotterUpdate} 284 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 285 | ``` 286 | 287 | The format of our output should seem straightforward; we have Date followed by Symbol, Shares and Price. We've executed approximately ten trades (entry and exit). Our strategy actually ends with an open position (notice the buy on the last entry). 288 | 289 | If we look at a sample of the `mktdata` object now available, we can see the indicators and signals we created earlier: 290 | 291 | ```{r 2-example4} 292 | knitr::kable(mktdata[81:85,]) 293 | ``` 294 | 295 | We can see in this subset we had four transactions; one on each side. Each position didn't last longer than a day but for our purposes now that's fine. Where `Cl.gte.SMA20 == 1` (TRUE) we would buy and where `Cl.lt.SMA20 == 1` (TRUE) we would sell per our rules. 296 | 297 | ## Update Portfolio, Account 298 | 299 | To dig into the analysis of our strategy we must update our portfolio and account. We do this by calling `udpatePortf`, `updateAcct` and `updateEndEq` passing our `strat.name` variable as the lone parameter. 300 | 301 | ```{r 2-update-portfolio} 302 | updatePortf(Portfolio = portfolio.st) 303 | updateAcct(name = account.st) 304 | updateEndEq(Account = account.st) 305 | ``` 306 | 307 | ## Save Strategy 308 | 309 | For this book all strategy data is saved in the `_data` directory If you simply use `save.strategy()` with the lone *strategy.name* parameter the RData file will be saved in the current working directory. 310 | 311 | ```{r} 312 | cwd <- getwd() 313 | setwd("./_data/") 314 | save.strategy(strategy.st) 315 | setwd(cwd) 316 | ``` 317 | 318 | ## Glimpse Our Returns 319 | 320 | To close this introduction we'll take a brief look at our transactions using the `chart.Posn` function from the `blotter` package. This chart will show us when we made trades, how many shares we purchased and what our profits and drawdown were. This is a great way to get a quick look at the profitability of a strategy. 321 | 322 | We pass `strat.name` to the `Portfolio` parameter and SPY to `Symbol`. In itself that is enough. However, because our trades are based on the SMA(20) indicator let's add that as well. We do this by passing the `add_sma` function from the `quantmod` package where `n` is our period (20) and `col` and `lwd` are the same as those we would pass to the base plot package. So we're asking for a line that is colored blue with a line-width of two By setting `on = 1` we are asking the indicator to be overlaid on the first panel (the price action panel). 323 | 324 | ```{r 2-equity-curve} 325 | a <- getAccount(account.st) 326 | equity <- a$summary$End.Eq 327 | plot(equity, main = "Consolidated Equity Curve") 328 | ``` 329 | -------------------------------------------------------------------------------- /_03-basic-strategy-ii.Rmd: -------------------------------------------------------------------------------- 1 | # Basic Strategy II {#basic-strategy-ii} 2 | 3 | In [Basic Strategy](#basic-strategy) we did a simple strategy of buying n number of shares on a fixed moving average. 4 | 5 | In this slightly modified example we'll pass the SMA `n` parameter to `applyStrategy()`. 6 | 7 | ```{r 3-rm-strat} 8 | rm.strat(portfolio.st) 9 | rm.strat(account.st) 10 | ``` 11 | 12 | ```{r 3-init-portf} 13 | initPortf(name = portfolio.st, 14 | symbols = symbols, 15 | initDate = init_date) 16 | ``` 17 | 18 | ```{r 3-init-acct} 19 | initAcct(name = account.st, 20 | portfolios = portfolio.st, 21 | initDate = init_date, 22 | initEq = init_equity) 23 | ``` 24 | 25 | ```{r 3-init-orders} 26 | initOrders(portfolio = portfolio.st, 27 | symbols = symbols, 28 | initDate = init_date) 29 | ``` 30 | 31 | ```{r 3-strategy} 32 | strategy(strategy.st, store = TRUE) 33 | ``` 34 | 35 | ```{r 3-add-indicators} 36 | add.indicator(strategy = strategy.st, 37 | name = "SMA", 38 | arguments = list(x = quote(Cl(mktdata))), 39 | label = "SMA20") 40 | ``` 41 | 42 | Notice in `add.indicator` we did not supply the `n` parameter as we did before. 43 | 44 | ```{r 3-add-signals} 45 | add.signal(strategy = strategy.st, 46 | name="sigCrossover", 47 | arguments = list(columns = c("Close", "SMA20"), 48 | relationship = "gte"), 49 | label = "Cl.gte.SMA20") 50 | 51 | add.signal(strategy = strategy.st, 52 | name="sigCrossover", 53 | arguments = list(columns = c("Close", "SMA20"), 54 | relationship = "lt"), 55 | label = "Cl.lt.SMA20") 56 | ``` 57 | 58 | ```{r 3-add-rules} 59 | # BTO when Cl crosses above SMA(20) 60 | add.rule(strategy = strategy.st, 61 | name = "ruleSignal", 62 | arguments = list(sigcol = "Cl.gte.SMA20", 63 | sigval = TRUE, 64 | orderqty = 100, 65 | ordertype = "market", 66 | orderside = "long"), 67 | type = "enter", 68 | label = "BTO") 69 | 70 | # STC when Cl crosses under SMA(20) 71 | add.rule(strategy.st, 72 | name = "ruleSignal", 73 | arguments = list(sigcol = "Cl.lt.SMA20", 74 | sigval = TRUE, 75 | orderqty = "all", 76 | ordertype = "market", 77 | orderside = "long"), 78 | type = "exit", 79 | label = "STC") 80 | ``` 81 | 82 | ```{r 3-apply-strategy, results = "hide"} 83 | # Results hidden to save space 84 | applyStrategy(strategy.st, 85 | portfolios = portfolio.st, 86 | parameters = list(n = 20)) 87 | ``` 88 | 89 | ```{r 3-update} 90 | updatePortf(portfolio.st) 91 | updateAcct(account.st) 92 | updateEndEq(account.st) 93 | ``` 94 | 95 | ```{r 3-checkBlotterUpdate} 96 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 97 | ``` 98 | 99 | ```{r 3-equity-curve} 100 | a <- getAccount(account.st) 101 | equity <- a$summary$End.Eq 102 | plot(equity, main = "Consolidated Equity Curve") 103 | ``` 104 | -------------------------------------------------------------------------------- /_Cl-SMA20-Crossover.Rmd: -------------------------------------------------------------------------------- 1 | ```{r libraries} 2 | library(dplyr) 3 | library(ggplot2) 4 | library(lattice) 5 | library(tidyr) 6 | library(TTR) 7 | ``` 8 | 9 | # Close/SMA(20) Crossover 10 | 11 | $$ Signal = 12 | \begin{cases} 13 | Cl >= SMA(20), BTO \\ 14 | Cl < SMA(20), STC 15 | \end{cases} 16 | $$ 17 | 18 | ```{r 2-1-set-symbols} 19 | symbols <- basic_symbols() 20 | ``` 21 | 22 | ```{r 2-1-settings} 23 | strategy_title <- "Close > SMA(20)" 24 | ``` 25 | 26 | ```{r 2-1-initialize-portfolio} 27 | Cl.SMA20.Crossover <- "Cl.Sma20.Crossover" 28 | rm.strat(Cl.SMA20.Crossover) 29 | initPortf(Cl.SMA20.Crossover, symbols = basic_symbols(), initDate = pv$init_date) 30 | initAcct(Cl.SMA20.Crossover, portfolios = Cl.SMA20.Crossover, 31 | initDate = pv$init_date, initEq = pv$account_equity) 32 | initOrders(portfolio = Cl.SMA20.Crossover, initDate = pv$init_date) 33 | strategy(Cl.SMA20.Crossover, store = TRUE) 34 | strat <- getStrategy(Cl.SMA20.Crossover) 35 | ``` 36 | 37 | ```{r 2-1-add-indicators} 38 | add.indicator(strategy = Cl.SMA20.Crossover, name = "SMA", 39 | arguments = list(x = quote(Cl(mktdata)), n = 20), label = "SMA20") 40 | ``` 41 | 42 | ```{r 2-1-add-signals} 43 | add.signal(Cl.SMA20.Crossover, name="sigCrossover", 44 | arguments = list(columns = c("Close", "SMA20"), relationship = "gte"), 45 | label="Cl.gte.SMA") 46 | add.signal(Cl.SMA20.Crossover, name = "sigCrossover", 47 | arguments = list(columns = c("Close", "SMA20"), relationship = "lt"), 48 | label = "Cl.lt.SMA") 49 | ``` 50 | 51 | ```{r 2-1-add-rules} 52 | # BTO when Cl crosses above SMA(20) 53 | add.rule(Cl.SMA20.Crossover, name = 'ruleSignal', 54 | arguments = list(sigcol = "Cl.gte.SMA", sigval = TRUE, orderqty = 100, 55 | ordertype = 'market', orderside = 'long'), 56 | type = 'enter') 57 | 58 | # STC when Cl crosses under SMA(20) 59 | add.rule(Cl.SMA20.Crossover, name = 'ruleSignal', 60 | arguments = list(sigcol = "Cl.lt.SMA", sigval = TRUE, orderqty = 'all', 61 | ordertype = 'market', orderside = 'long'), 62 | type = 'exit') 63 | ``` 64 | 65 | ```{r 2-1-apply-strategy, include = FALSE} 66 | applyStrategy(strategy = Cl.SMA20.Crossover, portfolios = Cl.SMA20.Crossover) 67 | ``` 68 | 69 | ```{r 2-1-update-portfolio} 70 | updatePortf(Cl.SMA20.Crossover) 71 | updateAcct(Cl.SMA20.Crossover) 72 | updateEndEq(Cl.SMA20.Crossover) 73 | checkBlotterUpdate(Cl.SMA20.Crossover, Cl.SMA20.Crossover) 74 | ``` 75 | 76 | ```{r 2-1-account-summary} 77 | a <- getAccount(Cl.SMA20.Crossover) 78 | p <- getPortfolio(Cl.SMA20.Crossover) 79 | ``` 80 | 81 | ### Per Trade Stats 82 | 83 | ```{r 2-1-trade-stats, include = TRUE} 84 | knitr::kable(t(tradeStats(Cl.SMA20.Crossover))[-c(1:2),], 85 | caption = "Trade Stats per Symbol") 86 | ``` 87 | 88 | ### Maximum Adverse Excursion 89 | 90 | ```{r 2-1-mae, include = TRUE, fig.caption = "Maximum Adverse Excursion by Symbol"} 91 | par(mfrow = c(2,2)) 92 | for(symbol in symbols) { 93 | chart.ME(Portfolio = Cl.SMA20.Crossover, Symbol = symbol, type = "MAE", 94 | scale = "percent") 95 | } 96 | par(mfrow = c(1,1)) 97 | ``` 98 | 99 | ### Maximum Favorable Excursion 100 | 101 | ```{r 2-1-mfe, include = TRUE, fig.caption = "Maximum Favorable Excursion by Symbol"} 102 | par(mfrow = c(2,2)) 103 | for(symbol in symbols) { 104 | chart.ME(Portfolio = Cl.SMA20.Crossover, Symbol = symbol, type = "MFE", 105 | scale = "percent", legend.loc = "none") 106 | } 107 | par(mfrow = c(1,1)) 108 | ``` 109 | 110 | ```{r 2-1-individual-asset-returns} 111 | rets.multi <- PortfReturns(Cl.SMA20.Crossover) 112 | colnames(rets.multi) <- symbols 113 | rets.multi <- na.omit(cbind(rets.multi, Return.calculate(a$summary$End.Eq))) 114 | names(rets.multi)[length(names(rets.multi))] <- "TOTAL" 115 | rets.multi <- rets.multi[,c("TOTAL", symbols)] 116 | ``` 117 | 118 | ### Cumulative Returns 119 | 120 | ```{r 2-1-return-distribution-analysis, include = TRUE, fig.cap = "Return Distribution Analysis"} 121 | as.data.frame(rets.multi) %>% 122 | mutate(Date = index(rets.multi)) %>% 123 | gather(key, value, 1:ncol(rets.multi)) %>% 124 | filter(value != 0) %>% 125 | ggplot(aes(x = key, y = value, fill = key)) + 126 | geom_boxplot() + 127 | coord_flip() + 128 | theme(legend.title = element_blank()) + 129 | theme_bw() + 130 | scale_x_discrete(name = NULL) + 131 | scale_y_continuous(name = NULL) + 132 | scale_fill_discrete(name = "Symbol") + 133 | ggtitle("Return Distribution Analysis") 134 | ``` 135 | 136 | ### Annualized Returns 137 | 138 | ```{r 2-1-annualized-risk-return, include = TRUE} 139 | ar.tab <- table.AnnualizedReturns(rets.multi) 140 | max.risk <- max(ar.tab["Annualized Std Dev",]) 141 | max.return <- max(ar.tab["Annualized Return",]) 142 | knitr::kable(data.frame("Max Risk" = max.risk, 143 | "Max Return" = max.return, 144 | "Ratio" = max.risk/max.return), 145 | booktabs = TRUE, caption = "Max Risk, Max Reward, Ratio") 146 | 147 | knitr::kable(t(ar.tab), booktabs = TRUE, caption = "Annualized Risk and Return") 148 | ``` 149 | 150 | ```{r 2-1-annualized-risk-return-chart, include = TRUE, fig.cap = "Annualized Risk and Return"} 151 | chart.RiskReturnScatter(rets.multi, main = "Performance", 152 | colorset = rich10equal, xlim = c(0, max.risk * 1.1), 153 | ylim = c(0, max.return)) 154 | ``` 155 | 156 | ```{r 2-1-consolidated-equity-curve, include = TRUE, fig.cap = "Consolidated Equity Curve"} 157 | equity <- a$summary$End.Eq 158 | plot(equity, main = "Consolidated Equity Curve") 159 | ``` 160 | 161 | ```{r 2-1-asummary, include = TRUE, fig.cap = "Account Summary"} 162 | a <- getAccount(Cl.SMA20.Crossover) 163 | xyplot(a$summary, type = "h", col = 4) 164 | ``` 165 | 166 | ```{r 2-1-performance-summary, include = TRUE} 167 | ret <- Return.calculate(equity, method = "log") 168 | charts.PerformanceSummary(ret, colorset = bluefocus, 169 | main = strategy_title) 170 | ``` 171 | -------------------------------------------------------------------------------- /_bollinger.bands.Rmd: -------------------------------------------------------------------------------- 1 | # Bollinger Bands 2 | 3 | ```{r, message = FALSE, warnings = FALSE} 4 | library(quantstrat) 5 | library(TTR) 6 | ``` 7 | 8 | ```{r} 9 | Sys.setenv(TZ = "UTC") 10 | ``` 11 | 12 | ```{r} 13 | currency("USD") 14 | ``` 15 | 16 | ```{r} 17 | stock("SPY", 18 | currency = "USD", 19 | multiplier = 1) 20 | ``` 21 | 22 | ## Get Symbols 23 | 24 | ```{r} 25 | init_date <- "2009-12-31" 26 | 27 | start_date <- "2010-01-01" 28 | 29 | end_date <- "2010-12-31" 30 | 31 | init_equity <- 1e5 # $10,000 32 | ``` 33 | 34 | ```{r} 35 | getSymbols(Symbols = "SPY", 36 | src = "yahoo", 37 | index.class = "POSIXct", 38 | from = start_date, 39 | to = end_date, 40 | adjust = TRUE) 41 | ``` 42 | 43 | ## Initialize Account, Portfolio and Strategy 44 | 45 | ```{r} 46 | strat.name <- "Bollinger.Band" 47 | ``` 48 | 49 | ```{r} 50 | rm.strat(strat.name) 51 | ``` 52 | 53 | ```{r} 54 | initPortf(strat.name, 55 | symbols = "SPY", 56 | initDate = init_date) 57 | ``` 58 | 59 | ```{r} 60 | initAcct(strat.name, 61 | portfolios = strat.name, 62 | initDate = init_date, 63 | initEq = init_equity) 64 | ``` 65 | 66 | ```{r} 67 | initOrders(portfolio = strat.name, 68 | symbols = "SPY", 69 | initDate = init_date) 70 | ``` 71 | 72 | ```{r} 73 | addPosLimit(portfolio = strat.name, 74 | symbol = "SPY", 75 | timestamp = start_date, 76 | maxpos = 200, 77 | longlevels = 2) 78 | ``` 79 | 80 | ```{r} 81 | strategy(strat.name, store = TRUE) 82 | ``` 83 | 84 | ```{r} 85 | strat <- getStrategy(strat.name) 86 | ``` 87 | 88 | ## Indicators 89 | 90 | ```{r 2-1-add-indicators} 91 | add.indicator(strategy = strat.name, 92 | name = "BBands", 93 | arguments = list(HLC = quote(HLC(mktdata)), 94 | n = 20, 95 | maType = "SMA", 96 | sd = 2), 97 | label = "BB.20.2") 98 | ``` 99 | 100 | ## Signals 101 | 102 | ```{r 2-1-add-signals} 103 | add.signal(strat.name, 104 | name="sigCrossover", 105 | arguments = list(columns = c("Close", "up"), 106 | relationship = "gt"), 107 | label="Cl.gt.Upper.Band") 108 | 109 | add.signal(strat.name, 110 | name = "sigCrossover", 111 | arguments = list(columns = c("Close", "dn"), 112 | relationship = "lt"), 113 | label = "Cl.lt.Lower.Band") 114 | 115 | add.signal(strat.name, 116 | name = "sigCrossover", 117 | arguments = list(columns = c("High", "Low", "mavg"), 118 | relationship = "op"), 119 | label = "Cross.Mid") 120 | ``` 121 | 122 | ## Adding Rules 123 | 124 | ```{r 2-1-add-rules} 125 | add.rule(strategy = strat.name, 126 | name = "ruleSignal", 127 | arguments = list(sigcol = "Cl.gt.Upper.Band", 128 | sigval = TRUE, 129 | orderqty = -100, 130 | ordertype = "market", 131 | orderside = NULL, 132 | threshold = NULL, 133 | osFUN = osMaxPos), 134 | type = "enter") 135 | 136 | add.rule(strategy = strat.name, 137 | name = "ruleSignal", 138 | arguments = list(sigcol = "Cl.lt.Lower.Band", 139 | sigval = TRUE, 140 | orderqty = 100, 141 | ordertype = "market", 142 | orderside = NULL, 143 | threshold = NULL, 144 | osFUN = osMaxPos), 145 | type = "enter") 146 | 147 | add.rule(strategy = strat.name, 148 | name = "ruleSignal", 149 | arguments = list(sigcol = "Cross.Mid", 150 | sigval = TRUE, 151 | orderqty = "all", 152 | ordertype = "market", 153 | orderside = NULL, 154 | threshold = NULL, 155 | osFUN = osMaxPos), 156 | label = "exitMid", 157 | type = "exit") 158 | ``` 159 | 160 | ```{r 2-1-apply-strategy} 161 | applyStrategy(strategy = strat.name, 162 | portfolios = strat.name) 163 | ``` 164 | 165 | ## Update Portfolio, Account 166 | 167 | ```{r 2-1-update-portfolio} 168 | updatePortf(strat.name) 169 | updateAcct(strat.name) 170 | updateEndEq(strat.name) 171 | ``` 172 | 173 | ## Returns 174 | 175 | ```{r} 176 | chart.Posn(Portfolio = strat.name, 177 | Symbol = "SPY", 178 | TA = "add_BBands(n = 20, sd = 2, maType = 'SMA', on = 1)") 179 | ``` 180 | 181 | -------------------------------------------------------------------------------- /_bookdown.yml: -------------------------------------------------------------------------------- 1 | chapter_name: "Chapter " 2 | output_dir: "book" 3 | edit: 4 | link: https://github.com/timtrice/backtesting-strategies/edit/master/%s 5 | text: "Edit This Page" 6 | rmd_files: ["index.Rmd", 7 | "introduction.Rmd", 8 | "get-symbols.Rmd", 9 | "basic-strategy.Rmd", 10 | "data-quality.Rmd", 11 | "parameter-optimization.Rmd", 12 | "stop-loss.Rmd", 13 | "stop-loss-optimization.Rmd", 14 | "trailing-stop.Rmd", 15 | "obtaining-resources.Rmd", 16 | "analyzing-results.Rmd", 17 | "walk-forward-analysis.Rmd", 18 | "monte-carlo-analysis.Rmd", 19 | "errors-warnings.Rmd", 20 | "about.Rmd"] 21 | -------------------------------------------------------------------------------- /_connors.rsi.2.Rmd: -------------------------------------------------------------------------------- 1 | # Connor's RSI(2) 2 | 3 | Let's step our analysis up a notch. First, we're going to run an analysis on twelve symbols. We're also going to add some conditional signals. 4 | 5 | The strategy we're going to use will be similar to Connor's RSI(2). The rules we'll apply are: 6 | 7 | 1. BTO if Cl > SMA(200) and RSI(2) < 5 8 | 9 | 1. sTC if Cl < SMA(200) or Cl > SMA(5) 10 | 11 | ```{r, message = FALSE, warnings = FALSE} 12 | library(quantstrat) 13 | library(TTR) 14 | ``` 15 | 16 | ```{r} 17 | Sys.setenv(TZ = "UTC") 18 | ``` 19 | 20 | ```{r} 21 | symbols <- c("IWM", "SPY", "QQQ", "XLF", "XLP", "XLE", 22 | "XLY", "XLV", "XLI", "XLB", "XLK", "XLU") 23 | ``` 24 | 25 | ```{r} 26 | currency("USD") 27 | ``` 28 | 29 | ## Get Symbols 30 | 31 | ```{r} 32 | init_date <- "2009-12-31" 33 | 34 | start_date <- "2010-01-01" 35 | 36 | end_date <- "2010-12-31" 37 | 38 | init_equity <- 1e4 # $10,000 39 | ``` 40 | 41 | ```{r} 42 | getSymbols(Symbols = symbols, 43 | src = "yahoo", 44 | index.class = "POSIXct", 45 | from = start_date, 46 | to = end_date, 47 | adjust = TRUE) 48 | ``` 49 | 50 | ```{r} 51 | stock(symbols, currency = "USD", multiplier = 1) 52 | ``` 53 | 54 | ## Initialize Account, Portfolio and Strategy 55 | 56 | ```{r} 57 | strat.name <- "Connors.RSI.2" 58 | ``` 59 | 60 | ```{r} 61 | rm.strat(strat.name) 62 | ``` 63 | 64 | ```{r} 65 | initPortf(strat.name, 66 | symbols = symbols, 67 | initDate = init_date) 68 | ``` 69 | 70 | ```{r} 71 | initAcct(strat.name, 72 | portfolios = strat.name, 73 | initDate = init_date, 74 | initEq = init_equity) 75 | ``` 76 | 77 | ```{r} 78 | initOrders(portfolio = strat.name, 79 | symbols = symbols, 80 | initDate = init_date) 81 | ``` 82 | 83 | ```{r} 84 | strategy(strat.name, store = TRUE) 85 | ``` 86 | 87 | ```{r} 88 | strat <- getStrategy(strat.name) 89 | ``` 90 | 91 | ## Indicators 92 | 93 | For our strategy, we need to add three indicators: SMA(5), SMA(200) and RSI(2). All of our indicators will use a simple moving average based on closing prices. 94 | 95 | ```{r 2-1-add-indicators} 96 | add.indicator(strategy = strat.name, 97 | name = "RSI", 98 | arguments = list(price = quote(getPrice(mktdata)), 99 | n = 2, 100 | maType = "SMA"), 101 | label = "RSI2") 102 | 103 | add.indicator(strategy = strat.name, 104 | name = "SMA", 105 | arguments = list(x = quote(Cl(mktdata)), 106 | n = 5), 107 | label = "SMA5") 108 | 109 | add.indicator(strategy = strat.name, 110 | name = "SMA", 111 | arguments = list(x = quote(Cl(mktdata)), 112 | n = 200), 113 | label = "SMA200") 114 | ``` 115 | 116 | ## Signals 117 | 118 | ```{r 2-1-add-signals} 119 | add.signal(strat.name, 120 | name="sigThreshold", 121 | arguments = list(threshold = 5, 122 | column = "RSI2", 123 | relationship = "lt", 124 | cross = FALSE), 125 | label = "RSI2.lt.5") 126 | 127 | add.signal(strat.name, 128 | name="sigCrossover", 129 | arguments = list(columns = c("Close", "SMA5"), 130 | relationship = "gte"), 131 | label="Cl.gte.SMA5") 132 | 133 | add.signal(strat.name, 134 | name = "sigComparison", 135 | arguments = list(columns = c("Close", "SMA200"), 136 | relationship = "lt"), 137 | label = "Cl.lt.SMA200") 138 | 139 | add.signal(strat.name, 140 | name = "sigFormula", 141 | arguments = list(columns = c("RSI2.lt.5", "Cl.lt.SMA200"), 142 | formula = "RSI2.lt.5 == TRUE & 143 | Cl.lt.SMA200 == FALSE", 144 | label = "trigger", 145 | cross = TRUE), 146 | label = "Buy") 147 | 148 | ``` 149 | 150 | ## Adding Rules 151 | 152 | ```{r 2-1-add-rules} 153 | add.rule(strat.name, 154 | name = 'ruleSignal', 155 | arguments = list(sigcol = "Buy", 156 | sigval = TRUE, 157 | orderqty = 100, 158 | ordertype = 'market', 159 | orderside = 'long'), 160 | type = 'enter') 161 | 162 | add.rule(strat.name, 163 | name = 'ruleSignal', 164 | arguments = list(sigcol = "Cl.lt.SMA200", 165 | sigval = TRUE, 166 | orderqty = 'all', 167 | ordertype = 'market', 168 | orderside = 'long'), 169 | type = 'exit') 170 | 171 | add.rule(strat.name, 172 | name = 'ruleSignal', 173 | arguments = list(sigcol = "Cl.gte.SMA5", 174 | sigval = TRUE, 175 | orderqty = 'all', 176 | ordertype = 'market', 177 | orderside = 'long'), 178 | type = 'exit') 179 | ``` 180 | 181 | ```{r, results = "hide"} 182 | # Trade output suppressed for length 183 | applyStrategy(strategy = strat.name, 184 | portfolios = strat.name) 185 | ``` 186 | 187 | ## Update Portfolio, Account 188 | 189 | ```{r 2-1-update-portfolio} 190 | updatePortf(strat.name) 191 | updateAcct(strat.name) 192 | updateEndEq(strat.name) 193 | ``` 194 | 195 | ### Per Trade Stats 196 | 197 | ```{r 2-1-trade-stats, include = TRUE} 198 | knitr::kable(t(tradeStats(strat.name))[-c(1:2), 1:6], 199 | caption = "Trade Stats per Symbol") 200 | 201 | knitr::kable(t(tradeStats(strat.name))[-c(1:2), 7:12], 202 | caption = "Trade Stats per Symbol") 203 | ``` 204 | 205 | ## Returns 206 | 207 | ```{r} 208 | for(symbol in symbols) { 209 | chart.Posn(Portfolio = strat.name, 210 | Symbol = symbol, 211 | TA = c("add_RSI(n = 2, maType = 'SMA')", 212 | "add_SMA(n = 5, col = 4, on = 1, lwd = 2)", 213 | "add_SMA(n = 200, col = 2, on = 1, lwd = 2)")) 214 | } 215 | ``` 216 | 217 | ## Equity Curve 218 | 219 | ```{r} 220 | a <- getAccount(strat.name) 221 | equity <- a$summary$End.Eq 222 | plot(equity, main = "Consolidated Equity Curve") 223 | ``` 224 | 225 | -------------------------------------------------------------------------------- /_output.yml: -------------------------------------------------------------------------------- 1 | bookdown::gitbook: 2 | css: custom_styles.css 3 | bookdown::pdf_book: 4 | keep_tex: yes 5 | -------------------------------------------------------------------------------- /about.Rmd: -------------------------------------------------------------------------------- 1 | # About "Backtesting Strategies with R" 2 | 3 | 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. 4 | 5 | ## Tim Trice 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | ## To You, the Reader 14 | 15 | The source code for this book is available [on Github](https://github.com/timtrice/stock-book). 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](https://github.com/timtrice/stock-book/issues). 16 | 17 | If you don't understand something, [let me know](http://timtrice.co/contact.html). I will attempt to address it the best I can. If not, I will find the answer. 18 | 19 | -------------------------------------------------------------------------------- /analyzing-results.Rmd: -------------------------------------------------------------------------------- 1 | # Analyzing Results 2 | 3 | ```{r analyzing-results-create-objects} 4 | portfolio.st <- "Port.Luxor" 5 | account.st <- "Acct.Luxor" 6 | strategy.st <- "Strat.Luxor" 7 | ``` 8 | 9 | ```{r analyzing-results-apply-paramset, results = "hide"} 10 | cwd <- getwd() 11 | setwd("./_data/") 12 | load.strategy(strategy.st) 13 | setwd(cwd) 14 | ``` 15 | 16 | ```{r analyzing-results-rm-strat} 17 | rm.strat(portfolio.st) 18 | rm.strat(account.st) 19 | ``` 20 | 21 | ```{r analyzing-results-init-portf} 22 | initPortf(name = portfolio.st, 23 | symbols = symbols, 24 | initDate = init_date) 25 | ``` 26 | 27 | ```{r analyzing-results-init-acct} 28 | initAcct(name = account.st, 29 | portfolios = portfolio.st, 30 | initDate = init_date, 31 | initEq = init_equity) 32 | ``` 33 | 34 | ```{r analyzing-results-init-orders} 35 | initOrders(portfolio = portfolio.st, 36 | initDate = init_date) 37 | ``` 38 | 39 | ## Apply Strategy 40 | 41 | ```{r analyzing-results-apply-strategy, results = "hide"} 42 | # Results hidden to save space 43 | applyStrategy(strategy.st, portfolios = portfolio.st) 44 | ``` 45 | 46 | ```{r analyzing-results-checkBlotterUpdate} 47 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 48 | ``` 49 | 50 | ```{r analyzing-results-update} 51 | updatePortf(portfolio.st) 52 | updateAcct(account.st) 53 | updateEndEq(account.st) 54 | ``` 55 | 56 | ## Chart Positions 57 | 58 | ```{r} 59 | for(symbol in symbols) { 60 | chart.Posn(portfolio.st, Symbol = symbol, 61 | TA = "add_SMA(n = 10, col = 4); add_SMA(n = 30, col = 2)") 62 | } 63 | ``` 64 | 65 | ## Trade Statistics 66 | 67 | ```{r} 68 | tstats <- tradeStats(portfolio.st) 69 | kable(t(tstats)) 70 | ``` 71 | 72 | ### Trade Related 73 | 74 | ```{r} 75 | tab.trades <- tstats %>% 76 | mutate(Trades = Num.Trades, 77 | Win.Percent = Percent.Positive, 78 | Loss.Percent = Percent.Negative, 79 | WL.Ratio = Percent.Positive/Percent.Negative) %>% 80 | select(Trades, Win.Percent, Loss.Percent, WL.Ratio) 81 | 82 | kable(t(tab.trades)) 83 | ``` 84 | 85 | ### Profit Related 86 | 87 | ```{r} 88 | tab.profit <- tstats %>% 89 | select(Net.Trading.PL, Gross.Profits, Gross.Losses, Profit.Factor) 90 | kable(t(tab.profit)) 91 | ``` 92 | 93 | ### Averages 94 | 95 | ```{r} 96 | tab.wins <- tstats %>% 97 | select(Avg.Trade.PL, Avg.Win.Trade, Avg.Losing.Trade, Avg.WinLoss.Ratio) 98 | 99 | kable(t(tab.wins)) 100 | ``` 101 | 102 | ### Performance Summary 103 | 104 | ```{r} 105 | rets <- PortfReturns(Account = account.st) 106 | rownames(rets) <- NULL 107 | charts.PerformanceSummary(rets, colorset = bluefocus) 108 | ``` 109 | 110 | ### Per Trade Statistics 111 | 112 | ```{r} 113 | for(symbol in symbols) { 114 | pts <- perTradeStats(portfolio.st, Symbol = symbol) 115 | kable(pts, booktabs = TRUE, caption = symbol) 116 | } 117 | kable(pts) 118 | ``` 119 | 120 | ### Performance Statistics 121 | 122 | ```{r} 123 | tab.perf <- table.Arbitrary(rets, 124 | metrics=c( 125 | "Return.cumulative", 126 | "Return.annualized", 127 | "SharpeRatio.annualized", 128 | "CalmarRatio"), 129 | metricsNames=c( 130 | "Cumulative Return", 131 | "Annualized Return", 132 | "Annualized Sharpe Ratio", 133 | "Calmar Ratio")) 134 | kable(tab.perf) 135 | ``` 136 | 137 | ### Risk Statistics 138 | 139 | ```{r} 140 | tab.risk <- table.Arbitrary(rets, 141 | metrics=c( 142 | "StdDev.annualized", 143 | "maxDrawdown", 144 | "VaR", 145 | "ES"), 146 | metricsNames=c( 147 | "Annualized StdDev", 148 | "Max DrawDown", 149 | "Value-at-Risk", 150 | "Conditional VaR")) 151 | kable(tab.risk) 152 | ``` 153 | 154 | ### Buy and Hold Performance 155 | 156 | ```{r} 157 | rm.strat("buyHold") 158 | 159 | # initialize portfolio and account 160 | initPortf("buyHold", "SPY", initDate = init_date) 161 | initAcct("buyHold", portfolios = "buyHold", 162 | initDate = init_date, initEq = init_equity) 163 | # place an entry order 164 | CurrentDate <- time(getTxns(Portfolio = portfolio.st, Symbol = "SPY"))[2] 165 | equity = getEndEq("buyHold", CurrentDate) 166 | ClosePrice <- as.numeric(Cl(SPY[CurrentDate,])) 167 | UnitSize = as.numeric(trunc(equity/ClosePrice)) 168 | addTxn("buyHold", Symbol = "SPY", TxnDate = CurrentDate, TxnPrice = ClosePrice, 169 | TxnQty = UnitSize, TxnFees = 0) 170 | # place an exit order 171 | LastDate <- last(time(SPY)) 172 | LastPrice <- as.numeric(Cl(SPY[LastDate,])) 173 | addTxn("buyHold", Symbol = "SPY", TxnDate = LastDate, TxnPrice = LastPrice, 174 | TxnQty = -UnitSize , TxnFees = 0) 175 | # update portfolio and account 176 | updatePortf(Portfolio = "buyHold") 177 | updateAcct(name = "buyHold") 178 | updateEndEq(Account = "buyHold") 179 | chart.Posn("buyHold", Symbol = "SPY") 180 | ``` 181 | 182 | ### Strategy vs. Market 183 | 184 | ```{r} 185 | rets <- PortfReturns(Account = account.st) 186 | rets.bh <- PortfReturns(Account = "buyHold") 187 | returns <- cbind(rets, rets.bh) 188 | charts.PerformanceSummary(returns, geometric = FALSE, wealth.index = TRUE, 189 | main = "Strategy vs. Market") 190 | ``` 191 | 192 | ### Risk/Return Scatterplot 193 | 194 | ```{r} 195 | chart.RiskReturnScatter(returns, Rf = 0, add.sharpe = c(1, 2), 196 | main = "Return vs. Risk", colorset = c("red", "blue")) 197 | ``` 198 | 199 | ### Relative Performance 200 | 201 | ```{r} 202 | for(n in 1:(ncol(returns) - 1)) { 203 | chart.RelativePerformance(returns[, n], returns[, ncol(returns)], 204 | colorset = c("red", "blue"), lwd = 2, 205 | legend.loc = "topleft") 206 | } 207 | ``` 208 | 209 | ### Portfolio Summary 210 | 211 | ```{r} 212 | #' Error 213 | pf <- getPortfolio(portfolio.st) 214 | xyplot(pf$summary, type = "h", col = 4) 215 | ``` 216 | 217 | ### Order Book 218 | 219 | ```{r} 220 | ob <- getOrderBook(portfolio.st) 221 | ``` 222 | 223 | ### Maximum Adverse Excursion 224 | 225 | ```{r} 226 | for(symbol in symbols) { 227 | chart.ME(Portfolio = portfolio.st, Symbol = symbol, type = "MAE", 228 | scale = "percent") 229 | } 230 | ``` 231 | 232 | ### Maximum Favorable Excursion 233 | 234 | ```{r} 235 | for(symbol in symbols) { 236 | chart.ME(Portfolio = portfolio.st, Symbol = symbol, type = "MFE", 237 | scale = "percent") 238 | } 239 | ``` 240 | 241 | ## Account Summary 242 | 243 | ```{r} 244 | a <- getAccount(account.st) 245 | xyplot(a$summary, type = "h", col = 4) 246 | ``` 247 | 248 | ### Equity Curve 249 | 250 | ```{r} 251 | equity <- a$summary$End.Eq 252 | plot(equity, main = "Equity Curve") 253 | ``` 254 | 255 | ### Account Performance Summary 256 | 257 | ```{r} 258 | ret <- Return.calculate(equity, method = "log") 259 | charts.PerformanceSummary(ret, colorset = bluefocus, 260 | main = "Strategy Performance") 261 | ``` 262 | 263 | ### Cumulative Returns 264 | 265 | ```{r} 266 | rets <- PortfReturns(Account = account.st) 267 | chart.CumReturns(rets, colorset = rich10equal, legend.loc = "topleft", 268 | main="SPDR Cumulative Returns") 269 | ``` 270 | 271 | ### Distribution Analysis 272 | 273 | ```{r} 274 | chart.Boxplot(rets, main = "SPDR Returns", colorset= rich10equal) 275 | ``` 276 | 277 | ### Annualized Returns 278 | 279 | ```{r} 280 | (ar.tab <- table.AnnualizedReturns(rets)) 281 | ``` 282 | 283 | ### Performance Scatter Plot 284 | 285 | ```{r} 286 | max.risk <- max(ar.tab["Annualized Std Dev",]) 287 | max.return <- max(ar.tab["Annualized Return",]) 288 | chart.RiskReturnScatter(rets, 289 | main = "SPDR Performance", colorset = rich10equal, 290 | xlim = c(0, max.risk * 1.1), ylim = c(0, max.return)) 291 | ``` 292 | 293 | ### Notional Costs 294 | 295 | ```{r} 296 | #quantstratII pp. 67/69 297 | mnc <- pts$Max.Notional.Cost 298 | pe <- sapply(pts$Start,getEndEq, Account = account.st)/3 299 | barplot(rbind(pe,mnc),beside=T,col=c(2,4),names.arg=format(pts$Start,"%m/%d/%y"), 300 | ylim=c(0,1.5e5),ylab="$",xlab="Trade Date") 301 | legend(x="topleft",legend=c("(Portfolio Equity)/9","Order Size"), 302 | pch=15,col=c(2,4),bty="n") 303 | title("Percent of Portfolio Equity versus Trade Size for XLU") 304 | ``` 305 | 306 | -------------------------------------------------------------------------------- /basic-strategy.Rmd: -------------------------------------------------------------------------------- 1 | # Basic Strategy {#basic-strategy} 2 | 3 | Let's kick things off with a variation of the Luxor trading strategy. This strategy uses two SMA indicators: SMA(10) and SMA(30). 4 | 5 | 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. 6 | 7 | ```{block type = "strategy"} 8 | If SMA(10) >= SMA(30): 9 | 10 | BTC short, BTO long 11 | 12 | Else if SMA(10) < SMA(30): 13 | 14 | STC long, STO short 15 | ``` 16 | 17 | **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. 18 | 19 | ## Strategy Setup 20 | 21 | We load our symbols into `symbols`. 22 | 23 | ```{r basic-strategy-symbols} 24 | print(basic_symbols()) 25 | symbols <- basic_symbols() 26 | ``` 27 | 28 | ```{r basic-strategy-getsymbols} 29 | getSymbols(Symbols = symbols, 30 | src = "yahoo", 31 | index.class = "POSIXct", 32 | from = start_date, 33 | to = end_date, 34 | adjust = adjustment) 35 | ``` 36 | 37 | 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. 38 | 39 | ```{r basic-strategy-stock} 40 | stock(symbols, 41 | currency = "USD", 42 | multiplier = 1) 43 | ``` 44 | 45 | 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. 46 | 47 | ```{r basic-strategy-create-objects} 48 | portfolio.st <- "Port.Luxor" 49 | account.st <- "Acct.Luxor" 50 | strategy.st <- "Strat.Luxor" 51 | ``` 52 | 53 | 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. 54 | 55 | ```{r basic-strategy-rm-strat} 56 | rm.strat(portfolio.st) 57 | rm.strat(account.st) 58 | ``` 59 | 60 | Now we initialize our portfolio, account and orders. We will also store our strategy to save for later. 61 | 62 | ```{r basic-strategy-init-portf} 63 | initPortf(name = portfolio.st, 64 | symbols = symbols, 65 | initDate = init_date) 66 | ``` 67 | 68 | ```{r basic-strategy-init-acct} 69 | initAcct(name = account.st, 70 | portfolios = portfolio.st, 71 | initDate = init_date, 72 | initEq = init_equity) 73 | ``` 74 | 75 | ```{r basic-strategy-init-orders} 76 | initOrders(portfolio = portfolio.st, 77 | symbols = symbols, 78 | initDate = init_date) 79 | ``` 80 | 81 | ```{r basic-strategy-strategy} 82 | strategy(strategy.st, store = TRUE) 83 | ``` 84 | 85 | ## Add Indicators 86 | 87 | 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. 88 | 89 | 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. 90 | 91 | ```{r basic-strategy-add-indicators} 92 | add.indicator(strategy = strategy.st, 93 | name = "SMA", 94 | arguments = list(x = quote(Cl(mktdata)), 95 | n = 10), 96 | label = "nFast") 97 | 98 | add.indicator(strategy = strategy.st, 99 | name = "SMA", 100 | arguments = list(x = quote(Cl(mktdata)), 101 | n = 30), 102 | label = "nSlow") 103 | ``` 104 | 105 | `add.indicator` is a function of `quantstrat` and adds our indicators to our strategy object. For now we'll use the following parameters: 106 | 107 | * `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. 108 | 109 | * `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... 110 | 111 | * `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. 112 | 113 | * `label`: Label of the variable that will be added to our dataset. This must be unique for each indicator we add. 114 | 115 | 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 116 | 117 | `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. 118 | 119 | 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. 120 | 121 | `Cl` is actually short-hand for Close as you may have guessed. In fact, we have several short-hand functions for our variables: 122 | 123 | * `Op()`: Open 124 | 125 | * `Hi()`: High 126 | 127 | * `Lo()`: Low 128 | 129 | * `Cl()`: Close 130 | 131 | * `Vo()`: Volume 132 | 133 | * `Ad()`: Adjusted 134 | 135 | * `OpCl()`: Open and Close (n x 2 dataset) 136 | 137 | * `HLC()`: High, Low and Close (n x 3 dataset) 138 | 139 | See the help for any of those symbols above for a more detailed listing. 140 | 141 | `quote()` is a R function that simply wraps the supplied parameter in quotes. 142 | 143 | So we've added two indicators to our `mktdata` object, `nFast` (SMA(10)) and `nSlow` (SMA(30)). Let's now add signals. 144 | 145 | ## Add Signals 146 | 147 | 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. 148 | 149 | ```{r basic-strategy-add-signals} 150 | add.signal(strategy = strategy.st, 151 | name="sigCrossover", 152 | arguments = list(columns = c("nFast", "nSlow"), 153 | relationship = "gte"), 154 | label = "long") 155 | 156 | add.signal(strategy = strategy.st, 157 | name="sigCrossover", 158 | arguments = list(columns = c("nFast", "nSlow"), 159 | relationship = "lt"), 160 | label = "short") 161 | ``` 162 | 163 | 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: 164 | 165 | * `sigComparison`: boolean, compare two variables by relationship 166 | + *gt* greater than 167 | + *lt* less than 168 | + *eq* equal to 169 | + *gte* greater than or equal to 170 | + *lte* less than or equal to 171 | 172 | * `sigCrossover`: boolean, TRUE when one signal crosses another. Uses the same relationships as `sigComparison` 173 | 174 | * `sigFormula`: apply a formula to multiple variables. 175 | 176 | * `sigPeak`: identify local minima or maxima of an indicator 177 | 178 | * `sigThreshold`: boolean, when an indicator crosses a value. Uses relationships as identified above. 179 | 180 | * `sigTimestamp`: generates a signal based on a timestamp. 181 | 182 | We'll attempt to use each of these signals throughout the book when possible. 183 | 184 | ## Add Rules 185 | 186 | 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. 187 | 188 | `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. 189 | 190 | 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. 191 | 192 | ```{r basic-strategy-add-rules-enterlong} 193 | add.rule(strategy = strategy.st, 194 | name = "ruleSignal", 195 | arguments = list(sigcol = "long", 196 | sigval = TRUE, 197 | orderqty = 100, 198 | ordertype = "stoplimit", 199 | orderside = "long", 200 | threshold = 0.0005, 201 | prefer = "High", 202 | TxnFees = -10, 203 | replace = FALSE), 204 | type = "enter", 205 | label = "EnterLONG") 206 | ``` 207 | 208 | 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`). 209 | 210 | ```{r basic-strategy-add-rules-entershort} 211 | add.rule(strategy.st, 212 | name = "ruleSignal", 213 | arguments = list(sigcol = "short", 214 | sigval = TRUE, 215 | orderqty = -100, 216 | ordertype = "stoplimit", 217 | threshold = -0.005, 218 | orderside = "short", 219 | replace = FALSE, 220 | TxnFees = -10, 221 | prefer = "Low"), 222 | type = "enter", 223 | label = "EnterSHORT") 224 | ``` 225 | 226 | 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. 227 | 228 | 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. 229 | 230 | ```{r basic-strategy-add-rules-exit2short} 231 | add.rule(strategy.st, 232 | name = "ruleSignal", 233 | arguments = list(sigcol = "short", 234 | sigval = TRUE, 235 | orderside = "long", 236 | ordertype = "market", 237 | orderqty = "all", 238 | TxnFees = -10, 239 | replace = TRUE), 240 | type = "exit", 241 | label = "Exit2SHORT") 242 | ``` 243 | 244 | 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`). 245 | 246 | ```{r basic-strategy-add-rules-exit2long} 247 | add.rule(strategy.st, 248 | name = "ruleSignal", 249 | arguments = list(sigcol = "long", 250 | sigval = TRUE, 251 | orderside = "short", 252 | ordertype = "market", 253 | orderqty = "all", 254 | TxnFees = -10, 255 | replace = TRUE), 256 | type = "exit", 257 | label = "Exit2LONG") 258 | ``` 259 | 260 | `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. 261 | 262 | 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. 263 | 264 | ## Apply Strategy 265 | 266 | 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! 267 | 268 | ```{r basic-strategy-apply-strategy} 269 | cwd <- getwd() 270 | setwd("./_data/") 271 | results_file <- paste("results", strategy.st, "RData", sep = ".") 272 | if( file.exists(results_file) ) { 273 | load(results_file) 274 | } else { 275 | results <- applyStrategy(strategy.st, portfolios = portfolio.st) 276 | updatePortf(portfolio.st) 277 | updateAcct(account.st) 278 | updateEndEq(account.st) 279 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 280 | save(list = "results", file = results_file) 281 | save.strategy(strategy.st) 282 | } 283 | } 284 | setwd(cwd) 285 | ``` 286 | 287 | Awesome! We know that at least our code is good. 288 | 289 | `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. 290 | 291 | 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. 292 | 293 | 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. 294 | 295 | 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. 296 | 297 | We also use the `checkBlotterUpdate()` mentioned in \@ref(checkBlotterUpdate). 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). 298 | 299 | 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. 300 | -------------------------------------------------------------------------------- /custom_styles.css: -------------------------------------------------------------------------------- 1 | .dataTable { 2 | caption-side: bottom; 3 | width: 700px; 4 | } 5 | 6 | div.cost { 7 | background-image: url(https://s3.amazonaws.com/backtesting-strategies-r/banknote.png); 8 | background-repeat: no-repeat; 9 | background-position: 10px; 10 | background-color: lightgreen; 11 | padding: 25px 25px 25px 65px; 12 | font-weight: bold; 13 | border: 1px solid black; 14 | } 15 | 16 | div.warning { 17 | background-image: url(https://s3.amazonaws.com/backtesting-strategies-r/fire.png); 18 | background-repeat: no-repeat; 19 | background-position: 10px; 20 | background-color: pink; 21 | padding: 25px 25px 25px 65px; 22 | font-weight: bold; 23 | border: 1px solid black; 24 | } 25 | 26 | div.info { 27 | background-image: url(https://s3.amazonaws.com/backtesting-strategies-r/bulb.png); 28 | background-repeat: no-repeat; 29 | background-position: 10px; 30 | background-color: lightgray; 31 | padding: 25px 25px 25px 65px; 32 | font-weight: bold; 33 | border: 1px solid black; 34 | } 35 | -------------------------------------------------------------------------------- /data-quality.Rmd: -------------------------------------------------------------------------------- 1 | # Data Quality 2 | 3 | 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. 4 | 5 | ## Yahoo! vs. Google 6 | 7 | I'll use **dplyr 0.4.3**, **ggplot2 2.0.0** and **tidyr 0.4.1** to help with analysis. 8 | 9 | ```{r data-quality-a-yahoo-getsymbols} 10 | getSymbols("SPY", 11 | src = "yahoo", 12 | index.class = c("POSIXt", "POSIXct"), 13 | from = "2010-01-01", 14 | to = "2011-01-01", 15 | adjust = TRUE) 16 | yahoo.SPY <- SPY 17 | summary(yahoo.SPY) 18 | ``` 19 | 20 | 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. 21 | 22 | ```{r data-quality-a-google-getsymbols} 23 | rm(SPY) 24 | getSymbols("SPY", 25 | src = "google", 26 | index.class = c("POSIXt", "POSIXct"), 27 | from = "2010-01-01", 28 | to = "2011-01-01", 29 | adjust = TRUE) 30 | google.SPY <- SPY 31 | summary(google.SPY) 32 | ``` 33 | 34 | 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`). 35 | 36 | ```{r data-quality-a-boxplot} 37 | bind_rows(as.data.frame(yahoo.SPY) %>% 38 | mutate(Src = "Yahoo"), 39 | as.data.frame(google.SPY) %>% 40 | mutate(Src = "Google")) %>% 41 | gather(key, value, 1:4, na.rm = TRUE) %>% 42 | ggplot(aes(x = key, y = value, fill = Src)) + 43 | geom_boxplot() + 44 | theme_bw() + 45 | theme(legend.title = element_blank(), legend.position = "bottom") + 46 | ggtitle("Google vs. Yahoo! (non-NA)") 47 | ``` 48 | 49 | 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: 50 | 51 | ```{r data-quality-a-holidays} 52 | as.data.frame(google.SPY) %>% 53 | mutate(Date = index(google.SPY)) %>% 54 | select(Date, starts_with("SPY"), -SPY.Volume) %>% 55 | filter(is.na(SPY.Open)) 56 | ``` 57 | 58 | We can see many of these dates correspond closely to national holidays; *2010-11-24* would be Thanksgiving, *2010-12-23* would be Christimas. 59 | 60 | 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. 61 | 62 | 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. 63 | 64 | However, it's up to you to ensure your data is top quality. 65 | 66 | > Garbage in, garbage out 67 | 68 | ## Examining Trades 69 | 70 | 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? 71 | 72 | Every backtest must be picked apart from beginning to end. Checking our data was the first step. Checking our trades is next. 73 | 74 | We'll reload our **Luxor** strategy and examine some of the trades for **SPY**. 75 | 76 | ```{r data-quality-b-load-strategy, results = "hide"} 77 | rm.strat(portfolio.st) 78 | rm.strat(account.st) 79 | symbols <- basic_symbols() 80 | getSymbols(Symbols = symbols, src = "yahoo", index.class = "POSIXct", 81 | from = start_date, to = end_date, adjust = adjustment) 82 | initPortf(name = portfolio.st, symbols = symbols, initDate = init_date) 83 | initAcct(name = account.st, portfolios = portfolio.st, initDate = init_date, 84 | initEq = init_equity) 85 | initOrders(portfolio = portfolio.st, symbols = symbols, initDate = init_date) 86 | applyStrategy(strategy.st, portfolios = portfolio.st) 87 | checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE) 88 | updatePortf(portfolio.st) 89 | updateAcct(account.st) 90 | updateEndEq(account.st) 91 | ``` 92 | 93 | ```{r data-quality-b-chart-posn-1, fig.cap = "SPY Trades for Jan 1, 2008 to July 1, 2008"} 94 | chart.Posn(portfolio.st, Symbol = "SPY", Dates="2008-01-01::2008-07-01", 95 | TA="add_SMA(n = 10, col = 2); add_SMA(n = 30, col = 4)") 96 | ``` 97 | 98 | 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. 99 | 100 | ```{r data-quality-b-mktdata} 101 | le <- as.data.frame(mktdata["2008-02-25::2008-03-07", c(1:4, 7:10)]) 102 | DT::datatable(le, 103 | rownames = TRUE, 104 | extensions = c("Scroller", "FixedColumns"), 105 | options = list(pageLength = 5, 106 | autoWidth = TRUE, 107 | deferRender = TRUE, 108 | scrollX = 200, 109 | scroller = TRUE, 110 | fixedColumns = TRUE), 111 | caption = htmltools::tags$caption( 112 | "Table 6.1: mktdata object for Feb. 25, 2008 to Mar. 7, 2008")) 113 | ``` 114 | 115 | 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. 116 | 117 | ```{r data-quality-b-order-book} 118 | ob <- as.data.table(getOrderBook(portfolio.st)$Quantstrat$SPY) 119 | DT::datatable(ob, 120 | rownames = FALSE, 121 | filter = "top", 122 | extensions = c("Scroller", "FixedColumns"), 123 | options = list(pageLength = 5, 124 | autoWidth = TRUE, 125 | deferRender = TRUE, 126 | scrollX = 200, 127 | scroller = TRUE, 128 | fixedColumns = TRUE), 129 | caption = htmltools::tags$caption( 130 | "Table 6.2: Order book for SPY")) 131 | ``` 132 | 133 | 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. 134 | 135 | (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.) 136 | 137 | 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. 138 | 139 | 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. 140 | 141 | 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. 142 | 143 | ```{r data-quality-b-chart-posn-2, fig.cap = "SPY Trades for Jan 1, 2008 to July 1, 2008"} 144 | chart.Posn(portfolio.st, Symbol = "SPY", Dates="2009-08-01::2009-12-31", 145 | TA="add_SMA(n = 10, col = 2); add_SMA(n = 30, col = 4)") 146 | ``` 147 | 148 | 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. 149 | 150 | So it seems the orders are triggering as expected. 151 | 152 | 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. 153 | 154 | 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. 155 | 156 | 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. 157 | 158 | You may also notice our CumPL and Drawdown graphs started at 0 on the last chart posted. 159 | 160 | `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. 161 | 162 | ```{r data-quality-b-chart-posn-3, fig.cap = "SPY Trades for Jan 1, 2008 to July 1, 2008"} 163 | chart.Posn(portfolio.st, Symbol = "SPY", 164 | TA="add_SMA(n = 10, col = 2); add_SMA(n = 30, col = 4)") 165 | ``` 166 | 167 | 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. 168 | 169 | 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. 170 | 171 | Again, we'll get into all of this later. Just something to keep in mind when you start doing analysis. 172 | -------------------------------------------------------------------------------- /errors-warnings.Rmd: -------------------------------------------------------------------------------- 1 | # Warnings and Errors 2 | 3 | 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. 4 | 5 | Messages below are not intended to state specifically where the issue may be. Hopefully they will point you in the right direction. 6 | 7 | ## Must use auto.assign=TRUE for multiple Symbols requests 8 | 9 | > Error in getSymbols(Symbols = symbols, reload.Symbols = FALSE, verbose = FALSE, : 10 | must use auto.assign=TRUE for multiple Symbols requests 11 | 12 | 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. 13 | 14 | See `?getSymbols` for more information on `auto.assign`. 15 | 16 | ## Missing in call to function add.distribution 17 | 18 | > Error in must.have.args(match.call(), c("strategy", "paramset.label", : 19 | > label: argument(s) missing in call to function add.distribution 20 | 21 | You're missing a label argument in your `add.distribution` call. 22 | -------------------------------------------------------------------------------- /get-symbols.Rmd: -------------------------------------------------------------------------------- 1 | # Get Symbols 2 | 3 | We start off by loading `basic_symbols()` into the `symbols` variable: 4 | 5 | ```{r get-symbols-symbols} 6 | print(basic_symbols()) 7 | 8 | symbols <- basic_symbols() 9 | ``` 10 | 11 | `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`. 12 | 13 | We send a request through `getSymbols()` to download data. We can use any of the following sources: 14 | 15 | ** Yahoo! 16 | 17 | ** Google 18 | 19 | ** MySQL 20 | 21 | ** RData 22 | 23 | ** CSV 24 | 25 | For data other than stocks we can use: 26 | 27 | ** [FRED (Federal Reserve Economic Data) source](https://research.stlouisfed.org/fred2/); FRED contains data such as unemployment, GDP, treasury rates and more. 28 | 29 | ** [OANDA](https://www.oanda.com/) is a subscription-service for forex and currency data. 30 | ## Yahoo! 31 | 32 | ```{r get-symbols-yahoo-getsymbols} 33 | getSymbols(Symbols = symbols, 34 | src = "yahoo", 35 | index.class = "POSIXct", 36 | from = start_date, 37 | to = end_date, 38 | adjust = adjustment) 39 | ``` 40 | 41 | 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. 42 | 43 | ```{r get-symbols-comment-1, include = FALSE} 44 | #' What is this message stating exactly and how will the next version of 45 | #' quantmod affect our code? 46 | ``` 47 | 48 | ** **Symbols**: one or a vector of multiple symbols. 49 | 50 | ** **src**: string, our source. In this case, Yahoo! 51 | 52 | ** **index.class**: POSIXct. This sets the class of our xts object index. 53 | 54 | ** **from**: string, first date of data we want to retrieve 55 | 56 | ** **end**: string, last date of data we want to retrieve 57 | 58 | ** **adjust**: boolean, whether to adjust our data or not. Suggested to set as TRUE. 59 | 60 | `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. 61 | 62 | ```{r get-symbols-remove-yahoo-symbols} 63 | head(IWM) 64 | tail(IWM) 65 | summary(IWM) 66 | # Clear symbols 67 | rm(list=basic_symbols()) 68 | ``` 69 | 70 | ## Google 71 | 72 | ```{r get-symbols-google-getsymbols} 73 | getSymbols(Symbols = symbols, 74 | src = "google", 75 | index.class = "POSIXct", 76 | from = start_date, 77 | to = end_date, 78 | adjust = adjustment) 79 | ``` 80 | 81 | We access data from Google same as Yahoo! only changing the **src** parameter. 82 | 83 | ```{r get-symbols-remove-google-symbols} 84 | head(IWM) 85 | tail(IWM) 86 | summary(IWM) 87 | # Clear symbols 88 | rm(list=basic_symbols()) 89 | ``` 90 | 91 | ## MySQL 92 | 93 | ```{r get-symbols-hidden-1, include = FALSE} 94 | source("../.my.conf.R") 95 | ``` 96 | 97 | ```{r get-symbols-mysql-getsymbols} 98 | getSymbols(Symbols = symbols, 99 | src = "MySQL", 100 | dbname = db, 101 | user = user, 102 | password = pw, 103 | host = host, 104 | index.class = "POSIXct", 105 | from = start_date, 106 | to = end_date, 107 | adjust = adjustment) 108 | ``` 109 | 110 | 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. 111 | 112 | `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. 113 | 114 | 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. 115 | 116 | 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. 117 | 118 | The data in my database is a duplicate of Yahoo!. The output below is to show the success of the `getSymbols(src="MySQL")` call. 119 | 120 | ```{r get-symbols-remove-mysql-symbols} 121 | head(IWM) 122 | tail(IWM) 123 | summary(IWM) 124 | # Clear symbols 125 | rm(list=basic_symbols()) 126 | ``` 127 | 128 | ## FRED 129 | 130 | For this example we'll bail on the `symbols` we have been using prior. FRED contains over 11,000 econonomic datasets at no cost. 131 | 132 | Let's look at the 10-Year Treasury Rate: 133 | 134 | > https://research.stlouisfed.org/fred2/series/DGS10 135 | 136 | Notice the tail of the url has the alphanumeric string **DGS10** which we will be using in our `getSymbols()` call. 137 | 138 | ```{r get-symbols-fred-getsymbols} 139 | getSymbols(Symbols = "DGS10", src = "FRED") 140 | ``` 141 | 142 | Notice our call is shorter than previous; we do not need to classify index nor do we need to adjust any data. 143 | 144 | In addition, passing data parameters doesn't seem to do anything; the entire dataset is returned. 145 | 146 | I'll use `quantmod::chartSeries()` to plot the data: 147 | 148 | ```{r get-symbols-dgs10-chartseries} 149 | chartSeries(DGS10) 150 | ``` 151 | 152 | ```{r get-symbols-remove-DGS10} 153 | rm(DGS10) 154 | ``` 155 | 156 | ## OANDA 157 | 158 | Contributions needed. 159 | -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Backtesting Strategies with R" 3 | author: "Tim Trice" 4 | date: "`r Sys.Date()`" 5 | knit: "bookdown::render_book" 6 | output: 7 | bookdown::gitbook: 8 | lib_dir: assets 9 | config: 10 | toolbar: 11 | position: static 12 | highlight: default 13 | bookdown::pdf_book: 14 | keep_tex: yes 15 | bookdown::html_chapters: 16 | css: toc.css 17 | documentclass: book 18 | description: "Backtesting strategies with R" 19 | --- 20 | 21 | ```{r index-comments, eval = FALSE, echo = FALSE} 22 | # Click "Knit" button above to view sample 23 | # Live serve: 24 | # bookdown:::serve_book() 25 | # Produce gitbook output: 26 | # bookdown:;render_book("index.Rmd", "bookdown::gitbook") 27 | # To produce PDF output: 28 | # bookdown::render_book("index.Rmd", "bookdown::pdf_book") 29 | # Preview a chapter: 30 | # bookdown::preview_chapter("file.Rmd") 31 | ``` 32 | 33 | # Introduction 34 | 35 | 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. 36 | 37 | * Test a strategy; reject if results are not promising 38 | 39 | * Apply a range of parameters to strategies for optimization 40 | 41 | * Attempt to kill any strategy that looks promising. 42 | 43 | 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. 44 | 45 | ## R Resources 46 | 47 | 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: 48 | 49 | * [Advanced R](http://adv-r.had.co.nz/) 50 | 51 | In addition, the packages used in this book can be found under the [TradeAnalytics projected on R-Forge](http://r-forge.r-project.org/projects/blotter/). You will find forums and source code that have helped inspire this book. 52 | 53 | I also recommend you read [Guy Yollin's presentations](http://www.r-programming.org/papers) on backtesting as well as the [Using Quantstrat](https://docs.google.com/presentation/d/1fGzDc-LFfCQJKHHzaonspuX1_TTm1EB5hlvCEDsz7zw/pub?slide=id.p) presentation by Jan Humme and Brian Peterson. 54 | 55 | 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. 56 | 57 | Also, this book is open-source. Anyone is welcome to contribute. You can find the source code available [on my Github account](https://github.com/timtrice/stock-book). 58 | 59 | ## Libraries 60 | 61 | The only required library needed to run backtesting strategies is `quantstrat`. `quantstrat` will load all additionally required libraries. 62 | 63 | * quantstrat 0.9.1739 64 | 65 | This version of `quantstrat` includes the following packages, among others: 66 | 67 | * blotter 0.9.1741 68 | 69 | * quantmod 0.4-5 70 | 71 | * TTR 0.23-1 72 | 73 | With these libraries we will have all we need to fully-test strategies and measure performance. See 1.3 SessionInfo for more details. 74 | 75 | ```{r index-mandatory-libraries} 76 | library(quantstrat) 77 | ``` 78 | 79 | Additional libraries we may use for analysis or book presentation: 80 | 81 | * ggplot2 2.0.0 82 | 83 | * dplyr 0.4.3 84 | 85 | * tidyr 0.4.1 86 | 87 | ```{r index-optional-libraries} 88 | library(data.table) 89 | library(dplyr) 90 | library(DT) 91 | library(ggplot2) 92 | library(htmltools) 93 | library(htmlwidgets) 94 | library(knitr) 95 | library(lattice) 96 | library(pander) 97 | library(tidyr) 98 | library(webshot) 99 | ``` 100 | 101 | ## SessionInfo 102 | 103 | ```{r session-info} 104 | sessionInfo() 105 | ``` 106 | -------------------------------------------------------------------------------- /introduction.Rmd: -------------------------------------------------------------------------------- 1 | # Terminology {#terminology} 2 | 3 | * BTO: Buy to Open (open long positions) 4 | 5 | * BTC: Buy to close (close short positions) 6 | 7 | * SL: Stop-limit order 8 | 9 | * STO: Sell to open (open short positions) 10 | 11 | * STC: Sell to close (close long positions) 12 | 13 | * TS: Trailing-stop order 14 | 15 | # Using Quantsrat {#using-quantstrat} 16 | 17 | ```{r knitr-settings, include = FALSE} 18 | knitr::opts_chunk$set(echo = TRUE, 19 | message = TRUE, 20 | warning = TRUE, 21 | include = TRUE, 22 | cache = FALSE, 23 | fig.align = "center") 24 | ``` 25 | 26 | 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. 27 | 28 | `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. 29 | 30 | `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. 31 | 32 | ## Settings and Variables 33 | 34 | 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. 35 | 36 | First we use `Sys.setenv()` to set our timezone to UTC. 37 | 38 | ```{r introduction-timezone} 39 | Sys.setenv(TZ = "UTC") 40 | ``` 41 | 42 | Next, since we'll be working with stocks in the U.S. market we need to set our `currency` object to **USD**. 43 | 44 | ```{r introduction-currency} 45 | currency('USD') 46 | ``` 47 | 48 | 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. 49 | 50 | * `init_date`: The date we will initialize our account and portfolio objects. This date should be the day prior to `start_date`. 51 | 52 | * `start_date`: First date of data to retrieve. 53 | 54 | * `end_date`: Last date of data to retrieve. 55 | 56 | * `init_equity`: Initial account equity. 57 | 58 | * `adjustment`: Boolean - TRUE if we should adjust the prices for dividend payouts, stock splits, etc; otherwise, FALSE. 59 | 60 | You should always work with adjusted pricing when possible to give you the truest results. 61 | 62 | ```{r introduction-variables} 63 | init_date <- "2007-12-31" 64 | start_date <- "2008-01-01" 65 | end_date <- "2009-12-31" 66 | init_equity <- 1e4 # $10,000 67 | adjustment <- TRUE 68 | ``` 69 | 70 | ## Symbols 71 | 72 | Most our strategies will use three ETF's: *IWM*, *QQQ* and *SPY*. This is only for demonstration purposes. They are loaded into `basic_symbols()`. 73 | 74 | ```{r introduction-basic-symbols} 75 | basic_symbols <- function() { 76 | symbols <- c( 77 | "IWM", # iShares Russell 2000 Index ETF 78 | "QQQ", # PowerShares QQQ TRust, Series 1 ETF 79 | "SPY" # SPDR S&P 500 ETF Trust 80 | ) 81 | } 82 | ``` 83 | 84 | 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*. 85 | 86 | ```{r introduction-enhanced-symnbols} 87 | enhanced_symbols <- function() { 88 | symbols <- c( 89 | basic_symbols(), 90 | "TLT", # iShares Barclays 20+ Yr Treas. Bond ETF 91 | "XLB", # Materials Select Sector SPDR ETF 92 | "XLE", # Energy Select Sector SPDR ETF 93 | "XLF", # Financial Select Sector SPDR ETF 94 | "XLI", # Industrials Select Sector SPDR ETF 95 | "XLK", # Technology Select Sector SPDR ETF 96 | "XLP", # Consumer Staples Select Sector SPDR ETF 97 | "XLU", # Utilities Select Sector SPDR ETF 98 | "XLV", # Health Care Select Sector SPDR ETF 99 | "XLY" # Consumer Discretionary Select Sector SPDR ETF 100 | ) 101 | } 102 | ``` 103 | 104 | 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. 105 | 106 | ```{r introduction-global-symnbols} 107 | global_symbols <- function() { 108 | symbols <- c( 109 | enhanced_symbols(), 110 | "EFA", # iShares EAFE 111 | "EPP", # iShares Pacific Ex Japan 112 | "EWA", # iShares Australia 113 | "EWC", # iShares Canada 114 | "EWG", # iShares Germany 115 | "EWH", # iShares Hong Kong 116 | "EWJ", # iShares Japan 117 | "EWS", # iShares Singapore 118 | "EWT", # iShares Taiwan 119 | "EWU", # iShares UK 120 | "EWY", # iShares South Korea 121 | "EWZ", # iShares Brazil 122 | "EZU", # iShares MSCI EMU ETF 123 | "IGE", # iShares North American Natural Resources 124 | "IYR", # iShares U.S. Real Estate 125 | "IYZ", # iShares U.S. Telecom 126 | "LQD", # iShares Investment Grade Corporate Bonds 127 | "SHY" # iShares 42372 year TBonds 128 | ) 129 | } 130 | ``` 131 | 132 | ## checkBlotterUpdate() {#checkBlotterUpdate} 133 | 134 | The `checkBlotterUpdate()` function comes courtesy of [Guy Yollin](http://www.r-programming.org/papers). 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?). 135 | 136 | ```{r checkBlotterUpdate} 137 | # Guy Yollin, 2014 138 | # http://www.r-programming.org/papers 139 | 140 | checkBlotterUpdate <- function(port.st = portfolio.st, 141 | account.st = account.st, 142 | verbose = TRUE) { 143 | 144 | ok <- TRUE 145 | p <- getPortfolio(port.st) 146 | a <- getAccount(account.st) 147 | syms <- names(p$symbols) 148 | port.tot <- sum( 149 | sapply( 150 | syms, 151 | FUN = function(x) eval( 152 | parse( 153 | text = paste("sum(p$symbols", 154 | x, 155 | "posPL.USD$Net.Trading.PL)", 156 | sep = "$"))))) 157 | 158 | port.sum.tot <- sum(p$summary$Net.Trading.PL) 159 | 160 | if(!isTRUE(all.equal(port.tot, port.sum.tot))) { 161 | ok <- FALSE 162 | if(verbose) print("portfolio P&L doesn't match sum of symbols P&L") 163 | } 164 | 165 | initEq <- as.numeric(first(a$summary$End.Eq)) 166 | endEq <- as.numeric(last(a$summary$End.Eq)) 167 | 168 | if(!isTRUE(all.equal(port.tot, endEq - initEq)) ) { 169 | ok <- FALSE 170 | if(verbose) print("portfolio P&L doesn't match account P&L") 171 | } 172 | 173 | if(sum(duplicated(index(p$summary)))) { 174 | ok <- FALSE 175 | if(verbose)print("duplicate timestamps in portfolio summary") 176 | 177 | } 178 | 179 | if(sum(duplicated(index(a$summary)))) { 180 | ok <- FALSE 181 | if(verbose) print("duplicate timestamps in account summary") 182 | } 183 | return(ok) 184 | } 185 | ``` 186 | 187 | * [http://r-forge.r-project.org/projects/blotter/](http://r-forge.r-project.org/projects/blotter/) 188 | 189 | * [http://www.r-programming.org/papers](http://www.r-programming.org/papers) 190 | -------------------------------------------------------------------------------- /monte-carlo-analysis.Rmd: -------------------------------------------------------------------------------- 1 | # Monte Carlo Analysis 2 | 3 | -------------------------------------------------------------------------------- /obtaining-resources.Rmd: -------------------------------------------------------------------------------- 1 | # Obtaining Resources 2 | 3 | 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. 4 | 5 | ## Amazon Web Services 6 | 7 | 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. 8 | 9 | The one we will focus on here is EC2 or [Elastic Compute Cloud](https://aws.amazon.com/ec2/?sc_channel=PS&sc_campaign=acquisition_US&sc_publisher=google&sc_medium=ec2_b&sc_content=ec2_e&sc_detail=amazon%20web%20service%20ec2&sc_category=ec2&sc_segment=73821516762&sc_matchtype=e&sc_country=US&s_kwcid=AL!4422!3!73821516762!e!!g!!amazon%20web%20service%20ec2&ef_id=Vx5b0AAABUmn0WIi:20160427010350:s). 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. 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | 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. 16 | 17 | ```{block, type = "cost"} 18 | 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. 19 | ``` 20 | 21 | We're also going to take advantage of a tremendous service offered by [Louis Aslett](http://www.louisaslett.com/). Louis has taken the time to create and maintain [dozens of Linux images](http://www.louisaslett.com/RStudio_AMI/) set up with RStudio. Don't worry if you're not familiar with Linux. After the initial setup you'll rarely need it. 22 | 23 | ## Getting Started 24 | 25 | If you have not used AWS before [go create an account](https://aws-portal.amazon.com/gp/aws/developer/registration/index.html). Registration takes just a few minutes. 26 | 27 | When finished, we'll go to [Louis Aslett's website](http://www.louisaslett.com/RStudio_AMI/) to grab an image. 28 | 29 | ![](https://s3.amazonaws.com/backtesting-strategies-r/rstudio-ami.png) 30 | 31 | 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). 32 | 33 | 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. 34 | 35 | ![](https://s3.amazonaws.com/backtesting-strategies-r/aws-instance-type.png) 36 | 37 | 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. 38 | 39 | 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. 40 | 41 | You can also create your own **Security group name** or leave the default. I used *rstudio-0-99-491*. 42 | 43 | ![](https://s3.amazonaws.com/backtesting-strategies-r/aws-security-group.png) 44 | 45 | ```{block type = "warning"} 46 | 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. 47 | ``` 48 | 49 | Next, click "Review and Launch". This will take you to a summary page reiterating your selections. If all looks good click "Launch". 50 | 51 | ![](https://s3.amazonaws.com/backtesting-strategies-r/aws-key-pair.png) 52 | 53 | 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". 54 | 55 | ```{block type = "info"} 56 | 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](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html#adding-security-group-rule) on changing the IP rules. 57 | ``` 58 | 59 | ```{block, type = "cost"} 60 | 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. 61 | ``` 62 | 63 | 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](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/putty.html). You will need to convert the pem file to a ppk file in order to log in. 64 | 65 | 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. 66 | 67 | ![](https://s3.amazonaws.com/backtesting-strategies-r/aws-ec2-dashboard.png) 68 | 69 | 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! 70 | 71 | 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](http://passwordsgenerator.net/). The defaults should suffice. After running `passwd()` you should get a confirmation message: 72 | 73 | > (current) UNIX password: Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully 74 | 75 | You are now set up to use RStudio as you have been. 76 | 77 | ### Installing Quantstrat 78 | 79 | 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. 80 | 81 | ``` 82 | install.packages("quantstrat", repos="http://R-Forge.R-project.org") 83 | ``` 84 | 85 | This will install the latest version, 0.9.1739, the same as used in this book along with the additional libraries needed. 86 | 87 | ## Testing Resources 88 | 89 | 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. 90 | 91 | 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. 92 | 93 | We will execute the strategy on one stock, **AAPL**, from 2007 to Jun. 1, 2014. 94 | 95 | These tests are performed using Ubuntu 14.04.3 LTS, x86 64 bit. 96 | 97 | The unevaluated code is as follows: 98 | 99 | ```{r obtaining-resources-test-code, eval = FALSE} 100 | start_t <- Sys.time() 101 | 102 | library(quantstrat) 103 | # For Linux 104 | library(doMC) 105 | registerDoMC(cores = parallel::detectCores()) 106 | 107 | stock.str <- "AAPL" # what are we trying it on 108 | 109 | #MA parameters for MACD 110 | fastMA <- 12 111 | slowMA <- 26 112 | signalMA <- 9 113 | maType <- "EMA" 114 | .FastMA <- (1:20) 115 | .SlowMA <- (30:80) 116 | 117 | currency("USD") 118 | stock(stock.str, currency = "USD", multiplier = 1) 119 | 120 | start_date <- "2006-12-31" 121 | initEq <- 1000000 122 | portfolio.st <- "macd" 123 | account.st <- "macd" 124 | 125 | rm.strat(portfolio.st) 126 | rm.strat(account.st) 127 | 128 | initPortf(portfolio.st, symbols = stock.str) 129 | initAcct(account.st, portfolios = portfolio.st) 130 | initOrders(portfolio = portfolio.st) 131 | 132 | strat.st <- portfolio.st 133 | # define the strategy 134 | strategy(strat.st, store = TRUE) 135 | 136 | #one indicator 137 | add.indicator(strat.st, 138 | name = "MACD", 139 | arguments = list(x = quote(Cl(mktdata)), 140 | nFast = fastMA, 141 | nSlow = slowMA), 142 | label = "_") 143 | 144 | #two signals 145 | add.signal(strat.st, 146 | name = "sigThreshold", 147 | arguments = list(column = "signal._", 148 | relationship = "gt", 149 | threshold = 0, 150 | cross = TRUE), 151 | label = "signal.gt.zero") 152 | 153 | add.signal(strat.st, 154 | name = "sigThreshold", 155 | arguments = list(column = "signal._", 156 | relationship = "lt", 157 | threshold = 0, 158 | cross = TRUE), 159 | label = "signal.lt.zero") 160 | 161 | # add rules 162 | 163 | # entry 164 | add.rule(strat.st, 165 | name = "ruleSignal", 166 | arguments = list(sigcol = "signal.gt.zero", 167 | sigval = TRUE, 168 | orderqty = 100, 169 | ordertype = "market", 170 | orderside = "long", 171 | threshold = NULL), 172 | type = "enter", 173 | label = "enter", 174 | storefun = FALSE) 175 | 176 | # exit 177 | add.rule(strat.st, 178 | name = "ruleSignal", 179 | arguments = list(sigcol = "signal.lt.zero", 180 | sigval = TRUE, 181 | orderqty = "all", 182 | ordertype = "market", 183 | orderside = "long", 184 | threshold = NULL, 185 | orderset = "exit2"), 186 | type = "exit", 187 | label = "exit") 188 | 189 | ### MA paramset 190 | 191 | add.distribution(strat.st, 192 | paramset.label = "MA", 193 | component.type = "indicator", 194 | component.label = "_", #this is the label given to the indicator in the strat 195 | variable = list(n = .FastMA), 196 | label = "nFAST") 197 | 198 | add.distribution(strat.st, 199 | paramset.label = "MA", 200 | component.type = "indicator", 201 | component.label = "_", #this is the label given to the indicator in the strat 202 | variable = list(n = .SlowMA), 203 | label = "nSLOW") 204 | 205 | add.distribution.constraint(strat.st, 206 | paramset.label = "MA", 207 | distribution.label.1 = "nFAST", 208 | distribution.label.2 = "nSLOW", 209 | operator = "<", 210 | label = "MA") 211 | 212 | 213 | getSymbols(stock.str, from = start_date, to = "2014-06-01") 214 | 215 | results <- apply.paramset(strat.st, 216 | paramset.label = "MA", 217 | portfolio.st = portfolio.st, 218 | account.st = account.st, 219 | nsamples = 0, 220 | verbose = TRUE) 221 | 222 | updatePortf(Portfolio = portfolio.st,Dates = paste("::",as.Date(Sys.time()),sep = "")) 223 | end_t <- Sys.time() 224 | print(end_t-start_t) 225 | ``` 226 | 227 | ```{r obtaining-resources-create-aws} 228 | servers <- c("t2.micro", "t2.medium", "m4.xlarge", "m4.2xlarge", "m4.4xlarge") 229 | aws <- data.frame("Server" = factor(servers, levels = servers), 230 | "Processor" = c("Intel Xeon E5-2676 v3, 2.40 GHz", 231 | "Intel Xeon E5-2670 v2, 2.50 GHz", 232 | "Intel Xeon E5-2676 v3, 2.40Ghz", 233 | "Intel Xeon E5-2676 v3, 2.40Ghz", 234 | "Intel Xeon E5-2676 v3, 2.40Ghz"), 235 | "VirtualCores" = c(1, 2, 4, 8, 16), 236 | "Memory" = c(1, 4, 16, 32, 64), 237 | "Seconds" = c(749.841, 293.6499, 118.1366, 62.24196, 34.68986), 238 | "Price" = c(0.013, 0.052, 0.239, 0.479, 0.958)) 239 | 240 | # knitr::kable(aws, caption = "Test Results for AWS", booktabs = TRUE) 241 | ``` 242 | 243 | ```{r obtaining-resources-aws-execute} 244 | aws %>% 245 | ggplot(aes(x = Server, y = Seconds, fill = Server)) + 246 | geom_bar(stat = "identity") + 247 | theme_bw() + 248 | labs(title = "Execution Time per Server") 249 | ``` 250 | 251 | ```{r obtaining-resources-aws-ppe} 252 | aws_ppe <- aws %>% 253 | mutate(ppe = (Price/60) * Seconds) 254 | 255 | aws_ppe %>% 256 | ggplot(aes(x = Server, y = ppe, fill = Server)) + 257 | geom_bar(stat = "identity") + 258 | theme_bw() + 259 | labs(title = "Cost per Second of Execution (ppe)", y = "Cost (USD)") 260 | 261 | # knitr::kable(aws_ppe, caption = "Cost per Second of Execution (ppe)", 262 | # y = "Cost (USD)", booktabs = TRUE) 263 | ``` 264 | 265 | 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. 266 | 267 | 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. 268 | 269 | ## Changing Instances 270 | 271 | ```{block, type = "cost"} 272 | Regardless if you're free-tier eligible or not, using any server instance beyond the t2.micro will incur charges. See [Amazon EC2 Pricing](https://aws.amazon.com/ec2/pricing/) for more details. 273 | ``` 274 | 275 | 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. 276 | 277 | ![](https://s3.amazonaws.com/backtesting-strategies-r/aws-change-instance.png) 278 | 279 | If your instance is already running stop it by selecting Instance State > Stop. 280 | 281 | 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. 282 | 283 | 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...". 284 | 285 | ## Stop the Server 286 | 287 | ```{block type = "warning"} 288 | 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. 289 | ``` 290 | 291 | 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. 292 | 293 | 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](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html) to keep a consistent IP address. However, this is only free if you leave your instance running 24/7. 294 | 295 | ```{block, type = "cost"} 296 | 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. 297 | ``` 298 | 299 | ## Reading Resources 300 | 301 | If you are interested in further utilizing AWS for your backtesting I recommend the book [Amazon Web Services in Action](http://amzn.to/1Uhki5A) (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. 302 | 303 | If you're going to use Ubuntu I would also recommend [The Official Ubuntu Server Book](http://amzn.to/1NTBxmR) (affiliate link) by Kyle Rankin and Benjamin Mako Hill. 304 | -------------------------------------------------------------------------------- /parameter-optimization.Rmd: -------------------------------------------------------------------------------- 1 | # Parameter Optimization {#parameter-optimization} 2 | 3 | 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? 4 | 5 | `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. 6 | 7 | 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. 8 | 9 | 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). 10 | 11 | ```{r parameter-optimization-strategy-vars} 12 | .fastSMA <- (1:30) 13 | .slowSMA <- (20:80) 14 | .nsamples <- 5 15 | ``` 16 | 17 | ```{r parameter-optimization-create-objects} 18 | portfolio.st <- "Port.Luxor.MA.Opt" 19 | account.st <- "Acct.Luxor.MA.Opt" 20 | strategy.st <- "Strat.Luxor.MA.Opt" 21 | 22 | rm.strat(portfolio.st) 23 | rm.strat(account.st) 24 | 25 | initPortf(name = portfolio.st, 26 | symbols = symbols, 27 | initDate = init_date) 28 | 29 | initAcct(name = account.st, 30 | portfolios = portfolio.st, 31 | initDate = init_date, 32 | initEq = init_equity) 33 | 34 | initOrders(portfolio = portfolio.st, 35 | symbols = symbols, 36 | initDate = init_date) 37 | 38 | strategy(strategy.st, store = TRUE) 39 | ``` 40 | 41 | Next we'll go through and re-initiate our portfolio and account objects as we did prior. 42 | 43 | ```{r parameter-optimization-rm-strat} 44 | rm.strat(portfolio.st) 45 | rm.strat(account.st) 46 | ``` 47 | 48 | ```{r parameter-optimization-init-portf} 49 | initPortf(name = portfolio.st, 50 | symbols = symbols, 51 | initDate = init_date) 52 | ``` 53 | 54 | ```{r parameter-optimization-init-acct} 55 | initAcct(name = account.st, 56 | portfolios = portfolio.st, 57 | initDate = init_date) 58 | ``` 59 | 60 | ```{r parameter-optimization-init-orders} 61 | initOrders(portfolio = portfolio.st, 62 | initDate = init_date) 63 | ``` 64 | 65 | ## Add Distribution 66 | 67 | We already saved our indicators, signals and rules - `strategy.st` - and loaded the strategy; we do not need to rewrite that code. 68 | 69 | 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`. 70 | 71 | * `paramset.label`: name of the function to which the parameter range will be applied; in this case `TTR:SMA()`. 72 | 73 | * `component.type`: indicator 74 | 75 | * `component.label`: label of the indicator when we added it (`nFast` and `nSlow`) 76 | 77 | * `variable`: the parameter of `SMA()` to which our integer vectors (`.fastSMA` and `.slowSMA`) will be applied; `n`. 78 | 79 | * `label`: unique identifier for the distribution. 80 | 81 | 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`. 82 | 83 | ```{r parameter-optimization-add-distribution} 84 | add.distribution(strategy.st, 85 | paramset.label = "SMA", 86 | component.type = "indicator", 87 | component.label = "nFast", 88 | variable = list(n = .fastSMA), 89 | label = "nFAST") 90 | 91 | add.distribution(strategy.st, 92 | paramset.label = "SMA", 93 | component.type = "indicator", 94 | component.label = "nSlow", 95 | variable = list(n = .slowSMA), 96 | label = "nSLOW") 97 | ``` 98 | 99 | ## Add Distribution Constraint 100 | 101 | 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. 102 | 103 | 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`. 104 | 105 | Our operator will be one of `c("<", ">", "<=", ">=", "=")`. Here, we're issuing a constraint to always keep `nFAST` less than `nSLOW`. 106 | 107 | We'll name this constraint `SMA.Con` by applying it to the `label` parameter. 108 | 109 | ```{r parameter-optimization-add-distribution-constraint} 110 | add.distribution.constraint(strategy.st, 111 | paramset.label = "SMA", 112 | distribution.label.1 = "nFAST", 113 | distribution.label.2 = "nSLOW", 114 | operator = "<", 115 | label = "SMA.Constraint") 116 | ``` 117 | 118 | ## Running Parallel 119 | 120 | `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. 121 | 122 | 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](http://www.r-programming.org/papers). It requires the `parallel` library and `doParallel` for Windows users and `doMC` for non-Windows users. 123 | 124 | ```{r} 125 | library(parallel) 126 | 127 | if( Sys.info()['sysname'] == "Windows") { 128 | library(doParallel) 129 | registerDoParallel(cores=detectCores()) 130 | } else { 131 | library(doMC) 132 | registerDoMC(cores=detectCores()) 133 | } 134 | ``` 135 | 136 | ## Apply Paramset 137 | 138 | When we ran our original strategy we used `applyStrategy()`. When running distributions we use `apply.paramset()`. 139 | 140 | For our current strategy we only need to pass in our portfolio and account objects. 141 | 142 | 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. 143 | 144 | ```{r parameter-optimization-apply-paramset, results = "hide"} 145 | cwd <- getwd() 146 | setwd("./_data/") 147 | results_file <- paste("results", strategy.st, "RData", sep = ".") 148 | if( file.exists(results_file) ) { 149 | load(results_file) 150 | } else { 151 | results <- apply.paramset(strategy.st, 152 | paramset.label = "SMA", 153 | portfolio.st = portfolio.st, 154 | account.st = account.st, 155 | nsamples = .nsamples) 156 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 157 | save(list = "results", file = results_file) 158 | save.strategy(strategy.st) 159 | } 160 | } 161 | setwd(cwd) 162 | ``` 163 | -------------------------------------------------------------------------------- /simple-sma.Rmd: -------------------------------------------------------------------------------- 1 | # Simple SMA Cross 2 | 3 | ```{r, message = FALSE, warnings = FALSE} 4 | library(quantstrat) 5 | library(ggplot2) 6 | library(dplyr) 7 | library(tidyr) 8 | library(TTR) 9 | ``` 10 | 11 | ```{r} 12 | Sys.setenv(TZ = "UTC") 13 | ``` 14 | 15 | ```{r} 16 | currency("USD") 17 | ``` 18 | 19 | ```{r} 20 | stock("SPY", 21 | currency = "USD", 22 | multiplier = 1) 23 | ``` 24 | 25 | We call `getSymbols` from the `quantmod` package to load our data. There are several sources we can use depending on the instrument we want to access. For stocks, I'll use Yahoo! 26 | 27 | We also set our index.class parameter to a vector of `POSIXt` and `POSIXct`. Set the `from` parameter to the first date of data you want to retrieve and the `to` parameter to the last date of data. The example below will pull all data for **SPY** for 2010. 28 | 29 | Lastly, we set `adjust` to `TRUE`. This adjusts the prices to accomodate stock splits, dividends, etc. 30 | 31 | ## Get Symbols 32 | 33 | ```{r} 34 | getSymbols(Symbols = "SPY", 35 | src = "yahoo", 36 | index.class = "POSIXct", 37 | from = "2010-01-01", 38 | to = "2011-01-01", 39 | adjust = TRUE) 40 | ``` 41 | 42 | ## Initialize Account, Portfolio and Strategy 43 | 44 | Next, we name our strategy. This will come in handy later when saving them and accessing them later on. It will also keep our "accounts" seperated. 45 | 46 | ```{r} 47 | strat.name <- "Simple.SMA" 48 | ``` 49 | 50 | When run the `rm.strat` command to clean out any residuals from a previous run. This is useless on a first-run. But if we make a parameter change to the script this will ensure we're not holding onto old data. 51 | 52 | ```{r} 53 | rm.strat(strat.name) 54 | ``` 55 | 56 | With `initPortf`, we create a new portfolio object that will hold all of our transactions, positions and more. 57 | 58 | We pass three parameters here: 59 | 60 | * `name`: for simplicity we can use our `strat.name` variable to keep our data organized. 61 | 62 | * `symbols`: Pretty self-explantory 63 | 64 | * `initDate`: This is a new parameter we havent seen before but will use often on other function calls. This is simply an "initilization date". 65 | 66 | `initDate` should be the date prior to our first record. So, in this example we're accessing all of 2010 data for **SPY** starting at 2010-01-01. So, our `initDate` should be 2009-12-31. 67 | 68 | Do not set an `initDate` beyond one day prior to your first observation. When running reports and charts you will get a lot of empty data/space. 69 | 70 | ```{r} 71 | initPortf(strat.name, 72 | symbols = "SPY", 73 | initDate = "2010-01-01") 74 | ``` 75 | 76 | Next we'll initialize our account with `initAcct`. This will hold multiple portfolios and our account details. Notice two of the parameters are the same as `initPortf` and two differences: 77 | 78 | * `portfolios`: The name of our current portfolio 79 | 80 | * `initEq`: This is the balance we want to start our portfolio against. 81 | 82 | There is also a `currency` parameter we can pass but because we set that earlier, we do not need it here. 83 | 84 | ```{r} 85 | initAcct(strat.name, 86 | portfolios = strat.name, 87 | initDate = "2010-01-01", 88 | initEq = 10000) 89 | ``` 90 | 91 | `initOrders` will create an object to hold all of the orders for our portfolio. 92 | 93 | ```{r} 94 | initOrders(portfolio = strat.name, 95 | symbols = "SPY", 96 | initDate = "2010-01-01") 97 | ``` 98 | 99 | `strategy` will construct our strategy object. `store` will hold the strategy settings for later use. 100 | 101 | ```{r} 102 | strategy(strat.name, store = TRUE) 103 | ``` 104 | 105 | ```{r} 106 | strat <- getStrategy(strat.name) 107 | ``` 108 | 109 | ## Indicators 110 | 111 | For our current strategy we only have one indicator to add: SMA(20). We'll add this indicator to our strategy with the `add.indicator` function. 112 | 113 | `name` parameter is the name of a function; do not misinterpret it as a label (an additinoal parameter). This allows you to create your own functions as you get comfortable with backtesting. For now we'll use the `TTR:SMA` function. 114 | 115 | `arguments` is a list of parameters passed to the function called in `name`. `SMA` requires only two parameters, `x`, our data object, and `n`, the number of periods to calculate. 116 | 117 | For this strategy we are basing our SMA(20) on closing prices. But the keen observer will recognize we're not passing an object as we normally would. For example, we might think to use: 118 | 119 | ```{r} 120 | arguments = list(SPY$Close, n = 20) 121 | ``` 122 | 123 | Instead we're passing a new object called `mktdata` wrapped inside a `Cl` function wrapped inside a `quote` function. This seems messy and may be a bit confusing. So let's start from the beginning. 124 | 125 | `mktdata` is a new object that will be created when our strategy runs. It will copy our symbol object then be manipulated as we will instruct. In this example, a new variable called *SMA20* (our `label` parameter) will be added. 126 | 127 | The `Cl()` function simply references the `Close` variable of `mktdata`. This is a shortcut function created in the `quantmod` package. Note that it will only call the first variable that begins with "Cl". 128 | 129 | For example, if we have a xts object with the column names `c("Open", "High", "Low", "Close", "Close.gt.SMA20")` then `Cl()` will reference our `Close` variable. 130 | 131 | If, however, we have ended up with a xts object where the column names are `c("Close.gt.SMA20", "Open", "High", "Low", "Close")` then `Cl()` will reference the `Close.gt.SMA20` variable. 132 | 133 | As long as you don't manipulate the original symbol object this shouldn't be an issue. 134 | 135 | We can use similar functions to represent the other variables in our object: `Op()` for Open, `Hi()` for High, `Lo()` for Low and `Ad()` for Adjusted. Run a help query on any of those calls to get more details. 136 | 137 | Lastly, we wrap our call in the `quote()` function which essentially wraps quotes around our arguments during the call. 138 | 139 | ```{r, include = FALSE} 140 | #' Would like to have more information here as to why all of that is required and what will happen if we did just attempt to reference, say by mktdata$Close 141 | ``` 142 | 143 | ```{r 2-1-add-indicators} 144 | add.indicator(strategy = strat.name, 145 | name = "SMA", 146 | arguments = list(x = quote(Cl(mktdata)), 147 | n = 20), 148 | label = "SMA20") 149 | ``` 150 | 151 | A sample of our `mktdata` object would look like this: 152 | 153 | ```{r} 154 | knitr::kable(data.frame("n" = c(1:6), 155 | "Close" = c(99, 101.50, 102, 101, 103, 102), 156 | "SMA20" = c(100, 101, 101.50, 100.50, 102.50, 102.50)), 157 | caption = "Sample mktdata with indicators") 158 | ``` 159 | 160 | ## Signals 161 | 162 | Now that we've added our indicator it's time to instruct our strategy on when to buy and sell. We do this by setting up signals. 163 | 164 | Signals are simply boolean values on if a given condition is met. In our current strategy, we want to buy when Close crosses over SMA(20) and sell when Close crosses under SMA(20). Each of these will be a signal. 165 | 166 | We build our signals similar to how we built our indicators with some distinction. In `add.signal()`, one parameter we pass, `name` is similar to what we did in `add.indicator`; it is the name of a function call. We can use technically any function we want but the `quantstrat` library already has some essential ones built in for us. 167 | 168 | * `sigComparison`: compare one value to another. For example, if High is higher than Close or SMA(20) is greather than SMA(50). 169 | 170 | * `sigCrossover`: If one variable has crossed another. 171 | 172 | * `sigFormula`: Use a formula to calculate the signal based on other observations. 173 | 174 | * `sigPeak`: Use to find a local minima or maxima. 175 | 176 | * `sigThreshold`: Use when an indicator or price object has crossed a certain value. 177 | 178 | * `sigTimestamp`: Signal based on date or time (Sell in May, go away?) 179 | 180 | All of the `name` parameters above should cover just about any strategy you want to run. For our current strategy, we'll use the `sigCrossover`. 181 | 182 | In our `arguments` list, we'll pass the two `columns` we are looking at for the crossover: `Close` and `SMA20` (the latter added in our `add.indicator` call above). We need to also pass the relationship we are looking for; in our example, `gte` and `lt`: 183 | 184 | * `gt`: greather than 185 | 186 | * `lt`: less than 187 | 188 | * `eq`: equal to 189 | 190 | * `gte`: greather than or equal to 191 | 192 | * `lte`: less than or equal to 193 | 194 | We also assign a label which can be any descriptive title we want to identify our variable. 195 | 196 | At a minimum you should always have two signals: one to open a position and one to close a position. The formula for our strategy is: 197 | 198 | $$ Signal = 199 | \begin{cases} 200 | Cl >= SMA(20), BTO \\ 201 | Cl < SMA(20), STC 202 | \end{cases} 203 | $$ 204 | 205 | So, if `Close` is `gte` `SMA20` then our first new variable, `Cl.gte.SMA20` will be TRUE. If `Close` is `lt` `SMA20` then our second new variable `Cl.lt.SMA20` will be TRUE. Obviously neither of these variables will be TRUE on the same date; they are dependent variables. 206 | 207 | Also note that you cannot have consistent TRUE values for either one of the variables. Therefore, you cannot have two consecutive signals. 208 | 209 | ```{r 2-1-add-signals} 210 | add.signal(strat.name, 211 | name="sigCrossover", 212 | arguments = list(columns = c("Close", "SMA20"), 213 | relationship = "gte"), 214 | label="Cl.gte.SMA20") 215 | 216 | add.signal(strat.name, 217 | name = "sigCrossover", 218 | arguments = list(columns = c("Close", "SMA20"), 219 | relationship = "lt"), 220 | label = "Cl.lt.SMA20") 221 | ``` 222 | 223 | An updated sample of our `mktdata` object would look like this: 224 | 225 | ```{r} 226 | knitr::kable(data.frame("n" = c(1:6), 227 | "Close" = c(99, 101.50, 102, 101, 103, 102), 228 | "SMA20" = c(100, 101, 101.50, 100.50, 102.50, 102.50), 229 | "Cl.gte.SMA20" = c(0, 1, 0, 0, 0, 0), 230 | "Cl.lt.SMA20" = c(0, 0, 0, 0, 0, 1)), 231 | caption = "Sample mktdata with indicators and signals") 232 | ``` 233 | 234 | In the example above, we see on row 2 that `Close` "crosses over" `SMA20`. This generates a TRUE value for `Cl.gte.SMA20`. Our next signal comes on row 6 when `Close` crosses back under `SMA20` (`Cl.lt.SMA20 == TRUE`). 235 | 236 | However, this does not create any trades. All we've done now is added indicators and signals. Now, we must tell our strategy what to do given the signals. We add rules. 237 | 238 | ## Adding Rules 239 | 240 | Rules are where we will instruct R to make our transactions. When I first began working with these libraries this is where I often struggled. We'll keep it simple for the time being and go over a few basics. 241 | 242 | As with `add.indicator()` and `add.signal()`, `add.rule()` expects the first parameter to be the name of your strategy. As with before, `name` will be the function we want to call. For now we'll stick with `ruleSignal`. 243 | 244 | You'll see below we have two rule sets; one for BTO orders and one for STC orders. We also use pretty much the same parameters in our `arguments` list: 245 | 246 | * `sigcol`: This is the signal column for which the rule references. 247 | 248 | * `sigval`: The value of our `sigcol` when the rule should be applied. 249 | 250 | * `orderqty`: numeric or "all" for the number of shares to be executed. 251 | 252 | * `ordertype`: c("market", "limit", "stoplimit", "stoptrailing", "iceberg") 253 | 254 | * `orderside`: long or short 255 | 256 | Our last parameter, `type` is the type of order we are placing. There are several options we'll get into later but for now our rules will simply be enter or exit. 257 | 258 | ```{r 2-1-add-rules} 259 | # BTO when Cl crosses above SMA(20) 260 | add.rule(strat.name, 261 | name = 'ruleSignal', 262 | arguments = list(sigcol = "Cl.gte.SMA20", 263 | sigval = TRUE, 264 | orderqty = 100, 265 | ordertype = 'market', 266 | orderside = 'long'), 267 | type = 'enter') 268 | 269 | # STC when Cl crosses under SMA(20) 270 | add.rule(strat.name, 271 | name = 'ruleSignal', 272 | arguments = list(sigcol = "Cl.lt.SMA20", 273 | sigval = TRUE, 274 | orderqty = 'all', 275 | ordertype = 'market', 276 | orderside = 'long'), 277 | type = 'exit') 278 | ``` 279 | 280 | Simply put, whenever our `Cl.gte.SMA` variable is TRUE we will submit a market order for 100 shares long. When `Cl.lt.SMA == TRUE` we will exit all long positions. 281 | 282 | ## Apply Strategy 283 | 284 | Up until this point if you've tried to execute certain blocks of code at a time you may have been disappointed. Nothing happens other than some little chunks of output. Unfortunately, we don't know how all of this works until we apply our strategy. 285 | 286 | `applyStrategy()` will execute trades based on the conditions we've specified. Notice we have two parameters which we assign to our strategy name. When we execute this next block we either get trades or we get errors. 287 | 288 | ```{r 2-1-apply-strategy} 289 | applyStrategy(strategy = strat.name, 290 | portfolios = strat.name) 291 | ``` 292 | 293 | The format of our output should seem straightforward; we have Date followed by Symbol, Shares and Price. We've executed approximately ten trades (entry and exit). Our strategy actually ends with an open position (notice the buy on the last entry). 294 | 295 | If we look at a sample of the `mktdata` object now available, we can see the indicators and signals we created earlier: 296 | 297 | ```{r} 298 | knitr::kable(mktdata[81:85,]) 299 | ``` 300 | 301 | We can see in this subset we had four transactions; one on each side. Each position didn't last longer than a day but for our purposes now that's fine. Where `Cl.gte.SMA20 == 1` (TRUE) we would buy and where `Cl.lt.SMA20 == 1` (TRUE) we would sell per our rules. 302 | 303 | ## Update Portfolio, Account 304 | 305 | To dig into the analysis of our strategy we must update our portfolio and account. We do this by calling `udpatePortf`, `updateAcct` and `updateEndEq` passing our `strat.name` variable as the lone parameter. 306 | 307 | ```{r 2-1-update-portfolio} 308 | updatePortf(strat.name) 309 | updateAcct(strat.name) 310 | updateEndEq(strat.name) 311 | ``` 312 | 313 | ## Glimpse Our Returns 314 | 315 | To close this introduction we'll take a brief look at our transactions using the `chart.Posn` function from the `blotter` package. This chart will show us when we made trades, how many shares we purchased and what our profits and drawdown were. This is a great way to get a quick look at the profitability of a strategy. 316 | 317 | We pass `strat.name` to the `Portfolio` parameter and SPY to `Symbol`. In itself that is enough. However, because our trades are based on the SMA(20) indicator let's add that as well. We do this by passing the `add_sma` function from the `quantmod` package where `n` is our period (20) and `col` and `lwd` are the same as those we would pass to the base plot package. So we're asking for a line that is colored blue with a line-width of two By setting `on = 1` we are asking the indicator to be overlaid on the first panel (the price action panel). 318 | 319 | ```{r} 320 | chart.Posn(Portfolio = strat.name, 321 | Symbol = "SPY", 322 | TA = "add_SMA(n = 20, col = 4, on = 1, lwd = 2)") 323 | ``` 324 | 325 | Notice the warning: 326 | 327 | > Warning in mapply(function(name, value) {: longer argument not a multiple of length of shorter 328 | 329 | This is basically saying we don't have as many data points for SMA(20) as we do the x-axis. In this instance, it can be ignored. 330 | 331 | So for the year 2010 this strategy actually wasn't bad at all. We managed to accumulate over $2,000 with a final drawdown of only $37. Note, however, that drawdown actually got below -$500. Pay attention to this as we work through other strategies. 332 | 333 | -------------------------------------------------------------------------------- /stock-book.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /stop-loss-optimization.Rmd: -------------------------------------------------------------------------------- 1 | # Stop Loss Optimization {#stop-loss-optimization} 2 | 3 | We take the code from \@ref(stop-loss) and reuse it here this time to test parameters for our stop-loss just as we did in \@ref(parameter-optimization). This time though instead of optimizing indicators we're going to optimize `.stopLoss` in a new integer vector `.StopLoss`. 4 | 5 | ```{r stop-loss-optimization-strategy-vars} 6 | .StopLoss = seq(0.05, 0.6, length.out = 48)/100 7 | ``` 8 | 9 | We'll name this strategy `Luxor.Stop.Loss.Opt`. 10 | 11 | ```{r stop-loss-optimization-strategy} 12 | strategy.st <- "Luxor.Stop.Loss.Opt" 13 | 14 | rm.strat(portfolio.st) 15 | rm.strat(account.st) 16 | 17 | initPortf(name = portfolio.st, 18 | symbols = symbols, 19 | initDate = init_date) 20 | 21 | initAcct(name = account.st, 22 | portfolios = portfolio.st, 23 | initDate = init_date) 24 | 25 | initOrders(portfolio = portfolio.st, 26 | initDate = init_date) 27 | 28 | strategy(strategy.st, store = TRUE) 29 | ``` 30 | 31 | ## Add Indicators 32 | 33 | ```{r stop-loss-optimization-add-indicators} 34 | add.indicator(strategy.st, 35 | name = "SMA", 36 | arguments = list(x = quote(Cl(mktdata)), 37 | n = .fast), 38 | label = "nFast") 39 | 40 | add.indicator(strategy.st, 41 | name = "SMA", 42 | arguments = list(x = quote(Cl(mktdata)), 43 | n = .slow), 44 | label = "nSlow") 45 | ``` 46 | 47 | ## Add Signals 48 | 49 | ```{r stop-loss-optimization-add-signals} 50 | add.signal(strategy.st, 51 | name = "sigCrossover", 52 | arguments = list(columns = c("nFast", "nSlow"), 53 | relationship = "gte"), 54 | label = "long" 55 | ) 56 | add.signal(strategy.st, 57 | name = "sigCrossover", 58 | arguments = list(columns = c("nFast", "nSlow"), 59 | relationship = "lt"), 60 | label = "short") 61 | ``` 62 | 63 | ## Add Rules 64 | 65 | ```{r stop-loss-optimization-add-rules} 66 | add.rule(strategy.st, 67 | name = "ruleSignal", 68 | arguments = list(sigcol = "long" , 69 | sigval = TRUE, 70 | replace = FALSE, 71 | orderside = "long" , 72 | ordertype = "stoplimit", 73 | prefer = "High", 74 | threshold = .threshold, 75 | TxnFees = .txnfees, 76 | orderqty = +.orderqty, 77 | osFUN = osMaxPos, 78 | orderset = "ocolong"), 79 | type = "enter", 80 | label = "EnterLONG") 81 | 82 | add.rule(strategy.st, 83 | name = "ruleSignal", 84 | arguments = list(sigcol = "short", 85 | sigval = TRUE, 86 | replace = FALSE, 87 | orderside = "short", 88 | ordertype = "stoplimit", 89 | prefer = "Low", 90 | threshold = .threshold, 91 | TxnFees = .txnfees, 92 | orderqty = -.orderqty, 93 | osFUN = osMaxPos, 94 | orderset = "ocoshort"), 95 | type = "enter", 96 | label = "EnterSHORT") 97 | 98 | add.rule(strategy.st, 99 | name = "ruleSignal", 100 | arguments = list(sigcol = "short", 101 | sigval = TRUE, 102 | replace = TRUE, 103 | orderside = "long" , 104 | ordertype = "market", 105 | TxnFees = .txnfees, 106 | orderqty = "all", 107 | orderset = "ocolong"), 108 | type = "exit", 109 | label = "Exit2SHORT") 110 | 111 | add.rule(strategy.st, 112 | name = "ruleSignal", 113 | arguments = list(sigcol = "long", 114 | sigval = TRUE, 115 | replace = TRUE, 116 | orderside = "short", 117 | ordertype = "market", 118 | TxnFees = .txnfees, 119 | orderqty = "all", 120 | orderset = "ocoshort"), 121 | type = "exit", 122 | label = "Exit2LONG") 123 | 124 | add.rule(strategy.st, 125 | name = "ruleSignal", 126 | arguments = list(sigcol = "long" , 127 | sigval = TRUE, 128 | replace = FALSE, 129 | orderside = "long", 130 | ordertype = "stoplimit", 131 | tmult = TRUE, 132 | threshold = quote(.stoploss), 133 | TxnFees = .txnfees, 134 | orderqty = "all", 135 | orderset = "ocolong"), 136 | type = "chain", 137 | parent = "EnterLONG", 138 | label = "StopLossLONG", 139 | enabled = FALSE) 140 | 141 | add.rule(strategy.st, 142 | name = "ruleSignal", 143 | arguments = list(sigcol = "short", 144 | sigval = TRUE, 145 | replace = FALSE, 146 | orderside = "short", 147 | ordertype = "stoplimit", 148 | tmult = TRUE, 149 | threshold = quote(.stoploss), 150 | TxnFees = .txnfees, 151 | orderqty = "all", 152 | orderset = "ocoshort"), 153 | type = "chain", 154 | parent = "EnterSHORT", 155 | label = "StopLossSHORT", 156 | enabled = FALSE) 157 | ``` 158 | 159 | ## Add Position Limit 160 | 161 | ```{r stop-loss-optimization-add-pos-limit} 162 | for(symbol in symbols){ 163 | addPosLimit(portfolio = portfolio.st, 164 | symbol = symbol, 165 | timestamp = init_date, 166 | maxpos = .orderqty) 167 | } 168 | ``` 169 | 170 | ## Add Distribution 171 | 172 | We use `add.distribution` again to assign our `.StopLoss` vector as values for the **StopLossLONG** and **StopLossSHORT** rule chains. 173 | 174 | ```{r stop-loss-optimization-add-distribution} 175 | add.distribution(strategy.st, 176 | paramset.label = "StopLoss", 177 | component.type = "chain", 178 | component.label = "StopLossLONG", 179 | variable = list(threshold = .StopLoss), 180 | label = "StopLossLONG") 181 | 182 | add.distribution(strategy.st, 183 | paramset.label = "StopLoss", 184 | component.type = "chain", 185 | component.label = "StopLossSHORT", 186 | variable = list(threshold = .StopLoss), 187 | label = "StopLossSHORT") 188 | ``` 189 | 190 | ## Add Distribution Constraint 191 | 192 | We set a distribution constraint to keep the **StopLossLONG** and **StopLossSHORT** parameters the same. 193 | 194 | ```{r stop-loss-optimization-add-distribution-constraint} 195 | add.distribution.constraint(strategy.st, 196 | paramset.label = "StopLoss", 197 | distribution.label.1 = "StopLossLONG", 198 | distribution.label.2 = "StopLossSHORT", 199 | operator = "==", 200 | label = "StopLoss") 201 | ``` 202 | 203 | ## Enable Rules 204 | 205 | ```{r stop-loss-optimization-enable-rules} 206 | enable.rule(strategy.st, 'chain', 'StopLoss') 207 | ``` 208 | 209 | ## Apply Paramset 210 | 211 | ```{r stop-loss-optimization-apply-paramset, results = "hide"} 212 | cwd <- getwd() 213 | setwd("./_data/") 214 | results_file <- paste("results", strategy.st, "RData", sep = ".") 215 | if( file.exists(results_file) ) { 216 | load(results_file) 217 | } else { 218 | results <- apply.paramset(strategy.st, 219 | paramset.label = "StopLoss", 220 | portfolio.st = portfolio.st, 221 | account.st = account.st, 222 | nsamples = .nsamples, 223 | verbose = TRUE) 224 | 225 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 226 | save(list = "results", file = results_file) 227 | save.strategy(strategy.st) 228 | } 229 | } 230 | setwd(cwd) 231 | ``` 232 | -------------------------------------------------------------------------------- /stop-loss.Rmd: -------------------------------------------------------------------------------- 1 | # Stop-Loss Orders {#stop-loss} 2 | 3 | We'll continue using a variation of the Luxor strategy. This time we're going to implement stop-loss orders. 4 | 5 | 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. 6 | 7 | ```{r stop-loss-strategy-vars} 8 | .fast <- 10 9 | .slow <- 30 10 | .threshold <- 0.0005 11 | .orderqty <- 100 12 | .txnfees <- -10 13 | .stoploss <- 3e-3 # 0.003 or 0.3% 14 | ``` 15 | 16 | ```{r stop-loss-create-objects} 17 | portfolio.st <- "Port.Luxor.Stop.Loss" 18 | account.st <- "Acct.Luxor.Stop.Loss" 19 | strategy.st <- "Strat.Luxor.Stop.Loss" 20 | 21 | rm.strat(portfolio.st) 22 | rm.strat(account.st) 23 | 24 | initPortf(name = portfolio.st, 25 | symbols = symbols, 26 | initDate = init_date) 27 | 28 | initAcct(name = account.st, 29 | portfolios = portfolio.st, 30 | initDate = init_date, 31 | initEq = init_equity) 32 | 33 | initOrders(portfolio = portfolio.st, 34 | symbols = symbols, 35 | initDate = init_date) 36 | 37 | strategy(strategy.st, store = TRUE) 38 | ``` 39 | 40 | ## Add Indicators 41 | 42 | ```{r stop-loss-add-indicators} 43 | add.indicator(strategy.st, 44 | name = "SMA", 45 | arguments = list(x = quote(Cl(mktdata)), 46 | n = .fast), 47 | label = "nFast") 48 | 49 | add.indicator(strategy.st, 50 | name = "SMA", 51 | arguments = list(x = quote(Cl(mktdata)), 52 | n = .slow), 53 | label = "nSlow") 54 | ``` 55 | 56 | ## Add Signals 57 | 58 | ```{r stop-loss-add-signals} 59 | add.signal(strategy.st, 60 | name = "sigCrossover", 61 | arguments = list(columns = c("nFast", "nSlow"), 62 | relationship = "gte"), 63 | label = "long" 64 | ) 65 | add.signal(strategy.st, 66 | name = "sigCrossover", 67 | arguments = list(columns = c("nFast", "nSlow"), 68 | relationship = "lt"), 69 | label = "short") 70 | ``` 71 | 72 | ## Add Rules 73 | 74 | Our rules are largely the same as they were in our original Luxor strategy. However, we have added some slight modifications. 75 | 76 | Let's start off with `osFUN` which is abbreviated for order size function. It is defined as: 77 | 78 | > function or text descriptor of function to use for order sizing. 79 | 80 | 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. 81 | 82 | 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. 83 | 84 | We've also included the `orderset` parameter with a value of "ocolong". This will help group our long and short orders together. 85 | 86 | ```{r stop-lossa-add-rules} 87 | add.rule(strategy.st, 88 | name = "ruleSignal", 89 | arguments = list(sigcol = "long" , 90 | sigval = TRUE, 91 | replace = FALSE, 92 | orderside = "long" , 93 | ordertype = "stoplimit", 94 | prefer = "High", 95 | threshold = .threshold, 96 | TxnFees = .txnfees, 97 | orderqty = +.orderqty, 98 | osFUN = osMaxPos, 99 | orderset = "ocolong"), 100 | type = "enter", 101 | label = "EnterLONG") 102 | 103 | add.rule(strategy.st, 104 | name = "ruleSignal", 105 | arguments = list(sigcol = "short", 106 | sigval = TRUE, 107 | replace = FALSE, 108 | orderside = "short", 109 | ordertype = "stoplimit", 110 | prefer = "Low", 111 | threshold = .threshold, 112 | TxnFees = .txnfees, 113 | orderqty = -.orderqty, 114 | osFUN = osMaxPos, 115 | orderset = "ocoshort"), 116 | type = "enter", 117 | label = "EnterSHORT") 118 | 119 | add.rule(strategy.st, 120 | name = "ruleSignal", 121 | arguments = list(sigcol = "short", 122 | sigval = TRUE, 123 | replace = TRUE, 124 | orderside = "long" , 125 | ordertype = "market", 126 | TxnFees = .txnfees, 127 | orderqty = "all", 128 | orderset = "ocolong"), 129 | type = "exit", 130 | label = "Exit2SHORT") 131 | 132 | add.rule(strategy.st, 133 | name = "ruleSignal", 134 | arguments = list(sigcol = "long", 135 | sigval = TRUE, 136 | replace = TRUE, 137 | orderside = "short", 138 | ordertype = "market", 139 | TxnFees = .txnfees, 140 | orderqty = "all", 141 | orderset = "ocoshort"), 142 | type = "exit", 143 | label = "Exit2LONG") 144 | ``` 145 | 146 | 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. 147 | 148 | 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. 149 | 150 | 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`. 151 | 152 | 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. 153 | 154 | For example, 155 | 156 | $$ \text{StopLossLONG} = \text{fill price } - \left( \text{.stoploss } * \text{fill price}\right) $$ 157 | 158 | $$ \text{StopLossLONG} = 134.39 - \left(0.003 * 134.39\right) $$ 159 | 160 | $$ \text{StopLossLONG} = $133.9868 $$ 161 | 162 | If market price moves below $ \$133.9868 $ the **StopLossLONG** order becomes a market order and the **Exit2SHORT** order is cancelled (OCO). 163 | 164 | The same applies to **StopLossSHORT** which is a child of **EnterSHORT** except `.stoploss` is added to the fill price. 165 | 166 | ```{r stop-lossb-add-rules} 167 | add.rule(strategy.st, 168 | name = "ruleSignal", 169 | arguments = list(sigcol = "long" , 170 | sigval = TRUE, 171 | replace = FALSE, 172 | orderside = "long", 173 | ordertype = "stoplimit", 174 | tmult = TRUE, 175 | threshold = quote(.stoploss), 176 | TxnFees = .txnfees, 177 | orderqty = "all", 178 | orderset = "ocolong"), 179 | type = "chain", 180 | parent = "EnterLONG", 181 | label = "StopLossLONG", 182 | enabled = FALSE) 183 | 184 | add.rule(strategy.st, 185 | name = "ruleSignal", 186 | arguments = list(sigcol = "short", 187 | sigval = TRUE, 188 | replace = FALSE, 189 | orderside = "short", 190 | ordertype = "stoplimit", 191 | tmult = TRUE, 192 | threshold = quote(.stoploss), 193 | TxnFees = .txnfees, 194 | orderqty = "all", 195 | orderset = "ocoshort"), 196 | type = "chain", 197 | parent = "EnterSHORT", 198 | label = "StopLossSHORT", 199 | enabled = FALSE) 200 | ``` 201 | 202 | ## Add Position Limit 203 | 204 | 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`. 205 | 206 | ```{r stop-loss-add-pos-limit} 207 | for(symbol in symbols){ 208 | addPosLimit(portfolio = portfolio.st, 209 | symbol = symbol, 210 | timestamp = init_date, 211 | maxpos = .orderqty) 212 | } 213 | ``` 214 | 215 | ## Enable Rules 216 | 217 | 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). 218 | 219 | `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`. 220 | 221 | ```{r stop-loss-enable-rules} 222 | enable.rule(strategy.st, 223 | type = "chain", 224 | label = "StopLoss") 225 | ``` 226 | 227 | ## Apply Strategy 228 | 229 | ```{r stop-loss-apply-strategy, results = "hide"} 230 | cwd <- getwd() 231 | setwd("./_data/") 232 | results_file <- paste("results", strategy.st, "RData", sep = ".") 233 | if( file.exists(results_file) ) { 234 | load(results_file) 235 | } else { 236 | results <- applyStrategy(strategy.st, portfolios = portfolio.st) 237 | if(checkBlotterUpdate(portfolio.st, account.st, verbose = TRUE)) { 238 | save(list = "results", file = results_file) 239 | save.strategy(strategy.st) 240 | } 241 | } 242 | setwd(cwd) 243 | ``` 244 | -------------------------------------------------------------------------------- /trailing-stop.Rmd: -------------------------------------------------------------------------------- 1 | # Trailing Stops {#trailing-stops} 2 | 3 | ```{block type = "strategy"} 4 | If Signal Line crosses above 0: 5 | BTO 6 | Else If Signal Line crosses below 0: 7 | STC 8 | ``` 9 | 10 | ```{r trailing-stop-strategy-vars} 11 | trailingStopPercent <- 0.07 12 | trade_size <- init_equity/length(symbols) 13 | ``` 14 | 15 | ```{r trailing-stop-create-objects} 16 | portfolio.st <- "Quantstrat" 17 | account.st <- "Strategies" 18 | strategy.st <- "MACD.TS" 19 | ``` 20 | 21 | ```{r trailing-stop-rm-strat} 22 | rm.strat(portfolio.st) 23 | rm.strat(account.st) 24 | ``` 25 | 26 | ```{r trailing-stop-init-portf} 27 | initPortf(name = portfolio.st, 28 | symbols = symbols, 29 | initDate = init_date) 30 | ``` 31 | 32 | ```{r trailing-stop-init-acct} 33 | initAcct(name = account.st, 34 | portfolios = portfolio.st, 35 | initDate = init_date, 36 | initEq = init_equity) 37 | ``` 38 | 39 | ```{r trailing-stop-init-orders} 40 | initOrders(portfolio = portfolio.st, 41 | symbols = symbols, 42 | initDate = init_date) 43 | ``` 44 | 45 | ```{r trailing-stop-strategy} 46 | strategy(strategy.st, store = TRUE) 47 | ``` 48 | 49 | ## osFixedDollar 50 | 51 | quantstratIII pg. 11/66 52 | 53 | $$ \text{orderqty} = \frac{\text{trade_size}}{\text{Cl}} $$ 54 | 55 | ```{r} 56 | osFixedDollar <- function(timestamp, orderqty, portfolio, symbol, ruletype, ...) { 57 | if(!exists("trade_size")) stop("You must set trade_size") 58 | ClosePrice <- as.numeric(Cl(mktdata[timestamp,])) 59 | orderqty <- round(trade_size/ClosePrice,-2) 60 | return(orderqty) 61 | } 62 | ``` 63 | 64 | ## Add Indicators 65 | 66 | ```{r trailing-stop-add-indicators} 67 | add.indicator(strategy = strategy.st, 68 | name = "MACD", 69 | arguments = list(x = quote(Cl(mktdata))), 70 | label = "osc") 71 | ``` 72 | 73 | ## Add Signals 74 | 75 | ```{r trailing-stop-add-signals} 76 | add.signal(strategy = strategy.st, 77 | name="sigThreshold", 78 | arguments = list(column ="signal.osc", 79 | relationshipo = "gt", 80 | threshold = 0, 81 | cross = TRUE), 82 | label = "signal.gt.zero") 83 | 84 | add.signal(strategy = strategy.st, 85 | name="sigThreshold", 86 | arguments = list(column = "signal.osc", 87 | relationship = "lt", 88 | threshold = 0, 89 | cross = TRUE), 90 | label = "signal.lt.zero") 91 | ``` 92 | 93 | ## Add Rules 94 | 95 | ```{r trailing-stop-add-rule} 96 | add.rule(strategy = strategy.st, 97 | name = "ruleSignal", 98 | arguments = list(sigcol = "signal.gt.zero", 99 | sigval = TRUE, 100 | orderqty = 100, 101 | orderside = "long", 102 | ordertype = "market", 103 | osFUN = "osFixedDollar", 104 | orderset = "ocolong"), 105 | type = "enter", 106 | label = "LE") 107 | 108 | add.rule(strategy = strategy.st, 109 | name = "ruleSignal", 110 | arguments = list(sigcol = "signal.lt.zero", 111 | sigval = TRUE, 112 | replace = TRUE, 113 | orderside = "long", 114 | ordertype = "market", 115 | orderqty = "all", 116 | orderset = "ocolong"), 117 | type = "exit", 118 | label = "LX") 119 | 120 | add.rule(strategy = strategy.st, 121 | name = "ruleSignal", 122 | arguments = list(sigcol = "signal.gt.zero", 123 | sigval = TRUE, 124 | replace = FALSE, 125 | orderside = "long", 126 | ordertype = "stoptrailing", 127 | tmult = TRUE, 128 | threshold = quote(trailingStopPercent), 129 | orderqty = "all", 130 | orderset = "ocolong"), 131 | type = "chain", 132 | parent = "LE", 133 | label = "StopTrailingLong", 134 | enabled = FALSE) 135 | ``` 136 | 137 | ## Enable Rules 138 | 139 | ```{r trailing-stop-enable-rules} 140 | enable.rule(strategy.st, type = "chain", label = "StopTrailingLong") 141 | ``` 142 | 143 | ## Save Strategy 144 | 145 | ```{r trailing-stop-save-strategy} 146 | cwd <- getwd() 147 | setwd("./_data/") 148 | save.strategy(strategy.st) 149 | setwd(cwd) 150 | ``` 151 | -------------------------------------------------------------------------------- /walk-forward-analysis.Rmd: -------------------------------------------------------------------------------- 1 | # Walk Forward Analysis 2 | --------------------------------------------------------------------------------