Skip to content
Closed
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 @@ -2766,6 +2766,27 @@ public WilderMovingAverage WWMA(Symbol symbol, int period, Resolution? resolutio
return wilderMovingAverage;
}

/// <summary>
/// Creates a new WaveTrendOscillator indicator for the symbol.
/// The indicator will be automatically updated on the given resolution.
/// </summary>
/// <param name="symbol">The symbol whose WaveTrend Oscillator we want</param>
/// <param name="channelPeriod">The period used to smooth the typical price and its mean deviation (n1)</param>
/// <param name="averagePeriod">The period used to smooth the channel index into the main wave trend line (n2)</param>
/// <param name="signalPeriod">The period used to smooth the wave trend line into the signal line</param>
/// <param name="resolution">The resolution</param>
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to casting the input value to a TradeBar</param>
/// <returns>The WaveTrendOscillator indicator for the requested symbol with the given parameters</returns>
[DocumentationAttribute(Indicators)]
public WaveTrendOscillator WTO(Symbol symbol, int channelPeriod = 10, int averagePeriod = 21, int signalPeriod = 4, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
{
var name = CreateIndicatorName(symbol, $"WTO({channelPeriod},{averagePeriod},{signalPeriod})", resolution);
var waveTrendOscillator = new WaveTrendOscillator(name, channelPeriod, averagePeriod, signalPeriod);
InitializeIndicator(waveTrendOscillator, resolution, selector, symbol);

return waveTrendOscillator;
}

/// <summary>
/// Creates a Wilder Swing Index (SI) indicator for the symbol.
/// The indicator will be automatically updated on the given resolution.
Expand Down
144 changes: 144 additions & 0 deletions Indicators/WaveTrendOscillator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* 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>
/// Represents the WaveTrend Oscillator (WTO) developed by LazyBear.
///
/// The oscillator uses the typical price (HLC3) and is computed as follows:
/// ESA = EMA(HLC3, channelPeriod)
/// D = EMA(|HLC3 - ESA|, channelPeriod)
/// CI = (HLC3 - ESA) / (0.015 * D)
/// WT1 = EMA(CI, averagePeriod)
/// WT2 = SMA(WT1, signalPeriod)
///
/// The indicator's current value is WT1 (the main wave trend line).
/// The <see cref="Signal"/> property exposes WT2, which is typically plotted as the signal line.
/// </summary>
public class WaveTrendOscillator : BarIndicator, IIndicatorWarmUpPeriodProvider
{
/// <summary>
/// Constant used to scale the channel index so most WTO values fall within +/- 100.
/// </summary>
private const decimal K = 0.015m;

private readonly ExponentialMovingAverage _channelEma;
private readonly ExponentialMovingAverage _meanDeviationEma;

/// <summary>
/// Gets the main wave trend line (WT1), computed as an EMA of the channel index.
/// </summary>
public ExponentialMovingAverage WaveTrend { get; }

/// <summary>
/// Gets the signal line (WT2), computed as an SMA of <see cref="WaveTrend"/>.
/// </summary>
public SimpleMovingAverage Signal { get; }

/// <summary>
/// Initializes a new instance of the <see cref="WaveTrendOscillator"/> class.
/// </summary>
/// <param name="name">The name of this indicator</param>
/// <param name="channelPeriod">The period used to smooth the typical price and its mean deviation (n1)</param>
/// <param name="averagePeriod">The period used to smooth the channel index into the main wave trend line (n2)</param>
/// <param name="signalPeriod">The period used to smooth the wave trend line into the signal line</param>
public WaveTrendOscillator(string name, int channelPeriod, int averagePeriod, int signalPeriod)
: base(name)
{
_channelEma = new ExponentialMovingAverage(name + "_ChannelEMA", channelPeriod);
_meanDeviationEma = new ExponentialMovingAverage(name + "_MeanDeviationEMA", channelPeriod);
WaveTrend = new ExponentialMovingAverage(name + "_WaveTrend", averagePeriod);
Signal = new SimpleMovingAverage(name + "_Signal", signalPeriod);

WarmUpPeriod = 2 * channelPeriod + averagePeriod + signalPeriod - 3;
}

/// <summary>
/// Initializes a new instance of the <see cref="WaveTrendOscillator"/> class using default parameters (10, 21, 4).
/// </summary>
/// <param name="channelPeriod">The period used to smooth the typical price and its mean deviation (n1)</param>
/// <param name="averagePeriod">The period used to smooth the channel index into the main wave trend line (n2)</param>
/// <param name="signalPeriod">The period used to smooth the wave trend line into the signal line</param>
public WaveTrendOscillator(int channelPeriod = 10, int averagePeriod = 21, int signalPeriod = 4)
: this($"WTO({channelPeriod},{averagePeriod},{signalPeriod})", channelPeriod, averagePeriod, signalPeriod)
{
}

/// <summary>
/// Gets a flag indicating when this indicator is ready and fully initialized.
/// </summary>
public override bool IsReady => Signal.IsReady;

/// <summary>
/// Required period, in data points, for the indicator to be ready and fully initialized.
/// </summary>
public int WarmUpPeriod { get; }

/// <summary>
/// Computes the next value of this indicator from the given state.
/// </summary>
/// <param name="input">The input given to the indicator</param>
/// <returns>The current value of the main wave trend line (WT1)</returns>
protected override decimal ComputeNextValue(IBaseDataBar input)
{
var typicalPrice = (input.High + input.Low + input.Close) / 3.0m;

_channelEma.Update(input.EndTime, typicalPrice);
if (!_channelEma.IsReady)
{
return 0m;
}

var absDeviation = Math.Abs(typicalPrice - _channelEma.Current.Value);
_meanDeviationEma.Update(input.EndTime, absDeviation);
if (!_meanDeviationEma.IsReady)
{
return 0m;
}

var weightedMeanDeviation = K * _meanDeviationEma.Current.Value;
if (weightedMeanDeviation == 0m)
{
return WaveTrend.Current.Value;
}

var channelIndex = (typicalPrice - _channelEma.Current.Value) / weightedMeanDeviation;
WaveTrend.Update(input.EndTime, channelIndex);
if (!WaveTrend.IsReady)
{
return WaveTrend.Current.Value;
}

Signal.Update(input.EndTime, WaveTrend.Current.Value);
return WaveTrend.Current.Value;
}

/// <summary>
/// Resets this indicator to its initial state.
/// </summary>
public override void Reset()
{
_channelEma.Reset();
_meanDeviationEma.Reset();
WaveTrend.Reset();
Signal.Reset();
base.Reset();
}
}
}
123 changes: 123 additions & 0 deletions Tests/Indicators/WaveTrendOscillatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;

