Strategy Overview:

Our strategy uses three separate indicators and five separate signals. The strategy requires both the threshold of the DVO_2_126 indicator to be under 20 and the SMA50 to be greater than the SMA200. The strategy sells when the DVO_2_126 crosses above 80, or the SMA50 crosses under the SMA200.

We specify 5 separate trading signals:

  1. sigComparison for SMA50 being greater than SMA200;
  2. sigThreshold with cross set to FALSE for DVO_2_126 less than 20;
  3. sigFormula to tie them together and set cross to TRUE;
  4. sigCrossover with SMA50 less than SMA200; and
  5. sigThreshold with cross set to TRUE for DVO_2_126 greater than 80.

Import Libraries

require(devtools)
## Loading required package: devtools
library(quantstrat)
## Loading required package: quantmod
## Loading required package: xts
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## Loading required package: TTR
## Version 0.4-0 included new data defaults. See ?getSymbols.
## Loading required package: blotter
## Loading required package: FinancialInstrument
## Loading required package: PerformanceAnalytics
## 
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
## 
##     legend
## Loading required package: foreach
library(quantmod)
library(TTR)
library(IKTrading)
## Loading required package: Rcpp
## Loading required package: digest
## Loading required package: roxygen2

Data

All data is obtained through Yahoo Finance. We’ll use Exchange-Traded-Funds (ETF) for purpose of this strategic analysis, specifically SPY ETF

Exploratory Data Analysis (EDA)

Let’s look at the closing price of SPY over the past 4 years.

getSymbols("SPY", 
           from = "2014-01-01", 
           to = "2018-11-16", 
           src =  "yahoo", 
           adjust =  TRUE)
## 'getSymbols' currently uses auto.assign=TRUE by default, but will
## use auto.assign=FALSE in 0.5-0. You will still be able to use
## 'loadSymbols' to automatically load data. getOption("getSymbols.env")
## and getOption("getSymbols.auto.assign") will still be checked for
## alternate defaults.
## 
## This message is shown once per session and may be disabled by setting 
## options("getSymbols.warning4.0"=FALSE). See ?getSymbols for details.
## 
## WARNING: There have been significant changes to Yahoo Finance data.
## Please see the Warning section of '?getSymbols.yahoo' for details.
## 
## This message is shown once per session and may be disabled by setting
## options("getSymbols.yahoo.warning"=FALSE).
## [1] "SPY"
# Plot the closing price of SPY
plot(Cl(SPY))

Let’s create a 200 day simple moving avergage (SMA) of SPY to observe the assets trend. Whenever an aesset’s price is above the 200-day moving average, a whole assortment of good things usually happen, such as the asset appreciating in price, low volatility, and so on.

lines(SMA(Cl(SPY), n = 200), col = "blue")

Setting up Trading Strategy

Define Initialization Settings

Date and Time Paremeters

Three important date parameters are used when setting up a trading strategy:

  • initDate
  • fromDate
  • toDate
# Create initdate, from, and to strings
initdate <- "2010-01-01"
from <- "2014-01-01"
to <- "2018-11-16"

# Set the timezone to UTC
Sys.setenv(TZ = "UTC")

# Set the currency to USD 
currency("USD")
## [1] "USD"

Obtained Adjusted Data between from:to dates

getSymbols("SPY", 
           from = from, 
           to = to, 
           src =  "yahoo", 
           adjust =  TRUE)
## [1] "SPY"
stock("SPY", currency = "USD")
## [1] "SPY"

Trade Size and Initial Equity

There are three important objects to note here when defining a trading strategy:

  • Account
    • Portfolio
      • Strategy
# Define your trade size and initial equity
tradesize <- 100000
initeq <- 100000

# Define the names of your strategy, portfolio and account
strategy.st <- "firststrat"
portfolio.st <- "firststrat"
account.st <- "firststrat"

# Remove the existing strategy if there is one
rm.strat(strategy.st)

Initialization

Initialize the portfolio, account, and orders of the trading strategy.

