R/txnsim.R

Defines functions print.txnsim summary.txnsim hist.txnsim quantile.txnsim plot.txnsim txnsim.portnames txnsim.portfs txnsim

Documented in hist.txnsim plot.txnsim print.txnsim quantile.txnsim summary.txnsim txnsim txnsim.portfs txnsim.portnames

#' Monte Carlo analysis of round turn trades
#'
#' Running simulations with similar properties as the backtest or production
#' portfolio may allow the analyst to evaluate the distribution of returns
#' possible with similar trading approaches and evaluate skill versus luck or
#' overfitting.
#'
#' @details
#'
#' Statisticians talk about the 'stylized facts' of a data set.  If you consider
#' the stylized facts of a series of transactions that are the output of a
#' discretionary or systematic trading strategy, it should be clear that there
#' is a lot of information available to work with.  Initial analysis such as
#' \code{\link{tradeStats}} and \code{\link{perTradeStats}} can describe the
#' results of the series of transactions which resulted from the trading
#' strategy.  What else can we learn from these transactions regarding trading
#' style or the skill of the trader? \code{txnsim} seeks to conduct a simulation
#' over the properties of sampled round turn trades to help evaluate this.
#'
#' With \code{tradeDef='flat.to.flat'}, the samples are simply rearranging
#' quantity and duration of round turns.  This may be enough for a strategy that
#' only puts on a single level per round turn.
#'
#' With \code{tradeDef='increased.to.reduced'}, typically used for more complex
#' strategies, the simulation is also significantly more complicated, especially
#' with \code{replacement=TRUE}.  In this latter case, the simulation must try
#' to retain stylized factos of the observed strategy, specifically:
#'
#' \itemize{
#'   \item{percent time in market}
#'   \item{percent time flat}
#'   \item{ratio of long to short position taking}
#'   \item{number of levels or layered trades observed}
#' }
#'
#' In order to do this, samples are taken and randomized for flat periods,
#' short periods, and long periods, and then these samples are interleaved and
#' layered to construct the random strategy.  The overall goal is to construct a
#' random strategy that preserves as many of the stylized facts (or style) of
#' the observed strategy as possible, while demonstrating no skill.  The round
#' turn trades of the random replicate strategies, while outwardly resembling
#' the original strategy in summary time series statistics, are the result of
#' random combinations of observed features taking place at random times in the
#' tested time period.
#'
#' It should be noted that the first opened trade of the observed series and the
#' replicates will take place at the same time.  Quantity and duration may differ,
#' but the trade will start at the same time, unless the first sampled period is
#' a flat one.  We may choose to relax this in the future and add or subtract a
#' short amount of duration to the replicates to randomize the first entry more
#' fully as well.  Inclusion of flat periods should provide a fair amount of
#' variation, so this may not be an issue.
#'
#' The user may wish to pass \code{Interval} in dots to mark the portfolio at a
#' different frequency than the market data, especially for intraday market
#' data.  Note that market data must be available to call
#' \code{\link{updatePortf}} on.
#'
#' We are including p-values for some sample statistics in the output as well.
#' Some notes are in order on how this is calculatated, and how it may be
#' interpreted. With small \code{n}, these p-values are meaningless.  With large
#' \code{n}, they should be fairly stable.  Per North et. al. (2002) who use
#' Davison & Hinkley (1997) as their source, the correct unbiased p-value for a
#' simulation sample statistic is:
#'
#' \deqn{ \frac{rank_bt+1}{n_samples+1} }{rank+1/n+1}
#'
#' where the rank of the observed statistic is compared against statistics
#' calculated on the simulation. Interpretation of this result takes some care.
#' The skeptical analyst would prefer to see a low p-value (e.g. the customary
#' 0.05). The same analyst should be concerned about overfitting if an
#' extraordinarily low p-value (e.g. 0.0001) is observed, or conclude that there
#' may be room to improve the strategy is available if the p-value is low but not
#' compelling (e.g. 0.15).  Issues of multiple testing bias should also be
#' considered.  Interpretation of the p-value of the mean is most easily fit into
#' the customary p<0.05 target.  Appropriate critical values for other statistics
#' may be lower or higher.
#'
#' @param Portfolio string identifying a portfolio
#' @param n number of simulations, default = 100
#' @param replacement sample with or without replacement, default TRUE
#' @param tradeDef string to determine which definition of 'trade' to use. See \code{\link{tradeStats}}
#' @param \dots any other passthrough parameters
#' @param CI numeric specifying desired Confidence Interval used in hist.txnsim(), default 0.95
#'
#' @return a list object of class 'txnsim' containing:
#' \itemize{
#'   \item{\code{replicates}:}{a list by symbol containing all the resampled start,quantity, duration time series replicates}
#'   \item{\code{transactions}:}{a list by symbol for each replicate of the Txn object passed to \code{\link{addTxns}}}
#'   \item{\code{backtest.trades}:}{list by symbol containing trade start, quantity, duration from the original backtest}
#'   \item{\code{cumpl}:}{an \code{xts} object containing the cumulative P&L of each replicate portfolio}
#'   \item{\code{initEq}:}{a numeric variable containing the initEq of the portfolio, for starting portfolio value}
#'   \item{\code{seed}:}{ the value of \code{.Random.seed} for replication, if required}
#'   \item{\code{call}:}{an object of type \code{call} that contains the \code{txnsim} call}
#'   \item{\code{Portfolio}:}{ string identifying a portfolio}
#'   \item{\code{n}:}{ number of simulations, default = 100}
#'   \item{\code{replacement}:}{ sample with or without replacement, default TRUE}
#'   \item{\code{samplestats}:}{a numeric dataframe of various statistics for each replicate series}
#'   \item{\code{original}:}{a numeric dataframe of the statistics for the original series}
#'   \item{\code{ranks}:}{a numeric dataframe containing the ranking of the statistics}
#'   \item{\code{pvalues}:}{a numeric dataframe containing the pvalues for the observed backtest compared to the sampled ranks }
#'   \item{\code{stderror}:}{a numeric dataframe of the standard error of the statistics for the replicates}
#'   \item{\code{CI}:}{numeric specifying desired Confidence Interval used in hist.txnsim(), default 0.95}
#'   \item{\code{CIdf}:}{a numeric dataframe of the Confidence Intervals of the statistics for the bootstrapped replicates}
#' }
#'
#' Note that this object and its slots may change in the future.
#' Slots \code{replicates},\code{transactions}, and \code{call} are likely
#' to exist in all future versions of this function, but other slots may be added
#' and removed as \code{S3method}'s are developed.
#'
#' The \code{backtest.trades} object contains the stylized facts of the observed
#' series, and consists of a list with one slot per instrument in the input
#' portfolio.  Each slot in that list contains a \code{data.frame} of
#' \itemize{
#'   \item{\code{Start}:}{timestamp of the start of the round turn, discarded later}
#'   \item{\code{duration}:}{duration (difference from beginning ot end) of the observed round turn trade}
#'   \item{\code{quantity}:}{quantity of the round turn trade, or 0 for flat periods}
#' }
#'
#' with additional attributes for the observed stylized facts:
#'
#' \itemize{
#'   \item{\code{calendar.duration}:}{total length/duration of the observed series}
#'   \item{\code{trade.duration}:}{total length/durtation used by round turn trades }
#'   \item{\code{flat.duration}:}{aggregate length/duration of periods when observed series was flat}
#'   \item{\code{flat.stddev}:}{standard deviation of the duration of individual flat periods}
#'   \item{\code{first.start}:}{timestamp of the start of the first trade, to avoid starting simulations during a training period}
#'   \item{\code{period}:}{periodicity of the observed series}
#' }
#'
#'
#' @author Jasen Mackie, Brian G. Peterson
#' @references
#' Burns, Patrick. 2006. Random Portfolios for Evaluating Trading Strategies. http://papers.ssrn.com/sol3/papers.cfm?abstract_id=881735
#'
#' North, B.V., D. Curtis, and P.C. Sham. Aug 2002. A Note on the Calculation of Empirical P Values from Monte Carlo Procedures. Am J Hum Genet. 2002 Aug; 71(2): 439-441. https://www.ncbi.nlm.nih.gov/pmc/articles/PMC379178/
#'
#' Davison & Hinkley. 1997. Bootstrap methods and their application.
#'
#' @seealso \code{\link{mcsim}}, \code{\link{updatePortf}} , \code{\link{perTradeStats}}, \code{\link{hist.txnsim}}, \code{\link{quantile.txnsim}}
#' @examples
#' \dontrun{
#'
#'  n <- 10
#'
#'  ex.txnsim <- function(Portfolio
#'                         ,n=10
#'                         ,replacement=FALSE
#'                         , tradeDef='increased.to.reduced'
#'                         , chart=FALSE
#'                         )
#'  {
#'     out <- txnsim(Portfolio,n,replacement, tradeDef = tradeDef)
#'     if(isTRUE(chart)) {
#'       portnames <- blotter:::txnsim.portnames(Portfolio, replacement, n)
#'       for (i in 1:n){
#'       p<- portnames[i]
#'         symbols<-names(getPortfolio(p)$symbols)
#'       for(symbol in symbols) {
#'         dev.new()
#'     	   chart.Posn(p,symbol)
#'       }
#'     }
#'   }
#'	 invisible(out)
#'  } # end ex.txnsim
#'
#'   demo('longtrend',ask=FALSE)
#'   lt.nr <- ex.txnsim('longtrend',n, replacement = FALSE)
#'   lt.wr <- ex.txnsim('longtrend',n, replacement = TRUE, chart = TRUE)
#'   plot(lt.wr)
#'   hist(lt.wr)
#'
#'   require('quantstrat') #sorry for the circular dependency
#'   demo('bbands',ask=FALSE)
#'   bb.nr <- ex.txnsim('bbands',n, replacement = FALSE)
#'   bb.wr <- ex.txnsim('bbands',n, replacement = TRUE, chart = TRUE)
#'   plot(rsi.wr)
#'   hist(bb.wr)
#'
#' } #end dontrun
#'
#' @export
txnsim <- function(Portfolio,
                   n = 10,
                   replacement = TRUE,
                   tradeDef = c('increased.to.reduced', 'flat.to.flat', 'flat.to.reduced'),
                   ...,
                   CI = 0.95)
{
  # befor doing anything inside the function which would affect the state,
  # store the current random seed for later replication, if needed
  seed <- .GlobalEnv$.Random.seed
  
  # use the first tradeDef
  tradeDef <- tradeDef[1]
  
  # First get strategy start dates, duration and quantity
  # get portfolio, account and symbols objects
  p <- getPortfolio(Portfolio)
  symbols <- names(p$symbols)
  initDate <- attr(p, "initDate")
  currency <- attr(p, "currency")
  initEq   <- attr(p, "initEq")
  
  
  txnstruct <- function(i) {
    pt <- perTradeStats(Portfolio, symbols[i], tradeDef = tradeDef, includeFlatPeriods = TRUE)
    
    nonflat <- which(pt$Init.Qty!=0)
    
    # get duration of non-flat periods
    tradeduration <- sum(pt$duration[nonflat])
    
    # get duration and standard deviation of flat periods
    zeroduration  <- sum(pt$duration[-nonflat])
    zerostddev    <- sd(pt$duration[-nonflat])
    
    # calendar duration of the entire strategy
    stratduration <- difftime(last(pt$End[nonflat]), pt$Start[1], units = "secs")
    
    # get max long and short positions
    if(any(pt$Max.Pos > 0) == TRUE){
      maxlongpos <- max(pt$Max.Pos)
    }
    if(any(pt$Max.Pos < 0) == TRUE){
      maxshortpos <- min(pt$Max.Pos) # maxshortpos implies maximum absolute short position
    }
    
    cum_txn_qty <- cumsum(getTxns(Portfolio, symbols[i])$Txn.Qty)
    startdiff <- merge(cum_txn_qty[-1], diff(index(cum_txn_qty)), diff(cum_txn_qty)[-1])
    colnames(startdiff) <- c("cumsum_qty", "days_diff", "lag_cumsum_qty")
    
    # Get duration difference between long start times and short start times
    # We will sample from these when building our replicate layers, however we should
    # only sample from differences in the same flat.to.flat continuous layer of
    # long or short positions.
    if(any(pt$Init.Qty > 0) == TRUE){
      # longstartdiff <- diff(pt$Start[-which(pt$Init.Qty == 0)])
      # tmp.pt.Init.Qty <- pt$Init.Qty[-which(pt$Init.Qty == 0)]
      # signlag <- c(tmp.pt.Init.Qty[-1], 0)
      # idx <- sign(signlag * tmp.pt.Init.Qty)
      # idx <- ifelse(tmp.pt.Init.Qty < 0, -1, idx)
      # # cbind(longstartdiff/86400, tmp.pt.Init.Qty, signlag, idx) # inspect for debugging
      # idx <- which(idx > 0)
      # longstartdiff <- longstartdiff[idx]
      # longstartdiff <- longstartdiff[-which(longstartdiff == 0)] # get rid of zero period durations, dont want to sample those as they will break things downstream
      
      # new attempt at longstartdiff
      # cum_txn_qty <- cumsum(getTxns(Portfolio, symbols[i])$Txn.Qty)
      # startdiff <- merge(cum_txn_qty[-1], diff(index(cum_txn_qty)), diff(cum_txn_qty)[-1])
      # colnames(startdiff) <- c("cumsum_qty", "days_diff", "lag_cumsum_qty")
      # TODO: implement rem_lidx more neatly
      rem_lidx <- which(startdiff$cumsum_qty == 0 | lag(startdiff$cumsum_qty) == 0 | 
                          startdiff$cumsum_qty < 0 | startdiff$cumsum_qty == startdiff$lag_cumsum_qty | 
                          startdiff$lag_cumsum_qty < 0) # rem_lidx means "remove long index"
      longstartdiff <- coredata(startdiff$days_diff[-rem_lidx])
      
    }
    if(any(pt$Init.Qty < 0) == TRUE){
      # shortstartdiff <- diff(pt$Start[-which(pt$Init.Qty == 0)])
      # tmp.pt.Init.Qty <- pt$Init.Qty[-which(pt$Init.Qty == 0)]
      # signlag <- c(tmp.pt.Init.Qty[-1], 0)
      # idx <- sign(signlag * tmp.pt.Init.Qty)
      # idx <- ifelse(tmp.pt.Init.Qty > 0, -1, idx)
      # # cbind(shortstartdiff/86400, tmp.pt.Init.Qty, signlag, idx) # inspect for debugging
      # idx <- which(idx > 0)
      # shortstartdiff <- shortstartdiff[idx]
      # shortstartdiff <- shortstartdiff[-which(shortstartdiff == 0)] # get rid of zero period durations, dont want to sample those as they will break things downstream
      
      # TODO: implement rem_sidx more neatly
      rem_sidx <- which(startdiff$cumsum_qty == 0 | lag(startdiff$cumsum_qty) == 0 | 
                          startdiff$cumsum_qty > 0 | startdiff$cumsum_qty == startdiff$lag_cumsum_qty | 
                          startdiff$lag_cumsum_qty > 0) # rem_lidx means "remove long index"
      shortstartdiff <- coredata(startdiff$days_diff[-rem_sidx])
    }
    
    # build dataframe of start dates and durations
    txnsimdf <- data.frame(start    = pt$Start,
                           duration = pt$duration,
                           quantity = pt$Init.Qty)
    
    attr(txnsimdf,"calendar.duration") <- stratduration
    attr(txnsimdf,"trade.duration")    <- tradeduration
    attr(txnsimdf,"flat.duration")     <- zeroduration
    attr(txnsimdf,"flat.stddev")       <- zerostddev
    attr(txnsimdf,"first.start")       <- pt$Start[1]
    attr(txnsimdf,"period")            <- attr(pt,'trade.periodicity')
    
    longcheck <- try(missing(maxlongpos), silent = TRUE)
    shortcheck <- try(missing(maxshortpos), silent = TRUE)
    if (class(longcheck) != "try-error") attr(txnsimdf,"maxlongpos") <- maxlongpos
    if (class(shortcheck) != "try-error") attr(txnsimdf,"maxshortpos") <- maxshortpos
    
    longcheck <- try(missing(maxlongpos), silent = TRUE)
    shortcheck <- try(missing(maxshortpos), silent = TRUE)
    if (class(longcheck) != "try-error") attr(txnsimdf,"longstartdiff") <- longstartdiff
    if (class(shortcheck) != "try-error") attr(txnsimdf,"shortstartdiff") <- shortstartdiff
    
    txnsimdf
  }
  
  # create a list of perTradeStats outputs per symbol
  backtest.trades <- lapply(1:length(symbols), txnstruct)
  names(backtest.trades) <- symbols
  
  ################################################################
  # common utility functions
  
  # no replacement functions are common to all tradeDef methods
  
  # index expression for the replicate call, without replacement
  idxexpr.nr <- function(i, ...) {
    sample(nrow(backtest.trades[[i]]))
  }
  
  # inner function to build the replicate df
  repsgen.nr <- function(j, i, idx) {
    # build a vector of start times
    start <- first(backtest.trades[[i]]$start) +
      cumsum(as.numeric(backtest.trades[[i]]$duration[idx[[j]]]))
    # add the fist start time back in
    start <- c(first(backtest.trades[[i]]$start), start)
    # take off the last end time, since we won't put in a closing trade
    start <- start[-length(start)]
    x <- data.frame(start = start,
                    duration = backtest.trades[[i]]$duration[idx[[j]]],
                    quantity = backtest.trades[[i]]$quantity[idx[[j]]])
  } # end inner lapply function
  
  # outer function over the symbols
  symsample.nr <- function(i) {
    idx <- replicate(n, idxexpr.nr(i), simplify = FALSE)
    symreps <- lapply(1:length(idx), repsgen.nr, i, idx)
  }
  
  ################################################################
  ################################################################
  
  if (tradeDef == "flat.to.flat") {
    ### first set up functions for the lapply
    ## with replacement fns are different between flat.to.flat and other methods
    
    # index expression for the replicate call, with replacement
    idxexpr.wr <- function(i) {
      fudgefactor <- 1.1 # fudgefactor is added to size for sampling
      targetdur   <- sum(backtest.trades[[i]]$duration)
      avgdur      <- as.numeric(mean(backtest.trades[[i]]$duration))
      
      dur <- 0 # initialize duration counter
      tdf <- data.frame() #initialize output data.frame
      nsamples <- round(nrow(backtest.trades[[i]]) * fudgefactor, 0)
      while (dur < targetdur) {
        s <- sample(1:nrow(backtest.trades[[i]]), nsamples, replace = TRUE)
        sdf <-
          data.frame(duration = backtest.trades[[i]]$duration[s],
                     quantity = backtest.trades[[i]]$quantity[s])
        if (is.null(tdf$duration)) {
          tdf <- sdf
        } else {
          tdf <- rbind(tdf, sdf)
        }
        dur <- sum(tdf$duration)
        nsamples <- round(((targetdur - dur) / avgdur) * fudgefactor, 0)
        nsamples <- ifelse(nsamples == 0, 1, nsamples)
        # print(nsamples) # for debugging
        dur
      }
      # could truncate data frame here to correct total duration
      
      # the row which takes our duration over the target
      xsrow <- last(which(cumsum(as.numeric(tdf$duration)) < (targetdur))) + 1
      if (xsrow == nrow(tdf)) {
        # the last row sampled takes us over targetdur
        adjxsrow <- sum(tdf$duration) - targetdur
        tdf$duration[xsrow] <- tdf$duration[xsrow] - adjxsrow
      } else if (xsrow < nrow(tdf)) {
        # the last iteration of the while loop added more than one row
        # which took our duration over the target
        tdf <- tdf[-seq.int(xsrow + 1, nrow(tdf), 1), ]
        adjxsrow <- sum(tdf$duration) - targetdur
        tdf$duration[xsrow] <- tdf$duration[xsrow] - adjxsrow
      }
      # build a vector of start times
      start <- first(backtest.trades[[i]]$start) + cumsum(as.numeric(tdf$duration))
      # add the first start time back in
      start <- c(first(backtest.trades[[i]]$start), start)
      # take off the last end time, since we won't put in a closing trade
      start <- start[-length(start)]
      # add start column to tdf
      tdf$start <- start
      # rearrange columns for consistency
      tdf <- tdf[, c("start", "duration", "quantity")]
      #return the data frame
      tdf
    } # end idexpr.wr
    
    # outer function over the symbols
    symsample.wr <- function(i) {
      symreps <- replicate(n, idxexpr.wr(i), simplify = FALSE)
    }
    
    # now create the replication series
    if (isTRUE(replacement)) {
      reps <- lapply(1:length(symbols), symsample.wr)
    } else {
      reps <- lapply(1:length(symbols), symsample.nr)
    }
    names(reps) <- symbols
    
  } # end flat.to.flat
  
  if (tradeDef == "flat.to.reduced" |
      tradeDef == "increased.to.reduced") {
    ### first set up functions for the lapply
    ## with replacement fns are different to other methods
    
    # sample and layer trades, with replacement
    tradesample <- function(trades, replacement=TRUE) {
      
      # fudgefactor is added to size for sampling
      if (isTRUE(replacement)){
        fudgefactor <- 1.1 # fudgefactor is added to size for sampling
      } else {
        fudgefactor <- 1 # no factor needed
      }
      
      # stylized facts
      calendardur <- attr(trades, 'calendar.duration')
      totaldur    <- sum(trades$duration)
      avgdur      <- as.numeric(mean(trades$duration))
      traderows   <- which(trades$quantity != 0)
      longrows    <- which(trades$quantity  > 0)
      shortrows   <- which(trades$quantity  < 0)
      flatrows    <- which(trades$quantity == 0)
      flatdur     <- sum(trades[flatrows,'duration'])
      longdur     <- sum(trades[longrows,'duration'])
      shortdur    <- sum(trades[shortrows,'duration'])
      lsratio     <- as.numeric(longdur)/(as.numeric(longdur) + as.numeric(shortdur))
      if(!is.null(attr(trades, 'maxlongpos'))){
        maxlongpos <- attr(trades, 'maxlongpos')
      }
      if(!is.null(attr(trades, 'maxshortpos'))){
        maxshortpos <- attr(trades, 'maxshortpos')
      }
      if(!is.null(attr(trades, 'maxlongpos'))){
        longstartdiff <- attr(trades, 'longstartdiff')
      }
      if(!is.null(attr(trades, 'maxshortpos'))){
        shortstartdiff <- attr(trades, 'shortstartdiff')
      }
      
      
      subsample <- function(svector, targetdur, replacement=TRUE) {
        #`trades` already exists in function scope
        
        dur <- 0 # initialize duration counter
        tdf <- data.frame() #initialize output data.frame
        nsamples <- round(length(svector) * fudgefactor, 0)
        while (dur < targetdur) {
          s <- sample(svector, nsamples, replace = replacement)
          sdf <- data.frame(duration = trades[s,'duration'],
                            quantity = trades[s,'quantity'])
          if (is.null(tdf$duration)) {
            tdf <- sdf
          } else {
            tdf <- rbind(tdf, sdf)
          }
          dur <- sum(tdf$duration)
          nsamples <- round(((targetdur - dur) / avgdur) * fudgefactor, 0)
          nsamples <- ifelse(nsamples == 0, 1, nsamples)
          # print(nsamples) # for debugging
          dur
        }
        # could truncate data frame here to correct total duration
        # the row which takes our duration over the target
        xsrow <- last(which(cumsum(as.numeric(tdf$duration)) < (targetdur))) + 1
        if (xsrow == nrow(tdf)) {
          # the last row sampled takes us over targetdur
          adjxsrow <- sum(tdf$duration) - targetdur
          tdf$duration[xsrow] <- tdf$duration[xsrow] - adjxsrow
        } else if (xsrow < nrow(tdf)) {
          # the last iteration of the while loop added more than one row
          # which took our duration over the target
          tdf <- tdf[-seq.int(xsrow + 1, nrow(tdf), 1), ]
          adjxsrow <- sum(tdf$duration) - targetdur
          tdf$duration[xsrow] <- tdf$duration[xsrow] - adjxsrow
        }
        
        tdf  # return target data frame
      } # end subsample
      
      #sample long, short, flat periods
      if(flatdur > 0){
        flatdf  <- subsample(svector = flatrows, targetdur = flatdur)
      } else {
        flatdf <- NULL
      }
      if(longdur > 0){ # ie. there are long round turn trades in the strategy
        longdf  <- subsample(svector = longrows, targetdur = longdur)
      } else {
        longdf <- NULL
      }
      if(shortdur > 0){ # ie. there are short round turn trades in the strategy
        shortdf <- subsample(svector = shortrows, targetdur = shortdur)
      } else {
        shortdf <- NULL
      }
      #browser()
      # make the first layer
      # 1. start with flat periods
      firstlayer <- flatdf
      # 2. segment trades for first layer
      targetlongdur <- structure(round((calendardur-flatdur)*lsratio),units='secs',class='difftime')
      if(!is.null(longdf)){ # ie. there are long round turn trades in the strategy
        targetlongrow <- last(which(cumsum(as.numeric(longdf$duration))<targetlongdur))
        firstlayer    <- rbind(firstlayer,longdf[1:targetlongrow,])
      } else {
        targetlongrow <- 0
      }
      # firstlayer    <- rbind(firstlayer,longdf[1:targetlongrow,])
      if(!is.null(shortdf)){ # ie. there are short round turn trades in the strategy
        targetshortrow <- last( which( cumsum(as.numeric(shortdf$duration))<(calendardur-sum(firstlayer$duration)) ) )
        firstlayer     <- rbind(firstlayer,shortdf[1:targetshortrow,])
      } else {
        targetshortrow <- 0
      }
      firstlayer    <- firstlayer[sample(nrow(firstlayer),replace=FALSE),]
      # firstlayer should be just slightly longer than calendardur, we'll truncate later
      
      tdf <- firstlayer # establish target data.frame
      
      # build a vector of start times
      start <- first(trades$start) + cumsum(as.numeric(tdf$duration))
      # add the first start time back in
      start <- c(first(trades$start), start)
      # take off the last end time, since we won't put in a closing trade
      start <- start[-length(start)]
      # add start column to tdf
      tdf$start <- start
      # rearrange columns for consistency
      tdf <- tdf[, c("start", "duration", "quantity")]
      
      ###########
      # now build the extra layers
      # further layers need to respect flat periods, and long/short.
      # they can 'overlap' existing round turns, even sequential shorter round turns
      # in many cases the layer one simulated portfolios may put multiple
      # trades on in sequence, without intervening flat periods
      
      # in a previous version we looped according to the number of overlaps (num_overlaps),
      # but in this version, we will loop until our target duration is met, and we will use
      # num_overlaps purely to ascertain if layering is required
      # once we determine layering is required, we will separately determine whether long &/or
      # short layering is required
      num_overlaps <- ceiling(as.numeric(totaldur)/as.numeric(calendardur))
      
      if(num_overlaps>1){ # ie. total duration > calendar duration
        ###
        # construct a temporary frame of the first layer for reference
        # the rows are the round turns, not a full time series of transactions
        # construct the cumulative position
        # tmpdf <- tdf
        # construct a vector of end times:
        # tmpdf$end <- first(trades$start) + cumsum(as.numeric(tmpdf$duration))
        # tmpdf$lsi <- ifelse(tmpdf$quantity>0, 1, ifelse(tmpdf$quantity<0,-1,0))
        # split remaining longs and shorts by num_overlaps -1  ?
        
        # construct series of random starts
        period <- attr(trades,'period')
        timeseq <- seq.POSIXt( from = attr(trades,"first.start")
                               , to = period$end
                               , by = period$units
        )
        
        # get the range and number of rows remaining of long and short trades
        if(targetshortrow != 0 && targetshortrow < nrow(shortdf)){ # ie. there are short round turn trades in the strategy
          shortrange <- (targetshortrow+1):nrow(shortdf)
          nshort     <- length(shortrange)
        } else {
          shortrange <- 0
          nshort <- 0
        }
        # nshort     <- length(shortrange)
        if(targetlongrow != 0 && targetlongrow < nrow(longdf)){ # ie. there are long round turn trades in the strategy
          longrange  <- (targetlongrow+1):nrow(longdf)
          nlong      <- length(longrange)
        } else {
          longrange <- 0
          nlong <- 0
        }
        # nlong      <- length(longrange)
        
        timesample <- function(timeseq, n, nsample) {
          x <- NULL
          if(is.null(x)) x<-data.frame(sample(x = timeseq, size = nsample, replace = FALSE))
          x
        }
        layerdfs<-list()
        li <- longrange[1]
        si <- shortrange[1]
        #some challenges:
        #  - each slot in the ln.samples and sh.samples list contains a number
        #    of timestamps equal to the *total* number of desired long/short trades
        #  - we don't know how many trades should occure on each layer
        #  - we don't really know how the trades were overlapped in the original,
        #    just the stylized facts.
        #  - the timestamps may not line up with long/short periods
        #  - any 'valid' timestamps, when paired with a trade, may overlap the
        #    end of a non-flat period
        #
        # given these challenges, we still need to construct a target series
        #
        ############
        # proposed process
        ############
        # - loop over layers
        #    - loop over long/short timestamps
        #       - if timestamp occurs in a long/short period
        #            - get the next trade from longdf/shortdf
        #            - increment li/si so we don't duplicate trades
        #            - if timestamp + trade duration overlaps the next flat period, do we truncate?
        #       - else move to next timestamp
        #    - if we run out of trades, stop
        #    - if on last layer, and we still have trades, find places to put them
        #
        # I think this can construct our layers using the randomized start times
        
        # another challenge:
        #  - limiting layering to max long or short pos of the original strategy
        #
        # proposed process
        #
        # - copy the tdf dataframe to tmp_tdf which is simply every sampled txn in the first layer
        # - this first layer dataframe includes 0-qty periods with their durations
        # - the initial variables are start time, duration and quantity
        # - for each of the long and short layering, we follow a fairly methodical approach to 
        #    identifying the first trade in the respective long or short period
        # - because we could end up sampling multiple sequential long or short trades which are all candidates
        #    for layering, we need to keep track of their cumsum relative to the max pos of the long or short
        #    position in the original strategy
        # - we make use of the maxlongpos and maxshortpos stylized facts, stored earlier in our tradesample function
        #    which is only called when tradeDef = "flat.to.reduced" || "increased.to.reduced"
        # - this is because there is no layering in "flat.to.flat"
        #
        # back to tmp_tdf
        # - we add some new columns to tmp_tdf in order to identify which row is the start of a long or short trade,
        #    regardless of how many long or short trades there are in sequence
        # - we use 'lqty' and 'sqty' to identify all long qty and short quantity trades respectively
        # - we use 'ltrade' and 'strade' to assign integer "1" to all long and short trades respectively
        # - we use 'f.ltrade' and 'f.strade' to identify the first long trade and the first short trade in the sequence respectively
        # - when sampling a timestamp for layering, we will make sure that adding the respective quantity does
        #    not take our cumsum position over (under) that of the maxlongpos (maxshortpos) by tracking the cumsum
        #    in the same row as the first long or short trade in the sequence. If adding the new layered quantity takes
        #    us over (under), do nothing. Move on. If we will still be inside, add the sampled row with relevant start
        #    timestamp, duration and quantity. Later on (in the txnsim.txns helper function) we use this data to build
        #    transactions which we add to a portfolio.
        # - we use 'lcumsum' and 'scumsum' to store the cumsum of each long and short trade quantity respectively
        # - we only add to the layer if doing so does not take our cumsum over the maxpos observed in the original strategy
        
        tmp_tdf <- tdf # set up a temp dataframe based on tdf
        tmp_tdf$last.layered.start <- tmp_tdf$start # add sampled int (from long[short]startdiff) to this timestamp to determine start of next layered trade
        tmp_tdf$last.layered.end <- tmp_tdf$last.layered.start + as.numeric(tmp_tdf$duration)
        tmp_tdf$num_layers <- 1
        
        # cumsum sequential longs on firstlayer
        # browser()
        # if(targetlongrow > 0){ # ie. there are long trades in the strategy
          tmp_tdf$lqty <- tmp_tdf$quantity
          tmp_tdf$lqty[which(tmp_tdf$quantity < 0)] <- 0
          tmp_tdf$ltrade <- 0
          tmp_tdf$ltrade[-1] <- ifelse(diff(cumsum(tmp_tdf$lqty)) > 0, tmp_tdf$ltrade[-1] <- 1, 0)
          if(tmp_tdf$lqty[1] > 0) tmp_tdf$ltrade[1] <- 1 else tmp_tdf$ltrade[1] <- 0
          # tmp_tdf$f.ltrade <- 0
          # tmp_tdf$f.ltrade[-1] <- ifelse(diff(tmp_tdf$ltrade) > 0, tmp_tdf$f.ltrade[-1] <- 1, 0)
          # if(tmp_tdf$ltrade[1] == 1) tmp_tdf$f.ltrade[1] <- 1 else tmp_tdf$f.ltrade[1] <- 0
          tmp_tdf$lcumsum <- 0 # tmp_tdf$cumsum for monitoring the cumsum of layered quantity
          # lfirstindex <- which(tmp_tdf$f.ltrade == 1) # index of the first long trade in the sequence
          # lindex <- which(tmp_tdf$ltrade == 1) # index of all long trades
          # interval <- findInterval(lindex,lfirstindex) # count of all duplicates equates to long trades in sequence
          # for(cs in 1:(length(lfirstindex)-1)){
          #   tmp_tdf$lcumsum[lfirstindex[cs]] <- sum(tmp_tdf$lqty[lfirstindex[cs]:lindex[(first(which(interval > cs))-1)]])
          # }
          # tmp_tdf$lcumsum[lfirstindex[length(lfirstindex)]] <- sum(tmp_tdf$lqty[lfirstindex[cs+1]:last(lindex)])
          tmp_tdf$lcumsum <- tmp_tdf$lqty
        # }
        
        # cumsum sequential shorts on firstlayer
        # if(targetshortrow > 0){ #ie. there are short trades in the strategy
          tmp_tdf$sqty <- tmp_tdf$quantity
          tmp_tdf$sqty[which(tmp_tdf$quantity > 0)] <- 0
          tmp_tdf$strade <- 0
          tmp_tdf$strade[-1] <- ifelse(diff(cumsum(tmp_tdf$sqty)) < 0, tmp_tdf$strade[-1] <- 1, 0)
          if(tmp_tdf$sqty[1] < 0) tmp_tdf$strade[1] <- 1 else tmp_tdf$strade[1] <- 0
          # tmp_tdf$f.strade <- 0
          # tmp_tdf$f.strade[-1] <- ifelse(diff(tmp_tdf$strade) > 0, tmp_tdf$f.strade[-1] <- 1, 0)
          # if(tmp_tdf$strade[1] == 1) tmp_tdf$f.strade[1] <- 1 else tmp_tdf$f.strade[1] <- 0
          tmp_tdf$scumsum <- 0 # tmp_tdf$cumsum for monitoring the cumsum of layered quantity
          # sfirstindex <- which(tmp_tdf$f.strade == 1) # index of the first short trade in the sequence
          # sindex <- which(tmp_tdf$strade == 1) # index of all short trades
          # interval <- findInterval(sindex,sfirstindex) # count of all duplicates equates to short trades in sequence
          # for(cs in 1:(length(sfirstindex)-1)){
          #   tmp_tdf$scumsum[sfirstindex[cs]] <- sum(tmp_tdf$sqty[sfirstindex[cs]:sindex[(first(which(interval > cs))-1)]])
          # }
          # tmp_tdf$scumsum[sfirstindex[length(sfirstindex)]] <- sum(tmp_tdf$sqty[sfirstindex[cs+1]:last(sindex)])
          tmp_tdf$scumsum <- tmp_tdf$sqty
        # }
        
        cumlongdur <- sum(tmp_tdf$duration[which(tmp_tdf$quantity > 0)])
        cumshortdur <- sum(tmp_tdf$duration[which(tmp_tdf$quantity < 0)])
        wlc2 <- 0
        # Use a while loop to build layers until total duration matches x% of target
        # Longs while loop
        layer.trades <- NULL
        while(cumlongdur < (1 * longdur) && wlc2 <= 1000){
          li <- sample(longrange, 1) # sample another row from longrange for layering
          wlc2 <- wlc2 + 1
          newlayerbuffer <- sample(longstartdiff*86400,1) # longstartdiff is in days, and we need unit in secs
          newlayerqty <- longdf[li,'quantity']
          # sample from the last layered start timestamps, upon which we will add a new layer if maxpos 
          # does not constrain us. call it prevlayer.tn for "previous layer transaction" which it soon will be
          prevlayer.tn <- sample(tmp_tdf$last.layered.start[which(tmp_tdf$lqty > 0)], 1) # keep lqty zero for layered longs, instead put the qty in "qty"
          prevlayer.idx <- last(which(tmp_tdf$last.layered.start == prevlayer.tn)) # use last as we may have more than 1 layers starting on last.layered.start date, so we pick last observation to add next layer to
          newlayerstart <- prevlayer.tn + newlayerbuffer # start timestamp of new layer
          
          if(newlayerstart >= tmp_tdf$last.layered.end[prevlayer.idx]) { # new layer start is after our previous layer ends...go back and sample another layer start date
            next()
          }
          
          flayer.trade <- tdf[last(which(tdf$start<=newlayerstart)),] # the first layer trade start timestamp, sourced from the 'tdf' dataframe
          newlayerend <- newlayerstart + longdf[li,'duration']
          new_ftend <- tmp_tdf$last.layered.start[prevlayer.idx] + difftime(tmp_tdf$last.layered.end[prevlayer.idx], tmp_tdf$last.layered.start[prevlayer.idx], units = "secs") # first layer target end; we cannot overlap this end timestamp
          txnlongdur <- longdf[li,'duration']
          wlc <- 0 # initialize while loop counter
          
          # Next we use a bool "insertnew" variable to indicate whether or not we need to insert a new observation into tmp_tdf or we can use the 
          # existing observation to update cumsum quantity. Any layered trades extending beyond our furthest possible point for the firstlayer
          # trade, will not need a new row. Instead, we will increment the cumsum qty of the current row, as the current row and the layered trade
          # share the same end date (after truncation). However, if the layered does not extend beyong the prior layering trade end date, we insert
          # a new row, and track the cumsum of this row separately...including the quantity from the prior layer, only up until the end date of the
          # new row, which ends before the end date of the prior layering trade.
          
          insertnew <- TRUE
          if(tmp_tdf$lcumsum[prevlayer.idx] + newlayerqty <= maxlongpos) {
            # browser()
            while(newlayerend >= new_ftend){ # # we've gone over the duration, check the next trade
              wlc <- wlc + 1
              # check if next trade is also a long, if not then we truncate
              if (!is.na(tmp_tdf[prevlayer.idx+wlc,'quantity']) && tmp_tdf[prevlayer.idx+wlc,'quantity']>0){
                # check if next trade will take us over our max pos, if not then extend previous layer end date
                if (tmp_tdf$lcumsum[prevlayer.idx+wlc] + newlayerqty <= maxlongpos) { # TODO: potentially check how much qty we can trade before breaching max pos
                  new_ftend <- new_ftend + tmp_tdf[prevlayer.idx+wlc,'duration'] # extend previous layer end date
                  # check if new layer end date is before prior layer end date, if so then use full sampled duration as the duration of the new trade 
                  if(newlayerend < new_ftend){
                    txnlongdur <- longdf[li,'duration']
                    insertnew <- TRUE # we will insert a new row, and increment cumsum qty with this new observation
                    break() # we're good, move on to adding the new row
                    } # we're still over the duration, check the next trade by proceeding to next increment in While loop
                  
                  } else { # TODO: potentially check what duration we can trade before breaching max pos with newlayerqty
                    txnlongdur <- difftime(new_ftend, newlayerstart, units = "secs")
                    insertnew <- FALSE # we wont insert new row
                    tmp_tdf$last.layered.start[prevlayer.idx] <- newlayerstart # update last layered start with newest layer start timestamp
                    tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] + 1  # increment num_layers, mostly for debugging
                    tmp_tdf$lcumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$lcumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] + newlayerqty
                    
                    if(is.null(layer.trades)) {
                      layer.trades <- data.frame(start=newlayerstart,
                                                 duration = txnlongdur,
                                                 quantity = longdf[li,'quantity'])
                      } else {
                        layer.trades <- rbind(layer.trades,
                                              data.frame(start=newlayerstart,
                                                         duration = txnlongdur,
                                                         quantity = longdf[li,'quantity']))
                      }
                    li <- sample(longrange, 1) # sample another row from longrange for layering
                    break()
                  }
                
                } else { # the next trade is not a long, so we truncate 'txnlongdur' if there is no maxpos violation
                  
                  txnlongdur <- difftime(new_ftend, newlayerstart, units = "secs")
                  insertnew <- FALSE # we wont insert new row
                  tmp_tdf$last.layered.start[prevlayer.idx] <- newlayerstart # update last layered start with newest layer start timestamp
                  tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] + 1  # increment num_layers, mostly for debugging
                  tmp_tdf$lcumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$lcumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] + newlayerqty
                  
                  if(is.null(layer.trades)) {
                    layer.trades <- data.frame(start=newlayerstart,
                                               duration = txnlongdur,
                                               quantity = longdf[li,'quantity'])
                    } else {
                      layer.trades <- rbind(layer.trades,
                                            data.frame(start=newlayerstart,
                                                       duration = txnlongdur,
                                                       quantity = longdf[li,'quantity']))
                    }
                  
                  li <- sample(longrange, 1) # sample another row from longrange for layering
                  break()
                  
                }
            }
          }
            
            # newlayerend is before the end of our first layer end timestamp, so we need to add this new layer to the first
            # layer, truncate the first layer where the new layer ends, and create a new separate first layer trade with the
            # balance
            if(insertnew == TRUE){ # insertnew is NULL meaning we need to insert a new row since newlayerend is < ftend
              if(tmp_tdf$lcumsum[prevlayer.idx] + newlayerqty <= maxlongpos) {
                newrow <- as.data.frame(t(rep.int(0,12)))
                colnames(newrow) <- c("start","duration","quantity","last.layered.start","last.layered.end","num_layers","lqty","ltrade","lcumsum","sqty","strade","scumsum")
                newrow$start <- newlayerend
                newrow$duration <- difftime(new_ftend, newlayerend, units = "secs")
                newrow$quantity <- tmp_tdf$lcumsum[prevlayer.idx+wlc]
                newrow$last.layered.start <- newlayerend
                newrow$last.layered.end <- new_ftend
                newrow$num_layers <- 1
                newrow$lqty <- tmp_tdf$lcumsum[prevlayer.idx+wlc]
                newrow$ltrade <- 1
                # newrow$f.ltrade <- 0
                newrow$lcumsum <- tmp_tdf$lcumsum[prevlayer.idx+wlc]
                newrow$sqty <- 0
                newrow$strade <- 0
                # newrow$f.strade <- 0
                newrow$scumsum <- 0
  
                tmp_tdf$last.layered.start[prevlayer.idx] <- newlayerstart # update last layered start with newest layer start timestamp
                tmp_tdf$last.layered.end[prevlayer.idx+wlc] <- newlayerend
                tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc)] <- tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc)] + 1  # increment num_layers, mostly for debugging
                tmp_tdf$lcumsum[prevlayer.idx:(prevlayer.idx+wlc)] <- tmp_tdf$lcumsum[prevlayer.idx:(prevlayer.idx+wlc)] + newlayerqty
                tmp_tdf$duration[prevlayer.idx:(prevlayer.idx+wlc)] <- difftime(tmp_tdf$last.layered.end[prevlayer.idx:(prevlayer.idx+wlc)],tmp_tdf$start[prevlayer.idx:(prevlayer.idx+wlc)], units = "secs")
  
                tmp_tdf <- rbind(tmp_tdf, newrow)
                tmp_tdf <- tmp_tdf[order(tmp_tdf$start),]
                
                if(is.null(layer.trades)){
                  layer.trades <- data.frame(start=newlayerstart,
                                               duration = txnlongdur,
                                               quantity = longdf[li,'quantity'])
                  } else {
                    layer.trades <- rbind(layer.trades,
                                          data.frame(start=newlayerstart,
                                                     duration = txnlongdur,
                                                     quantity = longdf[li,'quantity']))
                  }
                
                li <- sample(longrange, 1)
                
                } else { # this trade will take us over max pos, set txnlongdur to zero and proceed to next sampled start time and duration
                  txnlongdur <- 0
                }
            }
          cumlongdur <- cumlongdur + txnlongdur
          # print(cumlongdur/86400)
          # print(wlc2)
        } # end long layering While loop

        
        # Shorts while loop
        wlc2 <- 0
        while(cumshortdur < (1 * shortdur) && wlc2 <= 1000){
          # browser()
          si <- sample(shortrange, 1) # sample another row from shortrange for layering
          wlc2 <- wlc2 + 1
          newlayerbuffer <- sample(shortstartdiff*86400,1) # shortstartdiff is in days, and we need unit in secs
          newlayerqty <- shortdf[si,'quantity']
          # sample from the last layered start timestamps, upon which we will add a new layer if maxpos 
          # does not constrain us. call it prevlayer.tn for "previous layer transaction" which it soon will be
          prevlayer.tn <- sample(tmp_tdf$last.layered.start[which(tmp_tdf$sqty < 0)], 1) # keep lqty zero for layered longs, instead put the qty in "qty"
          prevlayer.idx <- last(which(tmp_tdf$last.layered.start == prevlayer.tn)) # use last as we may have more than 1 layers starting on last.layered.start date, so we pick last observation to add next layer to
          newlayerstart <- prevlayer.tn + newlayerbuffer # start timestamp of new layer
          
          if(newlayerstart >= tmp_tdf$last.layered.end[prevlayer.idx]) { # new layer start is after our previous layer ends...go back and sample another layer start date
            next()
          }
          
          flayer.trade <- tdf[last(which(tdf$start<=newlayerstart)),] # the first layer trade start timestamp, sourced from the 'tdf' dataframe
          newlayerend <- newlayerstart + shortdf[si,'duration']
          new_ftend <- tmp_tdf$last.layered.start[prevlayer.idx] + difftime(tmp_tdf$last.layered.end[prevlayer.idx], tmp_tdf$last.layered.start[prevlayer.idx], units = "secs") # first layer target end; we cannot overlap this end timestamp
          txnshortdur <- shortdf[si,'duration']
          wlc <- 0 # initialize while loop counter  
          
          # Next we use a bool "insertnew" variable to indicate whether or not we need to insert a new observation into tmp_tdf or we can use the 
          # existing observation to update cumsum quantity. Any layered trades extending beyond our furthest possible point for the firstlayer
          # trade, will not need a new row. Instead, we will increment the cumsum qty of the current row, as the current row and the layered trade
          # share the same end date (after truncation). However, if the layered does not extend beyong the prior layering trade end date, we insert
          # a new row, and track the cumsum of this row separately...including the quantity from the prior layer, only up until the end date of the
          # new row, which ends before the end date of the prior layering trade.
          
          insertnew <- TRUE
          if(tmp_tdf$scumsum[prevlayer.idx] + newlayerqty >= maxshortpos) {
            # browser()
            while(newlayerend >= new_ftend){ # # we've gone over the duration, check the next trade
              wlc <- wlc + 1
              # check if next trade is also a short, if not then we truncate
              if (!is.na(tmp_tdf[prevlayer.idx+wlc,'quantity']) && tmp_tdf[prevlayer.idx+wlc,'quantity']<0){
                # check if next trade will take us over our max pos, if not then extend previous layer end date
                if (tmp_tdf$scumsum[prevlayer.idx+wlc] + newlayerqty >= maxshortpos) { # TODO: potentially check how much qty we can trade before breaching max pos
                  new_ftend <- new_ftend + tmp_tdf[prevlayer.idx+wlc,'duration'] # extend previous layer end date
                  # check if new layer end date is before prior layer end date, if so then use full sampled duration as the duration of the new trade 
                  if(newlayerend < new_ftend){
                    txnshortdur <- shortdf[si,'duration']
                    insertnew <- TRUE # we will insert a new row, and increment cumsum qty with this new observation
                    break() # we're good, move on to adding the new row
                  } # we're still over the duration, check the next trade by proceeding to next increment in While loop
                  
                } else { # TODO: potentially check what duration we can trade before breaching max pos with newlayerqty
                  txnshortdur <- difftime(new_ftend, newlayerstart, units = "secs")
                  insertnew <- FALSE # we wont insert new row
                  tmp_tdf$last.layered.start[prevlayer.idx] <- newlayerstart # update last layered start with newest layer start timestamp
                  tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] + 1  # increment num_layers, mostly for debugging
                  tmp_tdf$scumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$scumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] + newlayerqty
                  
                  if(is.null(layer.trades)) {
                    layer.trades <- data.frame(start=newlayerstart,
                                               duration = txnshortdur,
                                               quantity = shortdf[si,'quantity'])
                  } else {
                    layer.trades <- rbind(layer.trades,
                                          data.frame(start=newlayerstart,
                                                     duration = txnshortdur,
                                                     quantity = shortdf[si,'quantity']))
                  }
                  si <- sample(shortrange, 1) # sample another row from shortrange for layering
                  break()
                }
                
              } else { # the next trade is not a short, so we truncate 'txnshortdur' if there is no maxpos violation
                
                txnshortdur <- difftime(new_ftend, newlayerstart, units = "secs")
                insertnew <- FALSE # we wont insert new row
                tmp_tdf$last.layered.start[prevlayer.idx] <- newlayerstart # update last layered start with newest layer start timestamp
                tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc-1)] + 1  # increment num_layers, mostly for debugging
                tmp_tdf$scumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] <- tmp_tdf$scumsum[prevlayer.idx:(prevlayer.idx+wlc-1)] + newlayerqty
                
                if(is.null(layer.trades)) {
                  layer.trades <- data.frame(start=newlayerstart,
                                             duration = txnshortdur,
                                             quantity = shortdf[si,'quantity'])
                } else {
                  layer.trades <- rbind(layer.trades,
                                        data.frame(start=newlayerstart,
                                                   duration = txnshortdur,
                                                   quantity = shortdf[si,'quantity']))
                }
                
                si <- sample(shortrange, 1) # sample another row from shortrange for layering
                break()
                
              }
            }
          }
          
          # newlayerend is before the end of our first layer end timestamp, so we need to add this new layer to the first
          # layer, truncate the first layer where the new layer ends, and create a new separate first layer trade with the
          # balance
          if(insertnew == TRUE){ # insertnew is NULL meaning we need to insert a new row since newlayerend is < ftend
            if(tmp_tdf$scumsum[prevlayer.idx] + newlayerqty >= maxshortpos) {
              newrow <- as.data.frame(t(rep.int(0,12)))
              colnames(newrow) <- c("start","duration","quantity","last.layered.start","last.layered.end","num_layers","lqty","ltrade","lcumsum","sqty","strade","scumsum")
              newrow$start <- newlayerend
              newrow$duration <- difftime(new_ftend, newlayerend, units = "secs")
              newrow$quantity <- tmp_tdf$scumsum[prevlayer.idx+wlc]
              newrow$last.layered.start <- newlayerend
              newrow$last.layered.end <- new_ftend
              newrow$num_layers <- 1
              newrow$lqty <- 0
              newrow$ltrade <- 0
              # newrow$f.ltrade <- 0
              newrow$lcumsum <- 0
              newrow$sqty <- tmp_tdf$scumsum[prevlayer.idx+wlc]
              newrow$strade <- 1
              # newrow$f.strade <- 0
              newrow$scumsum <- tmp_tdf$scumsum[prevlayer.idx+wlc]
              
              tmp_tdf$last.layered.start[prevlayer.idx] <- newlayerstart # update last layered start with newest layer start timestamp
              tmp_tdf$last.layered.end[prevlayer.idx+wlc] <- newlayerend
              tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc)] <- tmp_tdf$num_layers[prevlayer.idx:(prevlayer.idx+wlc)] + 1  # increment num_layers, mostly for debugging
              tmp_tdf$scumsum[prevlayer.idx:(prevlayer.idx+wlc)] <- tmp_tdf$scumsum[prevlayer.idx:(prevlayer.idx+wlc)] + newlayerqty
              tmp_tdf$duration[prevlayer.idx:(prevlayer.idx+wlc)] <- difftime(tmp_tdf$last.layered.end[prevlayer.idx:(prevlayer.idx+wlc)],tmp_tdf$start[prevlayer.idx:(prevlayer.idx+wlc)], units = "secs")
              
              tmp_tdf <- rbind(tmp_tdf, newrow)
              tmp_tdf <- tmp_tdf[order(tmp_tdf$start),]
              
              if(is.null(layer.trades)){
                layer.trades <- data.frame(start=newlayerstart,
                                           duration = txnshortdur,
                                           quantity = shortdf[si,'quantity'])
              } else {
                layer.trades <- rbind(layer.trades,
                                      data.frame(start=newlayerstart,
                                                 duration = txnshortdur,
                                                 quantity = shortdf[si,'quantity']))
              }
              
              si <- sample(shortrange, 1)
              
            } else { # this trade will take us over max pos, set txnlongdur to zero and proceed to next sampled start time and duration
              txnshortdur <- 0
            }
          }
          cumshortdur <- cumshortdur + txnshortdur
          # print(cumlongdur/86400)
          # print(wlc2)
        } # end short layering While loop

        #now store the result
        layerdfs <- layer.trades
        # layerdfs<-do.call(rbind,layerdfs)
        tdf <- rbind(tdf,layerdfs)
        # @TODO
        # double check that all long and short trades have been allocated, do something if not
        # ??? test for percentage of trades at each layer, and adjust accordingly ???
        # ??? test for maximum position? ???
      }
      #return the data frame
      tdf
    } # end tradesample.wr inner fn
    
    # outer function over the symbols
    symsample <- function(i) {
      symreps <- replicate(n, tradesample(trades=backtest.trades[[i]], replacement=replacement), simplify = FALSE)
    }
    reps <- lapply(1:length(symbols), symsample )
    names(reps) <- symbols
    
  } # end flat.to.reduced/increased.to.reduced method
  
  ################################################################
  # reps now exists as a list of structure reps[[symbol]][[rep]]
  # each rep has columns start, duration, quantity
  
  # ####################
  # # Generate Transactions
  # create the portfolios
  portnames <- txnsim.portnames(Portfolio, replacement, n)
  txnsim.portfs( Portfolio=Portfolio
                 , replacement=replacement
                 , n=length(reps[[1]])
                 , symbols=symbols
                 , initDate=initDate
                 , currency=currency
                 , initEq=initEq
                 , ...
  )
  
  # create the transactions
  ltxn <- txnsim.txns( reps = reps
                       , Portfolio=Portfolio
                       , replacement=replacement
                       , n=length(reps[[1]])
                       , ...
  )
  
  cumpl<-NULL
  perpl<-NULL
  for (i in seq_along(reps[[1]])) {
    # update the simulated portfolio
    p <- portnames[i]
    updatePortf(Portfolio = p, ...)
    # construct the cumulative P&L slot
    if (is.null(cumpl)) {
      perpl <- .getPortfolio(p)$summary$Net.Trading.PL[-1]
      cumpl <- cumsum(.getPortfolio(p)$summary$Net.Trading.PL[-1])
    } else {
      perpl <- cbind(perpl, .getPortfolio(p)$summary$Net.Trading.PL[-1])
      cumpl <- cbind(cumpl, cumsum(getPortfolio(p)$summary$Net.Trading.PL[-1]))
    }
  }
  
  colnames(perpl) <- portnames
  colnames(cumpl) <- portnames
  
  # add the observed portfolio here for comparison
  backtestperpl <- .getPortfolio(Portfolio)$summary$Net.Trading.PL[-1]
  colnames(backtestperpl) <- Portfolio
  backtestpl <- cumsum(backtestperpl)
  cumpl <- cbind(backtestpl,cumpl)
  perpl <- cbind(backtestperpl,perpl)
  
  if(any(is.na(cumpl))){
    cumpl <- cumpl[-which(complete.cases(cumpl) == FALSE),] # subset away rows with NA, needed for confidence intervals, quantiles
  }
  
  # compute sample stats
  sampleoutput <- data.frame(matrix(nrow = n+1, ncol = 6))
  colnames(sampleoutput) <- c("mean","median","stddev","maxDD","sharpe","totalPL")
  sampleoutput$mean    <- apply(perpl, 2, function(x) { mean(x, na.rm=TRUE) } )
  sampleoutput$median  <- apply(perpl, 2, function(x) { median(x, na.rm=TRUE) } )
  sampleoutput$stddev  <- apply(perpl, 2, function(x) { StdDev(x) } )
  sampleoutput$maxDD   <- apply(perpl, 2, function(x) { -max(cummax(cumsum(na.omit(x)))-cumsum(na.omit(x))) } )
  sampleoutput$sharpe  <- apply(perpl, 2, function(x) { mean(x, na.rm=TRUE)/StdDev(x) } )
  sampleoutput$totalPL <- apply(perpl, 2, function(x) { sum(na.omit(x)) } )
  rownames(sampleoutput)<-colnames(perpl)
  
  # store stats for use later in hist.mcsim and summary.mcsim
  original <- sampleoutput[1,]
  
  
  # compute p-values
  ranks <- apply(-sampleoutput,2,rank)
  ranks[,3] <- rank(sampleoutput[,3])
  # correct calc for unbiased p-value is rank+1/nsamples+1
  # where rank is rank of the sample statistic of the observation vs. samples
  # we've included the observed series in the sample, so the correct calc
  # is rank/nsamples
  pvalues <- ranks[1,]/nrow(ranks)
  sigd    <- nchar(n+1)
  pvalues <- round(pvalues, digits=sigd )
  
  # Compute standard errors of the sample stats
  stderror <- data.frame(matrix(nrow = 1, ncol = 6))
  colnames(stderror) <- c("mean","median","stddev","maxDD","sharpe","totalPL")
  row.names(stderror) <- "Std. Error"
  stderror$mean <- StdDev(sampleoutput[,1])
  stderror$median <- StdDev(sampleoutput[,2])
  stderror$stddev <- StdDev(sampleoutput[,3])
  stderror$maxDD <- StdDev(sampleoutput[,4])
  stderror$sharpe <- StdDev(sampleoutput[,5])
  stderror$totalPL <- StdDev(sampleoutput[,6])
  
  # Compute Confidence Intervals, but first add the CI functions
  CI_lower <- function(samplemean, merr) {
    #out <- original - bias - merr #based on boot package implementation in norm.ci
    out <- samplemean - merr #more generic implementation
    out
  }
  CI_upper <- function(samplemean, merr) {
    #out <- original - bias + merr #based on boot package implementation in norm.ci
    out <- samplemean + merr #more generic implementation
    out
  }
  CI_mean <- cbind(CI_lower(mean(sampleoutput[,1]), StdDev(sampleoutput[,1])*qnorm((1+CI)/2)),
                   CI_upper(mean(sampleoutput[,1]), StdDev(sampleoutput[,1])*qnorm((1+CI)/2)))
  
  CI_median <- cbind(CI_lower(mean(sampleoutput[,2]), StdDev(sampleoutput[,2])*qnorm((1+CI)/2)),
                     CI_upper(mean(sampleoutput[,2]), StdDev(sampleoutput[,2])*qnorm((1+CI)/2)))
  
  CI_stddev <- cbind(CI_lower(mean(sampleoutput[,3]), StdDev(sampleoutput[,3])*qnorm((1+CI)/2)),
                     CI_upper(mean(sampleoutput[,3]), StdDev(sampleoutput[,3])*qnorm((1+CI)/2)))
  
  CI_maxDD <- cbind(CI_lower(mean(sampleoutput[,4]), StdDev(sampleoutput[,4])*qnorm((1+CI)/2)),
                    CI_upper(mean(sampleoutput[,4]), StdDev(sampleoutput[,4])*qnorm((1+CI)/2)))
  
  CI_sharpe <- cbind(CI_lower(mean(sampleoutput[,5]), StdDev(sampleoutput[,5])*qnorm((1+CI)/2)),
                     CI_upper(mean(sampleoutput[,5]), StdDev(sampleoutput[,5])*qnorm((1+CI)/2)))
  
  CI_totalPL <- cbind(CI_lower(mean(sampleoutput[,6]), StdDev(sampleoutput[,6])*qnorm((1+CI)/2)),
                      CI_upper(mean(sampleoutput[,6]), StdDev(sampleoutput[,6])*qnorm((1+CI)/2)))
  
  # Build the Confidence Interval dataframes
  CIdf <- data.frame(matrix(nrow = 2, ncol = 6))
  colnames(CIdf) <- c("mean","median","stddev","maxDD","sharpe","totalPL")
  row.names(CIdf) <- c("Lower CI","Upper CI")
  CIdf$mean[row.names(CIdf) == "Lower CI"] <- CI_mean[1,1]
  CIdf$mean[row.names(CIdf) == "Upper CI"] <- CI_mean[1,2]
  
  CIdf$median[row.names(CIdf) == "Lower CI"] <- CI_median[1,1]
  CIdf$median[row.names(CIdf) == "Upper CI"] <- CI_median[1,2]
  
  CIdf$stddev[row.names(CIdf) == "Lower CI"] <- CI_stddev[1,1]
  CIdf$stddev[row.names(CIdf) == "Upper CI"] <- CI_stddev[1,2]
  
  CIdf$maxDD[row.names(CIdf) == "Lower CI"] <- CI_maxDD[1,1]
  CIdf$maxDD[row.names(CIdf) == "Upper CI"] <- CI_maxDD[1,2]
  
  CIdf$sharpe[row.names(CIdf) == "Lower CI"] <- CI_sharpe[1,1]
  CIdf$sharpe[row.names(CIdf) == "Upper CI"] <- CI_sharpe[1,2]
  
  CIdf$totalPL[row.names(CIdf) == "Lower CI"] <- CI_totalPL[1,1]
  CIdf$totalPL[row.names(CIdf) == "Upper CI"] <- CI_totalPL[1,2]
  
  # generate the return object
  ret <- list(
    replicates = reps,
    transactions = ltxn,
    backtest.trades = backtest.trades,
    perpl = perpl,
    cumpl = cumpl,
    initeq = initEq,
    seed = seed,
    call = match.call(),
    replacement = replacement,
    Portfolio = Portfolio,
    n = n,
    samplestats=sampleoutput,
    original=original,
    ranks=ranks,
    pvalues=pvalues,
    stderror=stderror,
    CI=CI,
    CIdf=CIdf
  )
  class(ret) <- "txnsim"
  ret
} # end txnsim fn


