- Introduction to Quantitative Trading
- Quantitative Trading - Advantages and Disadvantages
- Types of Quantitative Trading Strategies
- Momentum Strategies
- Mean Reversion Strategies
- Market Making Strategies and Day Trading Strategies
- How to Generate Trading Ideas
- Designing A Trading Strategy For Profit
- Backtesting a Trading Strategy - Considerations
- Risk Management of a Trading Strategy
- Risk Indicators - VIX Index and TED Spread
- Plotting the VIX Index and TED Spread in R
- Introduction to Quantmod in R
- Downloading Data Using Quantmod Package in R
- Creating Charts with Quantmod
- Data Analysis with Quantmod in R
- Measuring Overall ETFs Performance
- Quantstrat Example in R - EMA Crossover Strategy
- Quantstrat - EMA Crossover Strategy - Performance and Risk Metrics
- Quantstrat Example in R - RSI Strategy
- Quantstrat Case Study - Multiple Symbol Portfolio
Quantstrat Case Study - Multiple Symbol Portfolio
One of the main advantages of quantstrat package is that we can backtest strategies with multiple symbols as fast as with one symbol. The package provides fast computations for multiple symbols that allow analysts to get insights of strategies in an efficient approach.
In this case study we will build a strategy that works with 9 ETFs which are described below, and its signal is based on the three Exponential Moving Average (EMA) Crossover in order to run the trend. There are three EMAs that generate a crossover signal to buy when EMA4 > EMA9 > EMA18 and exit positions when EMA4 < EMA9 < EMA18.
In the performance analysis we will get the same info as we did before, but with the breakdown by symbol. This leads to an analysis by symbols where we can get an overview of the worst and best performing symbols. The steps are the same as before: Objects Initialization, Add Indicators, Add Signal, Add Rules, Apply the Strategy to the Portfolio, and Get Metrics.
Before proceeding, start a new R session and ensure that the quantstrat and ggplot2 libraries is loaded.
Note: For convenience, the complete R file is attached below.
Initialize Currency and Instruments
- Initialize current (USD)
- Create symbol variable (we will use the Apple stock - AAPL)
- Set timezone
- Define the stock metadata using the stock() function
# Make sure quantstrat, dplyr and ggplot2 are loaded before starting.
# library(quantstrat)
# library(dplyr)
# library(ggplot2)
# User search() to check what's loaded
# Initialize the currency. The accounting analytics back-end systems need to know what currency the prices are listed in
currency('USD')
# Set the time to UTC, since that’s the time zone for a “Date” class object. Some orders need the time to be in UTC
Sys.setenv(TZ="UTC")
# Create a Symbols vector
Symbols <- c('SPY',#
'IWM', # iShares Russell 2000 ETF
'QQQ', # PowerShares QQQ Trust, Series 1
'IYR', # iShares U.S. Real Estate ETF
'TLT', # iShares 20+ Year Treasury Bond ETF
'EEM', # iShares MSCI Emerging Markets ETF
'EFA', # iShares MSCI EAFE ETF
'GLD', # SPDR Gold Trust
'DBC' # Invesco DB Commodity Index
)
# Define the stock metadata
stock(Symbols,currency='USD',multiplier=1)
Define Dates and Load Historical Data
# The initDate is used in the initialization of the portfolio and orders objects, and should be less than the startDate, which is the date of the data.
initDate <- '2013-01-10'
startDate <- '2014-07-01'
startEquity <- 100000
endDate <- '2019-08-14'
tradeSize <- startEquity/length(symbols)
# Fetch the data from tiingo api. This command require an api key that can be acquired in the tiingo site
token <- token <- 'your api key'
getSymbols(Symbols,from=startDate,to=endDate,adjust=TRUE,src='tiingo',api.key=token)
Initialize Portfolio, Account and Order Objects
# Name the strategy, portfolio and account with the same name
portName <- stratName <- acctName <- 'multipleSymbols'
# Remove objects associated with a strategy
rm.strat(stratName)
# Initialization of the Portfolio, Account and Orders objects with their required parameters. The startEquity value in the initEq parameter on the account initialization is added in order to compute returns.
initPortf(portName,symbols=Symbols,initDate = startDate)
initAcct(acctName,portfolios = portName,initDate = startDate,initEq = startEquity)
initOrders(portfolio=portName,startDate=startDate)
Initialize and Store the Strategy
# Initialize and store the strategy
strategy(stratName,store=TRUE)
Add Indicators
# Add Indicators
add.indicator(strategy=stratName, name="EMA", arguments=list(x = quote(Cl(mktdata)), n=4), label="EMA4")
add.indicator(strategy=stratName, name="EMA", arguments=list(x = quote(Cl(mktdata)), n=9), label="EMA9")
add.indicator(strategy=stratName, name="EMA", arguments=list(x = quote(Cl(mktdata)), n=18), label="EMA18")
# Test the indicators while building the strategy.
# This allows a user to see how the indicators will be appended to the mktdata object in the backtest. If the call to applyIndicators fails, it means that there most likely is an issue with labeling (column naming).
test <- applyIndicators(stratName, mktdata=OHLC(QQQ))
tail(test,10)
QQQ.Open QQQ.High QQQ.Low QQQ.Close EMA.EMA4 EMA.EMA9 EMA.EMA18
2019-08-01 191.51 194.98 189.23 190.15 191.8198 192.5779 192.0640
2019-08-02 188.82 188.99 186.21 187.35 190.0319 191.5323 191.5678
2019-08-05 183.51 183.51 179.20 180.73 186.3111 189.3718 190.4270
2019-08-06 182.38 183.80 181.07 183.26 185.0907 188.1495 189.6726
2019-08-07 181.32 184.51 179.89 184.25 184.7544 187.3696 189.1018
2019-08-08 185.15 188.32 184.57 188.26 186.1566 187.5477 189.0132
2019-08-09 187.32 188.00 185.03 186.49 186.2900 187.3361 188.7476
2019-08-12 185.34 185.90 183.50 184.35 185.5140 186.7389 188.2847
2019-08-13 184.27 189.68 184.02 188.39 186.6644 187.0691 188.2958
2019-08-14 185.31 185.95 182.42 182.76 185.1026 186.2073 187.7130
Add Signals
# Add Signals
add.signal(strategy=stratName, name="sigCrossover", arguments = list(columns=c("EMA.EMA4","EMA.EMA9"),relationship="gt"),label="Up4X9")
add.signal(strategy=stratName, name="sigCrossover", arguments = list(columns=c("EMA.EMA9","EMA.EMA18"),relationship="gt"),label="Up9X18")
add.signal(strategy=stratName, name="sigCrossover", arguments = list(columns=c("EMA.EMA4","EMA.EMA9"),relationship="lt"),label="Down4X9")
add.signal(strategy=stratName, name="sigCrossover", arguments = list(columns=c("EMA.EMA9","EMA.EMA18"),relationship="lt"),label="Down9X18")
# We take the columns Up4X9 and Up9X18 to create the longEntry column in the mktdata object, which is True when EMA4 > EMA9 and EMA9 > EMA 18
add.signal(strategy=stratName, name="sigFormula", arguments = list(columns=c("Up4X9","Up9X18"),formula="Up4X9 & Up9X18"),label="longEntry")
# Define the long exit
add.signal(strategy=stratName, name="sigFormula", arguments = list(columns=c("Down4X9","Down9X18"),formula=("Down4X9 & Down9X18")),label="longExit")
Add Rules
# Rule to enter in a long position(type="enter")
add.rule(strategy = stratName,name='ruleSignal',
arguments = list(sigcol="longEntry",sigval=TRUE,
orderqty= 100, ordertype="market", orderside="long",
replace=FALSE, tradeSize = tradeSize, maxSize=tradeSize,threshold=NULL),
label="longEnter", enabled=TRUE, type="enter")
# Rule to exit (type="exit") of a long position. When the column shortEntry is True, we should exit our long position
add.rule(strategy = stratName,name="ruleSignal",
arguments = list(sigcol="longExit", sigval=TRUE,
orderqty= "all", ordertype="market", replace=FALSE,
orderside="long"),label="longExit", enabled=TRUE, type="exit")
Apply Strategy
We will now apply the strategy to portfolio and then update the portfolio, account and equity.
# Apply strategy to the portfolio
t1 <- Sys.time()
results <- applyStrategy(stratName,portfolios = portName,symbols = Symbols)
t2 <- Sys.time()
print(t2-t1)
# Set up analytics. Update portfolio, account and equity
updatePortf(portName)
dateRange <- time(getPortfolio(portName)$summary)[-1]
updateAcct(portName,dateRange)
updateEndEq(acctName)
Plot Accumulated Equity Returns
# Plot the equity curve of the strategy
final_acct <- getAccount(acctName)
options(width=70)
plot(final_acct$summary$End.Eq, main = "Multiple Symbols Portfolio Equity")
Multiple Symbols Portfolio Equity Triple EMA Crossover Strategy
Portfolio Statistics
tStats <- tradeStats(Portfolios = portName, use="trades", inclZeroDays=FALSE)
# Get performance information for each symbol
tab.profit <- tStats %>%
select(Net.Trading.PL, Gross.Profits, Gross.Losses, Profit.Factor)
# t: transpose
t(tab.profit)
DBC EEM EFA GLD IWM IYR
Net.Trading.PL 266.4998 3713.685370 -629.807645 211.000000 9403.746347 4037.07263
Gross.Profits 266.4998 4862.953129 125.911927 2752.000000 11603.902733 4354.18955
Gross.Losses 0.0000 -1149.267759 -755.719572 -2541.000000 -2200.156385 -317.11692
Profit.Factor NA 4.231349 0.166612 1.083038 5.274126 13.73055
QQQ SPY TLT
Net.Trading.PL 6569.62866 -708.0645 3222.506379
Gross.Profits 6890.56991 0.0000 3799.799792
Gross.Losses -320.94126 -708.0645 -577.293413
Profit.Factor 21.46988 0.0000 6.582094
# Average trade profit per symbol
tab.wins <- tStats %>%
select(Avg.Trade.PL, Avg.Win.Trade, Avg.Losing.Trade, Avg.WinLoss.Ratio)
(t(tab.wins))
DBC EEM EFA GLD IWM IYR
Avg.Trade.PL 266.4998 742.737074 -157.451911 52.750000 3134.58212 807.4145
Avg.Win.Trade 266.4998 2431.476565 62.955964 2752.000000 11603.90273 1451.3965
Avg.Losing.Trade Nan -383.089253 -377.859786 -847.000000 -1100.07819 -158.5585
Avg.WinLoss.Ratio NA 6.347024 0.166612 3.249115 10.54825 9.1537
QQQ SPY TLT
Avg.Trade.PL 1313.92573 -236.0215 537.084397
Avg.Win.Trade 1722.64248 Nan 949.949948
Avg.Losing.Trade -320.94126 -236.0215 -288.646706
Avg.WinLoss.Ratio 5.36747 NaN 3.291047
Cumulative Returns
# Returns on Equity(ROE) by Symbol
rets <- PortfReturns(Account = acctName)
rownames(rets) <- NULL
# Chart the cumulative returns for all the symbols
charts.PerformanceSummary(rets, colorset = bluefocus)
Cumulative Returns by ETF’s Triple EMA Cross Over Strategy
Performance Statistics for Each Symbol
# Performance Statistics and risk metrics for each symbol
# Performance : annualized and cumulative returns s
# Risk metrics : Sharpe and Calmar ratio
tab.perf <- table.Arbitrary(rets,
metrics=c(
"Return.cumulative",
"Return.annualized",
"SharpeRatio.annualized",
"CalmarRatio"),
metricsNames=c(
"Cumulative Return",
"Annualized Return",
"Annualized Sharpe Ratio",
"Calmar Ratio"))
round(tab.perf,5)
DBC.DailyEqPL EEM.DailyEqPL EFA.DailyEqPL GLD.DailyEqPL
Cumulative Return 0.00266 0.03719 -0.00635 0.00190
Annualized Return 0.00052 0.00716 -0.00124 0.00037
Annualized Sharpe Ratio 0.24670 0.46053 -0.23570 0.04058
Calmar Ratio 0.10078 0.23305 -0.10153 0.01016
IWM.DailyEqPL IYR.DailyEqPL QQQ.DailyEqPL SPY.DailyEqPL
Cumulative Return 0.09275 0.04061 0.06655 -0.00714
Annualized Return 0.01748 0.00781 0.01267 -0.00140
Annualized Sharpe Ratio 0.38301 0.52618 0.57011 -0.24452
Calmar Ratio 0.25103 0.38344 0.26922 -0.14163
TLT.DailyEqPL
Cumulative Return 0.03236
Annualized Return 0.00624
Annualized Sharpe Ratio 0.56136
Calmar Ratio 0.32163
Risk Statistics by Symbol
# Risk Statistics by Symbol
tab.risk <- table.Arbitrary(rets,
metrics=c(
"StdDev.annualized",
"maxDrawdown",
"VaR",
"ES"),
metricsNames=c(
"Annualized StdDev",
"Max DrawDown",
"Value-at-Risk",
"Conditional VaR"))
round(tab.risk,5)
DBC.DailyEqPL EEM.DailyEqPL EFA.DailyEqPL GLD.DailyEqPL IWM.DailyEqPL
Annualized StdDev 0.00210 0.01555 0.00528 0.00914 0.04565
Max DrawDown 0.00515 0.03072 0.01225 0.03649 0.06962
Value-at-Risk -0.00016 -0.00149 -0.00053 -0.00084 -0.00464
Conditional VaR -0.00016 -0.00342 -0.00110 -0.00115 -0.01233
IYR.DailyEqPL QQQ.DailyEqPL SPY.DailyEqPL TLT.DailyEqPL
Annualized StdDev 0.01484 0.02223 0.00572 0.01114
Max DrawDown 0.02036 0.04705 0.00987 0.01944
Value-at-Risk -0.00144 -0.00230 -0.00044 -0.00065
Conditional VaR -0.00358 -0.00500 -0.00044 -0.00065
The results above show that the symbol IWM has the best performance among all ETFs in the portfolio. The annual returns for IWM was 1,75% and the cumulative return was 9,3%. In the second place comes the QQQ ETF which has an annual return in the whole period of 1,23% and cumulative returns of 6,65%.
The worst performance among the ETF’s in the portfolio was for SPY ETF with an annualized return of -0.14% and a cumulative return of -0,71% in the whole period. Regards, the ETF with the Maximum Drawdown level, we can observe that the IWM ETF has the Maximum Drawdown lever among the rest of the ETF’s with a 7% of Maximum Drawdown.
In terms of Sharpe and Calmar Ratio, the best level of Sharpe Ratio is observed for IWR ETF with a value of 0.38 and Calmar ratio of 0.25. Remember, when we define Calmar Ratio in the risk management section, we point out that this ratio takes into account the Maximum Drawdown level in its calculation. So for a conservative strategy this ratio is fundamental.
Finally the QQQ ETF has the higher Sharpe Ratio with a value of 0.57 which is followed by the TLT ETF with a Sharpe Ratio value of 0.56. Overall, this strategy has positive returns at the end of the period, but is affected by a significant drawdown at the end of 2018. The exit rule of this strategy given by triple EMA cross down is to exit the position when the market reverse.
Lesson Resources
Related Downloads
Data Science in Finance: 9-Book Bundle
Master R and Python for financial data science with our comprehensive bundle of 9 ebooks.
What's Included:
- Getting Started with R
- R Programming for Data Science
- Data Visualization with R
- Financial Time Series Analysis with R
- Quantitative Trading Strategies with R
- Derivatives with R
- Credit Risk Modelling With R
- Python for Data Science
- Machine Learning in Finance using Python
Each book includes PDFs, explanations, instructions, data files, and R code for all examples.
Get the Bundle for $39 (Regular $57)Free Guides - Getting Started with R and Python
Enter your name and email address below and we will email you the guides for R programming and Python.