# Initialize the portfolio
initPortf(portfolio.st, symbols = "SPY", initDate = initdate, currency = "USD")
## [1] "firststrat"
# Initialize the account
initAcct(account.st, portfolios = portfolio.st, initDate = initdate, currency = "USD", initEq = initeq)
## [1] "firststrat"
# Initialize the orders
initOrders(portfolio.st, initDate = initdate)

# Store the strategy
strategy(strategy.st, store = TRUE)

Indicators

This strategy will incorporate a combination of basic moving average crossover and RSI as an oscillation indicator.

# Create a 200-day SMA
spy_sma <- SMA(Cl(SPY), n = 200)

# Create an RSI with a 3-day lookback period
spy_rsi <- RSI(Cl(SPY), n = 3)

Now let’s observe what this SMA indicator is telling us, there is clearly an upward trend.

# Plot the closing prices of SPY
plot(Cl(SPY))

# Overlay a 200-day SMA
lines(SMA(Cl(SPY), n = 200), col = "red")

Now Let’s look at the RSI as a reversion indicator.

plot(Cl(SPY))

# Plot the RSI 2day period, default is 14 day.
plot(RSI(Cl(SPY), n = 2))

Create indicators for trading strategy

SMA - 200 Day

# Add a 200-day SMA indicator to strategy.st
add.indicator(strategy = strategy.st, 
              
              # Add the SMA function
              name = "SMA", 
              
              # Create a lookback period
              arguments = list(x = quote(Cl(mktdata)), n = 200), 
              
              # Label your indicator SMA200
              label = "SMA200")
## [1] "firststrat"

SMA - 50 Day

Now, let’s make the strategy more robust by adding a 50-day SMA. A fast moving average with a slower moving average is a simple and standard way to predict when prices in an asset are expected to rise in the future. While a single indicator can provide a lot of information, a truly robust trading system requires multiple indicators to operate effectively.

add.indicator(strategy = strategy.st, 
              
              # Add the SMA function
              name = "SMA", 
              
              # Create a lookback period
              arguments = list(x = quote(Cl(mktdata)), n = 50), 
              
              # Label your indicator SMA50
              label = "SMA50")
## [1] "firststrat"

RSI

In financial markets, the goal of course is to buy low and sell high. The RSI can predict when a price has sufficiently pulled back, especially with a short period such as 2 or 3 trading days.

add.indicator(strategy = strategy.st, 
              
              # Add the SMA function
              name = "RSI", 
              
              # Create a lookback period
              arguments = list(price = quote(Cl(mktdata)), n = 3), 
              
              # Label your indicator SMA50
              label = "RSI_3")
## [1] "firststrat"

RSI_3_4

Generating 3.5 day RSI by creating a function that averages the 3 and 4 day RSI.

# Write the calc_RSI_avg function
calc_RSI_avg <- function(price, n1, n2) {
  
  # RSI 1 takes an input of the price and n1
  RSI_1 <- RSI(price = price, n = n1)
  
  # RSI 2 takes an input of the price and n2
  RSI_2 <- RSI(price = price, n = n2)
  
  # RSI_avg is the average of RSI_1 and RSI_2
  RSI_avg <- (RSI_1 + RSI_2)/2
  
  # Your output of RSI_avg needs a column name of RSI_avg
  colnames(price) <- "RSI_avg"
  return(price)
}

# Add this function as RSI_3_4 to your strategy with n1 = 3 and n2 = 4
add.indicator(strategy.st, name = "calc_RSI_avg", arguments = list(price = quote(Cl(mktdata)), n1 = 3, n2 = 4), label = "RSI_3_4")
## [1] "firststrat"

David Varadi Oscillator (DVO)

RSI is good but somewhat outdated as far as indicators go. The purpose of the DVO is similar to something like the RSI in that it attempts to find opportunities to buy a temporary dip and sell in a temporary uptrend. In addition to obligatory market data, an oscillator function takes in two lookback periods.

First, the function computes a ratio between the closing price and average of high and low prices. Next, it applies an SMA to that quantity to smooth out noise, usually on a very small time frame, such as two days. Finally, it uses the runPercentRank() function to take a running percentage rank of this average ratio, and multiplies it by 100 to convert it to a 0-100 quantity.