namespace QuantConnect.Tests.Indicators
{
[TestFixture]
public class WaveTrendOscillatorTests : CommonIndicatorTests<IBaseDataBar>
{
protected override IndicatorBase<IBaseDataBar> CreateIndicator()
{
RenkoBarSize = 1m;
VolumeRenkoBarSize = 0.5m;
return new WaveTrendOscillator(10, 21, 4);
}

protected override string TestFileName => "spy_wto.txt";

protected override string TestColumnName => "WaveTrend";

protected override Action<IndicatorBase<IBaseDataBar>, double> Assertion =>
(indicator, expected) =>
Assert.AreEqual(expected, (double)indicator.Current.Value, 1e-4);

[Test]
public void ComparesWithExternalDataSignal()
{
var wto = CreateIndicator();
TestHelper.TestIndicator(
wto,
TestFileName,
"Signal",
(ind, expected) => Assert.AreEqual(
expected,
(double)((WaveTrendOscillator)ind).Signal.Current.Value,
delta: 1e-4
)
);
}

[Test]
public override void ResetsProperly()
{
var wto = new WaveTrendOscillator(10, 21, 4);
foreach (var bar in TestHelper.GetTradeBarStream(TestFileName, false))
{
wto.Update(bar);
}
Assert.IsTrue(wto.IsReady);
Assert.IsTrue(wto.WaveTrend.IsReady);
Assert.IsTrue(wto.Signal.IsReady);

wto.Reset();
TestHelper.AssertIndicatorIsInDefaultState(wto);
TestHelper.AssertIndicatorIsInDefaultState(wto.WaveTrend);
TestHelper.AssertIndicatorIsInDefaultState(wto.Signal);
}

[Test]
public override void WarmsUpProperly()
{
const int channelPeriod = 3;
const int averagePeriod = 4;
const int signalPeriod = 2;
var wto = new WaveTrendOscillator(channelPeriod, averagePeriod, signalPeriod);
var expectedWarmUp = 2 * channelPeriod + averagePeriod + signalPeriod - 3;
Assert.AreEqual(expectedWarmUp, wto.WarmUpPeriod);

var reference = DateTime.Today;
for (var i = 0; i < expectedWarmUp - 1; i++)
{
Assert.IsFalse(wto.IsReady);
wto.Update(new TradeBar
{
Symbol = Symbols.SPY,
Time = reference.AddDays(i),
Open = 100m + i,
High = 101m + i,
Low = 99m + i,
Close = 100.5m + i,
Volume = 1000
});
}
wto.Update(new TradeBar
{
Symbol = Symbols.SPY,
Time = reference.AddDays(expectedWarmUp - 1),
Open = 100m,
High = 101m,
Low = 99m,
Close = 100.5m,
Volume = 1000
});
Assert.IsTrue(wto.IsReady);
Assert.IsTrue(wto.WaveTrend.IsReady);
Assert.IsTrue(wto.Signal.IsReady);
}

[Test]
public void DefaultConstructorUsesStandardParameters()
{
var wto = new WaveTrendOscillator();
Assert.AreEqual("WTO(10,21,4)", wto.Name);
Assert.AreEqual(2 * 10 + 21 + 4 - 3, wto.WarmUpPeriod);
}
}
}
3 changes: 3 additions & 0 deletions Tests/QuantConnect.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,9 @@
<Content Include="TestData\spy_with_vwap.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="TestData\spy_wto.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="TestData\spy_with_williamsR14.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down
Loading
Loading