/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this * notice you can do whatever you want with this stuff. If we meet some day, * and you think this stuff is worth it, you can buy me a beer in return. * Harald Wille * ---------------------------------------------------------------------------- */ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using PTLRuntime.NETScript; using PTLRuntime.NETScript.Charts; namespace HalfTrend { /// /// Half Trend indicator /// public class HalfTrend : NETIndicator { #region Parameters [InputParameter("Amplitude", 0, 0)] public int amplitude = 2; [InputParameter("Period of ATR", 1, 0)] public int periodATR = 100; [InputParameter("Source price of low MA", 2, new object[] { "Close", PriceType.Close, "Open", PriceType.Open, "High", PriceType.High, "Low", PriceType.Low, "Typical", PriceType.Typical, "Medium", PriceType.Medium, "Weighted", PriceType.Weighted})] public PriceType sourcePriceLow = PriceType.Low; [InputParameter("Source price of high MA", 3, new object[] { "Close", PriceType.Close, "Open", PriceType.Open, "High", PriceType.High, "Low", PriceType.Low, "Typical", PriceType.Typical, "Medium", PriceType.Medium, "Weighted", PriceType.Weighted })] public PriceType sourcePriceHigh = PriceType.High; [InputParameter("Linecap", 4)] public LineCap lineCap = LineCap.ArrowAnchor; [InputParameter("Cap only", 5)] public bool capOnly = false; [InputParameter("Arrow width", 6, 1, 10)] public int penWidth = 4; [InputParameter("Arrow length factor", 7, 0, 10)] public float arrowLengthFactor = 3.0f; [InputParameter("Color Bullish", 8)] public Color colorBullish = Color.DodgerBlue; [InputParameter("Color Bearish", 9)] public Color colorBearish = Color.IndianRed; [InputParameter("Enable alert", 10)] public bool enableAlert = false; #endregion enum Trend { Bearish, Neutral, Bullish } private Pen penBearish = null; // pen for bearish signals private Pen penBullish = null; // pen for bullish signals private int amountOfhistoricalBars = 0; // amount of historical bars private int precision = 0; // fractional digits of a price tick private double low = 0; // the low of the price private double high = 0; // the high of the price private Trend previousTrend = Trend.Neutral; // the previous trend private Trend currentTrend = Trend.Neutral; // the current trend private double limitCorridorHigh = 0.0f; // upper limit of the price corridor private double limitCorridorLow = 0.0f; // lower limit of the price corridor private double lowMA = 0.0f; // low of the current bar's MA value private double highMA = 0.0f; // high of the current bars ' MA value private double averageATR = 0.0f; // average of the current ATR value private List> signalsBullish = null; // bearish signals private List> signalsBearish = null; // bullish signals private BarData barData = null; // OHLC bar data public HalfTrend() : base() { #region Initialization Author = "Harald Wille"; Comments = "Based on the popular Ozymandias indicator"; Company = ""; Copyrights = "Harald Wille"; DateOfCreation = "02.03.2017"; ExpirationDate = 0; Version = "1.0.0"; Password = "66b4a6416f59370e942d353f08a9ae36"; ProjectName = "Half Trend Indicator"; #endregion SetIndicatorLine("Upper corridor line", Color.IndianRed, 1, LineStyle.HistogrammChart); SetIndicatorLine("Lower corridor line", Color.DodgerBlue, 1, LineStyle.HistogrammChart); SetIndicatorLine("MA highs", Color.DodgerBlue, 1, LineStyle.SimpleChart); SetIndicatorLine("MA lows", Color.IndianRed, 1, LineStyle.SimpleChart); SeparateWindow = false; } /// /// This function will be called after creating /// public override void Init() { base.Init(); // set the currentdata as bar data for easy access of OHLC prices barData = CurrentData as BarData; // set the init values of the trends previousTrend = Trend.Neutral; currentTrend = Trend.Neutral; // cache precision precision = Instruments.Current.Precision; // create objects signalsBearish = new List>(); signalsBullish = new List>(); // define pens to draw penBearish = new Pen(colorBearish, penWidth); penBearish.StartCap = lineCap; penBullish = new Pen(colorBullish, penWidth); penBullish.EndCap = lineCap; // begin to draw, when there are enough bars to calculate the ATR, based on the selected period on the chart SetIndexDrawBegin(0, periodATR); } /// /// Entry point. This function is called when new quote comes /// public override void OnQuote() { base.OnQuote(); // historical bars are the overall bar count - 1, as the last bar is the real-time bar amountOfhistoricalBars = barData.Count - 1; // there needs to be st least one bars if(amountOfhistoricalBars < 1) { return; } // get the lowest price in the range [barsback, barsback + amplitude] low = barData.Low(barData.Lowest(sourcePriceLow, 0, amplitude)); // get the highest price in the range [barsback, barsback + amplitude] high = barData.High(barData.Highest(sourcePriceHigh, 0, amplitude)); // get the low of the moving average for the current bar lowMA = Math.Round(Indicators.iMA(CurrentData, amplitude, PTLRuntime.NETScript.Indicators.MAMode.SMA, 0, sourcePriceLow).GetValue(), precision); // get the low of the moving average for the current bar highMA = Math.Round(Indicators.iMA(CurrentData, amplitude, PTLRuntime.NETScript.Indicators.MAMode.SMA, 0, sourcePriceHigh).GetValue(), precision); // get the half of the average ATR value for the current bar, calculated for a period of periodATR bars averageATR = Indicators.iATR(CurrentData, periodATR, PTLRuntime.NETScript.Indicators.MAMode.SMA).GetValue() * 0.5f; if (amountOfhistoricalBars < amplitude + 1) { // if there is more than one bar, we need to compare the difference between the candles int barsAgo = 1; // if there is only one bar, we need to compare the difference between same candle if (amountOfhistoricalBars == 1) { barsAgo = 0; } if (barData.Close(0) > barData.Open(barsAgo)) { currentTrend = Trend.Bullish; } else if (barData.Close(0) < barData.Open(barsAgo)) { currentTrend = Trend.Bearish; } // set values limitCorridorHigh = high; limitCorridorLow = low; // set the indicator line values SetValue(0, limitCorridorHigh); SetValue(1, limitCorridorLow); SetValue(2, highMA); SetValue(3, lowMA); } // the trend calculation is based on different parameters, once there are enough candles required for the alogirthm else { // the previous trend was Bullish if (previousTrend == Trend.Bullish) { // the lower limit of the price corridor becomes the higher low, if price is moving up limitCorridorLow = Math.Max(low, limitCorridorLow); // the high of the MA is lower than the low of the price corridor and the actual trend is bearish // a change in behaviour from bullish to bearish happened (trend change) if (highMA < limitCorridorLow && barData.Close(0) < barData.Low(1)) { // the current trend is bullish currentTrend = Trend.Bearish; // the upper limit of the price corridor becomes the current high limitCorridorHigh = high; } } // the previous trend was Bearish else if (previousTrend == Trend.Bearish) { // the upper limit of the price corridor becomes the lower high, if price is moving down limitCorridorHigh = Math.Min(high, limitCorridorHigh); // the low of the MA is higher than the high of the price corridor and the actual trend is bearish // a change in behaviour from bearish to bullish happened (trend change) if (lowMA > limitCorridorHigh && barData.Close(0) > barData.High(1)) { // the current trend is bullish currentTrend = Trend.Bullish; // the lower limit of the price corridor becomes the current low limitCorridorLow = low; } } // a strong bullish trend is in place if (previousTrend == Trend.Bullish && currentTrend == Trend.Bullish) { SetValue(2, Math.Max(limitCorridorLow, GetValue(2, 1))); SetValue(0, GetValue(2, 0) - averageATR); SetValue(1, GetValue(2, 0)); } // a strong bearish trend is in place else if (previousTrend == Trend.Bearish && currentTrend == Trend.Bearish) { SetValue(3, Math.Min(limitCorridorHigh, GetValue(3, 1))); SetValue(0, GetValue(3, 0) + averageATR); SetValue(1, GetValue(3, 0)); } // a trend change from bearish to bullish happened else if (previousTrend == Trend.Bearish && currentTrend == Trend.Bullish) { SetValue(2, GetValue(3, 1)); SetValue(2, 1, GetValue(2)); signalsBullish.Add(new Tuple(barData.TimeClose(1), GetValue(2) - 2 * averageATR)); // only show alarm at the current bar, with a time windows of one minute, as the bar plotting can take a few seconds if (enableAlert && barData.TimeClose(0).AddSeconds(60) >= DateTime.UtcNow) { Alert(string.Format("{0}: {1} {2} signal @ {3} with price {4} and recommended stop @ {5}", ProjectName.ToString(), Instruments.Current.Symbol.ToString(), Trend.Bullish.ToString(), barData.Time(0).ToLocalTime().ToString(), GetValue(2).ToString("F5"), (GetValue(2) - 2 * averageATR).ToString("F5"))); } } // a trend change from bullish to bearish happened else if (previousTrend == Trend.Bullish && currentTrend >= Trend.Bearish) { SetValue(3, GetValue(2, 1)); SetValue(3, 1, GetValue(3)); signalsBearish.Add(new Tuple(barData.TimeClose(1), GetValue(3) + 2 * averageATR)); // only show alarm at the current bar, with a time windows of one minute, as the bar plotting can take a few seconds if (enableAlert && barData.TimeClose(0).AddSeconds(60) >= DateTime.UtcNow) { Alert(string.Format("{0}: {1} {2} signal @ {3} with price {4} and recommended stop @ {5}", ProjectName.ToString(), Instruments.Current.Symbol.ToString(), Trend.Bearish.ToString(), barData.Time(0).ToLocalTime().ToString(), GetValue(3).ToString("F5"), (GetValue(3) + 2 * averageATR).ToString("F5"))); } } } previousTrend = currentTrend; } /// /// This function will be called when the chart is drawn and redrawn /// public override void OnPaintChart(object sender, PaintChartEventArgs args) { base.OnQuote(); // draw a very short line, so the line is hidden by the cap if (capOnly) { arrowLengthFactor = 0.1f; } var shift = (CurrentChart.GetChartPoint(CurrentData.Time(), 0).X - CurrentChart.GetChartPoint(CurrentData.Time(1), 0).X) * 0.5f; // draw the signals foreach (var signal in signalsBullish) { var point = CurrentChart.GetChartPoint(signal.Item1, signal.Item2); args.Graphics.DrawLine(penBullish, point.X + shift, point.Y + (float)(penWidth * arrowLengthFactor), point.X + shift, point.Y); } foreach (var signal in signalsBearish) { var point = CurrentChart.GetChartPoint(signal.Item1, signal.Item2); args.Graphics.DrawLine(penBearish, point.X + shift, point.Y, point.X + shift, point.Y - (float)(penWidth * arrowLengthFactor)); } } /// /// This function will be called before removing /// public override void Complete() { base.Complete(); // dispose the created objects signalsBearish.Clear(); signalsBearish = null; signalsBullish.Clear(); signalsBullish = null; penBearish.Dispose(); penBearish = null; penBullish.Dispose(); penBullish = null; } } }