**Note: High Low Close (HLC) is a built in function used to obtain the High, Low, and Closing Price of an assett on a given trading day.

# Declare the DVO function
DVO <- function(HLC, navg = 2, percentlookback = 126) {
  
  # Compute the ratio between closing prices to the average of high and low
  ratio <- Cl(HLC)/((Hi(HLC) + Lo(HLC))/2)
  
  # Smooth out the ratio outputs using a moving average
  avgratio <- SMA(ratio, n = navg)
  
  # Convert ratio into a 0-100 value using runPercentRank()
  out <- runPercentRank(avgratio, n = percentlookback, exact.multiplier = 1) * 100
  colnames(out) <- "DVO"
  return(out)
}

Apply Indicators into Trading Strategy

# Add the DVO indicator to the strategy
add.indicator(strategy = strategy.st, name = "DVO", 
              arguments = list (HLC=quote(HLC(mktdata)), navg = 2, percentlookback = 126), label = "DVO_2_126")
## [1] "firststrat"
# Use applyIndicators to test out your indicators
test <- applyIndicators(strategy = strategy.st, mktdata = OHLC(SPY))

# Subset the data between November 5th and November 9th th of 2013
test_subset <- test["2016-11-05/2016-11-09"]

Signals

We’ll integrate 4 types of signals:

Let’s add a (long filter) sigComparison signal that looks at whether the SMA50 > SMA200

add.signal(strategy.st, name = "sigComparison", 
           
           # We are interested in the relationship between the SMA50 and the SMA200
           arguments = list(columns = c("SMA50", "SMA200"), 
                            
  # Particularly, we are interested when the SMA50 is greater than the SMA200
                            relationship = "gt"),
           
           # We'll label this signal longfilter
           label = "longfilter")
## [1] "firststrat"

Let’s add a sigCrossover which specifies that the SMA50 is less than the SMA200 and label it filterexit

# Add a sigCrossover which specifies that the SMA50 is less than the SMA200 and label it filterexit
add.signal(strategy.st, name = "sigCrossover",
           
           # We're interested in the relationship between the SMA50 and the SMA200
           arguments = list(columns = c("SMA50", "SMA200"),
                            
          # The relationship is that the SMA50 crosses under the SMA200
                            relationship = "lt"),
           
           # Label it filterexit
           label = "filterexit")
## [1] "firststrat"

sigThreshold

# Implement a sigThreshold which specifies that DVO_2_126 must be less than 20, label it longthreshold
add.signal(strategy.st, name = "sigThreshold", 
           
           # Use the DVO_2_126 column
           arguments = list(column = "DVO_2_126", 
                            
                            # The threshold is 20
                            threshold = 20, 
                            
                            # We want the oscillator to be under this value
                            relationship = "lt", 
                            
     # We're interested in every instance that the oscillator is less than 20
                            cross = FALSE), 
           
           # Label it longthreshold
           label = "longthreshold")
## [1] "firststrat"

sigThreshold2

# Add a sigThreshold signal to your strategy that specifies that DVO_2_126 must cross above 80 and label it thresholdexit
add.signal(strategy.st, name = "sigThreshold", 
           
           # Reference the column of DVO_2_126
           arguments = list(column = "DVO_2_126", 
                            
                            # Set a threshold of 80
                            threshold = 80, 
                            
                            # The oscillator must be greater than 80
                            relationship = "gt", 
                            
                            # We are interested only in the cross
                            cross = TRUE), 
           
           # Label it thresholdexit
           label = "thresholdexit")
## [1] "firststrat"

sigFormula

test_init <- applyIndicators(strategy.st, mktdata = OHLC(SPY))
test <- applySignals(strategy = strategy.st, mktdata = test_init)

# Add a sigFormula signal to your code specifying that both longfilter and longthreshold must be TRUE, label it longentry
add.signal(strategy.st, name = "sigFormula",
           
           # Specify that longfilter and longthreshold must be TRUE
           arguments = list(formula = "longfilter & longthreshold", 
                            
                            # Specify that cross must be TRUE
                            cross = TRUE),
           
           # Label it longentry
           label = "longentry")
## [1] "firststrat"

Rules

