Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tradeGraphs will not render if more than two optimizing parameters are in a strategy #136

Open
QuantDevHacks opened this issue May 12, 2021 · 5 comments
Assignees

Comments

@QuantDevHacks
Copy link

Description

Ran a simple optimization of an MACD example using apply.paramset(.), with three paramsets: Fast MA, Slow MA, and Signal MA. When finished, I tried using tradeGraphs(.) on pairs of paramsets, but this results in an error saying Aggregation function missing: defaulting to length, and a plot showing a plane in the parameter space.

quantstrat version is 0.16.9.

Remark: I have verified that I can generate plots from tradeGraphs(.) in cases where there are exactly two paramsets in the model.

Expected behavior

Expected to be able to view plots of performance measures vs two paramsets chosen from the three that are used in the optimization.

Minimal, reproducible example

library(quantstrat)
symbol <- 'SPY'
currency("USD")
stock(symbol, currency="USD", multiplier=1)

initDate <- '2000-12-31'
startDate <- '2001-01-01'
endDate <- '2021-03-31'
initEq <- 1000000   # $1M
shs <- 500

Sys.setenv(TZ="UTC")
getSymbols(symbol, from=startDate, to=endDate, index.class="POSIXct", adjust=TRUE)
etfData <- get(symbol)

maType <- "EMA"  

stratName <- "macd.ema.opt"
portName <- "macd.ema.opt"
acctName <- "macd.ema.opt"
suppressWarnings(rm.strat(stratName)) # reset

# Distribution setup:
fastRange <- seq(10, 16, by=2)
slowRange <- seq(20, 32, by=4)
sigRange <- seq(5, 20, by=3)

initPortf(name=portName, symbols = symbol, initDate=initDate)
initAcct(name=acctName, portfolios=portName,
         initDate=initDate, initEq=initEq)
initOrders(portfolio=portName, initDate=initDate)
strategy(name = stratName, store=TRUE)

add.indicator(strategy = stratName, name = "MACD",
              arguments = list(x=quote(Cl(mktdata)), 
                               nFast = 0, nSlow = 0, nSig = 0),
              label='macd.osc')

add.signal(strategy = stratName, name="sigThreshold",
           arguments=list(column="signal.macd.osc",relationship="gt",threshold=0,cross=TRUE),
           label="signal.gt.zero")

add.signal(strategy = stratName, name="sigThreshold",
           arguments=list(column="signal.macd.osc",relationship="lt",threshold=0,cross=TRUE),
           label="signal.lt.zero")

add.rule(strategy = stratName, name='ruleSignal',
         arguments = list(sigcol="signal.gt.zero", sigval=TRUE, orderqty=shs,
                          ordertype='market',orderside='long'),
         type='enter',label='long_entry')   # ,storefun=FALSE)

add.rule(strategy = stratName, name='ruleSignal',
         arguments = list(sigcol="signal.lt.zero",sigval=TRUE,orderqty='all',
                          ordertype='market'),
         type='exit',label='long_exit')

add.distribution(strategy = stratName,
                 paramset.label = "macd.emas",
                 component.type = "indicator",
                 component.label = "macd.osc",
                 variable = list( nFast = fastRange),
                 label = "macd.fast")

add.distribution(strategy = stratName,
                 paramset.label = "macd.emas",
                 component.type = "indicator",
                 component.label = "macd.osc",
                 variable = list(nSlow = slowRange),
                 label = "macd.slow")

add.distribution(strategy = stratName,
                 paramset.label = "macd.emas",
                 component.type = "indicator",
                 component.label = "macd.osc",
                 variable = list(nSig = sigRange),
                 label = "macd.sig")

results <- apply.paramset(strategy.st = stratName, paramset.label = "macd.emas",
                            portfolio.st=portName, account.st=acctName, nsamples=0)

library(rgl)
library(reshape2)

tradeGraphs(stats = results$tradeStats, 
            free.params = c("macd.fast","macd.sig"),
            statistics = c("Profit.To.Max.Draw")) 

Session Info

Aggregation function missing: defaulting to length

image

@jaymon0703
Copy link
Collaborator

jaymon0703 commented May 12, 2021

Hi @QuantDevHacks. So whats happening here, is reshape2::recast calls dcast() which calls cast(), both of which have fun.aggregate=NULL for the default argument value. When there is any duplicate in the data being melted and cast in one step, the cast function uses fun.aggregate <- length. If you used values of the free.params which did not overlap the function would correctly sum the statistic of interest.

My thinking at this point is to add a fun.aggregate=sum argument to our call to reshape2::recast. It works, so i will push the change shortly, and bump the version one minor increment.

image

EDIT - on second thoughts...fun.aggregate=sum might not make sense for a statistic such as "Profit.To.Max.Draw", so passing it to the tradeGraphs() function definition might be necessary...since in some cases sum() makes sense, and in others perhaps mean() or median() makes more sense...

jaymon0703 added a commit that referenced this issue May 12, 2021
@jaymon0703
Copy link
Collaborator

jaymon0703 commented May 12, 2021

Ok tested my change locally...this is the output...

image

Would you mind testing @QuantDevHacks?

The code to install the version with the last commit is install_github("braverock/quantstrat", ref = "1e8dbbf").

The code i ran for the call to the newly defined tradeGraphs function is below...

tradeGraphs(stats = results$tradeStats, 
            free.params = c("macd.sig","macd.fast"),
            statistics = c("Profit.To.Max.Draw"),
            fun.aggregate=mean) 

@jaymon0703
Copy link
Collaborator

Apologies...i am too quick to type today...i think the optimal solution here is to not use recast() as we do not want any aggregating...this will require a bit more work...

@jaymon0703
Copy link
Collaborator

In the spirit of typing too fast, i think we do need to aggregate where there are duplicate column combinations...for example there are multiple observations in which macd.fast and macd.sig are equivalent...so how would we like to present that information...presumably mean or median makes sense.

@jaymon0703
Copy link
Collaborator

jaymon0703 commented May 13, 2021

Ok @QuantDevHacks i have updated the default to use the mean as the aggregation function. This is necessary for scenarios in which the param search covers more than 2 params. I think the mean makes sense. Appreciate your thoughts, and testing the latest commit? You can use install_github("braverock/quantstrat", ref = "f73c3c7").

Using your original example, please note you can benefit from parallel computation for the param search if you add the below 2 lines:

library(doParallel)
registerDoParallel()

And because we set the default to mean there is no need to specify it in the call to tradeGraphs():

tradeGraphs(stats = results$tradeStats, 
            free.params = c("macd.sig","macd.fast"),
            statistics = c("Profit.To.Max.Draw"))

Appreciate your feedback.

EDIT: With your permission i may add your example as a demo...

@jaymon0703 jaymon0703 self-assigned this May 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants