Combining shiny with Quantstrat backtests

2020-04-28 07:06发布

问题:

I am trying to make a web app with the intention of using quantstrat. However I am having a bit of difficulty integrating the two. There is no documentation on this so it's tough to find a place to start. Here is the code I have right now. It would be much appreciated if you could let me know what I am doing wrong. Thank you

library(shiny)
library(devtools)
library(quantmod)
library(quantstrat)
library(TTR)
library(png)
library(dplyr)
Sys.setenv(TZ = "UTC")
currency('USD')

ui <- fluidPage(

# Application title
titlePanel("myfirst"),


sidebarLayout(
  sidebarPanel(
    selectInput(
     "stocks", label = "chose stock", choices = 
      c("AAPL", "CAT")
    ),
    dateInput("init_date", "chose init date", 
     value = Sys.Date() -100),
    dateInput("start_date", "chose start date", 
     value = Sys.Date() - 99),
    dateInput("end_date", "chose end date", 
     value = Sys.Date()),
    selectInput("init_equity", "starting 
    equity", choices = c(1000, 50000))
  ),


  mainPanel(
     plotOutput("plot"),
     textOutput("text")
  )
  )

  )

  server <- function(input, output) {
  init_date = reactive({
  input$init_date
   })
  start_date = reactive({
input$start_date
})
end_date = reactive({
input$end_date
 })
 init_equity = reactive({
  input$init_equity
 })

  V = reactive({
  getSymbols(input$stocks, from = start_date(), 
 to = end_date(), index.class = "POSIXct", 
adjust = T)
 })

 observe({
stock(input$stocks, currency = "USD", multiplier 
= 1)
   })

  portfolio.st = account.st = strategy.st = 
 "my.first"

 rm.strat(portfolio.st)
 rm.strat(account.st)

 observe({ 
   initPortf(name = portfolio.st,
        symbols = "V",
        initDate = init_date())
 initAcct(name = account.st,
         portfolios = portfolio.st,
         initDate = init_date(),
         initEq = init_equity())
 initOrders(portfolio = portfolio.st,
           symbols = "V",
           initDate = init_date()
           )
 strategy(strategy.st, store = T)


 })

observe({ add.indicator(strategy = strategy.st,
            name = "SMA",
            arguments = list(x = 
  quote(Cl(mktdata)), 
                             n = 10),
            label = "nFast")

add.indicator(strategy = strategy.st, 
              name = "SMA", 
              arguments = list(x = 
quote(Cl(mktdata)), 
                               n = 30), 
              label = "nSlow")

add.signal(strategy = strategy.st,
           name="sigCrossover",
           arguments = list(columns = c("nFast", "nSlow"),
                            relationship = "gte"),
           label = "long")
add.signal(strategy = strategy.st,
           name="sigCrossover",
           arguments = list(columns = c("nFast", "nSlow"),
                            relationship = "lt"),
           label = "short")
add.rule(strategy = strategy.st,
         name = "ruleSignal",
         arguments = list(sigcol = "long",
                          sigval = TRUE,
                          orderqty = 100,
                          ordertype = "stoplimit",
                          orderside = "long", 
                          threshold = 0.0005,
                          prefer = "High", 
                          TxnFees = -10, 
                          replace = FALSE),
         type = "enter",
         label = "EnterLONG")
add.rule(strategy.st,
         name = "ruleSignal",
         arguments = list(sigcol = "short",
                          sigval = TRUE,
                          orderqty = -100,
                          ordertype = "stoplimit",
                          threshold = -0.005, 
                          orderside = "short", 
                          replace = FALSE, 
                          TxnFees = -10, 
                          prefer = "Low"),
         type = "enter",
         label = "EnterSHORT")
add.rule(strategy.st, 
         name = "ruleSignal", 
         arguments = list(sigcol = "short", 
                          sigval = TRUE, 
                          orderside = "long", 
                          ordertype = "market", 
                          orderqty = "all", 
                          TxnFees = -10, 
                          replace = TRUE), 
         type = "exit", 
         label = "Exit2SHORT")
add.rule(strategy.st, 
         name = "ruleSignal", 
         arguments = list(sigcol = "long", 
                          sigval = TRUE, 
                          orderside = "short", 
                          ordertype = "market", 
                          orderqty = "all", 
                          TxnFees = -10, 
                          replace = TRUE), 
         type = "exit", 
         label = "Exit2LONG")
applyStrategy(strategy.st, portfolios = portfolio.st)
updatePortf(portfolio.st)
updateAcct(account.st)
updateEndEq(account.st)

})

 output$plot = reactive(
  chart.Posn(portfolio.st, Symbol = "V")
  )
 }

 # Run the application 
  shinyApp(ui = ui, server = server)

回答1:

Interesting idea. What you're trying to do is a little challenging due to the nature of how the market data for the instruments traded is stored in the local environment in variables that have names equal to their symbols/tickers.

Also, you're some peculiar things with your shiny app; be careful how you use reactive({, isolate({ and other server components. For instance when you have server objects like

start_date = reactive({
input$start_date
})` 

that are redundant.

Here is an example that does what you're trying to achieve. I've tried to keep the variable names consistent with your example where possible.

You may want to reconsider your workflow: I think you should run large batches of simulations in quantstrat independently of shiny, then save the results to disk. Then load these results from disk when you launch your Shiny app. Nevertheless this example will hopefully sort out any remaining confusion you have.

Also, you should be careful about how often you request data from yahoo via getSymbols. What I do below is request the data only once when the app is first launched, and store the market data in the symbols in an environment called rawdata. Then if you stop and restart your app again, you won't keep making requests to yahoo for data (which might give you errors when they throttle how much you can download within a period of time).


# Could put these in global.R, these global variables are "hard coded"  ----------------
min_date_barrier <- "2012-01-01"
max_date_barrier <- "2019-04-17"
stock_universe <- c("AAPL", "CAT", "BB")

# These variables won't change when the app launches, so hard code them too:

Sys.setenv(TZ = "UTC")
currency('USD')
stock(stock_universe, currency = "USD", multiplier = 1)

portfolio.st <- account.st <- strategy.st <- "my.first"

# In here, store the original market data which contains your full range of possible values for the market data:
# Don't keep requesting data frequently otherwise you won't be able to download the data temporarily.
if (!exists("rawdata")) {
    rawdata <- new.env()
    assign("rawdata", rawdata, envir = .GlobalEnv)

    lapply(stock_universe, function(sym) {
        # if (exists(sym, envir = rawdata)) {
        #     message("Have already downloaded data for ", sym)
        #     return()
        # } else {
            getSymbols(stock_universe,
                       env = rawdata,  # important to specify environment
                       from = min_date_barrier,
                       to = max_date_barrier,
                       adjust = T, auto.assign = TRUE)
        #}
        return()
    })

}

# UI ----------------------------------------------------------------------

ui <- fluidPage(

    # Application title
    titlePanel("myfirst"),


    sidebarLayout(
        sidebarPanel(
            selectInput(
                "stock", label = "Choose stock", choices = stock_universe
            ),

            dateInput("start_date", "Choose start date",
                      value = "2018-02-03"),
            dateInput("end_date", "Choose end date",
                      value = "2019-04-10"),
            selectInput("init_equity", "starting
    equity", choices = c(1000, 50000))
        ),


        mainPanel(
            plotOutput("plot_backtest"),
            verbatimTextOutput("results")
        )
    )

)



# server ------------------------------------------------------------------

server <- function(input, output, session) {

    # all your reactives don't make sense -- only use the inputs when you need them on the server side


    backtest_setup <- reactive({

        # need these input variables in this reactive to avoid bugs in the app when you change the time range:

        input$start_date
        input$end_date
        rm.strat(portfolio.st, silent = FALSE)
        initPortf(name = portfolio.st,
                  symbols = input$stock,        #------------------------ correct way to apply the "stock" input
                  initDate = "2000-01-01")
        initAcct(name = account.st,
                 portfolios = portfolio.st,
                 initDate = "2000-01-01",
                 initEq = as.numeric(input$init_equity)) # convert equity to numeric from string
        initOrders(portfolio = portfolio.st,
                   symbols = input$stock,  # ----------------------------------
                   initDate = "2000-01-01"
        )
        strategy(strategy.st, store = T)


        add.indicator(strategy = strategy.st,
                      name = "SMA",
                      arguments = list(x =
                                           quote(Cl(mktdata)),
                                       n = 10),
                      label = "nFast")

        add.indicator(strategy = strategy.st,
                      name = "SMA",
                      arguments = list(x =
                                           quote(Cl(mktdata)),
                                       n = 30),
                      label = "nSlow")

        add.signal(strategy = strategy.st,
                   name="sigCrossover",
                   arguments = list(columns = c("nFast", "nSlow"),
                                    relationship = "gte"),
                   label = "long")
        add.signal(strategy = strategy.st,
                   name="sigCrossover",
                   arguments = list(columns = c("nFast", "nSlow"),
                                    relationship = "lt"),
                   label = "short")
        add.rule(strategy = strategy.st,
                 name = "ruleSignal",
                 arguments = list(sigcol = "long",
                                  sigval = TRUE,
                                  orderqty = 100,
                                  ordertype = "stoplimit",
                                  orderside = "long",
                                  threshold = 0.0005,
                                  prefer = "High",
                                  TxnFees = -10,
                                  replace = FALSE),
                 type = "enter",
                 label = "EnterLONG")
        add.rule(strategy.st,
                 name = "ruleSignal",
                 arguments = list(sigcol = "short",
                                  sigval = TRUE,
                                  orderqty = -100,
                                  ordertype = "stoplimit",
                                  threshold = -0.005,
                                  orderside = "short",
                                  replace = FALSE,
                                  TxnFees = -10,
                                  prefer = "Low"),
                 type = "enter",
                 label = "EnterSHORT")
        add.rule(strategy.st,
                 name = "ruleSignal",
                 arguments = list(sigcol = "short",
                                  sigval = TRUE,
                                  orderside = "long",
                                  ordertype = "market",
                                  orderqty = "all",
                                  TxnFees = -10,
                                  replace = TRUE),
                 type = "exit",
                 label = "Exit2SHORT")
        add.rule(strategy.st,
                 name = "ruleSignal",
                 arguments = list(sigcol = "long",
                                  sigval = TRUE,
                                  orderside = "short",
                                  ordertype = "market",
                                  orderqty = "all",
                                  TxnFees = -10,
                                  replace = TRUE),
                 type = "exit",
                 label = "Exit2LONG")

    })

    V <- reactive({

        validate(need(input$start_date >= as.Date(min_date_barrier), "start date cannot be less than hard coded min_date_barrier"))
        validate(need(input$end_date <= as.Date(max_date_barrier), "end date cannot be greater than  hard coded max_date_barrier"))
        validate(need(as.Date(input$start_date) < as.Date(input$end_date), "start date must be less than end date."))
        # assign symbol market data to the global environment for the range of dates you want:
        time_rng <- paste0(input$start_date, "/", input$end_date)
        mdata <- get(input$stock, envir = rawdata)
        mdata <- mdata[time_rng]

        validate(need(NROW(mdata) > 0, "no data available, choose an appropriate time range"))

        mdata
    })

    backtest_results <- reactive({

        backtest_setup()
        mdata <- V()
        assign(input$stock, mdata, envir = .GlobalEnv)
        # not supplying mktdata as a parameter, so look in global environment for objects with the symbol names (which will exist because V assigns to .GlobalEnv):
        applyStrategy(strategy.st, portfolios = portfolio.st)
        # alternatively you could pass in the data directly to apply strategy if you're just using one symbol of data in the applyStrategy call, instead of having applyStrategy directly search in the .GlobalEnv for the symbol name
        #applyStrategy(strategy.st, portfolios = portfolio.st, mktdata = mdata)
        updatePortf(portfolio.st)
        updateAcct(account.st)
        updateEndEq(account.st)

    })

    output$plot_backtest = renderPlot({
        backtest_results()
        chart.Posn(portfolio.st, Symbol = input$stock)
    })

    output$results = renderPrint({
        backtest_setup()
        tmpdata <- V() # need this here so that any changes to the inputs will reprint the trade stats table
        print(tradeStats(portfolio.st))
    })

}

# Run the application
shinyApp(ui = ui, server = server)

The app will look something like this: