Lines Trade

Created at 28 May 2024, 14:07
How’s your experience with the cTrader Platform?
Your feedback is crucial to cTrader's development. Please take a few seconds to share your opinion and help us improve your trading experience. Thanks!
CA

caputojr

Joined 30.03.2021

Lines Trade
28 May 2024, 14:07


Hello,

I found this bot from spotware which you can trade based on a trend line over the price chart however I would like to have this same ability (to place order by drawing the trend line) over the indicator chart. Which modifications should I do to the code below to make this possible?

 

Thanks in advance

 

Source Code


// -------------------------------------------------------------------------------------------------
//
//    This code is a cAlgo API sample.
//
//    This cBot is intended to be used as a sample and does not guarantee any particular outcome or
//    profit of any kind. Use it at your own risk
//
//    The "LinesTrader cBot" allows traders to draw lines on chart which can be used a break out or retracement lines. 
//    If the price crosses a Breakout Line then an order towards the crossing direction is placed.
//    If the price crosses a Retracement Line then an order towards the opposite direction of the crossing is placed
//
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class LinesTrader : Robot
    {
        private const string HowToUseText = "How to use:\nCtrl + Left Mouse Button - Draw Breakout line\nShift + Left Mouse Button - Draw Retracement line";

        private const string HowToUseObjectName = "LinesTraderText";

        [Parameter("Stop Loss Pips", DefaultValue = 0.0, MinValue = 0.0, Step = 1)]
        public double StopLossPips { get; set; }

        [Parameter("Take Profit Pips", DefaultValue = 0.0, MinValue = 0.0, Step = 1)]
        public double TakeProfitPips { get; set; }

        [Parameter("Volume", DefaultValue = 1000, MinValue = 0, Step = 1)]
        public double Volume { get; set; }

        [Parameter("Trade", DefaultValue = true)]
        public bool IsTradingAllowed { get; set; }

        [Parameter("Send Email", DefaultValue = false)]
        public bool IsEmailAllowed { get; set; }

        [Parameter("Email address")]
        public string EmailAddress { get; set; }

        [Parameter("Show How To Use", DefaultValue = true)]
        public bool ShowHowToUse { get; set; }

        private SignalLineDrawManager DrawManager { get; set; }
        private SignalLineRepository SignalLineRepository { get; set; }

        protected override void OnStart()
        {
            DrawManager = new SignalLineDrawManager(Chart);
            SignalLineRepository = new SignalLineRepository(Chart);

            if (ShowHowToUse)
            {
                ShowHowToUseText();
                Chart.MouseDown += OnChartMouseDown;
            }
        }

        protected override void OnTick()
        {
            var lastBarIndex = MarketSeries.Close.Count - 1;
            foreach (var signalLine in SignalLineRepository.GetLines())
                if (signalLine.CanExecute(Symbol.Bid, lastBarIndex))
                {
                    signalLine.MarkAsExecuted();
                    var message = string.Format("{0} Signal line {1}{2} was executed on {3}", Time, signalLine.TradeType, signalLine.SignalType, Symbol.Code);
                    Print(message);

                    if (IsTradingAllowed)
                    {
                        ExecuteMarketOrder(signalLine.TradeType, Symbol, Volume, "LinesTrader cBot", StopLossPips, TakeProfitPips);
                    }
                    if (IsEmailAllowed)
                    {
                        Notifications.SendEmail(EmailAddress, EmailAddress, "LinesTrader Alert", message);
                    }
                }
        }

        protected override void OnStop()
        {
            SignalLineRepository.Dispose();
            DrawManager.Dispose();
        }

        private void OnChartMouseDown(ChartMouseEventArgs args)
        {
            Chart.MouseDown -= OnChartMouseDown;
            HideHowToUseText();
        }

        private void ShowHowToUseText()
        {
            Chart.DrawStaticText(HowToUseObjectName, HowToUseText, VerticalAlignment.Center, HorizontalAlignment.Center, Chart.ColorSettings.ForegroundColor);
        }

        private void HideHowToUseText()
        {
            Chart.RemoveObject(HowToUseObjectName);
        }
    }

    public class SignalLineDrawManager : IDisposable
    {
        private readonly Chart _chart;
        private ProtoSignalLine _currentProtoSignalLine;
        private const string StatusTextName = "ProtoSignalLineStatus";



        public SignalLineDrawManager(Chart chart)
        {
            _chart = chart;
            _chart.DragStart += OnChartDragStart;
            _chart.DragEnd += OnChartDragEnd;
            _chart.Drag += OnChartDrag;
        }

        private void OnChartDragStart(ChartDragEventArgs args)
        {
            if (args.ChartArea != args.Chart)
                return;

            var signalType = GetSignalType(args);
            if (signalType.HasValue)
            {
                _chart.IsScrollingEnabled = false;
                _currentProtoSignalLine = new ProtoSignalLine(_chart, signalType, args.TimeValue, args.YValue);
                UpdateStatus();
            }
            else
            {
                _currentProtoSignalLine = null;
            }
        }

        private void OnChartDragEnd(ChartDragEventArgs args)
        {
            if (_currentProtoSignalLine != null)
            {
                _currentProtoSignalLine.Complete(args.TimeValue, args.YValue);
                _currentProtoSignalLine = null;

                _chart.IsScrollingEnabled = true;
                _chart.RemoveObject(StatusTextName);
            }
        }

        private void OnChartDrag(ChartDragEventArgs args)
        {
            if (_currentProtoSignalLine != null)
            {
                _currentProtoSignalLine.Update(args.TimeValue, args.YValue);
                UpdateStatus();
            }
        }

        private void UpdateStatus()
        {
            var text = string.Format("Creating {0} line", _currentProtoSignalLine.LineLabel);
            _chart.DrawStaticText(StatusTextName, text, VerticalAlignment.Top, HorizontalAlignment.Left, _chart.ColorSettings.ForegroundColor);
        }

        private SignalType? GetSignalType(ChartDragEventArgs args)
        {
            if (args.CtrlKey && !args.ShiftKey)
                return SignalType.Breakout;
            if (!args.CtrlKey && args.ShiftKey)
                return SignalType.Retracement;

            return null;
        }

        public void Dispose()
        {
            _chart.DragStart -= OnChartDragStart;
            _chart.DragEnd -= OnChartDragEnd;
            _chart.Drag -= OnChartDrag;
        }
    }

    public class SignalLineRepository : IDisposable
    {
        private readonly Chart _chart;
        private readonly Dictionary<string, SignalLine> _signalLines;

        public SignalLineRepository(Chart chart)
        {
            _chart = chart;
            _signalLines = new Dictionary<string, SignalLine>();

            foreach (var chartTrendLine in chart.FindAllObjects<ChartTrendLine>())
                TryAddSignalLine(chartTrendLine);

            _chart.ObjectAdded += OnChartObjectAdded;
            _chart.ObjectRemoved += OnChartObjectRemoved;
            _chart.ObjectUpdated += OnChartObjectUpdated;
        }

        public IEnumerable<SignalLine> GetLines()
        {
            return _signalLines.Values;
        }

        private void TryAddSignalLine(ChartObject chartObject)
        {
            var chartTrendLine = chartObject as ChartTrendLine;
            if (chartTrendLine != null && IsSignalLine(chartTrendLine))
                _signalLines.Add(chartTrendLine.Name, CreateSignalLine(chartTrendLine));
        }

        private void TryRemoveSignalLine(ChartObject chartObject)
        {
            if (_signalLines.ContainsKey(chartObject.Name))
                _signalLines.Remove(chartObject.Name);
        }

        private void OnChartObjectAdded(ChartObjectAddedEventArgs args)
        {
            if (args.Area != args.Chart)
                return;

            TryAddSignalLine(args.ChartObject);
        }

        private void OnChartObjectUpdated(ChartObjectUpdatedEventArgs args)
        {
            if (args.Area != args.Chart)
                return;

            TryRemoveSignalLine(args.ChartObject);
            TryAddSignalLine(args.ChartObject);
        }

        private void OnChartObjectRemoved(ChartObjectRemovedEventArgs args)
        {
            if (args.Area != args.Chart)
                return;

            TryRemoveSignalLine(args.ChartObject);
        }

        private SignalLine CreateSignalLine(ChartTrendLine chartTrendLine)
        {
            var signalType = GetLineSignalType(chartTrendLine);
            var tradeType = GetLineTradeType(chartTrendLine);
            var signalLine = new SignalLine(chartTrendLine, signalType, tradeType);
            return signalLine;
        }

        private bool IsSignalLine(ChartTrendLine line)
        {
            return SignalLineLabels.AllLables.Contains(line.Comment);
        }

        private SignalType GetLineSignalType(ChartTrendLine line)
        {
            var comment = line.Comment;
            if (comment == SignalLineLabels.BuyBreakoutLabel || comment == SignalLineLabels.SellBreakoutLabel)
                return SignalType.Breakout;
            if (comment == SignalLineLabels.BuyRetraceLabel || comment == SignalLineLabels.SellRetraceLabel)
                return SignalType.Retracement;
            throw new ArgumentException();
        }

        private TradeType GetLineTradeType(ChartTrendLine line)
        {
            var comment = line.Comment;
            if (comment == SignalLineLabels.BuyBreakoutLabel || comment == SignalLineLabels.BuyRetraceLabel)
                return TradeType.Buy;
            if (comment == SignalLineLabels.SellBreakoutLabel || comment == SignalLineLabels.SellRetraceLabel)
                return TradeType.Sell;
            throw new ArgumentException();
        }

        public void Dispose()
        {
            _chart.ObjectAdded -= OnChartObjectAdded;
            _chart.ObjectRemoved -= OnChartObjectRemoved;
            _chart.ObjectUpdated -= OnChartObjectUpdated;
        }
    }

    public class ProtoSignalLine
    {

        private static readonly Color BuyLineColor = Color.Green;
        private static readonly Color SellLineColor = Color.Red;

        private readonly Chart _chart;
        private readonly ChartTrendLine _line;
        private readonly SignalType? _signalType;

        public ProtoSignalLine(Chart chart, SignalType? signalType, DateTime startTimeValue, double startYValue)
        {
            _chart = chart;
            _signalType = signalType;

            _line = _chart.DrawTrendLine(string.Format("LinesTrader {0:N}", Guid.NewGuid()), startTimeValue, startYValue, startTimeValue, startYValue, LineColor);

            _line.ExtendToInfinity = true;
            _line.Thickness = 2;
            _line.IsInteractive = true;
        }

        private bool IsPriceAboveLine
        {
            get { return _line != null && _chart.Symbol.Bid >= _line.CalculateY(_chart.MarketSeries.Close.Count - 1); }
        }

        private TradeType LineTradeType
        {
            get { return _signalType == SignalType.Breakout ? (IsPriceAboveLine ? TradeType.Sell : TradeType.Buy) : (IsPriceAboveLine ? TradeType.Buy : TradeType.Sell); }
        }

        private Color LineColor
        {
            get { return LineTradeType == TradeType.Buy ? BuyLineColor : SellLineColor; }
        }

        public string LineLabel
        {
            get { return _signalType == SignalType.Breakout ? (LineTradeType == TradeType.Buy ? SignalLineLabels.BuyBreakoutLabel : SignalLineLabels.SellBreakoutLabel) : (LineTradeType == TradeType.Buy ? SignalLineLabels.BuyRetraceLabel : SignalLineLabels.SellRetraceLabel); }
        }

        private bool CanComplete
        {
            get { return _line.Time1 != _line.Time2 || Math.Abs(_line.Y1 - _line.Y2) >= _chart.Symbol.PipValue; }
        }

        public void Update(DateTime timeValue, double yValue)
        {
            _line.Time2 = timeValue;
            _line.Y2 = yValue;
            _line.Color = LineColor;
        }

        public void Complete(DateTime timeValue, double yValue)
        {
            Update(timeValue, yValue);

            if (CanComplete)
            {
                _line.Comment = LineLabel;
                _line.IsInteractive = true;
            }
            else
            {
                _chart.RemoveObject(_line.Name);
            }
        }
    }

    public class SignalLine
    {
        public TradeType TradeType { get; private set; }
        public SignalType SignalType { get; private set; }

        private readonly ChartTrendLine _chartTrendLine;



        public SignalLine(ChartTrendLine chartTrendLine, SignalType signalType, TradeType tradeType)
        {
            _chartTrendLine = chartTrendLine;
            SignalType = signalType;
            TradeType = tradeType;
        }

        public void MarkAsExecuted()
        {
            _chartTrendLine.Thickness = 1;
            _chartTrendLine.Color = Color.FromArgb(150, _chartTrendLine.Color);
        }

        public bool CanExecute(double price, int barIndex)
        {
            if (_chartTrendLine.Thickness <= 1)
                return false;

            var lineValue = _chartTrendLine.CalculateY(barIndex);

            switch (SignalType)
            {
                case SignalType.Breakout:
                    return CanExecuteForBreakout(price, lineValue);
                case SignalType.Retracement:
                    return CanExecuteForRetrace(price, lineValue);
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private bool CanExecuteForBreakout(double price, double lineValue)
        {
            return TradeType == TradeType.Buy && price > lineValue || TradeType == TradeType.Sell && price < lineValue;
        }

        private bool CanExecuteForRetrace(double price, double lineValue)
        {
            return TradeType == TradeType.Buy && price <= lineValue || TradeType == TradeType.Sell && price >= lineValue;
        }
    }

    public enum SignalType
    {
        Breakout,
        Retracement
    }

    public static class SignalLineLabels
    {
        public const string BuyBreakoutLabel = "BuyBreakout";
        public const string SellBreakoutLabel = "SellBreakout";
        public const string BuyRetraceLabel = "BuyRetracement";
        public const string SellRetraceLabel = "SellRetracement";

        public static readonly string[] AllLables = 
        {
            BuyBreakoutLabel,
            SellBreakoutLabel,
            BuyRetraceLabel,
            SellRetraceLabel
        };
    }
}

 


 


@caputojr
Replies

PanagiotisCharalampous
29 May 2024, 05:33

Hi there,

This would need a lot of work since you would need to define your requirements better e.g. what should cross the line if not the price. If you need professional assistance, you can contact a consultant.

Best regards,

Panagiotis


@PanagiotisCharalampous