Skip to content

Commit

Permalink
Fix options indicators data synchronization (#8564)
Browse files Browse the repository at this point in the history
* Add DualSymbolIndicator unit tests

* Introduce MultiSymbolIndicator for indicators working on multiple symbols

Use the new class as base for DualSymbolIndicator and OptionIndicatorBase.
The OptionIndicatorBase can now detect when ready even if underlying and options market close is different when resolution is daily.

* Accept any BaseData for options indicators

* Improve indicator conversion from python on registration

Also minor fixes

* Cleanup and simplification

* Fix ImpliedVolatility IsReady flag

* Update regression algorithm history count

AutomaticIndicatorWarmupOptionIndicatorsMirrorContractsRegressionAlgorithm history count decreased because options indicators period is now 1 instead of 2

* Address peer review
  • Loading branch information
jhonabreul authored Jan 31, 2025
1 parent 077b6e4 commit de9f9bf
Show file tree
Hide file tree
Showing 18 changed files with 530 additions and 368 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public override void Initialize()
/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public int AlgorithmHistoryDataPoints => 21;
public int AlgorithmHistoryDataPoints => 18;

/// <summary>
/// Final status of the algorithm
Expand Down
2 changes: 1 addition & 1 deletion Algorithm/QCAlgorithm.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3993,7 +3993,7 @@ private void InitializeIndicator<T>(IndicatorBase<T> indicator, Resolution? reso
}
}

