diff --git a/docs/indicators/volatility/rvi/calc.md b/docs/indicators/volatility/rvi/calc.md new file mode 100644 index 00000000..eeb283a4 --- /dev/null +++ b/docs/indicators/volatility/rvi/calc.md @@ -0,0 +1,50 @@ +# The Math Behind RVI + +## Components of RVI + +The **Relative Volatility Index (RVI)** measures the direction of volatility in the market, using components like: + +- Standard deviation of price changes +- Simple moving average (SMA) to smooth volatility +- Separation of up and down price movements + +### RVI Formula + +The RVI is calculated using the following formula: + +$$ +\text{RVI}_t = 100 \times \frac{\text{SMA}(\sigma_{\text{up}}, N)}{\text{SMA}(\sigma_{\text{up}}, N) + \text{SMA}(\sigma_{\text{down}}, N)} +$$ + +Where: +- \( \text{RVI}_t \) is the RVI value at time \( t \) +- \( \sigma_{\text{up}} \) is the standard deviation of up moves over the lookback period \( N \) +- \( \sigma_{\text{down}} \) is the standard deviation of down moves over the lookback period \( N \) +- \( \text{SMA} \) represents the simple moving average applied over \( N \) periods + +### Up and Down Move Calculation + +The standard deviations \( \sigma_{\text{up}} \) and \( \sigma_{\text{down}} \) are calculated based on the price changes: + +$$ +\Delta \text{Price} = \text{Close}_t - \text{Close}_{t-1} +$$ + +- If \( \Delta \text{Price} > 0 \), it contributes to \( \sigma_{\text{up}} \) +- If \( \Delta \text{Price} < 0 \), it contributes to \( \sigma_{\text{down}} \) + +### Parameter Definitions + +RVI uses the following main parameters: + +- **Lookback period** (\( N \)): The number of periods used to calculate the standard deviations and SMAs. A typical value is 14. +- **Smoothing with SMA**: The standard deviations of up and down moves are smoothed using a simple moving average (SMA), making the RVI less sensitive to short-term fluctuations. + +### Computational Process + +For each new data point: +- Calculate the price change (\( \Delta \text{Price} \)) from the previous period. +- Separate the price changes into up moves and down moves. +- Compute the standard deviations (\( \sigma_{\text{up}} \) and \( \sigma_{\text{down}} \)) over the last \( N \) periods. +- Apply the simple moving average (SMA) to both up and down standard deviations. +- Use the RVI formula to produce the final RVI value. diff --git a/docs/indicators/volatility/rvi/rvi.md b/docs/indicators/volatility/rvi/rvi.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/readme.md b/docs/readme.md index aa548a91..f3ee745e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -27,9 +27,10 @@ ## Installation to Quantower -- `` is the directory where Quantower is installed - where `Start.lnk` launcher is +- `` is the directory where Quantower is installed - where `Start.lnk` launcher is. Copy any or all `dll` files as below: - Copy `Averages.dll` from Releases to `\Settings\Scripts\Indicators\Averages\Averages.dll` - Copy `Statistics.dll` from Releases to `\Settings\Scripts\Indicators\Statistics\Statistics.dll` +- Copy `Volatility.dll` from Releases to `\Settings\Scripts\Indicators\Volatility\Volatility.dll` - Copy `SyntheticVendor.dll` from Releases to `\Settings\Scripts\Vendors\SyntheticVendor\SyntheticVendor.dll` diff --git a/lib/volatility/Historical.cs b/lib/volatility/Historical.cs new file mode 100644 index 00000000..1b9998ba --- /dev/null +++ b/lib/volatility/Historical.cs @@ -0,0 +1,85 @@ +namespace QuanTAlib; + +public class Historical : AbstractBase +{ + private readonly int Period; + private readonly bool IsAnnualized; + private readonly CircularBuffer _buffer; + private readonly CircularBuffer _logReturns; + private double _previousClose; + + public Historical(int period, bool isAnnualized = true) : base() + { + if (period < 2) + { + throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); + } + Period = period; + IsAnnualized = isAnnualized; + WarmupPeriod = period + 1; // We need one extra data point to calculate the first return + _buffer = new CircularBuffer(period + 1); + _logReturns = new CircularBuffer(period); + Name = $"Historical(period={period}, annualized={isAnnualized})"; + Init(); + } + + public Historical(object source, int period, bool isAnnualized = true) : this(period, isAnnualized) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + public override void Init() + { + base.Init(); + _buffer.Clear(); + _logReturns.Clear(); + _previousClose = 0; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Input.Value; + _index++; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + _buffer.Add(Input.Value, Input.IsNew); + + double volatility = 0; + if (_buffer.Count > 1) + { + if (_previousClose != 0) + { + double logReturn = Math.Log(Input.Value / _previousClose); + _logReturns.Add(logReturn, Input.IsNew); + } + + if (_logReturns.Count == Period) + { + var returns = _logReturns.GetSpan().ToArray(); + double mean = returns.Average(); + double sumOfSquaredDifferences = returns.Sum(x => Math.Pow(x - mean, 2)); + + double variance = sumOfSquaredDifferences / (Period - 1); // Using sample standard deviation + volatility = Math.Sqrt(variance); + + if (IsAnnualized) + { + // Assuming 252 trading days in a year. Adjust as needed. + volatility *= Math.Sqrt(252); + } + } + } + + _previousClose = Input.Value; + IsHot = _index >= WarmupPeriod; + return volatility; + } +} diff --git a/lib/volatility/Realized.cs b/lib/volatility/Realized.cs new file mode 100644 index 00000000..075a0dbe --- /dev/null +++ b/lib/volatility/Realized.cs @@ -0,0 +1,76 @@ +namespace QuanTAlib; +public class Realized : AbstractBase +{ + private readonly int Period; + private readonly bool IsAnnualized; + private readonly CircularBuffer _returns; + private double _previousClose; + private double _sumSquaredReturns; + + public Realized(int period, bool isAnnualized = true) : base() + { + if (period < 2) + { + throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); + } + Period = period; + IsAnnualized = isAnnualized; + WarmupPeriod = period + 1; // We need one extra data point to calculate the first return + _returns = new CircularBuffer(period); + Name = $"Realized(period={period}, annualized={isAnnualized})"; + Init(); + } + + public override void Init() + { + base.Init(); + _returns.Clear(); + _previousClose = 0; + _sumSquaredReturns = 0; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Input.Value; + _index++; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + double volatility = 0; + if (_previousClose != 0) + { + double logReturn = Math.Log(Input.Value / _previousClose); + + if (_returns.Count == Period) + { + // Remove the oldest squared return from the sum + _sumSquaredReturns -= Math.Pow(_returns[0], 2); + } + + _returns.Add(logReturn, Input.IsNew); + _sumSquaredReturns += Math.Pow(logReturn, 2); + + if (_returns.Count == Period) + { + double variance = _sumSquaredReturns / Period; + volatility = Math.Sqrt(variance); + + if (IsAnnualized) + { + // Assuming 252 trading days in a year. Adjust as needed. + volatility *= Math.Sqrt(252); + } + } + } + + _previousClose = Input.Value; + IsHot = _index >= WarmupPeriod; + return volatility; + } +} \ No newline at end of file diff --git a/lib/volatility/Rvi.cs b/lib/volatility/Rvi.cs new file mode 100644 index 00000000..276d733e --- /dev/null +++ b/lib/volatility/Rvi.cs @@ -0,0 +1,87 @@ +/* +Reference: + Donald Dorsey, who introduced the concept in the 1993 issue of Technical Analysis + of Stocks & Commodities Magazine. He designed the RVI to focus on the direction of + price movements in relation to volatility. Dorsey’s methodology is often cited in + technical analysis literature and further elaborated on in various technical analysis + guides and platforms. +*/ + + +using System; + +namespace QuanTAlib +{ + public class Rvi : AbstractBase + { + private readonly int Period; + private Stddev _upStdDev, _downStdDev; + private Sma _upSma, _downSma; + private double _previousClose; + + public Rvi(int period) : base() + { + if (period < 2) + { + throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); + } + Period = period; + WarmupPeriod = period; + Name = $"RVI(period={period})"; + _upStdDev = new Stddev(Period); + _downStdDev = new Stddev(Period); + _upSma = new(Period); + _downSma = new(Period); + Init(); + } + + public Rvi(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + public override void Init() + { + base.Init(); + _previousClose = 0; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + double close = Input.Value; + double change = close - _previousClose; + + double upMove = Math.Max(change, 0); + double downMove = Math.Max(-change, 0); + + _upSma.Calc(_upStdDev.Calc(new TValue(Input.Time, upMove, Input.IsNew))); + _downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew))); + + double rvi; + if (_upSma.Value + _downSma.Value != 0) + { + rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value); + } + else + { + rvi = 0; + } + + _previousClose = close; + IsHot = _index >= WarmupPeriod; + return rvi; + } + } +} \ No newline at end of file diff --git a/lib/volatility/todo.md b/lib/volatility/todo.md new file mode 100644 index 00000000..512a9e63 --- /dev/null +++ b/lib/volatility/todo.md @@ -0,0 +1,28 @@ +Single Value Input (Typically Closing Prices) + +Jurik Volatility (Volty) + +**Standard Deviation** +**Relative Volatility Index (RVI)** +Ulcer Index +ARCH/GARCH Models +Exponential Weighted Moving Average (EWMA) Volatility +Conditional Volatility +Volatility Ratio +Close-to-Close Volatility +Volatility of Volatility (VOV) +Volatility Cone +Bollinger Bands +Stochastic Volatility: Typically modeled using closing prices, but can incorporate other price information + +OHLC Input (Open, High, Low, Close) + +Garman-Klass Volatility +Rogers-Satchell Volatility +Yang-Zhang Volatility +Parkinson Volatility (High, Low) +Average True Range (ATR) (High, Low, Close) +Chaikin Volatility (High, Low) +Keltner Channels (typically Close, High, Low) +High-Low Volatility (High, Low) + diff --git a/quantower/Volatility/HistoricalIndicator.cs b/quantower/Volatility/HistoricalIndicator.cs new file mode 100644 index 00000000..9196da81 --- /dev/null +++ b/quantower/Volatility/HistoricalIndicator.cs @@ -0,0 +1,28 @@ +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class HistoricalIndicator : IndicatorBase +{ + [InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)] + public int Period { get; set; } = 20; + + [InputParameter("Annualized", sortIndex: 2)] + public bool IsAnnualized { get; set; } = true; + + private Historical? historical; + protected override AbstractBase QuanTAlib => historical!; + public override string ShortName => $"Historical Volatility {Period}{(IsAnnualized ? " - Annualized" : "")} : {SourceName}"; + + public HistoricalIndicator() : base() + { + Name = "HV - Historical Volatility"; + SeparateWindow = true; + } + + protected override void InitIndicator() + { + historical = new(Period, IsAnnualized); + MinHistoryDepths = historical.WarmupPeriod; + base.InitIndicator(); + } +} \ No newline at end of file diff --git a/quantower/Volatility/RealizedIndicator.cs b/quantower/Volatility/RealizedIndicator.cs new file mode 100644 index 00000000..44039edd --- /dev/null +++ b/quantower/Volatility/RealizedIndicator.cs @@ -0,0 +1,28 @@ +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class RealizedIndicator : IndicatorBase +{ + [InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)] + public int Period { get; set; } = 20; + + [InputParameter("Annualized", sortIndex: 2)] + public bool IsAnnualized { get; set; } = true; + + private Realized? realized; + protected override AbstractBase QuanTAlib => realized!; + public override string ShortName => $"Realized Volatility {Period}{(IsAnnualized ? " - Annualized" : "")} : {SourceName}"; + + public RealizedIndicator() : base() + { + Name = "RV - Realized Volatility"; + SeparateWindow = true; + } + + protected override void InitIndicator() + { + realized = new(Period, IsAnnualized); + MinHistoryDepths = realized.WarmupPeriod; + base.InitIndicator(); + } +} \ No newline at end of file diff --git a/quantower/Volatility/RviIndicator.cs b/quantower/Volatility/RviIndicator.cs new file mode 100644 index 00000000..7e85d13a --- /dev/null +++ b/quantower/Volatility/RviIndicator.cs @@ -0,0 +1,29 @@ +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class RviIndicator : IndicatorBase +{ + [InputParameter("Period", sortIndex: 1, 2, 100, 1, 0)] + public int Period { get; set; } = 10; + + private Rvi? rvi; + protected override AbstractBase QuanTAlib => rvi!; + public override string ShortName => $"RVI {Period} : {SourceName}"; + + public RviIndicator() : base() + { + Name = "RVI - Relative Volatility Index"; + SeparateWindow = true; + + // Adding upper and lower reference lines + //AddLineSeries("UpperLevel", 80, System.Drawing.Color.Gray, 1, LineStyle.Dot); + //AddLineSeries("LowerLevel", 20, System.Drawing.Color.Gray, 1, LineStyle.Dot); + } + + protected override void InitIndicator() + { + rvi = new Rvi(Period); + MinHistoryDepths = rvi.WarmupPeriod; + base.InitIndicator(); + } +}