#' convenience function to generate portfolios for txnsim replicates
#'
#' If you have a txnsim object and market data, you should be able to rebuild
#' the replicate portfolios. This function creates all those portfolios.
#'
#' @param Portfolio string identifying a portfolio
#' @param n number of simulations, default = 100
#' @param replacement sample with or without replacement, default TRUE
#' @param symbols character vector of symbol names
#' @param initDate initialization Date to use for replicate portfolios
#' @param currency base currency for replicate portfolios
#' @param initEq initial equity to use for replicate portfolios
#'
#' @seealso \code{\link{txnsim}}, \code{\link{txnsim.txns}}
txnsim.portfs <- function(Portfolio, replacement, n, symbols, initDate, currency, initEq) {
  portnames <- txnsim.portnames(Portfolio, replacement, n)
  # create portfolios
  for (i in 1:n) {
    # name the simulated portfolio
    simport <- portnames[i]
    # remove portfolio if it exists, we need to overwrite it anyway
    suppressWarnings(rm(list = paste0("portfolio.", simport), envir = .blotter))
    # generate portfolio
    simport <- initPortf(
      name = simport,
      symbols = symbols,
      initDate = initDate,
      currency = currency,
      initEq = initEq
    )
  }
}

#' convenience function to create transactions from txnsim replicates
#'
#' If you have a txnsim object and market data, you should be able to rebuild
#' the replicate portfolios.
#'
#' @param reps replicates slot from txnsim object
#' @param Portfolio string identifying a portfolio
#' @param replacement sample with or without replacement, default TRUE
#' @param n number of simulations, default = 100
#' @param \dots any other passthrough parameters, most usefully \code{env} and \code{prefer}
#'
#' @return a list by symbol of txns suitable for calling \code{\link{addTxns}}
#'
#' @seealso \code{\link{txnsim}}, \code{\link{txnsim.txns}} , \code{\link{addTxns}}
txnsim.txns <- function (reps, Portfolio, replacement, n, ...) {
  portnames <- txnsim.portnames(Portfolio, replacement, n)
  ltxn <- list()
  for (symbol in names(reps)){
    ltxn[[symbol]] <- lapply(1:length(reps[[symbol]]), function(i) {
      simport <- portnames[i]
      #print(paste(simport,symbol))
      dargs <- list(...)
      if (!is.null(dargs$env))
        env <- dargs$env
      else
        env <- .GlobalEnv
      if (!is.null(dargs$prefer))
        prefer <- dargs$prefer
      else
        prefer <- NULL
      
      prices <- getPrice(get(symbol, pos = env), prefer = prefer)[, 1]
      if(! "POSIXct" %in% class(index(prices))) {
        index(prices) <- as.POSIXct(index(prices))
      }
      
      # the rep list has a start, duration, quantity in each row
      # we'll loop by row over that object to create an object for addTxns
      # @TODO find something more efficient than a for loop here
      # txns <- list()
      df <- reps[[symbol]][[i]]
      if (class(df) == 'data.frame')
        df <- list('1' = df)
      dflist <- df
      txnlist <- list()
      for (li in 1:length(dflist)) {
        txns <- list()
        df <- dflist[[li]]
        #df <- df[which(df$quantity != 0),] # remove zero quantity trades
        df <- df[which(df$duration != 0), ] # remove zero duration trades
        # Build opening transaction dataframe
        txns_open <- data.frame(matrix(nrow = nrow(df), ncol = 3))
        df <- df[order(df[,1]),] # when layering, we have older order timestamps from layer 2+ after more recent timestamps from layer 1
        idx_open <- findInterval(df[,1],index(prices))
        txns_open[,1] <- index(prices)[idx_open]
        txns_open[,2] <- df[, "quantity"]
        txns_open[,3] <- as.numeric(prices[idx_open])
        # Build closing transaction dataframe
        txns_close <- data.frame(matrix(nrow = nrow(df), ncol = 3))
        idx_close <- findInterval(df[,1]+df[, "duration"],index(prices))
        txns_close[,1] <- index(prices)[idx_close]
        txns_close[,2] <- -1 * df[, "quantity"]
        txns_close[,3] <- as.numeric(prices[idx_close])
        # Combine open and close dataframes, order, rename columns, remove zero quantity transactions
        txns <- rbind(txns_close, txns_open) # bind open txns below close txns so txns on same timestamp are in correct order
        txns <- xts(txns[,-1], order.by = txns[, 1])
        colnames(txns) <- (c("TxnQty", "TxnPrice"))
        txns <- txns[which(txns$TxnQty != 0), ]
        txnlist[[li]] <- txns
      }
      txns <- do.call(rbind, txnlist)
      addTxns(Portfolio = simport,
              Symbol = symbol,
              TxnData = txns)
      txns # return the data for later use
    })
  }
  ltxn # return the transaction list for later use
}

