Parent: Object
The PeakTroughCalculator is a utility class the calculates peaks and troughs in the source time series data. Peaks and troughs are calculated by observing when the data moves by a certain reversal amount, which can be expressed as either percent or point value. The result is a list of PeakTrough objects available via the PeakTroughs property.
Due to the nature of this calculation, the detection of peaks and troughs always happens a little after the fact. The resulting PeakTrough instances provide both the point at which the peak trough began (the PeakTroughIndex property) as well as the point at which it was detected (the DetectedAtIndex property).
Draws lines connecting confirmed peaks. Pass this as the first parameter for the current instance of the UseStrategyBase.
Remarks
- If the last confirmed PeakTrough is a trough, then a dotted gray line is drawn to the highest point since the last trough to indicate the next potential Peak.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript42 { public class PlotPTStrategy123 : UserStrategyBase { public override void Initialize(BarHistory bars) { _ptc = new PeakTroughCalculator(bars, 20, PeakTroughReversalType.Percent); //draw lines to connect the 10% peaks _ptc.DrawPeakLines(this, WLColor.NeonFuschia); } public override void Execute(BarHistory bars, int idx) { } PeakTroughCalculator _ptc; } }
Draws lines connecting confirmed troughs. Pass this as the first parameter for the current instance of the UseStrategyBase.
Remarks
- If the last confirmed PeakTrough is a peak, then a dotted gray line is drawn to the lowest point since the last peak to indicate the next potential Trough.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript7 { public class PlotPTStrategy123 : UserStrategyBase { public override void Initialize(BarHistory bars) { _rsi = RSI.Series(bars.Close, 8); PlotIndicator(_rsi); //create a PeakTroughCalculator of an 8 period RSI _ptc = new PeakTroughCalculator(_rsi, 20, PeakTroughReversalType.Percent); //draw lines to connect the 20% rsi troughs _ptc.DrawTroughLines(this, WLColor.NeonFuschia, paneTag: _rsi.PaneTag); } public override void Execute(BarHistory bars, int idx) { } RSI _rsi; PeakTroughCalculator _ptc; } }
The class supports four constructors. The first three constructors contain a reversalType and reversalAmount parameter that determine how the peaks and troughs are calculated. The following reversalTypes are available:
- Percent - Peaks/troughs are detected after the data has reversed by a percentage specified in reversalAmount.
Note!
Percentage reversals for TimeSeries with negative values will produce unexpected/invalid results. Use Point-based reversals instead.
- Point - Peaks/troughs are detected after the data has reversed by a value specified in reversalAmount.
- ATR - Peaks/troughs are detected after the data has reversed by a number of ATRs (Active True Ranges) specified in reversalAmount. The ATR indicator is used to determine this.
- ATRPercent - Peaks/troughs are detected after the data has reversed by a percentage specified by multiplying the ATRP (ATR Percent) indicator value by reversalAmount.
The first constructor takes a single BarHistory object, and the peaks and troughs are calculated based on high and low prices. The optional atrPeriod parameter lets you set the period of the ATR for ATR and ATRPercent reversal types.
The second constructor takes two TimeSeries objects, which specify the desired highs and lows. ATR and ATRPercent reversal types are not allowed for this constructor.
The third constructor takes a single TimeSeries object on which the peaks and troughs are calculated. ATR and ATRPercent reversal types are not allowed for this constructor.
The fourth constructor allows you to pass in your own list of PeakTrough instances in the pts parameter. This way you can leverage the various helper methods (such as the Trendline methods) of the PeakTroughCalculator using your own precomputed Peaks/Troughs.
Use the Divergence method to detect bullish/bearish divergence for rising troughs/descending peaks between an indicator's PeakTroughCalculator ptc and price. The idea is to use a fast-changing indicator's peaks and troughs to detect divergence, predicting either a trend reversal (regular divergence) or trend continuation (hidden divergence).
Divergence returns a DivergenceType, which can be Bullish, Bearish, HiddenBullish, HiddenBearish, or None. The PeakTrough parameters indicate the PeakTroughs that diverged from the TimeSeries and can be used to obtain references for charting as shown in the example.
Remarks
- Divergence detection occurs only on bars when a new confirmed PeakTrough is identified.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Collections.Generic; namespace WealthScript123 { public class DivergenceDetector : UserStrategyBase { public DivergenceDetector() { AddParameter("Reversal", ParameterType.Double, 8, 1, 40, 1); } public override void Initialize(BarHistory bars) { _rsi = new RSI(bars.Close, 14); PlotIndicator(_rsi, WLColor.FromArgb(255, 120, 120, 248), PlotStyle.Line); //create the PeakTroughCalculator for an Indicator or TimeSeries double reversal = Parameters[0].AsDouble; PeakTroughReversalType reversalType = PeakTroughReversalType.Point; _ptc = new PeakTroughCalculator(_rsi, reversal, reversalType); //start after at least 2 peaks and 2 troughs PeakTrough pt = _ptc.PeakTroughs[3]; StartIndex = pt.DetectedAtIndex; } public override void Execute(BarHistory bars, int idx) { PeakTrough pt = null; PeakTrough pt2 = null; if (_ptc.Divergence(idx, bars.High, out pt, out pt2) == DivergenceType.Bearish) { WLColor bearClr = WLColor.Red; DrawBarAnnotation(TextShape.ArrowDown, idx, true, WLColor.Gold, 36); DrawLine(pt.XIndex, pt.YValue, pt2.XIndex, pt2.YValue, bearClr, 2, default, _rsi.PaneTag); DrawLine(pt.XIndex, bars.High[pt.XIndex], pt2.XIndex, bars.High[pt2.XIndex], bearClr, 2, default, "Price"); } else if (_ptc.Divergence(idx, bars.Low, out pt, out pt2) == DivergenceType.Bullish) { WLColor bullClr = WLColor.Green; DrawBarAnnotation(TextShape.ArrowUp, idx, false, WLColor.Gold, 36); DrawLine(pt.XIndex, pt.YValue, pt2.XIndex, pt2.YValue, bullClr, 2, default, _rsi.PaneTag); DrawLine(pt.XIndex, bars.Low[pt.XIndex], pt2.XIndex, bars.Low[pt2.XIndex], bullClr, 2, default, "Price"); } } PeakTroughCalculator _ptc; RSI _rsi; } }
Returns true if the peak detected as of the specified index (idx) in the source data has a lower value than the previous peak.
Remarks
- This method returns a value based on confirmed PeakTroughs.
- It may be obvious that the value should change based on an unconfirmed PeakTrough long before the confirmation occurs.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript3 { public class MyStrategy : UserStrategyBase { PeakTroughCalculator _ptc; PeakTroughCalculator _ptcRsi; IndicatorBase _rsi; public override void Initialize(BarHistory bars) { StartIndex = 100; //control variables double swing = 10.0; //calculate peaks and troughs based on closes _ptc = new PeakTroughCalculator(bars.Close, swing, PeakTroughReversalType.Percent); //calculate peaks and troughs based on RSI _rsi = RSI.Series(bars.Close, 14); PlotIndicatorLine(_rsi); _ptcRsi = new PeakTroughCalculator(_rsi, swing, PeakTroughReversalType.Point); } public override void Execute(BarHistory bars, int idx) { //shade sell zones red based on divergence if (_ptc.HasRisingPeaks(idx) && _ptcRsi.HasFallingPeaks(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Red)); //shade buy zones green based on divergence if (_ptc.HasFallingTroughs(idx) && _ptcRsi.HasRisingTroughs(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Green)); } } }
Returns true if the trough detected as of the specified index (idx) in the source data has a lower value than the previous trough.
Remarks
- This method returns a value based on confirmed PeakTroughs.
- It may be obvious that the value should change based on an unconfirmed PeakTrough long before the confirmation occurs.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; namespace WealthScript { public class GetTroughExample : UserStrategyBase { PeakTroughCalculator _ptc; //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //control variables double swingPct = 5.0; //calculate peaks and troughs based on high/lows _ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); //connect the troughs - also see: DrawTroughLines() //if trough is rising, use a green line int idx = bars.Count - 1; PeakTrough lastpt = null; do { PeakTrough pt = _ptc.GetTrough(idx); if (pt == null) break; if (lastpt == null) { DrawLine(idx, pt.Value, pt.XIndex, pt.Value, WLColor.Blue, 2); } else { WLColor clr = _ptc.HasFallingTroughs(lastpt.DetectedAtIndex) ? WLColor.Fuchsia : WLColor.Green; DrawLine(lastpt.XIndex, lastpt.Value, pt.XIndex, pt.Value, clr, 2); } lastpt = pt; idx = pt.XIndex - 1; } while (idx > 10); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { //color background light pink while the last detected trough was lower than the previous one if (_ptc.HasRisingTroughs(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(20, WLColor.Blue)); } } }
Returns true if the peak detected as of the specified index (idx) in the source data has a higher value than the previous peak.
Remarks
- This method returns a value based on confirmed PeakTroughs.
- It may be obvious that the value should change based on an unconfirmed PeakTrough long before the confirmation occurs.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript3 { public class MyStrategy : UserStrategyBase { PeakTroughCalculator _ptc; PeakTroughCalculator _ptcRsi; IndicatorBase _rsi; public override void Initialize(BarHistory bars) { StartIndex = 100; //control variables double swing = 10.0; //calculate peaks and troughs based on closes _ptc = new PeakTroughCalculator(bars.Close, swing, PeakTroughReversalType.Percent); //calculate peaks and troughs based on RSI _rsi = RSI.Series(bars.Close, 14); PlotIndicatorLine(_rsi); _ptcRsi = new PeakTroughCalculator(_rsi, swing, PeakTroughReversalType.Point); } public override void Execute(BarHistory bars, int idx) { //shade sell zones red based on divergence if (_ptc.HasRisingPeaks(idx) && _ptcRsi.HasFallingPeaks(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Red)); //shade buy zones green based on divergence if (_ptc.HasFallingTroughs(idx) && _ptcRsi.HasRisingTroughs(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Green)); } } }
Returns true if the trough detected as of the specified index (idx) in the source data has a higher value than the previous trough.
Remarks
- This method returns a value based on confirmed PeakTroughs.
- It may be obvious that the value should change based on an unconfirmed PeakTrough long before the confirmation occurs.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; namespace WealthScript { public class GetTroughExample : UserStrategyBase { PeakTroughCalculator _ptc; //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //control variables double swingPct = 5.0; //calculate peaks and troughs based on high/lows _ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); //connect the troughs - also see: DrawTroughLines() //if trough is rising, use a green line int idx = bars.Count - 1; PeakTrough lastpt = null; do { PeakTrough pt = _ptc.GetTrough(idx); if (pt == null) break; if (lastpt == null) { DrawLine(idx, pt.Value, pt.XIndex, pt.Value, WLColor.Blue, 2); } else { WLColor clr = _ptc.HasFallingTroughs(lastpt.DetectedAtIndex) ? WLColor.Fuchsia : WLColor.Green; DrawLine(lastpt.XIndex, lastpt.Value, pt.XIndex, pt.Value, clr, 2); } lastpt = pt; idx = pt.XIndex - 1; } while (idx > 10); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { //color background light pink while the last detected trough was lower than the previous one if (_ptc.HasRisingTroughs(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(20, WLColor.Blue)); } } }
As of the specified index (idx) in the source data, returns 1 if the most recently detected peak has a higher value than the previous peak, and -1 if it has a lower value. If the values are equal, or one or both peaks are not available, returns 0.
Remarks
- This method returns a value based on confirmed PeakTroughs.
- It may be obvious that the value should change based on an unconfirmed PeakTrough long before the confirmation occurs.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript3 { public class PeakTroughStateExample : UserStrategyBase { PeakTroughCalculator _ptc; PeakTroughCalculator _ptcRsi; IndicatorBase _rsi; public override void Initialize(BarHistory bars) { StartIndex = 100; //control variables double swing = 10.0; //calculate peaks and troughs based on closes _ptc = new PeakTroughCalculator(bars.Close, swing, PeakTroughReversalType.Percent); //calculate peaks and troughs based on RSI _rsi = RSI.Series(bars.Close, 14); PlotIndicatorLine(_rsi); _ptcRsi = new PeakTroughCalculator(_rsi, swing, PeakTroughReversalType.Point); } public override void Execute(BarHistory bars, int idx) { //shade areas where PeakStates and TroughStates don't match if (_ptc.PeakState(idx) != _ptcRsi.PeakState(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Red)); //shade buy zones green based on divergence if (_ptc.TroughState(idx) != _ptcRsi.TroughState(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Green)); } } }
As of the specified index (idx) in the source data, returns 1 if the most recently detected trough has a higher value than the previous trough, and -1 if it has a lower value. If the values are equal, or one or both troughs are not available, returns 0.
Remarks
- This method returns a value based on confirmed PeakTroughs.
- It may be obvious that the value should change based on an unconfirmed PeakTrough long before the confirmation occurs.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript3 { public class PeakTroughStateExample : UserStrategyBase { PeakTroughCalculator _ptc; PeakTroughCalculator _ptcRsi; IndicatorBase _rsi; public override void Initialize(BarHistory bars) { StartIndex = 100; //control variables double swing = 10.0; //calculate peaks and troughs based on closes _ptc = new PeakTroughCalculator(bars.Close, swing, PeakTroughReversalType.Percent); //calculate peaks and troughs based on RSI _rsi = RSI.Series(bars.Close, 14); PlotIndicatorLine(_rsi); _ptcRsi = new PeakTroughCalculator(_rsi, swing, PeakTroughReversalType.Point); } public override void Execute(BarHistory bars, int idx) { //shade areas where PeakStates and TroughStates don't match if (_ptc.PeakState(idx) != _ptcRsi.PeakState(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Red)); //shade buy zones green based on divergence if (_ptc.TroughState(idx) != _ptcRsi.TroughState(idx)) SetBackgroundColor(bars, idx, WLColor.FromArgb(40, WLColor.Green)); } } }
Returns the most recent peak (instance of the PeakTrough class) detected as of the specified index in the source data, or null if not available.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; namespace WealthScript { public class GetPeakExample : UserStrategyBase { PeakTroughCalculator _ptc; //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //control variables double swingPct = 5.0; //calculate peaks and troughs based on high/lows _ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); //connect the peaks - also see: DrawPeakLines() int idx = bars.Count - 1; PeakTrough lastpt = null; do { PeakTrough pt = _ptc.GetPeak(idx); if (pt == null) break; if (lastpt == null) { DrawLine(idx, pt.Value, pt.XIndex, pt.Value, WLColor.Red, 2); } else { DrawLine(lastpt.XIndex, lastpt.Value, pt.XIndex, pt.Value, WLColor.Green, 1); } lastpt = pt; idx = pt.XIndex - 1; } while (idx > 10); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
Access the list of peaks (only) that were generated as of the specified idx and that are not older than maxAgeInDays calendar days. These are instances of the PeakTrough class.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript123 { public class GetTroughsAsOfExample : UserStrategyBase { PeakTroughCalculator _ptc; public override void Initialize(BarHistory bars) { //control variables double swingPct = 10.0; StartIndex = 20; //calculate peaks and troughs based on high/lows _ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); List<PeakTrough> peaks = _ptc.GetPeaksAsOf(bars.Count - 1); //connect the peaks- also see: DrawPeakLines() PeakTrough lastpt = null; foreach (PeakTrough pt in peaks) { if (lastpt == null) { lastpt = pt; continue; } DrawLine(lastpt.XIndex, lastpt.YValue, pt.XIndex, pt.YValue, WLColor.Blue, 2); lastpt = pt; } } public override void Execute(BarHistory bars, int idx) { } } }
Returns the most recent peak or trough (instance of the PeakTrough class) detected as of the specified index in the source data, or null if not available.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript3 { public class MyStrategy : UserStrategyBase { PeakTroughCalculator _ptc; public override void Initialize(BarHistory bars) { //control variables double swing = 5.0; //calculate peaks and troughs based on High/Low _ptc = new PeakTroughCalculator(bars, swing, PeakTroughReversalType.Percent); //was the most recent PeakTrough a peak or a trough? PeakTrough pt = _ptc.GetPeakTrough(bars.Count - 1); if (pt != null) { string pttype = pt.Type == PeakTroughType.Peak ? "Peak" : "Trough"; string txt = string.Format("Last detected a {0:N1}% {1} on {2:d}", swing, pttype, bars.DateTimes[pt.PeakTroughIndex]); DrawHeaderText(txt, WLColor.Red, 12); SetBackgroundColor(bars, pt.PeakTroughIndex, WLColor.FromArgb(40, WLColor.Blue)); DrawLine(pt.PeakTroughIndex, pt.Value, bars.Count - 1, pt.Value, WLColor.Blue, 2); } else { string txt = string.Format("Could not find a {0:N1}% PeakTrough given these data!", swing); DrawHeaderText(txt, WLColor.Red, 12); } } public override void Execute(BarHistory bars, int idx) { } } }
Returns the first peak that occurred prior to the PeakTrough specified in the pt parameter, or null if not available.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript { public class DivergenceExample : UserStrategyBase { IndicatorBase _rsi; PeakTroughCalculator _ptcRsi; List<string> _test = new List<string>(); //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { StartIndex = 100; //control variables double swingPts = 13.0; //calculate peaks and troughs of the RSI _rsi = RSI.Series(bars.Close, 14); _ptcRsi = new PeakTroughCalculator(_rsi, swingPts, PeakTroughReversalType.Point); //plot ZigZagHL indicator, it's based on the peak/troughs we're using //ZigZag zz = new ZigZag(_rsi, swingPts, PeakTroughReversalType.Point, false); //PlotIndicator(zz); PlotIndicatorLine(_rsi, WLColor.Blue); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { //get the last 2 peaks PeakTrough pt = _ptcRsi.GetPeak(idx); if (pt == null) return; PeakTrough prept = _ptcRsi.GetPrevPeak(pt); if (prept == null) return; //save a key so we know these were processed string key = pt.XIndex + "," + prept.XIndex; //if the key was already processed, we're done if (_test.Contains(key)) return; //new set of peaks, save it _test.Add(key); WriteToDebugLog(key); //divergence test if (_ptcRsi.HasFallingPeaks(idx)) { if (bars.High[pt.XIndex] > bars.High[prept.XIndex]) { DrawLine(pt.XIndex, bars.High[pt.XIndex], prept.XIndex, bars.High[prept.XIndex], WLColor.Green, 2); DrawLine(pt.XIndex, _rsi[pt.XIndex], prept.XIndex, _rsi[prept.XIndex], WLColor.Red, 2, LineStyle.Solid, _rsi.PaneTag); } } } } }
Returns the first trough that occurred prior to the PeakTrough specified in the pt parameter, or null if not available.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript1 { public class DivergenceExample2 : UserStrategyBase { IndicatorBase _rsi; IndicatorBase _sma; PeakTroughCalculator _ptcRsi; List<string> _test = new List<string>(); //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { PlotStopsAndLimits(4); StartIndex = 100; //control variables double swingPts = 13.0; //calculate peaks and troughs of the RSI _rsi = RSI.Series(bars.Close, 14); _ptcRsi = new PeakTroughCalculator(_rsi, swingPts, PeakTroughReversalType.Point); PlotIndicatorLine(_rsi, WLColor.Blue); _sma = SMA.Series(bars.Close, 50); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { //get the last 2 troughs PeakTrough pt = _ptcRsi.GetTrough(idx); if (pt == null) return; PeakTrough prept = _ptcRsi.GetPrevTrough(pt); if (prept == null) return; //save a key so we know these were processed string key = pt.XIndex + "," + prept.XIndex; //if the key was already processed, we're done if (_test.Contains(key)) return; //new set of peaks, save it _test.Add(key); //divergence test if (_ptcRsi.HasRisingTroughs(idx)) { if (bars.Low[pt.XIndex] < bars.Low[prept.XIndex]) { //buy this divergence PlaceTrade(bars, TransactionType.Buy, OrderType.Market); DrawLine(pt.XIndex, bars.High[pt.XIndex], prept.XIndex, bars.High[prept.XIndex], WLColor.Green, 2); DrawLine(pt.XIndex, _rsi[pt.XIndex], prept.XIndex, _rsi[prept.XIndex], WLColor.Red, 2, LineStyle.Solid, _rsi.PaneTag); } } } else { Position p = LastPosition; if (p.MFEPctAsOf(idx) > 25) { PlaceTrade(bars, TransactionType.Sell, OrderType.Stop, _sma.GetHighest(idx, idx - p.EntryBar)); } else { double tgt = p.EntryPrice * 1.30; //30% gain double stop = p.EntryPrice * 0.85; //-15% stop PlaceTrade(bars, TransactionType.Sell, OrderType.Stop, stop); PlaceTrade(bars, TransactionType.Sell, OrderType.Limit, tgt); } } } } }
Returns the most recent trough (instance of the PeakTrough class) detected as of the specified index in the source data, or null if not available.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; namespace WealthScript2 { public class GetTroughExample : UserStrategyBase { PeakTroughCalculator _ptc; public override void Initialize(BarHistory bars) { //control variables double swingPct = 5.0; StartIndex = 100; //show the limit prices, usually the PeakTrough values PlotStopsAndLimits(4); //calculate peaks and troughs based on high/lows _ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); } public override void Execute(BarHistory bars, int idx) { //buy at the previous trough and sell at the previous peak if (!HasOpenPosition(bars, PositionType.Long)) { PeakTrough tgh = _ptc.GetTrough(idx); if (tgh == null) return; PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, tgh.Value); } else { Position p = LastPosition; PeakTrough peak = _ptc.GetPeak(p.EntryBar); double tgt = peak.Value > p.EntryPrice ? peak.Value : p.EntryPrice * 1.05; PlaceTrade(bars, TransactionType.Sell, OrderType.Limit, tgt); } } } }
Access the list of troughs (only) that were generated as of the specified idx and that are not older than maxAgeInDays calendar days. These are instances of the PeakTrough class.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript123 { public class GetTroughsAsOfExample : UserStrategyBase { PeakTroughCalculator _ptc; public override void Initialize(BarHistory bars) { //control variables double swingPct = 10.0; StartIndex = 20; //calculate peaks and troughs based on high/lows _ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); List<PeakTrough> troughs = _ptc.GetTroughsAsOf(bars.Count - 1); //connect the troughs - also see: DrawTroughLines() PeakTrough lastpt = null; foreach (PeakTrough pt in troughs) { if (lastpt == null) { lastpt = pt; continue; } DrawLine(lastpt.XIndex, lastpt.YValue, pt.XIndex, pt.YValue, WLColor.Blue, 2); lastpt = pt; } } public override void Execute(BarHistory bars, int idx) { } } }
Access the list of peaks and troughs that were generated. These are instances of the PeakTrough class.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; namespace WealthScript3 { public class PeakTroughIndexExample : UserStrategyBase { PeakTroughCalculator _ptc; public override void Initialize(BarHistory bars) { //control variables double swingPct = 5.0; //calculate peaks and troughs based on high/lows _ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); foreach (PeakTrough pt in _ptc.PeakTroughs) { //highlight where the peaks and troughs occurred SetBackgroundColor(bars, pt.PeakTroughIndex, WLColor.FromArgb(40, WLColor.Blue)); //add a line to detection bar DrawLine(pt.PeakTroughIndex, pt.Value, pt.DetectedAtIndex, pt.Value, WLColor.Purple, 2); } } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
Returns the lower TrendLine at the specified index (idx), calculated using the most recent numTroughs Troughs detected at that index. A trendline with more than 2 points is calculated using linear regression.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; namespace WealthLab { public class GetLowerTrendLineExample : UserStrategyBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //control variables double swingPct = 3.0; //plot ZigZagHL indicator, it's based on the peak/troughs we're using ZigZagHL zz = new ZigZagHL(bars, swingPct, PeakTroughReversalType.Percent, false); PlotIndicator(zz); //calculate peaks and troughs based on high/lows PeakTroughCalculator ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); //get bottom trendline TrendLine bottom = ptc.GetLowerTrendLine(bars.Count - 1, 4); if (bottom == null) return; DrawLine(bottom.Index1, bottom.Value1, bottom.Index2, bottom.Value2, WLColor.Red, 2, LineStyle.Dashed); //get upper trendline TrendLine top = ptc.GetUpperTrendLine(bars.Count - 1, 4); if (top == null) return; DrawLine(top.Index1, top.Value1, top.Index2, top.Value2, WLColor.Green, 2, LineStyle.Dashed); //display deviations DrawHeaderText("Top Trendline Deviation: " + top.Deviation.ToString("N2"), WLColor.Black, 12); DrawHeaderText("Bottom Trendline Deviation: " + bottom.Deviation.ToString("N2"), WLColor.Black, 12); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
Returns the upper TrendLine at the specified index (idx), calculated using the most recent numPeaks Peaks detected at that index. A trendline with more than 2 points is calculated using linear regression.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; namespace WealthLab { public class GetUpperTrendLineExample : UserStrategyBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //control variables double swingPct = 3.0; //plot ZigZagHL indicator, it's based on the peak/troughs we're using ZigZagHL zz = new ZigZagHL(bars, swingPct, PeakTroughReversalType.Percent, false); PlotIndicator(zz); //calculate peaks and troughs based on high/lows PeakTroughCalculator ptc = new PeakTroughCalculator(bars, swingPct, PeakTroughReversalType.Percent); //get bottom trendline TrendLine bottom = ptc.GetLowerTrendLine(bars.Count - 1, 4); if (bottom == null) return; DrawLine(bottom.Index1, bottom.Value1, bottom.Index2, bottom.Value2, WLColor.Red, 2, LineStyle.Dashed); //get upper trendline TrendLine top = ptc.GetUpperTrendLine(bars.Count - 1, 4); if (top == null) return; DrawLine(top.Index1, top.Value1, top.Index2, top.Value2, WLColor.Green, 2, LineStyle.Dashed); //display deviations DrawHeaderText("Top Trendline Deviation: " + top.Deviation.ToString("N2"), WLColor.Black, 12); DrawHeaderText("Bottom Trendline Deviation: " + bottom.Deviation.ToString("N2"), WLColor.Black, 12); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
TrendlineEnvelope is the method used by the Trend Line Break Advanced Building Block condition. It returns a TrendLine for the specified parameters as follows:
- 2-Point Lines - Returns the trendline with the least (most negative) slope for rising or falling Peaks. Likewise, the rule returns the trendline with the greatest (most positive) slope for rising or falling Troughs.
- Multi-point Lines - Since several active trends may be found, returns the trendline with the least percentage deviation from the composing peaks/troughs.
Remarks
- TrendlineEnvelope discovers TrendLines that you would recognize visually.
- Consecutive Peaks/Troughs are not required, i.e., a two peak line could "envelope" several lower peaks, hence the name TrendLineEnvelope.
- Internally, the method allows up to 2 closing prices to cross the line formed by and between 2 peaks/troughs, but a TrendLine is "de-activated" when closing price crosses the TrendLine's extension.
Usage
- Create an instance of the PeakTroughCalculator specifying the BarHistory and reversal points or percentages for finding peaks and troughs.
- Call TrendlineEnvelope method specifying:
- the BarHistory, bars
- the current index idx
- to use PeakTroughType.Peak or PeakTroughType.Trough
- number of points required to form a line
- a percentage allowed that an intervening peak or trough may cross the line. 0 may be specified.
- pass false for trendlines on a linear chart or true for a log chart
- specify maxLookbackDays (calendar days) to discard old peaks or troughs looking back from the current idx
- Using an instance of the TrendLine returned, check if the deviation is within your specification. TrendLine.Deviation is the average percent distance from each peak/trough that creates the line returned by the linear least squares method.
- Test for price crossing the TrendLine extension and draw the line (see example).
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; /* Buy when price closes above a linear descending trendline that is formed by at least four 10% peaks * in a line with less than 2% deviation. Allow an intervening peak of up to 3% to cross this line. */ namespace WealthScript6 { public class DescendingTrendlineBreak : UserStrategyBase { public override void Initialize(BarHistory bars) { StartIndex = 20; _pt = new PeakTroughCalculator(bars, 10.00, PeakTroughReversalType.Percent); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { bool tlFlag = false; TrendLine tl = _pt.TrendlineEnvelope(bars, idx, PeakTroughType.Peak, 4, _findDescending, 3.00, _useLog, 1500); if (tl != null) { double val = tl.ExtendTo(idx, _useLog); if (tl.Deviation <= 2.00) { if (bars.Close[idx] > val) tlFlag = bars.Close[idx - 1] < tl.ExtendTo(idx - 1, _useLog); //draw the line on a crossing or if the last bar if (tlFlag || idx == bars.Count - 1) { //extend the line up to 5 bars past the crossing int toIdx = idx + Math.Min(bars.Count - 1 - idx, 5); DrawLine(tl.Index1, tl.Value1, toIdx, tl.ExtendTo(toIdx, _useLog), WLColor.Green, 2, LineStyle.Solid, "Price", false, false); //trendline start and end points to debug WriteToDebugLog("idx = " + idx + "\tTrendline: [" + tl.Index1 + ", " + tl.Index2 + "]\tValue @idx: " + val.ToString("N2")); } } } if (tlFlag) PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, 0, "Buy At Market (1)"); } else { //sell after 5 bars Position p = LastPosition; if (idx + 1 - p.EntryBar >= 5) { PlaceTrade(bars, TransactionType.Sell, OrderType.Market, 0, 0, "TimeBased"); } } } PeakTroughCalculator _pt; bool _findDescending = true; bool _useLog = false; } }