private void InitializeOptionIndicator(IndicatorBase<IndicatorDataPoint> indicator, Resolution? resolution, Symbol symbol, Symbol mirrorOption)
private void InitializeOptionIndicator(IndicatorBase<IBaseData> indicator, Resolution? resolution, Symbol symbol, Symbol mirrorOption)
{
RegisterIndicator(symbol, indicator, ResolveConsolidator(symbol, resolution, typeof(QuoteBar)));
RegisterIndicator(symbol.Underlying, indicator, ResolveConsolidator(symbol.Underlying, resolution));
Expand Down
48 changes: 36 additions & 12 deletions Algorithm/QCAlgorithm.Python.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,23 +666,36 @@ public void RegisterIndicator(Symbol symbol, PyObject indicator, IDataConsolidat
IndicatorBase<IBaseDataBar> indicatorDataBar;
IndicatorBase<TradeBar> indicatorTradeBar;

if (indicator.TryConvert(out indicatorDataPoint))
if (indicator.TryConvert<PythonIndicator>(out var pythonIndicator))
{
RegisterIndicator(symbol, indicatorDataPoint, consolidator, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
return;
RegisterIndicator(symbol, WrapPythonIndicator(indicator, pythonIndicator), consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
}
else if (indicator.TryConvert(out indicatorDataPoint))
{
RegisterIndicator(symbol, indicatorDataPoint, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
}
else if (indicator.TryConvert(out indicatorDataBar))
{
RegisterIndicator(symbol, indicatorDataBar, consolidator, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
return;
RegisterIndicator(symbol, indicatorDataBar, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
}
else if (indicator.TryConvert(out indicatorTradeBar))
{
RegisterIndicator(symbol, indicatorTradeBar, consolidator, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
return;
RegisterIndicator(symbol, indicatorTradeBar, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
}
else if (indicator.TryConvert(out IndicatorBase<IBaseData> indicatorBaseData))
{
RegisterIndicator(symbol, indicatorBaseData, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
}
else
{
RegisterIndicator(symbol, WrapPythonIndicator(indicator), consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
}

RegisterIndicator(symbol, WrapPythonIndicator(indicator), consolidator, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
}

/// <summary>
Expand Down Expand Up @@ -1783,20 +1796,31 @@ private dynamic[] GetIndicatorArray(PyObject first, PyObject second = null, PyOb
/// Wraps a custom python indicator and save its reference to _pythonIndicators dictionary
/// </summary>
/// <param name="pyObject">The python implementation of <see cref="IndicatorBase{IBaseDataBar}"/></param>
/// <param name="convertedPythonIndicator">The C# converted <paramref name="pyObject"/> to avoid re-conversion</param>
/// <returns><see cref="PythonIndicator"/> that wraps the python implementation</returns>
private PythonIndicator WrapPythonIndicator(PyObject pyObject)
private PythonIndicator WrapPythonIndicator(PyObject pyObject, PythonIndicator convertedPythonIndicator = null)
{
PythonIndicator pythonIndicator;

if (!_pythonIndicators.TryGetValue(pyObject.Handle, out pythonIndicator))
{
pyObject.TryConvert(out pythonIndicator);
pythonIndicator?.SetIndicator(pyObject);
if (convertedPythonIndicator == null)
{
pyObject.TryConvert(out pythonIndicator);
}
else
{
pythonIndicator = convertedPythonIndicator;
}

if (pythonIndicator == null)
{
pythonIndicator = new PythonIndicator(pyObject);
}
else
{
pythonIndicator.SetIndicator(pyObject);
}

// Save to prevent future additions
_pythonIndicators.Add(pyObject.Handle, pythonIndicator);
Expand Down
69 changes: 25 additions & 44 deletions Indicators/Beta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@
namespace QuantConnect.Indicators
{
/// <summary>
/// In technical analysis Beta indicator is used to measure volatility or risk of a target (ETF) relative to the overall
/// risk (volatility) of the reference (market indexes). The Beta indicators compares target's price movement to the
/// In technical analysis Beta indicator is used to measure volatility or risk of a target (ETF) relative to the overall
/// risk (volatility) of the reference (market indexes). The Beta indicators compares target's price movement to the
/// movements of the indexes over the same period of time.
///
/// It is common practice to use the SPX index as a benchmark of the overall reference market when it comes to Beta
///
/// It is common practice to use the SPX index as a benchmark of the overall reference market when it comes to Beta
/// calculations.
///
/// The indicator only updates when both assets have a price for a time step. When a bar is missing for one of the assets,
///
/// The indicator only updates when both assets have a price for a time step. When a bar is missing for one of the assets,
/// the indicator value fills forward to improve the accuracy of the indicator.
/// </summary>
public class Beta : DualSymbolIndicator<decimal>
public class Beta : DualSymbolIndicator<IBaseDataBar>
{
/// <summary>
/// RollingWindow of returns of the target symbol in the given period
Expand All @@ -48,7 +48,7 @@ public class Beta : DualSymbolIndicator<decimal>
public override bool IsReady => _targetReturns.IsReady && _referenceReturns.IsReady;

/// <summary>
/// Creates a new Beta indicator with the specified name, target, reference,
/// Creates a new Beta indicator with the specified name, target, reference,
/// and period values
/// </summary>
/// <param name="name">The name of this indicator</param>
Expand All @@ -66,11 +66,11 @@ public Beta(string name, Symbol targetSymbol, Symbol referenceSymbol, int period

_targetReturns = new RollingWindow<double>(period);
_referenceReturns = new RollingWindow<double>(period);
WarmUpPeriod = period + 1 + (IsTimezoneDifferent ? 1 : 0);
WarmUpPeriod += (period - 2) + 1;
}

/// <summary>
/// Creates a new Beta indicator with the specified target, reference,
/// Creates a new Beta indicator with the specified target, reference,
/// and period values
/// </summary>
/// <param name="targetSymbol">The target symbol of this indicator</param>
Expand All @@ -82,7 +82,7 @@ public Beta(Symbol targetSymbol, Symbol referenceSymbol, int period)
}

/// <summary>
/// Creates a new Beta indicator with the specified name, period, target and
/// Creates a new Beta indicator with the specified name, period, target and
/// reference values
/// </summary>
/// <param name="name">The name of this indicator</param>
Expand All @@ -95,59 +95,40 @@ public Beta(string name, int period, Symbol targetSymbol, Symbol referenceSymbol
{
}

/// <summary>
/// Adds the closing price to the corresponding symbol's data set (target or reference).
/// Computes returns when there are enough data points for each symbol.
/// </summary>
/// <param name="input">The input value for this symbol</param>
protected override void AddDataPoint(IBaseDataBar input)
{
if (input.Symbol == TargetSymbol)
{
TargetDataPoints.Add(input.Close);
if (TargetDataPoints.Count > 1)
{
_targetReturns.Add(GetNewReturn(TargetDataPoints));
}
}
else if (input.Symbol == ReferenceSymbol)
{
ReferenceDataPoints.Add(input.Close);
if (ReferenceDataPoints.Count > 1)
{
_referenceReturns.Add(GetNewReturn(ReferenceDataPoints));
}
}
else
{
throw new ArgumentException($"The given symbol {input.Symbol} was not {TargetSymbol} or {ReferenceSymbol} symbol");
}
}

/// <summary>
/// Computes the returns with the new given data point and the last given data point
/// </summary>
/// <param name="rollingWindow">The collection of data points from which we want
/// to compute the return</param>
/// <returns>The returns with the new given data point</returns>
private static double GetNewReturn(RollingWindow<decimal> rollingWindow)
private static double GetNewReturn(IReadOnlyWindow<IBaseDataBar> rollingWindow)
{
return (double)((rollingWindow[0].SafeDivision(rollingWindow[1]) - 1));
return (double)(rollingWindow[0].Close.SafeDivision(rollingWindow[1].Close) - 1);
}

/// <summary>
/// Computes the beta value of the target in relation with the reference
/// using the target and reference returns
/// </summary>
protected override void ComputeIndicator()
protected override decimal ComputeIndicator()
{
if (TargetDataPoints.IsReady)
{
_targetReturns.Add(GetNewReturn(TargetDataPoints));
}

if (ReferenceDataPoints.IsReady)
{
_referenceReturns.Add(GetNewReturn(ReferenceDataPoints));
}

var varianceComputed = _referenceReturns.Variance();
var covarianceComputed = _targetReturns.Covariance(_referenceReturns);

// Avoid division with NaN or by zero
var variance = !varianceComputed.IsNaNOrZero() ? varianceComputed : 1;
var covariance = !covarianceComputed.IsNaNOrZero() ? covarianceComputed : 0;
IndicatorValue = (decimal)(covariance / variance);
return (decimal)(covariance / variance);
}

/// <summary>
Expand Down
60 changes: 21 additions & 39 deletions Indicators/Correlation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,29 @@
*/

using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Market;

namespace QuantConnect.Indicators
{
/// <summary>
/// The Correlation Indicator is a valuable tool in technical analysis, designed to quantify the degree of
/// relationship between the price movements of a target security (e.g., a stock or ETF) and a reference
/// market index. It measures how closely the target’s price changes are aligned with the fluctuations of
/// the index over a specific period of time, providing insights into the target’s susceptibility to market
/// The Correlation Indicator is a valuable tool in technical analysis, designed to quantify the degree of
/// relationship between the price movements of a target security (e.g., a stock or ETF) and a reference
/// market index. It measures how closely the target’s price changes are aligned with the fluctuations of
/// the index over a specific period of time, providing insights into the target’s susceptibility to market
/// movements.
/// A positive correlation indicates that the target tends to move in the same direction as the market index,
/// while a negative correlation suggests an inverse relationship. A correlation close to 0 implies a weak or
/// A positive correlation indicates that the target tends to move in the same direction as the market index,
/// while a negative correlation suggests an inverse relationship. A correlation close to 0 implies a weak or
/// no linear relationship.
/// Commonly, the SPX index is employed as the benchmark for the overall market when calculating correlation,
/// ensuring a consistent and reliable reference point. This helps traders and investors make informed decisions
/// Commonly, the SPX index is employed as the benchmark for the overall market when calculating correlation,
/// ensuring a consistent and reliable reference point. This helps traders and investors make informed decisions
/// regarding the risk and behavior of the target security in relation to market trends.
///
/// The indicator only updates when both assets have a price for a time step. When a bar is missing for one of the assets,
///
/// The indicator only updates when both assets have a price for a time step. When a bar is missing for one of the assets,
/// the indicator value fills forward to improve the accuracy of the indicator.
/// </summary>
public class Correlation : DualSymbolIndicator<double>
public class Correlation : DualSymbolIndicator<IBaseDataBar>
{
/// <summary>
/// Correlation type
Expand All @@ -47,7 +49,7 @@ public class Correlation : DualSymbolIndicator<double>
public override bool IsReady => TargetDataPoints.IsReady && ReferenceDataPoints.IsReady;

/// <summary>
/// Creates a new Correlation indicator with the specified name, target, reference,
/// Creates a new Correlation indicator with the specified name, target, reference,
/// and period values
/// </summary>
/// <param name="name">The name of this indicator</param>
Expand All @@ -63,12 +65,11 @@ public Correlation(string name, Symbol targetSymbol, Symbol referenceSymbol, int
{
throw new ArgumentException($"Period parameter for Correlation indicator must be greater than 2 but was {period}");
}
WarmUpPeriod = period + (IsTimezoneDifferent ? 1 : 0);
_correlationType = correlationType;
}

/// <summary>
/// Creates a new Correlation indicator with the specified target, reference,
/// Creates a new Correlation indicator with the specified target, reference,
/// and period values
/// </summary>
/// <param name="targetSymbol">The target symbol of this indicator</param>
Expand All @@ -80,47 +81,28 @@ public Correlation(Symbol targetSymbol, Symbol referenceSymbol, int period, Corr
{
}

/// <summary>
/// Adds the closing price to the target or reference symbol's data set.
/// </summary>
/// <param name="input">The input value for this symbol</param>
/// <exception cref="ArgumentException">Thrown if the input symbol is not the target or reference symbol.</exception>
protected override void AddDataPoint(IBaseDataBar input)
{
if (input.Symbol == TargetSymbol)
{
TargetDataPoints.Add((double)input.Close);
}
else if (input.Symbol == ReferenceSymbol)
{
ReferenceDataPoints.Add((double)input.Close);
}
else
{
throw new ArgumentException($"The given symbol {input.Symbol} was not {TargetSymbol} or {ReferenceSymbol} symbol");
}
}

/// <summary>
/// Computes the correlation value usuing symbols values
/// correlation values assing into _correlation property
/// </summary>
protected override void ComputeIndicator()
protected override decimal ComputeIndicator()
{
var targetDataPoints = TargetDataPoints.Select(x => (double)x.Close);
var referenceDataPoints = ReferenceDataPoints.Select(x => (double)x.Close);
var newCorrelation = 0d;
if (_correlationType == CorrelationType.Pearson)
{
newCorrelation = MathNet.Numerics.Statistics.Correlation.Pearson(TargetDataPoints, ReferenceDataPoints);
newCorrelation = MathNet.Numerics.Statistics.Correlation.Pearson(targetDataPoints, referenceDataPoints);
}
if (_correlationType == CorrelationType.Spearman)
{
newCorrelation = MathNet.Numerics.Statistics.Correlation.Spearman(TargetDataPoints, ReferenceDataPoints);
newCorrelation = MathNet.Numerics.Statistics.Correlation.Spearman(targetDataPoints, referenceDataPoints);
}
if (newCorrelation.IsNaNOrZero())
{
newCorrelation = 0;
}
IndicatorValue = Extensions.SafeDecimalCast(newCorrelation);
return Extensions.SafeDecimalCast(newCorrelation);
}
}
}
Loading

0 comments on commit de9f9bf

Please sign in to comment.