From 4ff0442b3f5ec1f368cc3213d7d2898b52cd9af8 Mon Sep 17 00:00:00 2001
From: Onur Cinar <onur.cinar@gmail.com>
Date: Wed, 26 Jan 2022 14:12:28 -0800
Subject: [PATCH] Volatility: Ulcer Index (UI) (#60)

Fixes #43
---
 README.md                     |  1 +
 helper.go                     | 11 +++++++++++
 volatility_indicators.go      | 23 +++++++++++++++++++++++
 volatility_indicators.md      | 22 ++++++++++++++++++++++
 volatility_indicators_test.go |  9 +++++++++
 5 files changed, 66 insertions(+)

diff --git a/README.md b/README.md
index d7ff656..7da3a0a 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ The following list of indicators are currently supported by this package:
 - [Chandelier Exit](volatility_indicators.md#chandelier-exit)
 - [Moving Standard Deviation (Std)](volatility_indicators.md#moving-standard-deviation-std)
 - [Projection Oscillator (PO)](volatility_indicators.md#projection-oscillator-po)
+- [Ulcer Index (UI)](volatility_indicators.md#ulcer-index-ui)
 
 ### Volume Indicators
 
diff --git a/helper.go b/helper.go
index 9647859..f6418af 100644
--- a/helper.go
+++ b/helper.go
@@ -233,3 +233,14 @@ func testEquals(t *testing.T, actual, expected []float64) {
 		}
 	}
 }
+
+// Sqrt of given values.
+func sqrt(values []float64) []float64 {
+	result := make([]float64, len(values))
+
+	for i := 0; i < len(values); i++ {
+		result[i] = math.Sqrt(values[i])
+	}
+
+	return result
+}
diff --git a/volatility_indicators.go b/volatility_indicators.go
index bd3f120..191a867 100644
--- a/volatility_indicators.go
+++ b/volatility_indicators.go
@@ -164,3 +164,26 @@ func ProjectionOscillator(period, smooth int, high, low, closing []float64) ([]f
 
 	return po, spo
 }
+
+// The Ulcer Index (UI) measures downside risk. The index increases in value
+// as the price moves farther away from a recent high and falls as the price
+// rises to new highs.
+//
+// High Closings = Max(period, Closings)
+// Percentage Drawdown = 100 * ((Closings - High Closings) / High Closings)
+// Squared Average = Sma(period, Percent Drawdown * Percent Drawdown)
+// Ulcer Index = Sqrt(Squared Average)
+//
+// Returns ui.
+func UlcerIndex(period int, closing []float64) []float64 {
+	highClosing := Max(period, closing)
+	percentageDrawdown := multiplyBy(divide(substract(closing, highClosing), highClosing), 100)
+	squaredAverage := Sma(period, multiply(percentageDrawdown, percentageDrawdown))
+	ui := sqrt(squaredAverage)
+	return ui
+}
+
+// The default ulcer index with the default period of 14.
+func DefaultUlcerIndex(closing []float64) []float64 {
+	return UlcerIndex(14, closing)
+}
diff --git a/volatility_indicators.md b/volatility_indicators.md
index 13c7917..47a70c0 100644
--- a/volatility_indicators.md
+++ b/volatility_indicators.md
@@ -9,6 +9,7 @@ Volatility indicators measure the rate of movement regardless of its direction.
 - [Chandelier Exit](#chandelier-exit)
 - [Moving Standard Deviation (Std)](#moving-standard-deviation-std)
 - [Projection Oscillator (PO)](#projection-oscillator-po)
+- [Ulcer Index (UI)](#ulcer-index-ui)
 
 #### Acceleration Bands
 
@@ -103,6 +104,27 @@ SPO = EMA(smooth, PO)
 po, spo := indicator.ProjectionOscillator(12, 4, high, low, closing)
 ```
 
+#### Ulcer Index (UI)
+
+The [UlcerIndex](https://pkg.go.dev/github.com/cinar/indicator#UlcerIndex) measures downside risk. The index increases in value as the price moves farther away from a recent high and falls as the price rises to new highs.
+
+```
+High Closings = Max(period, Closings)
+Percentage Drawdown = 100 * ((Closings - High Closings) / High Closings)
+Squared Average = Sma(period, Percent Drawdown * Percent Drawdown)
+Ulcer Index = Sqrt(Squared Average)
+```
+
+```golang
+ui := indicator.UlcerIndex(period, closing)
+```
+
+The [DefaultUlcerIndex](https://pkg.go.dev/github.com/cinar/indicator#DefaultUlcerIndex) measures the ulcer index with the default period of 14.
+
+```golang
+ui := indicator.DefaultUlcerIndex(closing)
+```
+
 ## Disclaimer
 
 The information provided on this project is strictly for informational purposes and is not to be construed as advice or solicitation to buy or sell any security.
diff --git a/volatility_indicators_test.go b/volatility_indicators_test.go
index 5d3f8c1..25c306d 100644
--- a/volatility_indicators_test.go
+++ b/volatility_indicators_test.go
@@ -17,3 +17,12 @@ func TestStd(t *testing.T) {
 	actual := Std(period, values)
 	testEquals(t, roundDigitsAll(actual, 3), expected)
 }
+
+func TestUlcerIndex(t *testing.T) {
+	closing := []float64{9, 11, 7, 10, 8, 7, 7, 8, 10, 9, 5, 4, 6, 7}
+	expected := []float64{0, 0, 20.99, 18.74, 20.73, 24.05, 26.17, 26.31,
+		24.99, 24.39, 28.49, 32.88, 34.02, 34.19}
+
+	actual := DefaultUlcerIndex(closing)
+	testEquals(t, roundDigitsAll(actual, 2), expected)
+}