#' helper function for generating txnsim portfolio names
#'
#' called internally by txnsim and other txnsim generics to generate list
#' of portfolios to/which hold the replcates
#'
#' @param Portfolio root portfolio string name
#' @param replacement boolean
#' @param n number of replicate numbers
#'
#' @return character vector of portfolio names
txnsim.portnames <- function(Portfolio, replacement, n) {
  # name portfolios
  if (isTRUE(replacement)) {
    rpcstr <- 'wr'
  } else {
    rpcstr <- 'nr'
  }
  i <- 1:n
  # NOTE we may still want to clean out existing portfolios,
  # or allow some other naming options
  portnames <- paste("txnsim", rpcstr, Portfolio, i, sep = ".")
  return(portnames)
}


#' plot method for objects of type 'txnsim'
#'
#' @param x object of type 'txnsim' to plot
#' @param y not used, to match generic signature, may hold overlay data in the future
#' @param \dots any other passthrough parameters
#' @author Jasen Mackie, Brian G. Peterson
#' @seealso \code{\link{txnsim}}
#' @export
plot.txnsim <- function(x, y, ...) {
  cumpl <- x$cumpl
  
  backtestpl <- cumpl[,1]
  
  #TODO FIXME make grid.ticks.on smarter based on periodicity
  pt <- plot.xts(  cumpl
                   , col = "lightgray"
                   , main = paste(x$Portfolio, 'txnsim cumulative P&L',x$n,'reps. with replace=',x$replacement)
                   , grid.ticks.on = 'years'
  )
  pt <- lines(backtestpl, col = "red")
  print(pt)
  
  invisible(cumpl)
}

