The gain/loss asymmetry is a well known stylized fact: It basically states that real financial time series take longer for going up than going down.

To detect it a heavy statistical machinery is needed: Detrending the time series, calculating the inverse statistics, normalizing the distribution, fitting a Generalized Gamma distribution... to name but a few.

The result are plots like these (from http://papers.ssrn.com/sol3/papers.cfm?abstract_id=844364):

from: http://papers.ssrn.com/sol3/papers.cfm?abstract_id=844364

My question
I want to reproduce those plots. Do you know any software, tools and/or preferably R code/packages with which this can be done? Every little hint may be helpful - Thank you.


Here is a quick example, you grab the total returns for each holding period, avg them out and compare the days for each level of return.

You can change tmp1 for whatever is your preferred filtered data set.




period_seq = 1:nrow(tmp1)
###Remove impossible investments
colnames(combos) = c('start','finish')
combos$day_length = combos[,2]-combos[,1]
###Calculater return for each period

###Calculate return for each combo

for(i in 1:nrow(combos)){
  combos[i,]$perreturn = as.numeric(last(cumprod(1+(tmp1[combos[i,1]:combos[i,2]])))-1)


###Round the total return
combos$roundedperreturn = round(combos$perreturn,2)
###Calulate the avg day length per return level
ans=sqldf('select avg(day_length) as avg_day_length,roundedperreturn as return_level from combos group by 2')

##Plot it
plot(ans$avg_day_length,ans$return_level,main="Holding period per level of return",xlab="periods",ylab='Return level')

##look only at levels that have a + and -
down_side = ans[ans$return_level<=abs(min(ans$return_level))&ans$return_level<0,]
down_side$return_level = abs(down_side$return_level)
plot(up_side,col="blue",type='b',main='Comparison of days required to return a return level')
points(x = down_side$avg_day_length,y=down_side$return_level,col="red",type='b')

###Constant level of return plot


up_five_pct_plot = data.frame(density=as.numeric(up_five_pct_plot),periods=as.integer(names(up_five_pct_plot)))
down_five_pct_plot = data.frame(density=as.numeric(down_five_pct_plot),periods=as.integer(names(down_five_pct_plot)))
plot(x=up_five_pct_plot$periods,y=up_five_pct_plot$density,type='b',col='blue',main='Density plot of time required for a five percent return (loss in red)',xlab='periods',ylab='density')

Holding period required

enter image description here

enter image description here It paints a different picture likely due to my use of monthly sample data.

  • $\begingroup$ Could you please elaborate on this because I don't quite see how this could help - Thank you $\endgroup$ – vonjd Jul 31 '15 at 19:25
  • $\begingroup$ You would use the start of the gain or drawdown till end for days and return at each point in time $\endgroup$ – Kyle Balkissoon Jul 31 '15 at 20:28
  • $\begingroup$ I think the problem is that you need the lengths of all possible windows until you reach the respective return level for each. I still cannot see how this approach might help you with this. $\endgroup$ – vonjd Aug 3 '15 at 17:35
  • $\begingroup$ Got it, amended answer. Will add the asymmetry plot later. $\endgroup$ – Kyle Balkissoon Aug 4 '15 at 2:34
  • $\begingroup$ Thank you. Although this is still different I think it is interesting in its own right, so I upvoted and accepted the answer. When I understand it correctly the original plot sets a return level (e.g. 5%) and counts the number of periods (of all possible periods) that you need to reach it (so how many 1-day periods, 2-day periods, ... 10-day periods and so on). It then calculates a density based on that distribution. The result is the above chart. (more can be found in the papers I linked to in my question). $\endgroup$ – vonjd Aug 4 '15 at 4:50

I wrote an R function to create those plots:


getSymbols("^GSPC", from = "1950-01-01")
## [1] "GSPC"

inv_stat <- function(symbol, name, target = 0.05) {
  p <- coredata(Cl(symbol))
  end <- length(p)
  days_n <- days_p <- integer(end)

  # go through all days and look when target is reached the first time from there
  for (d in 1:end) {
    ret <- cumsum(as.numeric(na.omit(ROC(p[d:end]))))
    cond_n <- ret < -target
    cond_p <- ret > target
    suppressWarnings(days_n[d] <- min(which(cond_n)))
    suppressWarnings(days_p[d] <- min(which(cond_p)))

  days_n_norm <- prop.table(as.integer(table(days_n, exclude = "Inf")))
  days_p_norm <- prop.table(as.integer(table(days_p, exclude = "Inf")))

  plot(days_n_norm, log = "x", xlim = c(1, 1000), main = paste0(name, " gain-/loss-asymmetry with target ", target), xlab = "days", ylab = "density", col = "red")
  points(days_p_norm, col = "blue")

  c(which.max(days_n_norm), which.max(days_p_norm))

inv_stat(GSPC, name = "S&P 500")
 ## [1] 10 24

The following plot is being produced (will take some time):

enter image description here

Two things are missing:

  • Detrending of time series
  • Fitted probability distribution

If you want to add them or if you have ideas how to improve the code, please let me know!


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.