// Copyright PFSOFT LLC. © 2003-2017. All rights reserved. using System; using System.Collections.Generic; using PTLRuntime.NETScript; using System.Drawing; using System.Linq; using System.Text; using PTLRuntime.NETScript.Indicators; using System.Threading.Tasks; using System.Threading; using PTLRuntime.NETScript.Charts; namespace RentTrade { /// /// RentTrade /// public class RT : NETIndicator { #region Parameters [InputParameter("TimeFrame", 0, new object[] { "1 minute", Period.Min, "5 minutes",Period.Min5, "15 minutes", Period.Min15, "30 minutes", Period.Min30, "1 hour", Period.Hour, "4 hours", Period.Hour4, "1 day", Period.Day, "1 week", Period.Week, })] public int TimeFrame = Period.Min15; [InputParameter("Up band price", 1, 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 UpBandPrice = PriceType.Close; [InputParameter("Down band price", 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 DownBandPrice = PriceType.Close; [InputParameter("Period of Bollinger Bands", 3, 1, 9999)] public int BollBandPeriod = 12; [InputParameter("Moving average mode", 4, new object[]{ "Simple", MAMode.SMA, "Exponential", MAMode.EMA, "Modified", MAMode.SMMA, "Linear Weights", MAMode.LWMA, })] public MAMode MaMode = MAMode.SMA; [InputParameter("Deviation", 5, 0.1, 10.0, 1, 0.1)] public double Deviation = 1.0; [InputParameter("Period of Standart Deviation", 6, 1, 9999)] public int DeviationPeriod = 7; [InputParameter("Money risk", 7, 0.1, 1.0, 1, 0.1)] public double MoneyRisk = 1.0; [InputParameter("Enable alert mode", 8)] public bool AlertMode = false; [InputParameter("Signal mode", 9, new object[]{ "All entities", SignalMode.AllEntities, "Display line", SignalMode.DisplayLine, "Arrows for enter", SignalMode.Arrows, })] public SignalMode SignalMode = SignalMode.AllEntities; [InputParameter("Up arrow color", 10)] public Color UpArrowColor = Color.OrangeRed; [InputParameter("Down arrow color", 11)] public Color DownArrowColor = Color.DodgerBlue; #endregion private bool _isSelectedHistDataLoaded; private HistoricalData _historicalData; private Indicator _upMA; private Indicator _dnMA; private Indicator _upStdDev; private Indicator _dnStdDev; private double upBand1; private double dnBand1; private double dnBand2; private double upBand2; private int currentTrend; private int prevTrend; private double prevUpBand1; private double prevDnBand1; private double prevDnBand2; private double prevUpBand2; private int prevIndex; List signals = new List(); private Pen penBearish; private Pen penBullish; private bool _ChartDrawn; private Dictionary _periodDict; public RT() : base() { #region Initialization base.Author = ""; base.Comments = ""; base.Company = ""; base.Copyrights = ""; base.DateOfCreation = "14.03.2017"; base.ExpirationDate = 0; base.Version = ""; base.Password = "66b4a6416f59370e942d353f08a9ae36"; base.ProjectName = "RentTrade"; #endregion base.SetIndicatorLine("Trend line", Color.Orange, 2, LineStyle.SimpleChart); base.SeparateWindow = false; _periodDict = new Dictionary { { Period.Min,"1 minute" }, { Period.Min5 , "5 minutes" }, { Period.Min15, "15 minutes" }, { Period.Min30, "30 minutes" }, { Period.Hour,"1 hour" }, { Period.Hour4,"4 hours" }, { Period.Day, "1 day" }, { Period.Week, "1 week" }, }; } /// /// This function will be called after creating /// public override void Init() { if (TimeFrame < CurrentData.Period) TimeFrame = CurrentData.Period; base.IndicatorShortName(string.Format("{0} ({1}; {2})", ProjectName, BollBandPeriod, _periodDict[TimeFrame])); penBearish = new Pen(DownArrowColor); penBearish.StartCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor; penBullish = new Pen(UpArrowColor); penBullish.StartCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor; signals.Insert(0, new Signal()); _historicalData = GetHistoricalData(new HistoricalDataRequest(Instruments.Current, TimeFrame, CurrentData.DataType)); _isSelectedHistDataLoaded = false; } /// /// Entry point. This function is called when new quote comes /// public override void OnQuote() { if (_isSelectedHistDataLoaded) { // Calculate a position into new historicalData if (_historicalData.FindInterval(CurrentData.Time()) < 0) return; var hdIndex = _historicalData.Count - _historicalData.FindInterval(CurrentData.Time()) - 1; var currIndex = CurrentData.FindInterval(_historicalData.Time(hdIndex)); var counter = currIndex >= 0 ? CurrentData.Count - currIndex : 1; if (currIndex > prevIndex) { MTFNextBar(); // Invoke MTFNextBar of current timeframe prevIndex = currIndex; } var result = 0d; var signal = new Signal(); upBand1 = _upMA.GetValue(0, hdIndex) + Deviation * _upStdDev.GetValue(0, hdIndex); dnBand1 = _dnMA.GetValue(0, hdIndex) - Deviation * _dnStdDev.GetValue(0, hdIndex); upBand2 = upBand1 + 0.5 * (MoneyRisk - 1) * (upBand1 - dnBand1); dnBand2 = dnBand1 - 0.5 * (MoneyRisk - 1) * (upBand1 - dnBand1); if (_historicalData.GetPrice(PriceType.Close, hdIndex) > prevUpBand1) currentTrend = 1; if (_historicalData.GetPrice(PriceType.Close, hdIndex) < prevDnBand1) currentTrend = -1; if (currentTrend > 0) { if (dnBand1 < prevDnBand1) dnBand1 = prevDnBand1; if (dnBand2 < prevDnBand2) dnBand2 = prevDnBand2; result = dnBand2; if (prevTrend < 0) { var value = Math.Min(dnBand2, _historicalData.GetPrice(PriceType.Low, hdIndex)); signal = new Signal(_historicalData.Time(hdIndex), OperationType.Buy, value); } } if (currentTrend < 0) { if (upBand1 > prevUpBand1) upBand1 = prevUpBand1; if (upBand2 > prevUpBand2) upBand2 = prevUpBand2; result = upBand2; if (prevTrend > 0) { var value = Math.Max(upBand2, _historicalData.GetPrice(PriceType.High, hdIndex)); signal = new Signal(_historicalData.Time(hdIndex), OperationType.Sell, value); } } // Display Alert if (_ChartDrawn && signal.Operation != OperationType.None && signals[0].Operation == OperationType.None && AlertMode) Alert(string.Format("{0} signal. StopLoss value is {1}.", signal.Operation, result)); signals[0] = signal; // Rewrite the active (zero index) signal object // Redraw "whole" bar (on big timeframes) if (SignalMode != SignalMode.Arrows) { for (int i = 0; i < counter; i++) SetValue(0, i, result); } } } private void MTFNextBar() { prevDnBand1 = dnBand1; prevDnBand2 = dnBand2; prevUpBand1 = upBand1; prevUpBand2 = upBand2; prevTrend = currentTrend; if (signals[0].Operation != OperationType.None) signals.Insert(0, new Signal()); // Paste the empty signal for rewriting in the OnQuote() } public override void NextBar() { // Load a new historicaData if (!_isSelectedHistDataLoaded) { var task = Task.Run(() => Initialize()); _isSelectedHistDataLoaded = task.Result; } _ChartDrawn = IsHistoryLoaded(); } /// /// This function will be called before removing /// public override void Complete() { _dnStdDev.Dispose(); _upStdDev.Dispose(); _upMA.Dispose(); _dnMA.Dispose(); _historicalData = null; _dnMA = null; _dnStdDev = null; _upStdDev = null; _upMA = null; } public override void OnPaintChart(object sender, PaintChartEventArgs args) { if (SignalMode != SignalMode.DisplayLine) { args.Graphics.SetClip(args.Rectangle); var barWidth = CurrentChart.GetChartPoint(CurrentData.Time(), 0).X - CurrentChart.GetChartPoint(CurrentData.Time(TimeFrame / CurrentData.Period), 0).X; var selectedSignals = signals.Where(s => s.BarTime >= CurrentChart.GetTimePrice(0, 0).Time && s.BarTime <= CurrentChart.GetTimePrice(args.Rectangle.Width, 0).Time); foreach (Signal signal in selectedSignals) { var point = CurrentChart.GetChartPoint(signal.BarTime, signal.Value); switch (signal.Operation) { case OperationType.Buy: penBearish.Color = DownArrowColor; penBearish.Width = barWidth / 2; args.Graphics.DrawLine(penBearish, new PointF(point.X + barWidth / 2, point.Y + barWidth), new PointF(point.X + barWidth / 2, point.Y + barWidth * 2)); break; case OperationType.Sell: penBullish.Color = UpArrowColor; penBullish.Width = barWidth / 2; args.Graphics.DrawLine(penBullish, new PointF(point.X + barWidth / 2, point.Y - barWidth), new PointF(point.X + barWidth / 2, point.Y - barWidth * 2)); break; } } } base.OnPaintChart(sender, args); } #region Misc private bool IsHistoryLoaded() { var dt = DateTime.UtcNow; var totalSecondsOfCurrentDateTime = dt.TimeOfDay.TotalSeconds; var BarTimeSeconds = CurrentData.Time().TimeOfDay.TotalSeconds; var i = Period.Second == CurrentData.Period ? 0 : 60; return CurrentData.Time().Date == dt.Date && BarTimeSeconds + CurrentData.Period * i > totalSecondsOfCurrentDateTime; } private bool Initialize() { var date = CurrentData.Time(CurrentData.Count - 1); _upMA = Indicators.iMA(_historicalData, BollBandPeriod, MaMode, 0, UpBandPrice); _dnMA = Indicators.iMA(_historicalData, BollBandPeriod, MaMode, 0, DownBandPrice); _upStdDev = Indicators.iStdDev(_historicalData, DeviationPeriod, MaMode, UpBandPrice); _dnStdDev = Indicators.iStdDev(_historicalData, DeviationPeriod, MaMode, UpBandPrice); _historicalData.Load(date); return true; } #endregion } public enum SignalMode { AllEntities, DisplayLine, Arrows } enum OperationType { None, Buy, Sell } class Signal { public DateTime BarTime { get; private set; } public OperationType Operation { get; private set; } public double Value { get; private set; } public Signal(DateTime time, OperationType operation, double value) { BarTime = time; Operation = operation; Value = value; } public Signal() { BarTime = new DateTime(); Operation = OperationType.None; Value = 0; } } }