- ago
I threw together a simple indicator that returns for each bar its body percent, bottom tail percent or top tail percent of the entire bar's size. The code is below.

I thought this would be handy for users that use block strategies. I could not find an equivalent existing indicator. Example of use is for detecting those nasty big top tails (e.g. perhaps > 50 percent) when considering to go long or if you're already in a long position. Its nothing fancy and wasn't intended to be anything more than bar-by-bar.

@Glitch, Cone and Eugene - if you like, please feel free to include in a release without attribution to me.

CODE:
using System; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { /// <summary> /// The part of the bar to calculate its percent. /// This enum needs to be in the same scope level as the indicator so /// that Wealth-Lab can parse (find) it for code generation. /// </summary> public enum BarPart { Body = 0, BottomTail = 1, TopTail = 2 } /// <summary> /// Wealth-Lab indicator that calculates the percent of a bar's body, bottom tail, or top tail. /// </summary> public class BarPartPercent : IndicatorBase { public BarPartPercent() { // needed for WL8 } public BarPartPercent(BarHistory bars, BarPart barPart) { Parameters[0].Value = bars; Parameters[1].Value = barPart; Populate(); } public override string Name => "BarPartPercent"; public override string Abbreviation => "BarPartPercent"; public override string HelpDescription => "Each bar's body, bottom tail, or top tail expressed as a percent (0 to 100) of the bar's total size."; public override string PaneTag => "BarPartPercent"; public override PlotStyle DefaultPlotStyle => PlotStyle.Histogram; protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); // default to body var part = AddParameter("Bar Part", ParameterType.StringChoice, "Body"); part.Choices.Add("Body"); part.Choices.Add("BottomTail"); part.Choices.Add("TopTail"); part.TypeName = "BarPart"; } public override void Populate() { var source = Parameters[0].AsBarHistory; // if part can't be parsed, we'll get an exception var part = (BarPart) Enum.Parse(typeof(BarPart), Parameters[1].AsString); DateTimes = source.DateTimes; // lets be speedy var closes = source.Close; var opens = source.Open; var highs = source.High; var lows = source.Low; var count = source.Count; // Although tedious, switch on the part and then use loops to speed things up. That way we don't // have to do switching within a loop. switch (part) { case BarPart.Body: for (var idx = 0; idx < count; idx++) { if (double.IsNaN(closes[idx])) { Values[idx] = double.NaN; continue; } var barSize = highs[idx] - lows[idx]; Values[idx] = barSize > 0.0 ? Math.Abs(opens[idx] - closes[idx]) / barSize * 100.0 : 0.0; } break; case BarPart.BottomTail: for (var idx = 0; idx < count; idx++) { if (double.IsNaN(closes[idx])) { Values[idx] = double.NaN; continue; } var barSize = highs[idx] - lows[idx]; Values[idx] = barSize > 0.0 ? (Math.Min(opens[idx], closes[idx]) - lows[idx]) / barSize * 100.0 : 0.0; } break; case BarPart.TopTail: for (var idx = 0; idx < count; idx++) { if (double.IsNaN(closes[idx])) { Values[idx] = double.NaN; continue; } var barSize = highs[idx] - lows[idx]; Values[idx] = barSize > 0.0 ? (highs[idx] - Math.Max(opens[idx], closes[idx])) / barSize * 100.0 : 0.0; } break; // no default needed because we'll get an exception when parsing for the part, above } } public static BarPartPercent Series(BarHistory bars, BarPart barPart) { var key = CacheKey("BarPartPercent", (int) barPart); if (bars.Cache.TryGetValue(key, out var obj)) { return (BarPartPercent) obj; } var result = new BarPartPercent(bars, barPart); bars.Cache[key] = result; return result; } } }
0
169
Solved
6 Replies

Reply

Bookmark

Sort
Glitch8
 ( 8.31% )
- ago
#1
We will add it to Community Indicators, thanks!
0
Best Answer
Cone8
 ( 3.70% )
- ago
#2
No harm in adding it, but these indicators are part of the Candlestick Genetic Evolver extension.

0
- ago
#3
I updated the code, below, to change the word Bar to Candle where appropriate to keep in line with the naming conventions of the candlestick indicators.

Also, please note that CandlePartPercent differs in its results from the existing CandeBodyFactor, CandleRangeFactor and CandleShadowFactor indicators. The existing indicators use a period parameter to determine values by utilizing recent candles. See the WL description of each indicator for details.

CODE:
using System; using WealthLab.Core; using WealthLab.Indicators; namespace WLUtility.Indicators { /// <summary> /// The part of the candle to calculate its percent. /// This enum needs to be in the same scope level as the indicator so /// that Wealth-Lab can parse (find) it for code generation. /// </summary> public enum CandlePart { Body = 0, BottomTail = 1, TopTail = 2 } /// <summary> /// Wealth-Lab indicator that calculates the percent of a candle's body, bottom tail, or top tail. /// </summary> public class CandlePartPercent : IndicatorBase { public CandlePartPercent() { // needed for WL8 } public CandlePartPercent(BarHistory bars, CandlePart candlePart) { Parameters[0].Value = bars; Parameters[1].Value = candlePart; Populate(); } public override string Name => "CandlePartPercent"; public override string Abbreviation => "CandlePartPercent"; public override string HelpDescription => "Each candle's (bar's) body, bottom tail, or top tail expressed as a percent (0 to 100) of the candle's total size."; public override string PaneTag => "CandlePartPercent"; public override PlotStyle DefaultPlotStyle => PlotStyle.Histogram; protected override void GenerateParameters() { AddParameter("Source", ParameterType.BarHistory, null); // default to body var part = AddParameter("Candle Part", ParameterType.StringChoice, "Body"); part.Choices.Add("Body"); part.Choices.Add("BottomTail"); part.Choices.Add("TopTail"); part.TypeName = "CandlePart"; } public override void Populate() { var source = Parameters[0].AsBarHistory; // if part can't be parsed, we'll get an exception var part = (CandlePart) Enum.Parse(typeof(CandlePart), Parameters[1].AsString); DateTimes = source.DateTimes; // lets be speedy var closes = source.Close; var opens = source.Open; var highs = source.High; var lows = source.Low; var count = source.Count; // Although tedious, switch on the part and then use loops to speed things up. That way we don't // have to do switching within a loop. switch (part) { case CandlePart.Body: for (var idx = 0; idx < count; idx++) { if (double.IsNaN(closes[idx])) { Values[idx] = double.NaN; continue; } var barSize = highs[idx] - lows[idx]; Values[idx] = barSize > 0.0 ? Math.Abs(opens[idx] - closes[idx]) / barSize * 100.0 : 0.0; } break; case CandlePart.BottomTail: for (var idx = 0; idx < count; idx++) { if (double.IsNaN(closes[idx])) { Values[idx] = double.NaN; continue; } var barSize = highs[idx] - lows[idx]; Values[idx] = barSize > 0.0 ? (Math.Min(opens[idx], closes[idx]) - lows[idx]) / barSize * 100.0 : 0.0; } break; case CandlePart.TopTail: for (var idx = 0; idx < count; idx++) { if (double.IsNaN(closes[idx])) { Values[idx] = double.NaN; continue; } var barSize = highs[idx] - lows[idx]; Values[idx] = barSize > 0.0 ? (highs[idx] - Math.Max(opens[idx], closes[idx])) / barSize * 100.0 : 0.0; } break; // no default needed because we'll get an exception when parsing for the part, above } } public static CandlePartPercent Series(BarHistory bars, CandlePart candlePart) { var key = CacheKey("CandlePartPercent", (int) candlePart); if (bars.Cache.TryGetValue(key, out var obj)) { return (CandlePartPercent) obj; } var result = new CandlePartPercent(bars, candlePart); bars.Cache[key] = result; return result; } } }
0
Cone8
 ( 3.70% )
- ago
#4
1-period CandleShadowFactor(1, Upper) should be the same as CandlePartPercent(CandlePart.TopTail), right?
0
- ago
#5
@Cone - they are different. Please see screenshot...

0
Cone8
 ( 3.70% )
- ago
#6
A little different, yes. I thought CandleShadowFactor compared the shadow to the candle's height, but actually it compares it to the average of the two shadows. Even though the description says that (compared to the recent average shadow heights), in my mind I had always assumed the former.
0

Reply

Bookmark

Sort