Search Framework:
PeakTroughCalculator
Namespace: WealthLab.Indicators
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).

Chart Rendering
DrawPeakLines
public void DrawPeakLines(UserStrategyBase usb, WLColor color = null, int lineWidth = 1, LineStyle lineStyle = LineStyle.Solid, string paneTag = "Price", bool behindBars = false)

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.
Example Code
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;
	}
}

DrawTroughLines
public void DrawTroughLines(UserStrategyBase usb, WLColor color = null, int lineWidth = 1, LineStyle lineStyle = LineStyle.Solid, string paneTag = "Price", bool behindBars = false)

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.
Example Code
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;
	}
}


Constructors
PeakTroughCalculator
public PeakTroughCalculator(BarHistory source, double reversal, PeakTroughReversalType reversalType = PeakTroughReversalType.Percent, int atrPeriod = 10)
public PeakTroughCalculator(TimeSeries highs, TimeSeries lows, double reversalAmount, PeakTroughReversalType reversalType = PeakTroughReversalType.Percent)
public PeakTroughCalculator(TimeSeries source, double reversal, PeakTroughReversalType reversalType = PeakTroughReversalType.Percent)
public PeakTroughCalculator(List<PeakTrough> pts)

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.



Divergences
Divergence
public DivergenceType Divergence(int bar, TimeSeries price, out PeakTrough pt, out PeakTrough pt2)

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.
Example Code
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;
	}
}

HasFallingPeaks
public bool HasFallingPeaks(int idx)

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.
Example Code
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));
		}
	}
}

HasFallingTroughs
public bool HasFallingTroughs(int idx)

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.
Example Code
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));
		}
	}
}

HasRisingPeaks
public bool HasRisingPeaks(int idx)

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.
Example Code
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));
		}
	}
}

HasRisingTroughs
public bool HasRisingTroughs(int idx)

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.
Example Code
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));
		}
	}
}

PeakState
public int PeakState(int idx)

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.
Example Code
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));
		}
	}
}

TroughState
public int TroughState(int idx)

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.
Example Code
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));
		}
	}
}


Peaks & Troughs
GetPeak
public PeakTrough GetPeak(int idx)

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.

Example Code
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)
		{

		}
	}
}

GetPeaksAsOf
public List<PeakTrough> GetPeaksAsOf(int idx, int maxAgeInDays = Int32.MaxValue)

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.

Example Code
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)
		{
		}
	}
}

GetPeakTrough
public PeakTrough GetPeakTrough(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.

Example Code
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)
		{

		}
	}
}

GetPrevPeak
public PeakTrough GetPrevPeak(PeakTrough pt)

Returns the first peak that occurred prior to the PeakTrough specified in the pt parameter, or null if not available.

Example Code
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);
				}
			}			
		}
	}
}

GetPrevTrough
public PeakTrough GetPrevTrough(PeakTrough pt)

Returns the first trough that occurred prior to the PeakTrough specified in the pt parameter, or null if not available.

Example Code
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);
				}
			}
		}
	}
}

GetTrough
public PeakTrough GetTrough(int idx)

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.

Example Code
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);
			}
		}
	}
}

GetTroughsAsOf
public List<PeakTrough> GetTroughsAsOf(int idx, int maxAgeInDays = Int32.MaxValue)

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.

Example Code
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)
		{
		}
	}
}

PeakTroughs
public List<PeakTrough> PeakTroughs

Access the list of peaks and troughs that were generated. These are instances of the PeakTrough class.

Example Code
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)
		{
		}
	}
}


TrendLines
GetLowerTrendLine
public TrendLine GetLowerTrendLine(int idx, int numTroughs)

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.

Example Code
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)
		{
		}
	}
}

GetUpperTrendLine
public TrendLine GetUpperTrendLine(int idx, int numPeaks)

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.

Example Code
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
public TrendLine TrendlineEnvelope(BarHistory bars, int idx, PeakTroughType ptType, int points, bool useDescendingPeakTroughs, double allowableIncursionPct = 3, bool useLog = false, int maxLookbackDays = 1000)

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

  1. Create an instance of the PeakTroughCalculator specifying the BarHistory and reversal points or percentages for finding peaks and troughs.
  2. 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
  1. 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.
  2. Test for price crossing the TrendLine extension and draw the line (see example).
Example Code
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;
	}
}