We’ll use quantrat’s add.rule function to add trading rules to enter/exit an assett according to the trading signals we’ve established above. Like signals and indicators, all rules reference a column already present in the strategy. Rules relies on signals, and must therefore reference the signal columns in your strategy.

Exit Rule:

add.rule(strategy.st, name = "ruleSignal", 
         arguments = list(sigcol = "filterexit", sigval = TRUE, orderqty = "all", 
                        ordertype = "market", orderside = "long", 
                        replace = FALSE, prefer = "Open"), 
         type = "exit")
## [1] "firststrat"

Let’s look at three necessary arguments to our Exit Rule:

  • orderqty: When orderqty is set to “all”, complete liquidation of assett holding occurs, otherwise set to an integer value to specify number of shares to sell.
  • ordertype: When set to “market” the assett will be bought/sold now, depending on market price. We can also use “limit” to signal buying when assett falls below specific price, or “stoplimit” when assett goes above a certain price.
  • orderside: Argument used to classify different rules, “long” is a long trade strategy, denoting we wish to “buy low and sell high.” orderside can also take a “short”, indicating that we think the assett will drop in price and therefore we will short the stock i.e. selling it before we own it, hoping to buy it back at a lower price.

Finally, let’s examine the “replace” and “prefer” arguments to rules.

  • replace: TRUE will cancel other signals in the strategy, to keep other signals set to FALSE. Good rule of thumb is to keep set to FALSE.
  • prefer: Indicates when to enter a position
    • bar: open, high, low, close i.e. a day’s worth of trading data
    • default: buy at close of next day/bar

Note: There is a day lag between observing signal and buying on next day’s opening price

Enter Rule

# Create an entry rule of 1 share when all conditions line up to enter into a position
add.rule(strategy.st, name = "ruleSignal", 
         
         # Use the longentry column as the sigcol
         arguments=list(sigcol = "longentry", 
                        # Set sigval to TRUE
                        sigval = TRUE, 
                        # Set orderqty to 1
                        orderqty = 1,
                        # Use a market type of order
                        ordertype = "market",
                        # Take the long orderside
                        orderside = "long",
                        # Do not replace other signals
                        replace = FALSE, 
                        # Buy at the next day's opening price
                        prefer = "Open"),
         # This is an enter type rule, not an exit
         type = "enter")
## [1] "firststrat"

Order Sizing Functions

The constructs that allow quantstrat to vary the amount of shares bought or sold are called order sizing functions. The first thing to know is that when using an order sizing function, the orderqty argument is no longer relevant, as the order quantity is determined by the order sizing function. The additional arguments to this function are tradeSize and maxSize, both of which should take tradesize, which we defined early on in our code.

add.rule(strategy = strategy.st, name = "ruleSignal",
         arguments = list(sigcol = "longentry", sigval = TRUE, ordertype = "market",
                          orderside = "long", replace = FALSE, prefer = "Open",
                          # Use the osFUN called osMaxDollar
                          osFUN = osMaxDollar,
                          # The tradeSize argument should be equal to tradesize (defined earlier)
                          tradeSize = tradesize,
                          # The maxSize argument should also be equal to tradesize.
                          maxSize = tradesize),
         type = "enter")
## [1] "firststrat"

Analyzing the Strategy

