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