1
$\begingroup$

I am using the Riskfolio-Lib Python library to calculate the Ulcer Index. When I run the function, the values I get are on the order of hundredths (for example, 0.0X).

My issue is that, based on other resources and examples I've seen online, the Ulcer Index is typically presented as a value between 0 and 4. This discrepancy makes me wonder if the results I'm getting are correct.

I've reviewed the library's code and identified the specific line where the function is calculated: https://github.com/dcajasn/Riskfolio-Lib/blob/2ba0ccd217b5de63c2c20e2bcfcfd40ee0ad72d2/riskfolio/src/RiskFunctions.py#L1005

My question is: Do I need to multiply the result from the function by 100 to get the value in the more common range (0-4)? Or is there a conceptual difference in how this library calculates the index compared to other sources?

I appreciate any help you can provide.

$\endgroup$

1 Answer 1

1
$\begingroup$

From https://en.wikipedia.org/wiki/Ulcer_index and https://www.tangotools.com/ui/ui.htm , the Ulcer index is the square root of the mean squared amount "under water" of a series, with "under water" defined as 1 - current_value/prior_peak.

Assuming a portfolio can be between 0 and 100% under water, any value for the Ulcer index must be between 0 and 100%, which numerically is between 0 and 1. You can, of course, multiply this value by 100, if you prefer percentage points (0.01 is the same as 1%).

The implementation you referenced differs somewhat from that definition, but that is mentioned in the code: it ignores compounding (it takes returns as input and then sums them), and it divides by N - 1 instead of N. It does not multiply by 100.

For reference, in base R (without any packages):

Ulcer <- function(s) {
    D <- 1 - s/cummax(s)
    sqrt(mean(D * D))
}

One can check with the example file provided at tangotools.com:

download.file("https://www.tangotools.com/ui/UlcerIndex.xls",
              "~/Downloads/UlcerIndex.xls")
f <- xlsx::read.xlsx("~/Desktop/UlcerIndex.xls", 1,
                     startRow = 15, header = FALSE)

Ulcer(f[, 2])
## [1] 0.165385

Which is the result provided in the file. Or, using the (daily) returns over the last 10 years of the US market portfolio, from Kenneth French's website:

library("NMOF")
library("zoo")

market <- French(dataset = "market", dest.dir = tempdir(),
                 frequency = "daily", return.class = "zoo",
                 price.series = TRUE)

Ulcer(window(market, start = as.Date("2015-01-01")))
## [1] 0.07806045

In both examples the results were outside the 0-4 range. But what "normal" values are will much depend on the type of series you look at. For the calendar years 1980 to 2024, the US equity market had the following values.

summary(
    sapply(
        as.list(1980:2024),
        function(y) {
            Ulcer(window(market,
                         start = as.Date(paste0(y, "-01-01")),
                         end   = as.Date(paste0(y, "-12-31"))))
        }))

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.00759 0.02587 0.03701 0.05546 0.06869 0.19363 
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.