# Use applyStrategy() to apply the strategy. Save this to out
out <- applyStrategy(strategy = strategy.st, portfolios = portfolio.st)
## [1] "2014-10-24 00:00:00 SPY 1 @ 180.341869922637"
## [1] "2014-10-24 00:00:00 SPY 556 @ 180.341869922637"
## [1] "2014-12-02 00:00:00 SPY 1 @ 190.095569209189"
## [1] "2014-12-09 00:00:00 SPY 1 @ 188.765516283636"
## [1] "2014-12-12 00:00:00 SPY 1 @ 187.1676125008"
## [1] "2014-12-22 00:00:00 SPY 1 @ 192.017694764566"
## [1] "2015-01-02 00:00:00 SPY 1 @ 191.674064355984"
## [1] "2015-01-13 00:00:00 SPY 1 @ 189.57509501937"
## [1] "2015-01-29 00:00:00 SPY 1 @ 186.101604048427"
## [1] "2015-02-10 00:00:00 SPY 1 @ 191.209692663687"
## [1] "2015-03-09 00:00:00 SPY 1 @ 192.93715535903"
## [1] "2015-03-12 00:00:00 SPY 1 @ 190.633862477806"
## [1] "2015-03-25 00:00:00 SPY 1 @ 195.03894534867"
## [1] "2015-04-28 00:00:00 SPY 1 @ 196.596866799614"
## [1] "2015-05-06 00:00:00 SPY 1 @ 195.496052177342"
## [1] "2015-05-27 00:00:00 SPY 1 @ 197.072635124112"
## [1] "2015-06-05 00:00:00 SPY 1 @ 195.85987764776"
## [1] "2015-06-25 00:00:00 SPY 1 @ 197.891015002519"
## [1] "2015-07-10 00:00:00 SPY 1 @ 194.319402883556"
## [1] "2015-07-24 00:00:00 SPY 1 @ 197.141069947211"
## [1] "2015-08-07 00:00:00 SPY 1 @ 195.13497538455"
## [1] "2015-08-20 00:00:00 SPY 1 @ 193.588211071463"
## [1] "2015-09-02 00:00:00 SPY 1 @ 182.442194484519"
## [1] "2015-09-04 00:00:00 SPY -578 @ 180.782957583534"
## [1] "2015-12-10 00:00:00 SPY 1 @ 193.568115628397"
## [1] "2015-12-10 00:00:00 SPY 514 @ 193.568115628397"
## [1] "2015-12-21 00:00:00 SPY 1 @ 190.918581564005"
## [1] "2015-12-21 00:00:00 SPY 5 @ 190.918581564005"
## [1] "2016-01-04 00:00:00 SPY 1 @ 190.046505199217"
## [1] "2016-01-08 00:00:00 SPY 1 @ 185.022578706246"
## [1] "2016-01-08 00:00:00 SPY 18 @ 185.022578706246"
## [1] "2016-01-14 00:00:00 SPY 1 @ 179.676366563266"
## [1] "2016-01-14 00:00:00 SPY 1 @ 179.676366563266"
## [1] "2016-01-19 00:00:00 SPY -543 @ 180.065013505131"
## [1] "2016-05-02 00:00:00 SPY 1 @ 197.152808183215"
## [1] "2016-05-02 00:00:00 SPY 507 @ 197.152808183215"
## [1] "2016-05-13 00:00:00 SPY 1 @ 196.476330700189"
## [1] "2016-05-19 00:00:00 SPY 1 @ 194.427808005108"
## [1] "2016-05-19 00:00:00 SPY 4 @ 194.427808005108"
## [1] "2016-06-14 00:00:00 SPY 1 @ 198.181831135088"
## [1] "2016-06-21 00:00:00 SPY 1 @ 199.500206685796"
## [1] "2016-06-27 00:00:00 SPY 1 @ 193.073669172192"
## [1] "2016-08-17 00:00:00 SPY 1 @ 208.790419736592"
## [1] "2016-08-25 00:00:00 SPY 1 @ 208.215761458682"
## [1] "2016-08-29 00:00:00 SPY 1 @ 208.254079289475"
## [1] "2016-09-12 00:00:00 SPY 1 @ 203.417417610387"
## [1] "2016-09-15 00:00:00 SPY 1 @ 203.963345177237"
## [1] "2016-09-21 00:00:00 SPY 1 @ 206.225829346696"
## [1] "2016-09-27 00:00:00 SPY 1 @ 206.042934840007"
## [1] "2016-10-03 00:00:00 SPY 1 @ 207.746727475967"
## [1] "2016-10-06 00:00:00 SPY 1 @ 207.313549284452"
## [1] "2016-10-11 00:00:00 SPY 1 @ 207.592709782712"
## [1] "2016-10-18 00:00:00 SPY 1 @ 206.225829346696"
## [1] "2016-10-28 00:00:00 SPY 1 @ 205.166971783486"
## [1] "2016-12-01 00:00:00 SPY 1 @ 212.473046230525"
## [1] "2016-12-15 00:00:00 SPY 1 @ 217.699931392142"
## [1] "2016-12-29 00:00:00 SPY 1 @ 217.356306964821"
## [1] "2017-01-11 00:00:00 SPY 1 @ 219.176651544101"
## [1] "2017-03-09 00:00:00 SPY 1 @ 229.188516229768"
## [1] "2017-03-20 00:00:00 SPY 1 @ 230.506507923658"
## [1] "2017-03-27 00:00:00 SPY 1 @ 225.546863244042"
## [1] "2017-04-06 00:00:00 SPY 1 @ 228.474031392951"
## [1] "2017-04-17 00:00:00 SPY 1 @ 226.694395305593"
## [1] "2017-04-20 00:00:00 SPY 1 @ 227.705765831292"
## [1] "2017-05-18 00:00:00 SPY 1 @ 229.242283339915"
## [1] "2017-06-07 00:00:00 SPY 1 @ 236.895696536885"
## [1] "2017-06-22 00:00:00 SPY 1 @ 237.425522099579"
## [1] "2017-06-27 00:00:00 SPY 1 @ 237.503686065926"
## [1] "2017-07-03 00:00:00 SPY 1 @ 237.347342497703"
## [1] "2017-08-09 00:00:00 SPY 1 @ 240.855560517451"
## [1] "2017-08-11 00:00:00 SPY 1 @ 238.461372996427"
## [1] "2017-08-18 00:00:00 SPY 1 @ 237.366876160135"
## [1] "2017-08-25 00:00:00 SPY 1 @ 239.321317346002"
## [1] "2017-10-25 00:00:00 SPY 1 @ 251.586755990659"
## [1] "2017-10-27 00:00:00 SPY 1 @ 251.871564226763"
## [1] "2017-11-01 00:00:00 SPY 1 @ 253.413422413945"
## [1] "2017-11-21 00:00:00 SPY 1 @ 254.5329668135"
## [1] "2017-12-06 00:00:00 SPY 1 @ 258.579091433184"
## [1] "2017-12-14 00:00:00 SPY 1 @ 262.301145629269"
## [1] "2017-12-20 00:00:00 SPY 1 @ 264.806641950537"
## [1] "2018-01-02 00:00:00 SPY 1 @ 264.382200130501"
## [1] "2018-01-17 00:00:00 SPY 1 @ 274.440650894801"
## [1] "2018-01-31 00:00:00 SPY 1 @ 279.079986064145"
## [1] "2018-02-09 00:00:00 SPY 1 @ 257.433078148075"
## [1] "2018-02-21 00:00:00 SPY 1 @ 268.389783836429"
## [1] "2018-02-28 00:00:00 SPY 1 @ 272.120983310129"
## [1] "2018-03-14 00:00:00 SPY 1 @ 274.223490092533"
## [1] "2018-03-29 00:00:00 SPY 1 @ 258.781255834878"
## [1] "2018-04-09 00:00:00 SPY 1 @ 259.029016692711"
## [1] "2018-04-25 00:00:00 SPY 1 @ 260.555232496357"
## [1] "2018-05-01 00:00:00 SPY 1 @ 261.506625271048"
## [1] "2018-06-28 00:00:00 SPY 1 @ 268.076470279096"
## [1] "2018-09-27 00:00:00 SPY 1 @ 290.410004"
## [1] "2018-10-04 00:00:00 SPY 1 @ 291.179993"
## [1] "2018-10-11 00:00:00 SPY 1 @ 277.079987"
## [1] "2018-10-22 00:00:00 SPY 1 @ 277"
## [1] "2018-10-25 00:00:00 SPY 1 @ 267.380005"
## [1] "2018-10-30 00:00:00 SPY 1 @ 263.670013"
## [1] "2018-11-13 00:00:00 SPY 1 @ 273.089996"
# Update the portfolio (portfolio.st)
updatePortf(portfolio.st)
## [1] "firststrat"
daterange <- time(getPortfolio(portfolio.st)$summary)[-1]

