Help to create a bot that uses the Smoothed Heiken Ashi Indicator

Created at 22 Sep 2022, 14:54
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!
SA

samachua2011

Joined 18.04.2022

Help to create a bot that uses the Smoothed Heiken Ashi Indicator
22 Sep 2022, 14:54


.


@samachua2011
Replies

PanagiotisCharalampous
22 Sep 2022, 14:58

Hi samachua2011,

You can do this but first you need to make the haClose and haOpen series public. Then you need to check them from the cBot.

Best Regards,

Panagiotis 

Join us on Telegram and Facebook


@PanagiotisCharalampous

samachua2011
23 Sep 2022, 13:38

Hi Panagiotis,

I have read your feedback and tried to implement the changes. See below and kindly advise.

Also, how can one reference this indicator in a cbot on the Parameters, On Start & On Tick/On Bar section

 

Best Regards,

Samuel Machua.

Indicator Code

using System;
using cAlgo.API;
using cAlgo.API.Indicators;

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    public class HeikinAshiSmoothed : Indicator
    {
        [Parameter(DefaultValue = 5, MinValue = 1)]
        public int Periods { get; set; }

        [Parameter("MA Type", DefaultValue = MovingAverageType.Exponential)]
        public MovingAverageType MAType { get; set; }

        public MovingAverage maOpen;
        public MovingAverage maClose;
        public MovingAverage maHigh;
        public MovingAverage maLow;

        public IndicatorDataSeries haClose;
        public IndicatorDataSeries haOpen;

        protected override void Initialize()
        {
            maOpen = Indicators.MovingAverage(MarketSeries.Open, Periods, MAType);
            maClose = Indicators.MovingAverage(MarketSeries.Close, Periods, MAType);
            maHigh = Indicators.MovingAverage(MarketSeries.High, Periods, MAType);
            maLow = Indicators.MovingAverage(MarketSeries.Low, Periods, MAType);
            haOpen = CreateDataSeries();
            haClose = CreateDataSeries();
        }

        public override void Calculate(int index)
        {
            double haHigh;
            double haLow;
            Colors Color;

            if (index > 0 && !double.IsNaN(maOpen.Result[index - 1]))
            {
                haOpen[index] = (haOpen[index - 1] + haClose[index - 1]) / 2;
                haClose[index] = (maOpen.Result[index] + maClose.Result[index] + maHigh.Result[index] + maLow.Result[index]) / 4;
                haHigh = Math.Max(maHigh.Result[index], Math.Max(haOpen[index], haClose[index]));
                haLow = Math.Min(maLow.Result[index], Math.Min(haOpen[index], haClose[index]));
                Color = (haOpen[index] > haClose[index]) ? Colors.Red : Colors.LimeGreen;
                ChartObjects.DrawLine("BarHA" + index, index, haOpen[index], index, haClose[index], Color, 5, LineStyle.Solid);
                ChartObjects.DrawLine("LineHA" + index, index, haHigh, index, haLow, Color, 1, LineStyle.Solid);
            }
            else if (!double.IsNaN(maOpen.Result[index]))
            {
                haOpen[index] = (maOpen.Result[index] + maClose.Result[index]) / 2;
                haClose[index] = (maOpen.Result[index] + maClose.Result[index] + maHigh.Result[index] + maLow.Result[index]) / 4;
                haHigh = maHigh.Result[index];
                haLow = maLow.Result[index];

            }
        }
    }
}

 


@samachua2011

samachua2011
24 Sep 2022, 12:21

RE:

PanagiotisCharalampous said:

Hi samachua2011,

You can do this but first you need to make the haClose and haOpen series public. Then you need to check them from the cBot.

Best Regards,

Panagiotis 

Join us on Telegram and Facebook

Hi Panagiotis,

In the alternative, how would I move the logic from the indicator to a cbot?

 

Best Regards,

Samuel machua.


@samachua2011

PanagiotisCharalampous
26 Sep 2022, 10:20

Hi Samuel,

Can you make your question more specific? If you don't know how to do this, better assing the job to a professional.

Best Regards,

Panagiotis 

Join us onTelegram andFacebook


@PanagiotisCharalampous

samachua2011
27 Sep 2022, 09:24

RE:

PanagiotisCharalampous said:

Hi Samuel,

Can you make your question more specific? If you don't know how to do this, better assing the job to a professional.

Best Regards,

Panagiotis 

Join us onTelegram andFacebook

Hi Panagiotis,

Trying to be a bit more specific, we want to reference the Smooth Heiken-Ashi Indicator into the cbot whose source code I have shared below such that:

  • when the Smooth Heiken Ashi bars turn Red, the bot executes a Short trade AND
  • when the Smooth Heiken-Ashi bars turn Green, the bot executes a Long trade.

