Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SqueezeMomentum (SM) indicator #8462

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Algorithm/QCAlgorithm.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,27 @@ public SortinoRatio SORTINO(Symbol symbol, int sortinoPeriod, double minimumAcce
return sortinoRatio;
}

/// <summary>
/// Creates a Squeeze Momentum indicator to identify market squeezes and potential breakouts.
/// Compares Bollinger Bands and Keltner Channels to signal low or high volatility periods.
/// </summary>
/// <param name="symbol">The symbol for which the indicator is calculated.</param>
/// <param name="bollingerPeriod">The period for Bollinger Bands.</param>
/// <param name="bollingerMultiplier">The multiplier for the Bollinger Bands' standard deviation.</param>
/// <param name="keltnerPeriod">The period for Keltner Channels.</param>
/// <param name="keltnerMultiplier">The multiplier for the Average True Range in Keltner Channels.</param>
/// <param name="resolution">The resolution of the data.</param>
/// <param name="selector">Selects a value from the BaseData to send into the indicator. If null, defaults to the Value property of BaseData (x => x.Value).</param>
/// <returns>The configured Squeeze Momentum indicator.</returns>
[DocumentationAttribute(Indicators)]
public SqueezeMomentum SM(Symbol symbol, int bollingerPeriod = 20, decimal bollingerMultiplier = 2m, int keltnerPeriod = 20,
decimal keltnerMultiplier = 1.5m, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
{
var name = CreateIndicatorName(symbol, $"SM({bollingerPeriod}, {bollingerMultiplier}, {keltnerPeriod}, {keltnerMultiplier})", resolution);
var squeezeMomentum = new SqueezeMomentum(name, bollingerPeriod, bollingerMultiplier, keltnerPeriod, keltnerMultiplier);
InitializeIndicator(squeezeMomentum, resolution, selector, symbol);
return squeezeMomentum;
}

/// <summary>
/// Creates an SimpleMovingAverage indicator for the symbol. The indicator will be automatically
Expand Down
11 changes: 9 additions & 2 deletions Indicators/IndicatorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public abstract class IndicatorBase<T> : IndicatorBase
/// <param name="name">The name of this indicator</param>
protected IndicatorBase(string name)
: base(name)
{}
{ }

/// <summary>
/// Updates the state of this indicator with the given value and returns true
Expand Down Expand Up @@ -271,7 +271,14 @@ public override bool Update(IBaseData input)

if (!(input is T))
{
throw new ArgumentException($"IndicatorBase.Update() 'input' expected to be of type {typeof(T)} but is of type {input.GetType()}");
if (typeof(T) == typeof(IndicatorDataPoint))
{
input = new IndicatorDataPoint(input.EndTime, input.Value);
}
else
{
throw new ArgumentException($"IndicatorBase.Update() 'input' expected to be of type {typeof(T)} but is of type {input.GetType()}");
}
}
_previousInput[input.Symbol.ID] = (T)input;

Expand Down
4 changes: 2 additions & 2 deletions Indicators/KeltnerChannels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public KeltnerChannels(string name, int period, decimal k, MovingAverageType mov
WarmUpPeriod = period;

//Initialise ATR and SMA
AverageTrueRange = new AverageTrueRange(name + "_AverageTrueRange", period, movingAverageType);
AverageTrueRange = new AverageTrueRange(name + "_AverageTrueRange", period, MovingAverageType.Simple);
MiddleBand = movingAverageType.AsIndicator(name + "_MiddleBand", period);

//Compute Lower Band
Expand Down Expand Up @@ -117,7 +117,7 @@ protected override decimal ComputeNextValue(IBaseDataBar input)
{
AverageTrueRange.Update(input);

var typicalPrice = (input.High + input.Low + input.Close)/3m;
var typicalPrice = (input.High + input.Low + input.Close) / 3m;
MiddleBand.Update(input.EndTime, typicalPrice);

// poke the upper/lower bands, they actually don't use the input, they compute
Expand Down
104 changes: 104 additions & 0 deletions Indicators/SqueezeMomentum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using QuantConnect.Data.Market;

namespace QuantConnect.Indicators
{
/// <summary>
/// The SqueezeMomentum indicator calculates whether the market is in a "squeeze" condition,
/// determined by comparing Bollinger Bands to Keltner Channels. When the Bollinger Bands are
/// inside the Keltner Channels, the indicator returns 1 (squeeze on). Otherwise, it returns -1 (squeeze off).
/// </summary>
public class SqueezeMomentum : BarIndicator, IIndicatorWarmUpPeriodProvider
{
/// <summary>
/// The Bollinger Bands indicator used to calculate the upper, lower, and middle bands.
/// </summary>
public BollingerBands BollingerBands { get; }

/// <summary>
/// The Keltner Channels indicator used to calculate the upper, lower, and middle channels.
/// </summary>
public KeltnerChannels KeltnerChannels { get; }

/// <summary>
/// Initializes a new instance of the <see cref="SqueezeMomentum"/> class.
/// </summary>
/// <param name="name">The name of the indicator.</param>
/// <param name="bollingerPeriod">The period used for the Bollinger Bands calculation.</param>
/// <param name="bollingerMultiplier">The multiplier for the Bollinger Bands width.</param>
/// <param name="keltnerPeriod">The period used for the Average True Range (ATR) calculation in Keltner Channels.</param>
/// <param name="keltnerMultiplier">The multiplier applied to the ATR for calculating Keltner Channels.</param>
public SqueezeMomentum(string name, int bollingerPeriod, decimal bollingerMultiplier, int keltnerPeriod, decimal keltnerMultiplier) : base(name)
{
BollingerBands = new BollingerBands(bollingerPeriod, bollingerMultiplier);
KeltnerChannels = new KeltnerChannels(keltnerPeriod, keltnerMultiplier, MovingAverageType.Exponential);
WarmUpPeriod = Math.Max(bollingerPeriod, keltnerPeriod);
}

/// <summary>
/// Gets the warm-up period required for the indicator to be ready.
/// This is determined by the warm-up period of the Bollinger Bands indicator.
/// </summary>
public int WarmUpPeriod { get; }

/// <summary>
/// Indicates whether the indicator is ready and has enough data for computation.
/// The indicator is ready when both the Bollinger Bands and the Average True Range are ready.
/// </summary>
public override bool IsReady => BollingerBands.IsReady && KeltnerChannels.IsReady;

/// <summary>
/// Computes the next value of the indicator based on the input data bar.
/// </summary>
/// <param name="input">The input data bar.</param>
/// <returns>
/// Returns 1 if the Bollinger Bands are inside the Keltner Channels (squeeze on),
/// or -1 if the Bollinger Bands are outside the Keltner Channels (squeeze off).
/// </returns>
protected override decimal ComputeNextValue(IBaseDataBar input)
{
BollingerBands.Update(input);
KeltnerChannels.Update(input);
if (!IsReady)
{
return decimal.Zero;
}

// Calculate Bollinger Bands upper, lower
var bbUpper = BollingerBands.UpperBand.Current.Value;
var bbLower = BollingerBands.LowerBand.Current.Value;

// Calculate Keltner Channels upper and lower bounds
var kcUpper = KeltnerChannels.UpperBand.Current.Value;
var kcLower = KeltnerChannels.LowerBand.Current.Value;

// Determine if the squeeze condition is on or off
return (kcUpper > bbUpper && kcLower < bbLower) ? 1m : -1m;
}

/// <summary>
/// Resets the state of the indicator, including all sub-indicators.
/// </summary>
public override void Reset()
{
BollingerBands.Reset();
KeltnerChannels.Reset();
base.Reset();
}
}
}
10 changes: 5 additions & 5 deletions Tests/Indicators/IndicatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ public void UpdatesProperly()
}

[Test]
public void ThrowsOnDifferentDataType()
public void ShouldNotThrowOnDifferentDataType()
{
var target = new TestIndicator();
Assert.Throws<ArgumentException>(() =>
Assert.DoesNotThrow(() =>
{
target.Update(new Tick());
}, "expected to be of type");
});
}

[Test]
Expand Down Expand Up @@ -174,7 +174,7 @@ public void IndicatorMustBeEqualToItself()
{
try
{
instantiatedIndicator = Activator.CreateInstance(indicator, new object[] {10});
instantiatedIndicator = Activator.CreateInstance(indicator, new object[] { 10 });
counter++;
}
catch (Exception)
Expand Down Expand Up @@ -364,7 +364,7 @@ private static MethodInfo GetOperatorMethodInfo<T>(string @operator, int argInde
{
var methodName = "op_" + @operator;
var method =
typeof (IndicatorBase).GetMethods(BindingFlags.Static | BindingFlags.Public)
typeof(IndicatorBase).GetMethods(BindingFlags.Static | BindingFlags.Public)
.SingleOrDefault(x => x.Name == methodName && x.GetParameters()[argIndex].ParameterType == typeof(T));

if (method == null)
Expand Down
34 changes: 34 additions & 0 deletions Tests/Indicators/SqueezeMomentumTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;

namespace QuantConnect.Tests.Indicators
{
[TestFixture]
public class SqueezeMomentumTests : CommonIndicatorTests<IBaseDataBar>
{
protected override IndicatorBase<IBaseDataBar> CreateIndicator()
{
VolumeRenkoBarSize = 0.5m;
return new SqueezeMomentum("SM", 20, 2, 20, 1.5m);
}
protected override string TestFileName => "spy_sm.csv";

protected override string TestColumnName => "squeeze on";
}
}
3 changes: 3 additions & 0 deletions Tests/QuantConnect.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,9 @@
<Content Include="TestData\spy_hurst_exponent.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="TestData\spy_sm.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="TestData\symbol-properties\symbol-properties-database.csv">
Expand Down
Loading