#' quantile method for objects of type \code{txnsim}
#'
#' calculates quantiles of cumulative P&L of the simulated strategies
#'
#' @param x object of type 'txnsim' to produce replicate quantiles
#' @param \dots any other passthrough parameters to \code{\link{quantile}}
#' @author Jasen Mackie, Brian G. Peterson
#'
#' @export
quantile.txnsim <- function(x, ...) {
  ret <- x$cumpl
  q   <- quantile(na.omit(ret))
  q
}

#' hist method for objects of type \code{txnsim}
#'
#' @param x object of type txnsim to plot
#' @param \dots any other passthrough parameters
#' @param normalize TRUE/FALSE whether to normalize the hist by div, default FALSE as no normalized data yet
#' @param methods are statistics to include in hist output, default methods=c("mean","median","stddev","maxDD","sharpe")
#' @author Jasen Mackie, Brian G. Peterson
#'
#' @importFrom graphics axis box hist lines par text
#'
#' @export
hist.txnsim <- function(x, ..., normalize=FALSE,
                        methods = c("mean",
                                    "median",
                                    "stddev",
                                    "maxDD",
                                    "sharpe")) {
  ret <- x
  hh <- function(x, main, breaks="FD"
                 , xlab, ylab = "Density"
                 , col = "lightgray", border = "white", freq=FALSE, ...
                 , b, b.label, v, c.label, t, u, u.label, ci_L,ci_H, tci_L="Lower Confidence Interval", tci_H="Upper Confidence Interval"
  ){
    
    hhh <- hist(x, main=main, breaks=breaks, xlab=xlab, ylab=ylab, col=col, border=border, freq=freq, cex.main=0.70)
    hhh
    box(col = "darkgray")
    abline(v = b, col = "red", lty = 2)
    b.label = b.label
    hhh = rep(0.2 * par("usr")[3] + 1 * par("usr")[4], length(b))
    text(b, hhh/1.85, b.label, offset = 0.4, pos = 2, cex = 0.8, srt = 90, col = "red")
    abline(v = v, col = "darkgray", lty = 2)
    c.label = c.label
    text(t, hhh/1.85, c.label, offset = 0.4, pos = 2, cex = 0.8, srt = 90)
    abline(v = ci_L, col="blue", lty=2)
    text(ci_L, hhh, tci_L, offset = 0.4, pos = 2, cex = 0.8, srt = 90, col="blue")
    abline(v = ci_H, col="blue", lty=2)
    text(ci_H, hhh, tci_H, offset = 0.4, pos = 2, cex = 0.8, srt = 90, col="blue")
    abline(v = u, col="blue", lty=2)
    text(u, hhh, u.label, offset = 0.4, pos = 2, cex = 0.8, srt = 90, col="blue")
    hhh
  }
  if(isTRUE(normalize) && ret$initeq>1) {
    xname <- paste(ret$n, "replicates", ret$w, "using", ret$CI, "confidence interval")
    h <- NULL
    for (method in methods) {
      switch (method,
              mean = {
                hh(ret$percsamplestats$mean, paste("Mean distribution of" , xname)
                   , xlab="Mean Return"
                   , b = ret$percoriginal$mean
                   , b.label = ("Backtest Mean Return")
                   , v = median(na.omit(ret$percsamplestats$mean))
                   , c.label = ("Simulation Median Return")
                   , t = median(na.omit(ret$percsamplestats$mean))
                   , u.label = ("Simulation Mean Return")
                   , u = mean(na.omit(ret$percsamplestats$mean))
                   , ci_L = ret$CIdf_perc$mean[1]
                   , ci_H = ret$CIdf_perc$mean[2]
                )
              },
              median = {
                hh(ret$percsamplestats$median, paste("Median distribution of", xname)
                   , xlab="Median Return"
                   , b = ret$percoriginal$median
                   , b.label = ("Backtest Median Return")
                   , v = median(na.omit(ret$percsamplestats$median))
                   , c.label = ("Simulation Median Return")
                   , t = median(na.omit(ret$percsamplestats$median))
                   , u.label = ("Simulation Mean Return")
                   , u = mean(na.omit(ret$percsamplestats$median))
                   , ci_L = ret$CIdf_perc$median[1]
                   , ci_H = ret$CIdf_perc$median[2]
                )
              },
              stddev = {
                hh(ret$percsamplestats$stddev, paste("Std Dev distribution of" , xname)
                   , xlab="stddev"
                   , b = ret$percoriginal$stddev
                   , b.label = ("Backtest Std Dev")
                   , v = median(na.omit(ret$percsamplestats$stddev))
                   , c.label = ("Simulation Median Std Dev")
                   , t = median(na.omit(ret$percsamplestats$stddev))
                   , u.label = ("Simulation Mean Std Dev")
                   , u = mean(na.omit(ret$percsamplestats$stddev))
                   , ci_L = ret$CIdf_perc$stddev[1]
                   , ci_H = ret$CIdf_perc$stddev[2]
                )
              },
              maxDD = {
                hh(ret$percsamplestats$maxDD, paste("maxDrawdown distribution of" , xname)
                   , xlab="Max Drawdown"
                   , b = ret$percoriginal$maxDD
                   , b.label = ("Backtest Max Drawdown")
                   , v = median(na.omit(ret$percsamplestats$maxDD))
                   , c.label = ("Simulation Median Max Drawdown")
                   , t = median(na.omit(ret$percsamplestats$maxDD))
                   , u.label = ("Simulation Mean Max Drawdown")
                   , u = mean(na.omit(ret$percsamplestats$maxDD))
                   , ci_L = ret$CIdf_perc$maxDD[1]
                   , ci_H = ret$CIdf_perc$maxDD[2]
                )
              },
              sharpe = {
                hh(ret$percsamplestats$sharpe, paste("quasi-Sharpe distribution of" , xname)
                   , xlab="quasi-sharpe"
                   , b = ret$percoriginal$sharpe
                   , b.label = ("Backtest quasi-Sharpe ratio")
                   , v = median(na.omit(ret$percsamplestats$sharpe))
                   , c.label = ("Simulation Median quasi-Sharpe ratio")
                   , t = median(na.omit(ret$percsamplestats$sharpe))
                   , u.label = ("Simulation Mean quasi-Sharpe ratio")
                   , u = mean(na.omit(ret$percsamplestats$sharpe))
                   , ci_L = ret$CIdf_perc$sharpe[1]
                   , ci_H = ret$CIdf_perc$sharpe[2]
                )
              }
      )
    }
    
  } else {
    # do not normalize
    xname <- paste(ret$n, "replicates", ret$w, "using", ret$CI, "confidence interval")
    h <- NULL
    for (method in methods) {
      switch (method,
              mean = {
                hh(ret$samplestats$mean, paste("Mean distribution of" , xname)
                   , xlab="Mean Return"
                   , b = ret$original$mean
                   , b.label = ("Backtest Mean Return")
                   , v = median(na.omit(ret$samplestats$mean))
                   , c.label = ("Simulation Median Return")
                   , t = median(na.omit(ret$samplestats$mean))
                   , u.label = ("Simulation Mean Return")
                   , u = mean(na.omit(ret$samplestats$mean))
                   , ci_L = ret$CIdf$mean[1]
                   , ci_H = ret$CIdf$mean[2]
                )
              },
              median = {
                hh(ret$samplestats$median, paste("Median distribution of" , xname)
                   , xlab="Median Return"
                   , b = ret$original$median
                   , b.label = ("Backtest Median Return")
                   , v = median(na.omit(ret$samplestats$median))
                   , c.label = ("Simulation Median Return")
                   , t = median(na.omit(ret$samplestats$median))
                   , u.label = ("Simulation Mean Return")
                   , u = mean(na.omit(ret$samplestats$median))
                   , ci_L = ret$CIdf$median[1]
                   , ci_H = ret$CIdf$median[2]
                )
              },
              stddev = {
                hh(ret$samplestats$stddev, paste("Std Dev distribution of" , xname)
                   , xlab="stddev"
                   , b = ret$original$stddev
                   , b.label = ("Backtest Std Dev")
                   , v = median(na.omit(ret$samplestats$stddev))
                   , c.label = ("Simulation Median Std Dev")
                   , t = median(na.omit(ret$samplestats$stddev))
                   , u.label = ("Simulation Mean Std Dev")
                   , u = mean(na.omit(ret$samplestats$stddev))
                   , ci_L = ret$CIdf$stddev[1]
                   , ci_H = ret$CIdf$stddev[2]
                )
              },
              maxDD = {
                hh(ret$samplestats$maxDD, paste("maxDrawdown distribution of" , xname)
                   , xlab="Max Drawdown"
                   , b = ret$original$maxDD
                   , b.label = ("Backtest Max Drawdown")
                   , v = median(na.omit(ret$samplestats$maxDD))
                   , c.label = ("Simulation Median Max Drawdown")
                   , t = median(na.omit(ret$samplestats$maxDD))
                   , u.label = ("Simulation Mean Max Drawdown")
                   , u = mean(na.omit(ret$samplestats$maxDD))
                   , ci_L = ret$CIdf$maxDD[1]
                   , ci_H = ret$CIdf$maxDD[2]
                )
              },
              sharpe = {
                hh(ret$samplestats$sharpe, paste("quasi-Sharpe distribution of" , xname)
                   , xlab="quasi-sharpe"
                   , b = ret$original$sharpe
                   , b.label = ("Backtest quasi-Sharpe ratio")
                   , v = median(na.omit(ret$samplestats$sharpe))
                   , c.label = ("Simulation Median quasi-Sharpe ratio")
                   , t = median(na.omit(ret$samplestats$sharpe))
                   , u.label = ("Simulation Mean quasi-Sharpe ratio")
                   , u = mean(na.omit(ret$samplestats$sharpe))
                   , ci_L = ret$CIdf$sharpe[1]
                   , ci_H = ret$CIdf$sharpe[2]
                )
              }
      )
    }
  }
}



#' summary and print methods for objects of type txnsim
#'
#' @param x an object of type txnsim
#' @param object an object of type txnsim
#' @param ... any other passthrough parameters
#'
#' @method summary txnsim
#' @export
summary.txnsim <- function(object,...){
  out<-t(rbind(object$original,object$stderror,object$CIdf))
  colnames(out)[1]<-'backtest'
  out
}

#' @rdname summary.txnsim
#' @method print txnsim
#' @export
print.txnsim <- function(x,...){
  round(summary.txnsim(x,...),3)
}

###############################################################################
# R (http://r-project.org/) Quantitative Strategy Model Framework
#
# Copyright (c) 2009-2016
# Peter Carl, Dirk Eddelbuettel, Brian G. Peterson, Jeffrey Ryan, and Joshua Ulrich
#
# This library is distributed under the terms of the GNU Public License (GPL)
# for full details see the file COPYING
#
# $Id$
#
###############################################################################
braverock/blotter documentation built on Sept. 15, 2024, 8:45 p.m.