What we need is to know whether it is easier to:

  • reference the Smooth Heiken-Ashi Indicator within the bot allowing it to get signals to execute trades OR
  • move the indicator code into the cbot and refrain from referencing the indicator thereby enabling the cbot make the trades without reference to any indicator

What we also need to know is:

  • In case we go with the 1st option of referencing the indicator, how would the cbot code shared below be written to reference the indicator allowing it to get signals to execute trades.
  • In case the easier option is to move the indicator code into the cbot code below, how would the cbot code shared below be written allowing it to get signals to execute trades.

On the code, I have referenced the area that is giving us a challenge i.e. [Problematic area starts here - Problematic area ends here]

Kindly assist.

Best Regards,

Samuel Machua.

 

//////////////////////////////////////////////////////////////////////////////////////////////////////////
///Assemblies
//////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
using System.Collections.Generic;
using System.Globalization;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class PriceActionBot : Robot
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///Definitions of Enumerators
        //////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Risk Manager Enumerator
        /// </summary>
        public enum ENUM_TP_TYPE
        {
            Fixed = 0,
            RiskRatio = 1
        }

        /// <summary>
        /// Cash Manager Enumerators
        /// </summary>
        public enum ENUM_RISK_SOURCE
        {
            Equity = 0,
            Balance = 1
        }
        public enum ENUM_LOT_TYPE
        {
            Fixed_Lot = 0,
            Percent = 1
            // Fixed_Amount = 2
        }

        /// <summary>
        /// Position Manager1 Enumerator
        /// </summary>
        public enum ENUM_BAR_CHECK
        {
            Current_Bar = 0,
            Formed_Bar = 1
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///User Defined Parameters
        //////////////////////////////////////////////////////////////////////////////////////////////////////////

        #region Smooth Heiken-Ashi Parameters
        [Parameter("Period", Group = "Smooth Heikenashi Parameters", DefaultValue = 5, MinValue = 1)]
        public int Periods { get; set; }

        [Parameter("MA Type", Group = "Smooth Heikenashi Parameters", DefaultValue = MovingAverageType.Exponential)]
        public MovingAverageType MAType { get; set; }
        #endregion

        #region Input Cash Manager Parameters
        [Parameter("Lot Type", Group = "Cash Manager", DefaultValue = ENUM_LOT_TYPE.Percent)]
        public ENUM_LOT_TYPE lotType { get; set; }

        [Parameter("Risk Source", Group = "Cash Manager", DefaultValue = ENUM_RISK_SOURCE.Balance)]
        public ENUM_RISK_SOURCE riskSource { get; set; }

        [Parameter("Risk/Lot Value", Group = "Cash Manager", MinValue = 0.1)]
        public double risk { get; set; }
        #endregion

        #region Input Position Manager Parameters
        [Parameter("Bar to Check", Group = "Position Manager", DefaultValue = ENUM_BAR_CHECK.Formed_Bar)]
        public ENUM_BAR_CHECK barCheck { get; set; }

        [Parameter("Label", Group = "Position Manager", DefaultValue = "Price Action Bot")]
        public string Label { get; set; }

        [Parameter("SLoss * (ATR Value)", Group = "Position Manager", MinValue = 1)]
        public double SL { get; set; }

        [Parameter("Take Profit type", Group = "Position Manager", DefaultValue = ENUM_TP_TYPE.Fixed)]
        public ENUM_TP_TYPE tpType { get; set; }

        [Parameter("Take Profit value", Group = "Position Manager", DefaultValue = 0)]
        public double TP { get; set; }

        [Parameter("Close on the opposite signal", Group = "Position Manager", DefaultValue = true)]
        public bool oppositeClose { get; set; }

        [Parameter("Only one trade in one direction", Group = "Position Manager", DefaultValue = true)]
        public bool onlyOne { get; set; }

        [Parameter("Use Reverse Trade", Group = "Position Manager", DefaultValue = true)]
        public bool reverseTrade { get; set; }
        #endregion

        #region Input Risk Manager1
        [Parameter("Max Spread", DefaultValue = 2.0, MinValue = -5.0, Group = "Risk Manager1")]
        public double MaxSpread { get; set; }

        [Parameter("Days", DefaultValue = "Saturday,Sunday", Group = "Risk Manager1")]
        public string Days { get; set; }

        [Parameter("Start Time", DefaultValue = "07:00:00", Group = "Risk Manager1")]
        public string StartTime { get; set; }

        [Parameter("End Time", DefaultValue = "16:00:00", Group = "Risk Manager1")]
        public string EndTime { get; set; }
        #endregion

        #region Risk Manager2 Parameters
        [Parameter("Include Trailing Stop", DefaultValue = true, Group = "Risk Manager2")]
        public bool IncludeTrailingStop { get; set; }

        [Parameter("Trailing Stop Trigger (pips)", MinValue = 0, MaxValue = 500, Step = 1, Group = "Risk Manager2")]
        public int TrailingStopTrigger { get; set; }

        [Parameter("Trailing Stop Step (pips)", MinValue = 0, MaxValue = 500, Step = 1, Group = "Risk Manager2")]
        public int TrailingStopStep { get; set; }
        #endregion

        #region Risk Manager3 Parameters
        [Parameter("Use BreakEven", Group = "Risk Manager3", DefaultValue = false)]
        public bool UseBE { get; set; }

        [Parameter("BE Start (pips)", Group = "Risk Manager3", DefaultValue = 10)]
        public double BEStart { get; set; }

        [Parameter("BE Profit (pips)", Group = "Risk Manager3", DefaultValue = 0)]
        public double BEProfit { get; set; }
        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///Private & Public Call Events
        //////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Risk Manager1 Private & Public call arguments
        /// </summary>
        private AverageTrueRange atr;
        private List<DayOfWeek> _pauseDays;
        private TimeSpan _startTime, _endTime;
        private bool _isPaused;
        string label = "Spread Test";

        /// <summary>
        /// Risk Manager2 Private & Public call arguments
        /// </summary>

        /// <summary>
        /// Smooth Heiken Ashi Private & Public call arguments
        /// </summary>
        public HeikinAshiSmoothed has;
        public IndicatorDataSeries haClose;
        public IndicatorDataSeries haOpen;

        // Problematic area begins here

        protected override void OnStart()
        {
            /// <summary>
            /// Smooth Heiken Ashi arguments
            /// </summary>

            /// <summary>
            /// Stop Loss arguments with ATR
            /// </summary>
            atr = Indicators.AverageTrueRange(14, MovingAverageType.Exponential);
            var PrevATR = Math.Round(atr.Result.Last(1) / Symbol.PipSize);

            /// <summary>
            /// Risk Manager1 arguments
            /// </summary>
            _pauseDays = Days.Split(',').Select(day => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), day)).ToList();

            if (TimeSpan.TryParse(StartTime, CultureInfo.InvariantCulture, out _startTime) == false)
            {
                Print("Invalid StartTime");
                Stop();
            }

            if (TimeSpan.TryParse(EndTime, CultureInfo.InvariantCulture, out _endTime) == false)
            {
                Print("Invalid EndTime");
                Stop();
            }

            Timer.Start(1);

        }

        /// <summary>
        /// Risk Manager1 arguments
        /// </summary>
        protected override void OnTimer()
        {
            if (_pauseDays.Contains(Server.Time.DayOfWeek) && Server.Time.TimeOfDay >= _startTime && Server.Time.TimeOfDay <= _endTime)
            {
                _isPaused = true;
            }

            _isPaused = false;
        }


        protected override void OnTick()
        {
            /// <summary>
            /// Risk Manager2 arguments
            /// </summary>
            if (IncludeTrailingStop)
            {
                SetTrailingStop();
            }

            /// <summary>
            /// Risk Manager3 arguments
            /// </summary>
            if (UseBE)
                BreakEven();

            //buy & sell command
            //sell

            {
                if (oppositeClose)
                {
                    CloseOrders(TradeType.Buy);
                }
            }

            //buy

            {
                if (oppositeClose)
                {
                    CloseOrders(TradeType.Sell);
                }
            }
        }

        // Problematic area ends here

        void CloseOrders(TradeType type)
        {
            if (reverseTrade)
                type = type == TradeType.Buy ? TradeType.Sell : TradeType.Buy;

            foreach (var pos in Positions.FindAll(Label, SymbolName, type))
            {
                ClosePosition(pos);
            }
        }

        bool CheckOrder(TradeType type)
        {
            if (reverseTrade)
                type = type == TradeType.Buy ? TradeType.Sell : TradeType.Buy;

            if (Positions.Find(Label, SymbolName, type) != null)
                return false;
            return true;
        }

        void OpenOrder(TradeType type)
        {
            if (reverseTrade)
                type = type == TradeType.Buy ? TradeType.Sell : TradeType.Buy;

            double op;
            double tp = tpType == ENUM_TP_TYPE.Fixed ? TP : SL * TP;
            double sl;

            double source = riskSource == ENUM_RISK_SOURCE.Balance ? Account.Balance : Account.Equity;

            double volumeInUnits = 0;
            if (lotType == ENUM_LOT_TYPE.Fixed_Lot)
                volumeInUnits = Symbol.QuantityToVolumeInUnits(risk);
            else
                volumeInUnits = CalculateVolume(SL, risk, source);

            if (volumeInUnits == -1)
                return;
            ExecuteMarketOrder(type, SymbolName, volumeInUnits, Label, (Math.Round(atr.Result.Last(1) / Symbol.PipSize)) * SL, TP);
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///Cash Manager
        //////////////////////////////////////////////////////////////////////////////////////////////////////////

        private double CalculateVolume(double stopLossPips, double riskSize, double source)
        {
            // source = Account.Balance or Account.Equity
            double riskPerTrade = source * riskSize / 100;
            double totalPips = stopLossPips;

            double _volume;
            double exactVolume = riskPerTrade / (Symbol.PipValue * totalPips);
            if (exactVolume >= Symbol.VolumeInUnitsMin)
            {
                _volume = Symbol.NormalizeVolumeInUnits(exactVolume);
            }
            else
            {
                _volume = -1;
                Print("Not enough Equity to place minimum trade, exactVolume " + exactVolume + " is not >= Symbol.VolumeInUnitsMin " + Symbol.VolumeInUnitsMin);
            }
            return _volume;
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////
        ///Risk Managers
        //////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Risk Manager2 arguments
        /// </summary>
        private void SetTrailingStop()
        {
            var sellPositions = Positions.FindAll(Label, SymbolName, TradeType.Sell);
            foreach (var position in sellPositions)
            {
                double distance = position.EntryPrice - Symbol.Ask;
                if (distance < TrailingStopTrigger * Symbol.PipSize)
                    continue;

                double newStopLossPrice = Symbol.Ask + TrailingStopStep * Symbol.PipSize;
                if (position.StopLoss == null || newStopLossPrice < position.StopLoss)
                {
                    ModifyPosition(position, newStopLossPrice, (position.TakeProfit.HasValue ? position.TakeProfit.GetValueOrDefault() : (double?)null));
                }
            }

            var buyPositions = Positions.FindAll(Label, SymbolName, TradeType.Buy);
            foreach (var position in buyPositions)
            {
                double distance = Symbol.Bid - position.EntryPrice;
                if (distance < TrailingStopTrigger * Symbol.PipSize)
                    continue;

                double newStopLossPrice = Symbol.Bid - TrailingStopStep * Symbol.PipSize;
                if (position.StopLoss == null || newStopLossPrice > position.StopLoss)
                {
                    ModifyPosition(position, newStopLossPrice, (position.TakeProfit.HasValue ? position.TakeProfit.GetValueOrDefault() : (double?)null));
                }
            }
        }

        /// <summary>
        /// Risk Manager3 arguments
        /// </summary>
        private void BreakEven()
        {
            if (!UseBE)
                return;

            foreach (var pos in Positions.FindAll(Label, SymbolName))
            {
                if (pos.TradeType == TradeType.Buy)
                {
                    if (Symbol.Ask >= pos.EntryPrice + BEStart * Symbol.PipSize && (pos.StopLoss < pos.EntryPrice + BEProfit * Symbol.PipSize || pos.StopLoss == null))
                    {
                        ModifyPosition(pos, pos.EntryPrice + BEProfit * Symbol.PipSize, pos.TakeProfit);
                    }
                }
                if (pos.TradeType == TradeType.Sell)
                {
                    if (Symbol.Bid <= pos.EntryPrice - BEStart * Symbol.PipSize && (pos.StopLoss > pos.EntryPrice - BEProfit * Symbol.PipSize || pos.StopLoss == null))
                    {
                        ModifyPosition(pos, pos.EntryPrice + BEProfit * Symbol.PipSize, pos.TakeProfit);
                    }
                }
            }
        }

        protected override void OnStop()
        {
            // Put your deinitialization logic here
        }
    }
}

 


