Skip to content

Commit de9f9bf

Browse files
authored
Fix options indicators data synchronization (#8564)
* 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
1 parent 077b6e4 commit de9f9bf

18 files changed

+530
-368
lines changed

Algorithm.CSharp/AutomaticIndicatorWarmupOptionIndicatorsMirrorContractsRegressionAlgorithm.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public override void Initialize()
8282
/// <summary>
8383
/// Data Points count of the algorithm history
8484
/// </summary>
85-
public int AlgorithmHistoryDataPoints => 21;
85+
public int AlgorithmHistoryDataPoints => 18;
8686

8787
/// <summary>
8888
/// Final status of the algorithm

Algorithm/QCAlgorithm.Indicators.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3993,7 +3993,7 @@ private void InitializeIndicator<T>(IndicatorBase<T> indicator, Resolution? reso
39933993
}
39943994
}
39953995

3996-
private void InitializeOptionIndicator(IndicatorBase<IndicatorDataPoint> indicator, Resolution? resolution, Symbol symbol, Symbol mirrorOption)
3996+
private void InitializeOptionIndicator(IndicatorBase<IBaseData> indicator, Resolution? resolution, Symbol symbol, Symbol mirrorOption)
39973997
{
39983998
RegisterIndicator(symbol, indicator, ResolveConsolidator(symbol, resolution, typeof(QuoteBar)));
39993999
RegisterIndicator(symbol.Underlying, indicator, ResolveConsolidator(symbol.Underlying, resolution));

Algorithm/QCAlgorithm.Python.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -666,23 +666,36 @@ public void RegisterIndicator(Symbol symbol, PyObject indicator, IDataConsolidat
666666
IndicatorBase<IBaseDataBar> indicatorDataBar;
667667
IndicatorBase<TradeBar> indicatorTradeBar;
668668

669-
if (indicator.TryConvert(out indicatorDataPoint))
669+
if (indicator.TryConvert<PythonIndicator>(out var pythonIndicator))
670670
{
671-
RegisterIndicator(symbol, indicatorDataPoint, consolidator, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
672-
return;
671+
RegisterIndicator(symbol, WrapPythonIndicator(indicator, pythonIndicator), consolidator,
672+
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
673+
}
674+
else if (indicator.TryConvert(out indicatorDataPoint))
675+
{
676+
RegisterIndicator(symbol, indicatorDataPoint, consolidator,
677+
selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
673678
}
674679
else if (indicator.TryConvert(out indicatorDataBar))
675680
{
676-
RegisterIndicator(symbol, indicatorDataBar, consolidator, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
677-
return;
681+
RegisterIndicator(symbol, indicatorDataBar, consolidator,
682+
selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
678683
}
679684
else if (indicator.TryConvert(out indicatorTradeBar))
680685
{
681-
RegisterIndicator(symbol, indicatorTradeBar, consolidator, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
682-
return;
686+
RegisterIndicator(symbol, indicatorTradeBar, consolidator,
687+
selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
688+
}
689+
else if (indicator.TryConvert(out IndicatorBase<IBaseData> indicatorBaseData))
690+
{
691+
RegisterIndicator(symbol, indicatorBaseData, consolidator,
692+
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
693+
}
694+
else
695+
{
696+
RegisterIndicator(symbol, WrapPythonIndicator(indicator), consolidator,
697+
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
683698
}
684-
685-
RegisterIndicator(symbol, WrapPythonIndicator(indicator), consolidator, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
686699
}
687700

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

17911805
if (!_pythonIndicators.TryGetValue(pyObject.Handle, out pythonIndicator))
17921806
{
1793-
pyObject.TryConvert(out pythonIndicator);
1794-
pythonIndicator?.SetIndicator(pyObject);
1807+
if (convertedPythonIndicator == null)
1808+
{
1809+
pyObject.TryConvert(out pythonIndicator);
1810+
}
1811+
else
1812+
{
1813+
pythonIndicator = convertedPythonIndicator;
1814+
}
17951815

17961816
if (pythonIndicator == null)
17971817
{
17981818
pythonIndicator = new PythonIndicator(pyObject);
17991819
}
1820+
else
1821+
{
1822+
pythonIndicator.SetIndicator(pyObject);
1823+
}
18001824

18011825
// Save to prevent future additions
18021826
_pythonIndicators.Add(pyObject.Handle, pythonIndicator);

Indicators/Beta.cs

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@
2020
namespace QuantConnect.Indicators
2121
{
2222
/// <summary>
23-
/// In technical analysis Beta indicator is used to measure volatility or risk of a target (ETF) relative to the overall
24-
/// risk (volatility) of the reference (market indexes). The Beta indicators compares target's price movement to the
23+
/// In technical analysis Beta indicator is used to measure volatility or risk of a target (ETF) relative to the overall
24+
/// risk (volatility) of the reference (market indexes). The Beta indicators compares target's price movement to the
2525
/// movements of the indexes over the same period of time.
26-
///
27-
/// It is common practice to use the SPX index as a benchmark of the overall reference market when it comes to Beta
26+
///
27+
/// It is common practice to use the SPX index as a benchmark of the overall reference market when it comes to Beta
2828
/// calculations.
29-
///
30-
/// The indicator only updates when both assets have a price for a time step. When a bar is missing for one of the assets,
29+
///
30+
/// The indicator only updates when both assets have a price for a time step. When a bar is missing for one of the assets,
3131
/// the indicator value fills forward to improve the accuracy of the indicator.
3232
/// </summary>
33-
public class Beta : DualSymbolIndicator<decimal>
33+
public class Beta : DualSymbolIndicator<IBaseDataBar>
3434
{
3535
/// <summary>
3636
/// RollingWindow of returns of the target symbol in the given period
@@ -48,7 +48,7 @@ public class Beta : DualSymbolIndicator<decimal>
4848
public override bool IsReady => _targetReturns.IsReady && _referenceReturns.IsReady;
4949

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

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

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

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

98-
/// <summary>
99-
/// Adds the closing price to the corresponding symbol's data set (target or reference).
100-
/// Computes returns when there are enough data points for each symbol.
101-
/// </summary>
102-
/// <param name="input">The input value for this symbol</param>
103-
protected override void AddDataPoint(IBaseDataBar input)
104-
{
105-
if (input.Symbol == TargetSymbol)
106-
{
107-
TargetDataPoints.Add(input.Close);
108-
if (TargetDataPoints.Count > 1)
109-
{
110-
_targetReturns.Add(GetNewReturn(TargetDataPoints));
111-
}
112-
}
113-
else if (input.Symbol == ReferenceSymbol)
114-
{
115-
ReferenceDataPoints.Add(input.Close);
116-
if (ReferenceDataPoints.Count > 1)
117-
{
118-
_referenceReturns.Add(GetNewReturn(ReferenceDataPoints));
119-
}
120-
}
121-
else
122-
{
123-
throw new ArgumentException($"The given symbol {input.Symbol} was not {TargetSymbol} or {ReferenceSymbol} symbol");
124-
}
125-
}
126-
12798
/// <summary>
12899
/// Computes the returns with the new given data point and the last given data point
129100
/// </summary>
130101
/// <param name="rollingWindow">The collection of data points from which we want
131102
/// to compute the return</param>
132103
/// <returns>The returns with the new given data point</returns>
133-
private static double GetNewReturn(RollingWindow<decimal> rollingWindow)
104+
private static double GetNewReturn(IReadOnlyWindow<IBaseDataBar> rollingWindow)
134105
{
135-
return (double)((rollingWindow[0].SafeDivision(rollingWindow[1]) - 1));
106+
return (double)(rollingWindow[0].Close.SafeDivision(rollingWindow[1].Close) - 1);
136107
}
137108

138109
/// <summary>
139110
/// Computes the beta value of the target in relation with the reference
140111
/// using the target and reference returns
141112
/// </summary>
142-
protected override void ComputeIndicator()
113+
protected override decimal ComputeIndicator()
143114
{
115+
if (TargetDataPoints.IsReady)
116+
{
117+
_targetReturns.Add(GetNewReturn(TargetDataPoints));
118+
}
119+
120+
if (ReferenceDataPoints.IsReady)
121+
{
122+
_referenceReturns.Add(GetNewReturn(ReferenceDataPoints));
123+
}
124+
144125
var varianceComputed = _referenceReturns.Variance();
145126
var covarianceComputed = _targetReturns.Covariance(_referenceReturns);
146127

147128
// Avoid division with NaN or by zero
148129
var variance = !varianceComputed.IsNaNOrZero() ? varianceComputed : 1;
149130
var covariance = !covarianceComputed.IsNaNOrZero() ? covarianceComputed : 0;
150-
IndicatorValue = (decimal)(covariance / variance);
131+
return (decimal)(covariance / variance);
151132
}
152133

153134
/// <summary>

Indicators/Correlation.cs

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,29 @@
1414
*/
1515

1616
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
1719
using QuantConnect.Data.Market;
1820

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

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

7071
/// <summary>
71-
/// Creates a new Correlation indicator with the specified target, reference,
72+
/// Creates a new Correlation indicator with the specified target, reference,
7273
/// and period values
7374
/// </summary>
7475
/// <param name="targetSymbol">The target symbol of this indicator</param>
@@ -80,47 +81,28 @@ public Correlation(Symbol targetSymbol, Symbol referenceSymbol, int period, Corr
8081
{
8182
}
8283

83-
/// <summary>
84-
/// Adds the closing price to the target or reference symbol's data set.
85-
/// </summary>
86-
/// <param name="input">The input value for this symbol</param>
87-
/// <exception cref="ArgumentException">Thrown if the input symbol is not the target or reference symbol.</exception>
88-
protected override void AddDataPoint(IBaseDataBar input)
89-
{
90-
if (input.Symbol == TargetSymbol)
91-
{
92-
TargetDataPoints.Add((double)input.Close);
93-
}
94-
else if (input.Symbol == ReferenceSymbol)
95-
{
96-
ReferenceDataPoints.Add((double)input.Close);
97-
}
98-
else
99-
{
100-
throw new ArgumentException($"The given symbol {input.Symbol} was not {TargetSymbol} or {ReferenceSymbol} symbol");
101-
}
102-
}
103-
10484
/// <summary>
10585
/// Computes the correlation value usuing symbols values
10686
/// correlation values assing into _correlation property
10787
/// </summary>
108-
protected override void ComputeIndicator()
88+
protected override decimal ComputeIndicator()
10989
{
90+
var targetDataPoints = TargetDataPoints.Select(x => (double)x.Close);
91+
var referenceDataPoints = ReferenceDataPoints.Select(x => (double)x.Close);
11092
var newCorrelation = 0d;
11193
if (_correlationType == CorrelationType.Pearson)
11294
{
113-
newCorrelation = MathNet.Numerics.Statistics.Correlation.Pearson(TargetDataPoints, ReferenceDataPoints);
95+
newCorrelation = MathNet.Numerics.Statistics.Correlation.Pearson(targetDataPoints, referenceDataPoints);
11496
}
11597
if (_correlationType == CorrelationType.Spearman)
11698
{
117-
newCorrelation = MathNet.Numerics.Statistics.Correlation.Spearman(TargetDataPoints, ReferenceDataPoints);
99+
newCorrelation = MathNet.Numerics.Statistics.Correlation.Spearman(targetDataPoints, referenceDataPoints);
118100
}
119101
if (newCorrelation.IsNaNOrZero())
120102
{
121103
newCorrelation = 0;
122104
}
123-
IndicatorValue = Extensions.SafeDecimalCast(newCorrelation);
105+
return Extensions.SafeDecimalCast(newCorrelation);
124106
}
125107
}
126108
}

0 commit comments

Comments
 (0)