diff --git a/README.md b/README.md
index f7f5136..ab47ec2 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ The following list of indicators are currently supported by this package:
- Keltner Channel (KC)
- [Moving Standard Deviation (Std)](volatility/README.md#type-movingstd)
- Projection Oscillator (PO)
-- Ulcer Index (UI)
+- [Ulcer Index (UI)](volatility/README.md#type-ulcerindex)
### 📢 Volume Indicators
diff --git a/volatility/README.md b/volatility/README.md
index 43d5e33..225c688 100644
--- a/volatility/README.md
+++ b/volatility/README.md
@@ -50,6 +50,10 @@ The information provided on this project is strictly for informational purposes
- [func NewMovingStdWithPeriod\[T helper.Number\]\(period int\) \*MovingStd\[T\]](<#NewMovingStdWithPeriod>)
- [func \(m \*MovingStd\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#MovingStd[T].Compute>)
- [func \(m \*MovingStd\[T\]\) IdlePeriod\(\) int](<#MovingStd[T].IdlePeriod>)
+- [type UlcerIndex](<#UlcerIndex>)
+ - [func NewUlcerIndex\[T helper.Number\]\(\) \*UlcerIndex\[T\]](<#NewUlcerIndex>)
+ - [func \(u \*UlcerIndex\[T\]\) Compute\(closings \<\-chan T\) \<\-chan T](<#UlcerIndex[T].Compute>)
+ - [func \(u \*UlcerIndex\[T\]\) IdlePeriod\(\) int](<#UlcerIndex[T].IdlePeriod>)
## Constants
@@ -81,6 +85,15 @@ const (
)
```
+
+
+```go
+const (
+ // DefaultUlcerIndexPeriod is the default period for the Ulcer Index.
+ DefaultUlcerIndexPeriod = 14
+)
+```
+
## type [AccelerationBands]()
@@ -301,7 +314,7 @@ Chandelier Exit Short = 22-Period SMA Low + ATR(22) * 3
Example:
```
-ce := volatility.NewChandelierExist[float64]()
+ce := volatility.NewChandelierExit[float64]()
ceLong, ceShort := ce.Compute(highs, lows, closings)
```
@@ -319,7 +332,7 @@ type ChandelierExit[T helper.Number] struct {
func NewChandelierExit[T helper.Number]() *ChandelierExit[T]
```
-NewChandelierExit function initializes a new Chandleir Exist instance with the default parameters.
+NewChandelierExit function initializes a new Chandelier Exit instance with the default parameters.
### func \(\*ChandelierExit\[T\]\) [Compute]()
@@ -328,7 +341,7 @@ NewChandelierExit function initializes a new Chandleir Exist instance with the d
func (c *ChandelierExit[T]) Compute(highs, lows, closings <-chan T) (<-chan T, <-chan T)
```
-Compute function takes a channel of numbers and computes the Chandleir Exist over the specified period.
+Compute function takes a channel of numbers and computes the Chandelier Exit over the specified period.
### func \(\*ChandelierExit\[T\]\) [IdlePeriod]()
@@ -337,7 +350,7 @@ Compute function takes a channel of numbers and computes the Chandleir Exist ove
func (c *ChandelierExit[T]) IdlePeriod() int
```
-IdlePeriod is the initial period that Acceleration Bands won't yield any results.
+IdlePeriod is the initial period that Chandelier Exit won't yield any results.
## type [MovingStd]()
@@ -393,4 +406,57 @@ func (m *MovingStd[T]) IdlePeriod() int
IdlePeriod is the initial period that Moving Standard Deviation won't yield any results.
+
+## type [UlcerIndex]()
+
+UlcerIndex represents the configuration parameters for calculating the Ulcer Index \(UI\). It 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)
+```
+
+Example:
+
+```
+ui := volatility.NewUlcerIndex[float64]()
+ui.Compute(closings)
+```
+
+```go
+type UlcerIndex[T helper.Number] struct {
+ // Time period.
+ Period int
+}
+```
+
+
+### func [NewUlcerIndex]()
+
+```go
+func NewUlcerIndex[T helper.Number]() *UlcerIndex[T]
+```
+
+NewUlcerIndex function initializes a new Ulcer Index instance with the default parameters.
+
+
+### func \(\*UlcerIndex\[T\]\) [Compute]()
+
+```go
+func (u *UlcerIndex[T]) Compute(closings <-chan T) <-chan T
+```
+
+Compute function takes a channel of numbers and computes the Ulcer Index over the specified period.
+
+
+### func \(\*UlcerIndex\[T\]\) [IdlePeriod]()
+
+```go
+func (u *UlcerIndex[T]) IdlePeriod() int
+```
+
+IdlePeriod is the initial period that Ulcer Index won't yield any results.
+
Generated by [gomarkdoc]()
diff --git a/volatility/chandelier_exit.go b/volatility/chandelier_exit.go
index 32297a4..d2d6961 100644
--- a/volatility/chandelier_exit.go
+++ b/volatility/chandelier_exit.go
@@ -22,21 +22,21 @@ const (
//
// Example:
//
-// ce := volatility.NewChandelierExist[float64]()
+// ce := volatility.NewChandelierExit[float64]()
// ceLong, ceShort := ce.Compute(highs, lows, closings)
type ChandelierExit[T helper.Number] struct {
// Time period.
Period int
}
-// NewChandelierExit function initializes a new Chandleir Exist instance with the default parameters.
+// NewChandelierExit function initializes a new Chandelier Exit instance with the default parameters.
func NewChandelierExit[T helper.Number]() *ChandelierExit[T] {
return &ChandelierExit[T]{
Period: DefaultChandelierExitPeriod,
}
}
-// Compute function takes a channel of numbers and computes the Chandleir Exist over the specified period.
+// Compute function takes a channel of numbers and computes the Chandelier Exit over the specified period.
func (c *ChandelierExit[T]) Compute(highs, lows, closings <-chan T) (<-chan T, <-chan T) {
highsSplice := helper.Duplicate(highs, 2)
lowsSplice := helper.Duplicate(lows, 2)
@@ -64,7 +64,7 @@ func (c *ChandelierExit[T]) Compute(highs, lows, closings <-chan T) (<-chan T, <
return ceLong, ceShort
}
-// IdlePeriod is the initial period that Acceleration Bands won't yield any results.
+// IdlePeriod is the initial period that Chandelier Exit won't yield any results.
func (c *ChandelierExit[T]) IdlePeriod() int {
return c.Period - 1
}
diff --git a/volatility/testdata/ulcer_index.csv b/volatility/testdata/ulcer_index.csv
new file mode 100644
index 0000000..b715e15
--- /dev/null
+++ b/volatility/testdata/ulcer_index.csv
@@ -0,0 +1,252 @@
+Close,UlcerIndex
+318.600006,0
+315.839996,0
+316.149994,0
+310.570007,0
+307.779999,0
+305.820007,0
+305.98999,0
+306.390015,0
+311.450012,0
+312.329987,0
+309.290009,0
+301.910004,0
+300,0
+300.029999,0
+302,0
+307.820007,0
+302.690002,0
+306.48999,0
+305.549988,0
+303.429993,0
+309.059998,0
+308.899994,0
+309.910004,0
+314.549988,0
+312.899994,0
+318.690002,0
+315.529999,1.95
+316.350006,1.59
+320.369995,1.27
+318.929993,1.11
+317.640015,0.95
+314.859985,0.94
+308.299988,1.06
+305.230011,1.19
+309.869995,1.35
+310.420013,1.49
+311.299988,1.64
+311.899994,1.83
+310.950012,2
+309.170013,2.25
+307.329987,2.47
+311.519989,2.62
+310.570007,2.8
+311.859985,2.9
+308.51001,2.99
+308.429993,2.94
+312.970001,2.67
+308.480011,2.44
+307.209991,2.34
+309.890015,2.18
+313.73999,1.98
+310.790009,1.86
+309.630005,1.74
+308.179993,1.62
+308.23999,1.45
+302.720001,1.51
+303.160004,1.56
+303.070007,1.67
+304.019989,1.75
+304.660004,1.88
+305.179993,2.07
+304.619995,2.18
+307.75,2.18
+312.450012,2.14
+316.970001,2.14
+311.119995,2.21
+311.369995,2.24
+304.820007,2.39
+303.630005,2.56
+302.880005,2.63
+305.329987,2.65
+297.880005,2.84
+302.01001,2.95
+293.51001,3.28
+301.059998,3.44
+303.850006,3.53
+299.730011,3.78
+298.369995,4.17
+298.920013,4.46
+302.140015,4.54
+302.320007,4.48
+305.299988,4.21
+305.079987,3.91
+308.769989,3.59
+310.309998,3.33
+309.070007,2.93
+310.390015,2.59
+312.51001,2.06
+312.619995,1.71
+313.700012,1.41
+314.549988,1.02
+318.049988,0.6
+319.73999,0.32
+323.790009,0.11
+324.630005,0.04
+323.089996,0.07
+323.820007,0.08
+324.329987,0.09
+326.049988,0.09
+324.339996,0.1
+320.529999,0.22
+326.230011,0.22
+328.549988,0.22
+330.170013,0.22
+325.859985,0.31
+323.220001,0.46
+320,0.68
+323.880005,0.82
+326.140015,0.9
+324.869995,0.98
+322.98999,1.12
+322.640015,1.28
+322.48999,1.44
+323.529999,1.55
+323.75,1.57
+327.390015,1.63
+329.76001,1.64
+330.390015,1.64
+329.130005,1.57
+323.109985,1.58
+320.200012,1.58
+319.019989,1.69
+320.600006,1.81
+322.190002,1.88
+321.079987,1.92
+323.119995,1.92
+329.480011,1.77
+328.579987,1.66
+333.410004,1.53
+335.420013,1.47
+335.950012,1.46
+335.290009,1.47
+333.600006,1.49
+336.390015,1.34
+335.899994,1.13
+339.820007,0.88
+338.309998,0.7
+338.670013,0.55
+338.609985,0.37
+336.959991,0.27
+335.25,0.35
+334.119995,0.43
+335.339996,0.53
+334.149994,0.65
+336.910004,0.71
+341,0.69
+342,0.64
+341.559998,0.65
+341.459991,0.65
+340.899994,0.68
+341.130005,0.66
+343.369995,0.64
+345.350006,0.61
+343.540009,0.59
+341.089996,0.58
+344.25,0.48
+345.339996,0.39
+342.429993,0.33
+346.609985,0.27
+345.76001,0.29
+349.630005,0.29
+347.579987,0.32
+349.799988,0.31
+349.309998,0.3
+349.809998,0.28
+351.959991,0.28
+352.26001,0.28
+351.190002,0.26
+353.809998,0.17
+349.98999,0.23
+362.579987,0.23
+363.730011,0.17
+358.019989,0.28
+356.980011,0.4
+358.350006,0.5
+358.480011,0.56
+354.5,0.74
+354.109985,0.92
+353.190002,1.13
+352.559998,1.35
+352.089996,1.58
+350.570007,1.81
+354.26001,2
+354.299988,2.11
+355.929993,2.26
+355.549988,2.32
+358.290009,2.21
+361.059998,2.08
+360.200012,1.99
+362.459991,1.89
+360.470001,1.74
+361.670013,1.57
+361.799988,1.38
+363.149994,1.16
+365.519989,0.93
+367.779999,0.67
+367.820007,0.49
+369.5,0.3
+367.859985,0.18
+370.429993,0.12
+370.480011,0.12
+366.820007,0.19
+363.279999,0.31
+360.160004,0.51
+361.709991,0.64
+359.420013,0.84
+357.779999,1.07
+357.059998,1.33
+350.299988,1.72
+348.079987,2.15
+343.040009,2.68
+343.690002,3.19
+345.059998,3.65
+346.339996,4.12
+345.450012,4.53
+348.559998,4.75
+348.429993,4.87
+345.660004,4.99
+345.089996,5.11
+346.230011,5.13
+345.390015,5.11
+340.890015,5.05
+338.660004,4.86
+335.859985,4.69
+336.839996,4.4
+338.630005,4.09
+336.899994,3.84
+336.160004,3.63
+331.709991,3.55
+337.410004,3.49
+341.329987,3.33
+343.75,3.06
+349.019989,2.78
+351.809998,2.55
+346.630005,2.42
+346.170013,2.34
+346.299988,2.25
+348.179993,2.07
+350.559998,1.85
+350.01001,1.68
+354.25,1.44
+356.790009,1.19
+359.859985,0.85
+358.929993,0.64
+361.329987,0.54
+361,0.49
+361.799988,0.49
+362.679993,0.49
+361.339996,0.41
+360.049988,0.35
+358.690002,0.32
diff --git a/volatility/ulcer_index.go b/volatility/ulcer_index.go
new file mode 100644
index 0000000..650da3e
--- /dev/null
+++ b/volatility/ulcer_index.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2021-2023 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package volatility
+
+import (
+ "github.com/cinar/indicator/helper"
+ "github.com/cinar/indicator/trend"
+)
+
+const (
+ // DefaultUlcerIndexPeriod is the default period for the Ulcer Index.
+ DefaultUlcerIndexPeriod = 14
+)
+
+// UlcerIndex represents the configuration parameters for calculating the Ulcer Index (UI).
+// It 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)
+//
+// Example:
+//
+// ui := volatility.NewUlcerIndex[float64]()
+// ui.Compute(closings)
+type UlcerIndex[T helper.Number] struct {
+ // Time period.
+ Period int
+}
+
+// NewUlcerIndex function initializes a new Ulcer Index instance with the default parameters.
+func NewUlcerIndex[T helper.Number]() *UlcerIndex[T] {
+ return &UlcerIndex[T]{
+ Period: DefaultUlcerIndexPeriod,
+ }
+}
+
+// Compute function takes a channel of numbers and computes the Ulcer Index over the specified period.
+func (u *UlcerIndex[T]) Compute(closings <-chan T) <-chan T {
+ closingsSplice := helper.Duplicate(closings, 2)
+
+ // High Closings = Max(period, Closings)
+ max := trend.NewMovingMaxWithPeriod[T](u.Period)
+ highsSplice := helper.Duplicate(
+ max.Compute(closingsSplice[0]),
+ 2,
+ )
+
+ // Percentage Drawdown = 100 * ((Closings - High Closings) / High Closings)
+ closingsSplice[1] = helper.Skip(closingsSplice[1], max.Period-1)
+
+ percentageDrawdown := helper.MultiplyBy(
+ helper.Divide(
+ helper.Subtract(closingsSplice[1], highsSplice[0]),
+ highsSplice[1],
+ ),
+ 100,
+ )
+
+ // Squared Average = Sma(period, Percent Drawdown * Percent Drawdown)
+ sma := trend.NewSmaWithPeriod[T](u.Period)
+ squaredAverage := helper.Pow(
+ sma.Compute(percentageDrawdown),
+ 2,
+ )
+
+ // Ulcer Index = Sqrt(Squared Average)
+ ulcerIndex := helper.Sqrt(squaredAverage)
+
+ return ulcerIndex
+}
+
+// IdlePeriod is the initial period that Ulcer Index won't yield any results.
+func (u *UlcerIndex[T]) IdlePeriod() int {
+ return (u.Period - 1) * 2
+}
diff --git a/volatility/ulcer_index_test.go b/volatility/ulcer_index_test.go
new file mode 100644
index 0000000..ce3ca7d
--- /dev/null
+++ b/volatility/ulcer_index_test.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2021-2023 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package volatility_test
+
+import (
+ "testing"
+
+ "github.com/cinar/indicator/helper"
+ "github.com/cinar/indicator/volatility"
+)
+
+func TestUlcerIndex(t *testing.T) {
+ type Data struct {
+ Close float64
+ UlcerIndex float64
+ }
+
+ input, err := helper.ReadFromCsvFile[Data]("testdata/ulcer_index.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ inputs := helper.Duplicate(input, 2)
+ closings := helper.Map(inputs[0], func(d *Data) float64 { return d.Close })
+ expected := helper.Map(inputs[1], func(d *Data) float64 { return d.UlcerIndex })
+
+ ui := volatility.NewUlcerIndex[float64]()
+ actual := ui.Compute(closings)
+ actual = helper.RoundDigits(actual, 2)
+
+ expected = helper.Skip(expected, ui.IdlePeriod())
+
+ err = helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+}