@samachua2011

PanagiotisCharalampous
27 Sep 2022, 11:12

Hi Samuel,

You cannot throw at me 400 lines of code and just tell me that it has a problem and I need to figure it out ,or expect me to write the code for you. You need to be very specific regaridng the problem is. Your description needs to fullfil the following

  • A clear explanation of the expected outcome
  • A clear explanation of the problem
  • Explicit steps to reproduce the problem
  • Your cBot needs to include the minimum code necessary to reproduce your problem 

Regarding your questions

  • reference the Smooth Heiken-Ashi Indicator within the bot allowing it to get signals to execute trades OR
  • move the indicator code into the cbot and refrain from referencing the indicator thereby enabling the cbot make the trades without reference to any indicator

This is really a matter of choice. Whatever is easier for you

  • In case we go with the 1st option of referencing the indicator, how would the cbot code shared below be written to reference the indicator allowing it to get signals to execute trades.
  • In case the easier option is to move the indicator code into the cbot code below, how would the cbot code shared below be written allowing it to get signals to execute trades.

Here you are actually asking me to write the code for you. I can't :) If you have specific questions about the API, I would be happy to address them. But if you need somebody to actually implement this for you

Best Regards,

Panagiotis 

Join us onTelegram andFacebook


@PanagiotisCharalampous

