- ago
Guys, can you please assist me in turning this code into a custom indicator? I've tried my hand at using the custom indicator builder but hitting a roadblock? Its compiling okay but the indicator is not being plotted on the chart. My latest attempt is below. Any assistance will be appreciated. Thanks

CODE:
using WealthLab.Core; using System; using System.Drawing; using WealthLab.Indicators; namespace WealthLab.MyIndicators { public class Momentum : IndicatorBase { //parameterless constructor public Momentum() : base() { } //for code based construction public Momentum(TimeSeries source, TimeSeries msac, TimeSeries msal, TimeSeries msah, TimeSeries msao, Int32 period) : base() {          Parameters[0].Value = source;          Parameters[1].Value = msac;          Parameters[2].Value = msal;          Parameters[3].Value = msah;          Parameters[4].Value = msao;          Parameters[5].Value = period; Populate(); }       //static Series method       public static Momentum Series(TimeSeries source, TimeSeries msac, TimeSeries msal, TimeSeries msah, TimeSeries msao, Int32 period)       {          return new Momentum(source, msac, msal, msah, msao, period);       } //name public override string Name { get { return "Momentum"; } } //abbreviation public override string Abbreviation { get { return "Momentum"; } } //description public override string HelpDescription { get { return ""; } } //price pane public override string PaneTag { get { return "Price"; } }       //default color       public override WLColor DefaultColor       {          get          {             return WLColor.FromArgb(255,0,0,255);          }       }       //default plot style       public override PlotStyle DefaultPlotStyle       {          get          {             return PlotStyle.BarChart;          }       } //populate public override void Populate() {          TimeSeries source = Parameters[0].AsTimeSeries;          TimeSeries msac = Parameters[1].AsTimeSeries;          TimeSeries msal = Parameters[2].AsTimeSeries;          TimeSeries msah = Parameters[3].AsTimeSeries;          TimeSeries msao = Parameters[4].AsTimeSeries;          Int32 period = Parameters[5].AsInt; DateTimes = source.DateTimes;          //modify the code below to implement your own indicator calculation for (int n = 0; n < source.Count; n++)          {             TimeSeries maParam = SMA.Series(Bars.Close, 40) >> 1;             TimeSeries MSAH = (Bars.High - maParam) / maParam * 100;             TimeSeries MSAO = (Bars.Open - maParam) / maParam * 100;             TimeSeries MSAC = (Bars.Close - maParam) / maParam * 100;             TimeSeries MSAL = (Bars.Low - maParam) / maParam * 100;                       BarHistory msaBars = new BarHistory(Bars);             for (int i = 0; i < Bars.Count; i++)             {                msaBars.Add(Bars.DateTimes[i], MSAO[i], MSAH[i], MSAL[i], MSAC[i], 0);             }          } } //generate parameters protected override void GenerateParameters() {          AddParameter("Source", ParameterType.TimeSeries, PriceComponent.Close);          AddParameter("MSAC", ParameterType.TimeSeries, PriceComponent.Close);          AddParameter("MSAL", ParameterType.TimeSeries, PriceComponent.Low);          AddParameter("MSAH", ParameterType.TimeSeries, PriceComponent.High);          AddParameter("MSAO", ParameterType.TimeSeries, PriceComponent.Open);          AddParameter("Period", ParameterType.Int32, 40); } } }
0
356
Solved
7 Replies

Reply

Bookmark

Sort
- ago
#1
Hi,

The Populate() does not assign any Values. Click on built-in indicator examples in the Custom Indicator wizard to learn how to implement this part. Or check out a "complete example" below:

https://www.wealth-lab.com/Support/ExtensionApi/IndicatorLibrary
1
Best Answer
- ago
#2
Your code is not close to what I believe you want to achieve. I'll see if I can put together a solution some time today.
0
- ago
#3
There's no need to pass the msa* TimeSeries as parameters ever. Only "source" and "period" should be left as the parameters here, with appropriate adjustment of Parameters[n] where applicable.
CODE:
public Momentum(TimeSeries source, TimeSeries msac, TimeSeries msal, TimeSeries msah, TimeSeries msao, Int32 period)


These TimeSeries should not be put into a loop. They're being needlessly created bars count * times:
CODE:
//modify the code below to implement your own indicator calculation for (int n = 0; n < source.Count; n++)          {             TimeSeries maParam = SMA.Series(Bars.Close, 40) >> 1; ...


1. Move them out of the loop
2. Delete the outer loop by source.Count, leaving the 2nd one intact
3. Assign msaBars to Values.

Also, consider renaming your Momentum class to avoid confusion with the built-in Momentum indicator.
1
- ago
#4
Here you go. I created three indicators:

LTMomentumBars - draws the bars per your original spec over in the other thread.
LTMomentumUpper and companion LTMomentumLower - draws the band per your spec over in the other thread.

See the comments in LTMomentumBars. That is, the price component of the bar history is assumed to be Close because your original spec indicated that. Hence, there is no Parameter for that.

Also, in your original spec, you are plotting bars and bands and so that is why you should have seperate indicators. I didn't add an indicator for the SMA shifted.

Named begin with LT because I wanted to distinguish versus existing Momentum indicator per Eugene.

Don't forget to run WL8 as admin or otherwise the custom indicators won't show up (or so that I'm told/understand).

CODE:
using System; using System.Collections.Generic; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { /// <summary> /// Generate 'Momentum Bars' based on an n-Period SMA, offset by one bar. /// </summary> public class LTMomentumBars : IndicatorBase { private TimeSeries _maSeries; private TimeSeries _msac; private TimeSeries _msah; private TimeSeries _msal; private TimeSeries _msao; public LTMomentumBars() { // for WL8 - do not remove } public LTMomentumBars(BarHistory source, int period, PriceComponent pc = PriceComponent.Close) { Parameters[0].Value = source; Parameters[1].Value = period; // we don't expose the PriceComponent as a parameter because it is assumed that the SMA is to be based // on the source.Close price component. But, we need the price component in the constructor // so that the Populate method can build correct Values for the bars when being called indirectly from // GetBarChartCompanion(). PriceComponent = pc; Populate(); } public override WLColor DefaultColor { get; } = WLColor.FromArgb(255, 0, 0, 255); public override PlotStyle DefaultPlotStyle => PlotStyle.BarChart; private BarHistory Source => Parameters[0].AsBarHistory; private int Period => Parameters[1].AsInt; private PriceComponent PriceComponent { get; } private TimeSeries MaSeries => _maSeries ?? (_maSeries = SMA.Series(Source.Close, Period) >> 1); private TimeSeries MSAH => _msah ?? (_msah = (Bars.High - MaSeries) / MaSeries * 100); private TimeSeries MSAO => _msao ?? (_msao = (Bars.Open - MaSeries) / MaSeries * 100); private TimeSeries MSAC => _msac ?? (_msac = (Bars.Close - MaSeries) / MaSeries * 100); private TimeSeries MSAL => _msal ?? (_msal = (Bars.Low - MaSeries) / MaSeries * 100); public override string Name => "LTMomentumBars"; public override string Abbreviation => "LTMomentumBars"; public override string HelpDescription => "LT Momentum Bars"; public override string PaneTag => "LT Momentum Bars"; /// <summary> /// Builds the various values of the bars based on the PriceComponent. See WL documentation for details. /// </summary> public override IndicatorBase GetBarChartCompanion(PriceComponent pc) => new LTMomentumBars(Source, Period, pc); protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); AddParameter("Period", ParameterType.Int32, 40); } public override void Populate() { DateTimes = Source.DateTimes; switch (PriceComponent) { case PriceComponent.Close: // note: make copies of the values or otherwise there are no values after editing // an existing indicator instance... Values = new List<double>(MSAC.Values); break; case PriceComponent.High: Values = new List<double>(MSAH.Values); break; case PriceComponent.Low: Values = new List<double>(MSAL.Values); break; case PriceComponent.Open: Values = new List<double>(MSAO.Values); break; default: throw new ArgumentException($"Invalid PriceComponent: {PriceComponent}"); } FirstValidIndex = MSAC.FirstValidIndex; } public static LTMomentumBars Series(BarHistory bars, int period) { var key = CacheKey("LTMomentumBars", period); if (bars.Cache.TryGetValue(key, out var obj)) { return (LTMomentumBars) obj; } var result = new LTMomentumBars(bars, period); bars.Cache[key] = result; return result; } } }


CODE:
using System.Collections.Generic; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { public class LTMomentumLower : IndicatorBase { private TimeSeries _maSeries; private TimeSeries _msal; public LTMomentumLower() { // for WL8 - do not remove } public LTMomentumLower(BarHistory source, int period) { Parameters[0].Value = source; Parameters[1].Value = period; Populate(); } public override string Name => "LTMomentumLower"; public override string Abbreviation => "LTMomentumLower"; public override string HelpDescription => "LT Momentum Lower"; public override string PaneTag => "LT Momentum"; private BarHistory Source => Parameters[0].AsBarHistory; private int Period => Parameters[1].AsInt; public override PlotStyle DefaultPlotStyle => PlotStyle.Bands; public override WLColor DefaultColor { get; } = WLColor.Blue; private TimeSeries MaSeries => _maSeries ?? (_maSeries = SMA.Series(Source.Close, Period) >> 1); private TimeSeries MSAL => _msal ?? (_msal = (Bars.Low - MaSeries) / MaSeries * 100); public override List<string> Companions => new List<string> {"LTMomentumUpper"}; public override IndicatorBase BandCompanion => new LTMomentumUpper(Source, Period); protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); AddParameter("Period", ParameterType.Int32, 40); } public override void Populate() { DateTimes = Source.DateTimes; Values = new List<double>(MSAL.Values); } public static LTMomentumLower Series(BarHistory bars, int period) { var key = CacheKey("LTMomentumLower", period); if (bars.Cache.TryGetValue(key, out var obj)) { return (LTMomentumLower) obj; } var result = new LTMomentumLower(bars, period); bars.Cache[key] = result; return result; } } }


CODE:
using System.Collections.Generic; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { public class LTMomentumUpper : IndicatorBase { private TimeSeries _maSeries; private TimeSeries _msah; public LTMomentumUpper() { // for WL8 - do not remove } public LTMomentumUpper(BarHistory source, int period) { Parameters[0].Value = source; Parameters[1].Value = period; Populate(); } public override string Name => "LTMomentumUpper"; public override string Abbreviation => "LTMomentumUpper"; public override string HelpDescription => "LT Momentum Upper"; public override string PaneTag => "LT Momentum"; private BarHistory Source => Parameters[0].AsBarHistory; private int Period => Parameters[1].AsInt; public override PlotStyle DefaultPlotStyle => PlotStyle.Bands; public override WLColor DefaultColor { get; } = WLColor.Blue; private TimeSeries MaSeries => _maSeries ?? (_maSeries = SMA.Series(Source.Close, Period) >> 1); private TimeSeries MSAH => _msah ?? (_msah = (Bars.High - MaSeries) / MaSeries * 100); public override List<string> Companions => new List<string> {"LTMomentumLower"}; public override IndicatorBase BandCompanion => new LTMomentumLower(Source, Period); protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); AddParameter("Period", ParameterType.Int32, 40); } public override void Populate() { DateTimes = Source.DateTimes; Values = new List<double>(MSAH.Values); } public static LTMomentumUpper Series(BarHistory bars, int period) { var key = CacheKey("LTMomentumUpper", period); if (bars.Cache.TryGetValue(key, out var obj)) { return (LTMomentumUpper) obj; } var result = new LTMomentumUpper(bars, period); bars.Cache[key] = result; return result; } } }
1
- ago
#5
I cleaned the code up, and added shift forward bars...

CODE:
using System; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { /// <summary> /// Generate 'Momentum Bars' based on an n-Period SMA, offset by one bar. /// </summary> public class LTMomentumBars : IndicatorBase { public LTMomentumBars() { // for WL8 - do not remove } /// <summary> /// To create an LTMomentumBars instance use the Series method. /// </summary> public LTMomentumBars(BarHistory source, int period, int shiftForwardBars = 1) { Parameters[0].Value = source; Parameters[1].Value = period; Parameters[2].Value = shiftForwardBars; PriceComponent = PriceComponent.Close; Populate(); } private LTMomentumBars(BarHistory source, int period, int shiftForwardBars = 1, PriceComponent pc = PriceComponent.Close) { Parameters[0].Value = source; Parameters[1].Value = period; Parameters[2].Value = shiftForwardBars; // we don't expose the PriceComponent as a parameter because it is assumed that the SMA is to be based // on the source.Close price component. But, we need the price component in the constructor // so that the Populate method can build correct Values for the bars when being called indirectly from // GetBarChartCompanion(). PriceComponent = pc; Populate(); } public override WLColor DefaultColor { get; } = WLColor.FromArgb(255, 0, 0, 255); public override PlotStyle DefaultPlotStyle => PlotStyle.BarChart; private BarHistory Source => Parameters[0].AsBarHistory; private int Period => Parameters[1].AsInt; private int ShiftForwardBars => Parameters[2].AsInt; private PriceComponent PriceComponent { get; } public override string Name => "LTMomentumBars"; public override string Abbreviation => "LTMomentumBars"; public override string HelpDescription => "LT Momentum Bars"; public override string PaneTag => "LT Momentum Bars"; /// <summary> /// Builds the various values of the bars based on the PriceComponent. See WL documentation for details. /// </summary> public override IndicatorBase GetBarChartCompanion(PriceComponent pc) => new LTMomentumBars(Source, Period, ShiftForwardBars, pc); protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); var p = AddParameter("Period", ParameterType.Int32, 40); p.MinValue = 1; p = AddParameter("Shift Forward Bars", ParameterType.Int32, 1); p.MinValue = 0; } public override void Populate() { DateTimes = Source.DateTimes; var barHistory = Source; var maSeries = SMA.Series(barHistory.Close, Period) >> ShiftForwardBars; switch (PriceComponent) { case PriceComponent.Close: Values = ((barHistory.Close - maSeries) / maSeries * 100).Values; break; case PriceComponent.High: Values = ((barHistory.High - maSeries) / maSeries * 100).Values; break; case PriceComponent.Low: Values = ((barHistory.Low - maSeries) / maSeries * 100).Values; break; case PriceComponent.Open: Values = ((barHistory.Open - maSeries) / maSeries * 100).Values; break; default: throw new ArgumentException($"Invalid PriceComponent: {PriceComponent}"); } FirstValidIndex = maSeries.FirstValidIndex; } public static LTMomentumBars Series(BarHistory bars, int period, int shiftForwardBars = 1) { var key = CacheKey("LTMomentumBars", period, shiftForwardBars); if (bars.Cache.TryGetValue(key, out var obj)) { return (LTMomentumBars) obj; } var result = new LTMomentumBars(bars, period, shiftForwardBars); bars.Cache[key] = result; return result; } } }

CODE:
using System.Collections.Generic; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { public class LTMomentumLower : IndicatorBase { public LTMomentumLower() { // for WL8 - do not remove } public LTMomentumLower(BarHistory source, int period, int shiftForwardBars = 1) { Parameters[0].Value = source; Parameters[1].Value = period; Parameters[2].Value = shiftForwardBars; Populate(); } public override string Name => "LTMomentumLower"; public override string Abbreviation => "LTMomentumLower"; public override string HelpDescription => "LT Momentum Lower"; public override string PaneTag => "LT Momentum"; private BarHistory Source => Parameters[0].AsBarHistory; private int Period => Parameters[1].AsInt; private int ShiftForwardBars => Parameters[2].AsInt; public override PlotStyle DefaultPlotStyle => PlotStyle.Bands; public override WLColor DefaultColor { get; } = WLColor.Blue; public override List<string> Companions => new List<string> {"LTMomentumUpper"}; public override IndicatorBase BandCompanion => new LTMomentumUpper(Source, Period, ShiftForwardBars); protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); var p = AddParameter("Period", ParameterType.Int32, 40); p.MinValue = 1; p = AddParameter("Shift Forward Bars", ParameterType.Int32, 1); p.MinValue = 0; } public override void Populate() { var barHistory = Source; DateTimes = barHistory.DateTimes; var maSeries = SMA.Series(barHistory.Close, Period) >> ShiftForwardBars; Values = ((barHistory.Low - maSeries) / maSeries * 100).Values; FirstValidIndex = maSeries.FirstValidIndex; } public static LTMomentumLower Series(BarHistory bars, int period, int shiftForwardBars = 1) { var key = CacheKey("LTMomentumLower", period, shiftForwardBars); if (bars.Cache.TryGetValue(key, out var obj)) { return (LTMomentumLower) obj; } var result = new LTMomentumLower(bars, period, shiftForwardBars); bars.Cache[key] = result; return result; } } }

CODE:
using System.Collections.Generic; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { public class LTMomentumUpper : IndicatorBase { public LTMomentumUpper() { // for WL8 - do not remove } public LTMomentumUpper(BarHistory source, int period, int shiftForwardBars = 1) { Parameters[0].Value = source; Parameters[1].Value = period; Parameters[2].Value = shiftForwardBars; Populate(); } public override string Name => "LTMomentumUpper"; public override string Abbreviation => "LTMomentumUpper"; public override string HelpDescription => "LT Momentum Upper"; public override string PaneTag => "LT Momentum"; private BarHistory Source => Parameters[0].AsBarHistory; private int Period => Parameters[1].AsInt; private int ShiftForwardBars => Parameters[2].AsInt; public override PlotStyle DefaultPlotStyle => PlotStyle.Bands; public override WLColor DefaultColor { get; } = WLColor.Blue; public override List<string> Companions => new List<string> {"LTMomentumLower"}; public override IndicatorBase BandCompanion => new LTMomentumLower(Source, Period, ShiftForwardBars); protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); var p = AddParameter("Period", ParameterType.Int32, 40); p.MinValue = 1; p = AddParameter("Shift Forward Bars", ParameterType.Int32, 1); p.MinValue = 0; } public override void Populate() { var barHistory = Source; DateTimes = barHistory.DateTimes; var maSeries = SMA.Series(barHistory.Close, Period) >> ShiftForwardBars; Values = ((barHistory.High - maSeries) / maSeries * 100).Values; FirstValidIndex = maSeries.FirstValidIndex; } public static LTMomentumUpper Series(BarHistory bars, int period, int shiftForwardBars = 1) { var key = CacheKey("LTMomentumUpper", period, shiftForwardBars); if (bars.Cache.TryGetValue(key, out var obj)) { return (LTMomentumUpper) obj; } var result = new LTMomentumUpper(bars, period, shiftForwardBars); bars.Cache[key] = result; return result; } } }
2
- ago
#6
Thanks a lot Paul.

Just took a quick look and appears to be behaving as expected. I'll take a closer look this weekend. Thanks again.
0
- ago
#7
I just wanted to thank you on showing us an illustration of how
CODE:
GetBarChartCompanion(PriceComponents pc)
works with the BarChart plot style. I didn't think an indicator could create a BarChart plot, but your example shows exactly how it's done. Thank you very much.

And implementing the companions with the switch(...) statement is a nice touch for mutual exclusion clarity.
0

Reply

Bookmark

Sort