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) + } +}