samachua2011
27 Sep 2022, 11:28

Such language is really not necessary. We will manage on our own


@samachua2011

PanagiotisCharalampous
27 Sep 2022, 11:32

Hi Samuel,

I am sorry if my message was misunderstood but I am trying help you. You need to make it easy for me to do so.

Best Regards,

Panagiotis 

Join us on Telegram and Facebook


@PanagiotisCharalampous

samachua2011
07 Oct 2022, 17:09

RE:

 

I have attached a simple cbot below where we need to pass the indicator values to the cbot. How can we go about writing the (if statements) to execute Long/Short positions on the cbot? Below is a simplified cbot and the indicator source code:

cBot

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class HeikenashiSmooth : Robot
    {
        [Parameter(DefaultValue = 0.0)]
        public double Parameter { get; set; }

        public HeikinAshiSmoothed has;

        protected override void OnStart()
        {
            has = Indicators.GetIndicator<HeikinAshiSmoothed>(5, MovingAverageType.Exponential);

            // Put your initialization logic here
        }

        protected override void OnTick()
        {
            // Put your core logic here
        }

        protected override void OnBar()
        {
            ///ExecuteMarketOrder(TradeType.Buy, SymbolName, VolumeInUnits, label);

            ///ExecuteMarketOrder(TradeType.Sell, SymbolName, VolumeInUnits, label);
        }

        protected override void OnStop()
        {
            // Put your deinitialization logic here
        }
    }
}

 

