This commit is contained in:
Miha Kralj
2024-10-04 21:31:25 -07:00
parent 99a3785d72
commit 30d93e724d
10 changed files with 413 additions and 1 deletions
+50
View File
@@ -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.
+2 -1
View File
@@ -27,9 +27,10 @@
## Installation to Quantower
- `<Quantower_root>` is the directory where Quantower is installed - where `Start.lnk` launcher is
- `<Quantower_root>` 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 `<Quantower_root>\Settings\Scripts\Indicators\Averages\Averages.dll`
- Copy `Statistics.dll` from Releases to `<Quantower_root>\Settings\Scripts\Indicators\Statistics\Statistics.dll`
- Copy `Volatility.dll` from Releases to `<Quantower_root>\Settings\Scripts\Indicators\Volatility\Volatility.dll`
- Copy `SyntheticVendor.dll` from Releases to `<Quantower_root>\Settings\Scripts\Vendors\SyntheticVendor\SyntheticVendor.dll`
+85
View File
@@ -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;
}
}
+76
View File
@@ -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;
}
}
+87
View File
@@ -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. Dorseys 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;
}
}
}
+28
View File
@@ -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)
@@ -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();
}
}
+28
View File
@@ -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();
}
}
+29
View File
@@ -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();
}
}