# Update the account (account.st)
updateAcct(account.st, daterange)
## [1] "firststrat"
updateEndEq(account.st)
## [1] "firststrat"

View portfolio performance with tradeStats

A profit factor above 1 means the strategy is profitable. A profit factor below 1 means we’re losing money!

# Get the tradeStats for the portfolio
tstats <- tradeStats(Portfolios = portfolio.st)

# Print the profit factor
str(tstats)
## 'data.frame':    1 obs. of  32 variables:
##  $ Portfolio         : Factor w/ 1 level "firststrat": 1
##  $ Symbol            : Factor w/ 1 level "SPY": 1
##  $ Num.Txns          : num 99
##  $ Num.Trades        : int 2
##  $ Net.Trading.PL    : num 33896
##  $ Avg.Trade.PL      : num -3561
##  $ Med.Trade.PL      : num -3561
##  $ Largest.Winner    : num 1.37
##  $ Largest.Loser     : num -7123
##  $ Gross.Profits     : num 1.37
##  $ Gross.Losses      : num -7123
##  $ Std.Dev.Trade.PL  : num 5037
##  $ Std.Err.Trade.PL  : num 3562
##  $ Percent.Positive  : num 50
##  $ Percent.Negative  : num 50
##  $ Profit.Factor     : num 0.000192
##  $ Avg.Win.Trade     : num 1.37
##  $ Med.Win.Trade     : num 1.37
##  $ Avg.Losing.Trade  : num -7123
##  $ Med.Losing.Trade  : num -7123
##  $ Avg.Daily.PL      : num -3561
##  $ Med.Daily.PL      : num -3561
##  $ Std.Dev.Daily.PL  : num 5037
##  $ Std.Err.Daily.PL  : num 3562
##  $ Ann.Sharpe        : num -11.2
##  $ Max.Drawdown      : num -20885
##  $ Profit.To.Max.Draw: num 1.62
##  $ Avg.WinLoss.Ratio : num 0.000192
##  $ Med.WinLoss.Ratio : num 0.000192
##  $ Max.Equity        : num 44832
##  $ Min.Equity        : num -10201
##  $ End.Equity        : num 33896