Indicator:

using System;
using cAlgo.API;
using cAlgo.API.Indicators;

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    public class HeikinAshiSmoothed : Indicator
    {
        [Parameter(DefaultValue = 5, MinValue = 1)]
        public int Periods { get; set; }

        [Parameter("MA Type1", DefaultValue = MovingAverageType.Exponential)]
        public MovingAverageType MAType { get; set; }

        public MovingAverage maOpen;
        public MovingAverage maClose;
        public MovingAverage maHigh;
        public MovingAverage maLow;

        public IndicatorDataSeries haClose;
        public IndicatorDataSeries haOpen;

        protected override void Initialize()
        {
            maOpen = Indicators.MovingAverage(MarketSeries.Open, Periods, MAType);
            maClose = Indicators.MovingAverage(MarketSeries.Close, Periods, MAType);
            maHigh = Indicators.MovingAverage(MarketSeries.High, Periods, MAType);
            maLow = Indicators.MovingAverage(MarketSeries.Low, Periods, MAType);
            haOpen = CreateDataSeries();
            haClose = CreateDataSeries();
        }

        public override void Calculate(int index)
        {
            double haHigh;
            double haLow;
            Colors Color;

            if (index > 0 && !double.IsNaN(maOpen.Result[index - 1]))
            {
                haOpen[index] = (haOpen[index - 1] + haClose[index - 1]) / 2;
                haClose[index] = (maOpen.Result[index] + maClose.Result[index] + 
                maHigh.Result[index] + maLow.Result[index]) / 4;
                haHigh = Math.Max(maHigh.Result[index], Math.Max(haOpen[index], haClose[index]));
                haLow = Math.Min(maLow.Result[index], Math.Min(haOpen[index], haClose[index]));
                Color = (haOpen[index] > haClose[index]) ? Colors.Red : Colors.LimeGreen;
                ChartObjects.DrawLine("BarHA" + index, index, haOpen[index], index, 
                haClose[index], Color, 5, LineStyle.Solid);
                ChartObjects.DrawLine("LineHA" + index, index, haHigh, index, haLow, Color, 1, 
                LineStyle.Solid);
            }
            else if (!double.IsNaN(maOpen.Result[index]))
            {
                haOpen[index] = (maOpen.Result[index] + maClose.Result[index]) / 2;
                haClose[index] = (maOpen.Result[index] + maClose.Result[index] + 
                maHigh.Result[index] + maLow.Result[index]) / 4;
                haHigh = maHigh.Result[index];
                haLow = maLow.Result[index];
            }
        }
    }
}

@samachua2011