Visualizing Strategy

Basic visualization before adding indicators.

chart.Posn(Portfolio = portfolio.st, Symbol = "SPY")

Now let’s add some of the indicators we’ve created to our visualization:

# Compute the SMA50
sma50 <- SMA(x = Cl(SPY), n = 50)

# Compute the SMA200
sma200 <- SMA(x = Cl(SPY), n = 200)

# Compute the DVO_2_126 with an navg of 2 and a percentlookback of 126
dvo <- DVO(HLC = HLC(SPY), navg = 2, percentlookback = 126)

# Recreate the chart.Posn of the strategy from above code block
chart.Posn(Portfolio = portfolio.st, Symbol = "SPY")

# Overlay the SMA50 on the plot as a blue line
add_TA(sma50, on = 1, col = "blue")

# Overlay the SMA200 on the plot as a red line
add_TA(sma200, on = 1, col = "red")

# Add the DVO_2_126 to the plot in a new window
add_TA(dvo)

Generate P&L Time Series

The P&L Time Series allows us to take the sharpe ratio i.e. the ratio of reward to risk from our strategy.

Compute Sharpe Ratio

# Get instrument returns
instrets <- PortfReturns(portfolio.st)

# Compute Sharpe ratio from returns
SharpeRatio.annualized(instrets, geometric = FALSE)
##                                 SPY.DailyEqPL
## Annualized Sharpe Ratio (Rf=0%)     0.5131222

Conclusion

Strategy Overview:

To conclude, our strategy uses three separate indicators and five separate signals. The strategy requires both the threshold of the DVO_2_126 indicator to be under 20 and the SMA50 to be greater than the SMA200. The strategy sells when the DVO_2_126 crosses above 80, or the SMA50 crosses under the SMA200.

We specified 5 separate trading signals:

  1. sigComparison for SMA50 being greater than SMA200;
  2. sigThreshold with cross set to FALSE for DVO_2_126 less than 20;
  3. sigFormula to tie them together and set cross to TRUE;
  4. sigCrossover with SMA50 less than SMA200; and
  5. sigThreshold with cross set to TRUE for DVO_2_126 greater than 80.