Brainstorming/Discussion Strategy Encapsulation

Created at 12 Jun 2021, 10:37
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!
CT

ctid2032775

Joined 03.05.2020

Brainstorming/Discussion Strategy Encapsulation
12 Jun 2021, 10:37


Dear all,

I've developed a cTrader algo that includes automatic optimization at startup and under some circumstances (e. g. MDD exceeds a specific maximum).

To achieve this I'm using an optimization class that fetches the bar data of a specific time frame (e. g. 1 year), iterates through this data and applies the trading algorithm - i. e. simulates the real operations (open/close positions) - to find the parameters for maximum profit. Then it uses the result (i. e. trading parameters) for the "regular" trading strategy (OnBar -> Check strategy conditions -> Open/Close positions -> ...).

This is working fine but in the current structure it has some crucial disadvantages:

  • The same algorithm needs to be written twice (optimization and trading class)
  • It's risky to exactly use the same algorithm for both optimization and trading (e. g. typing error, missing brackets, etc.)
  • All changes need to be done in both classes

Now I want to extend the existing logic with some new features. But before doing this I want to merge the algorithms into one class so that I can apply all changes to optimization and trading!

The problem is that the logic is quite nested - this means the strategy doesn't return unique signals like "Open Signal", "Close Signal". in addition to that there are also additional conditions based on the result of the trend detection (i. e. new maximum/minimum, reversal up/ddown, ...).

Does someone have a good idea how to realize this? (e. g. one central strategy class, trigger different events, ...)

Any hint and support is warmly welcome!

Many thanks and regards,
Christian


@ctid2032775
Replies

ClickAlgo
12 Jun 2021, 11:27

RE:

Hi, Christian,

Can you supply your architecture document or class diagram in something like UML, this would help to see how it all fits together, it looks like at this stage you need design help and not specific code implementation.

Paul.

 


@ClickAlgo

ctid2032775
12 Jun 2021, 16:21 ( Updated at: 12 Jun 2021, 16:23 )

RE: RE:

ClickAlgo said:

Hi, Christian,

Can you supply your architecture document or class diagram in something like UML, this would help to see how it all fits together, it looks like at this stage you need design help and not specific code implementation.

Paul.

 

Hi Paul,

many thanks for your reply - I guess it's a combination of design and code issue.

Therefore, I think it's better to share the relevant code snippets instead of class diagrams:

MainClass (i. e. real trading):

using cAlgo.API;
using System;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class MainClass : Robot
    {
        // class properties

        // class variables

        protected override void OnStart()
        {
            // run optimizer
            this.RunOptimizer();
        }

        // TODO - shorten method by prob. extract some functions, optimize algorithm, ...
        protected override void OnBar()
        {
			var _trendStatus = trend.CheckTrend(_dateTime, _midPrice);	// this is part of the algorithm
			var _position = Positions.Find("MainClass", Symbol.Name);

			if ((_trendStatus == ETrendStatus.ReversalUp) && (_position != null))
			{
				var _result = ClosePosition(_position);
				if (_result.IsSuccessful)
				{
					Print("Position {0} successfully closed!", _result.Position);
					_position = null;
				}

				if (Account.Balance > balanceMax)
				{
					balanceMax = Account.Balance;
				}
				else
				{
					var _drawDown = (balanceMax - Account.Balance) / balanceMax;
					if (_drawDown > optimize.BalanceDrawDownMax)
					{
						// run optimizer
						this.RunOptimizer();
					}
				}
			}
			else if ((trend.TrendDirection == ETrendDirection.Down) && ... && (_position == null))	// trend is part of the algorithm
			{
				var _tradeSize = Symbol.NormalizeVolumeInUnits(((riskTrade * Account.Balance) / (Symbol.PipValue / Symbol.PipSize)) / (StoppLoss * Symbol.PipSize) / Symbol.LotSize);

				var _result = ExecuteMarketOrder(TradeType.Buy, Symbol.Name, _tradeSize, "MainClass");
				if (_result.IsSuccessful)
				{
					Print("Position {0} successfully opened!", _result.Position);
				}
			}
        }

        protected override void OnStop()
        {

        }

        private void RunOptimizer()
        {
            // run optimizer
            var _optimizer = new Optimizer(this, BalanceOptimize, RiskOptimize, StoppLoss);
            optimize = _optimizer.RunOptimizer();

            // perform initial optimization
            riskTrade = Math.Round(BalanceSecurityFactorTrade * RiskOptimize * BalanceDrawDownMaxTrade / (optimize.BalanceDrawDownMax > 0.0 ? optimize.BalanceDrawDownMax : BalanceDrawDownMaxTrade), 2);
            trend = new Trend(this);
            balanceMax = Account.Balance;
            firstRun = true;
        }
    }
}

OptimizeClass (i. e. simulated trading for automized optimization):

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

namespace cAlgo
{
    public class Optimize
    {
        // class properties

        // class constructors
        public Optimize(double profit) : this(0.0, 0.0, profit, 0.0, 0) { }
        public Optimize(..., double profit, double balanceDrawDownMax, int numberOfTrades)
        {
        }
    }

    public class Optimizer
    {
        // class properties
        private readonly Robot CurrentRobot;
        private readonly Chart CurrentChart;

        private readonly double BalanceStart;
        private readonly double Risk;
        private readonly double StoppLoss;

        // class constructors
        public Optimizer(Robot robot, double balanceStart, double risk, double stoppLoss)
        {
            CurrentRobot = robot;
            CurrentChart = CurrentRobot.Chart;
            BalanceStart = balanceStart;
            Risk = risk;
            StoppLoss = stoppLoss;
        }

        // optimization
        public Optimize RunOptimizer()
        {
            var _dateTimeFrom = CurrentChart.Bars.LastBar.OpenTime.AddYears(-1);
            var _listAskBid = new List<AskBid>();
            GetAskBid(_listAskBid, _dateTimeFrom);

            var _listOptimize = new List<Optimize>();

			foreach (var _askBid in _listAskBid)
			{
				var _trendStatus = _trend.CheckTrend(_dateTime, _midPrice);	// part of the algorithm

				if ((_trendStatus == ETrendStatus.ReversalUp) && (_trade.Position == true))
				{
					_trade.ClosePosition(_dateTime, _askBid.Bid);

					var _profit = Math.Round((_trade.ClosePrice - _trade.OpenPrice) * CurrentChart.Symbol.LotSize * _trade.TradeSize, 2);
					_balance += _profit;

					if (_balance > _balanceMax)
					{
						_balanceMax = _balance;
					}
					else
					{
						var _balanceDrawDown = (_balanceMax - _balance) / _balanceMax;
						if (_balanceDrawDown > _balanceDrawDownMax)
						{
							_balanceDrawDownMax = _balanceDrawDown;
						}
					}

					_totalTrades++;
				}
				else if ((_trend.TrendDirection == ETrendDirection.Down) && ... && (_trade.Position == false))	// _trend is part of the algorithm
				{
					double _tradeSize = Math.Round(((Risk * _balance) / (CurrentChart.Symbol.PipValue / CurrentChart.Symbol.PipSize)) / 
						(StoppLoss * CurrentChart.Symbol.PipSize) / CurrentChart.Symbol.LotSize, 2);
					
					_trade.OpenBuy(_tradeSize, _dateTime, _askBid.Ask);
				}
			}

			// calculate total profit
			var _totalProfit = Math.Round(_balance - BalanceStart, 2);

			// add optimization
			_listOptimize.Add(new Optimize(..., _totalProfit, _balanceDrawDownMax, _totalTrades));

            // detect the "best" optimization result
            var _optimizeQuery =
                from _optimize in _listOptimize
                orderby _optimize.Profit descending, _optimize.BalanceDrawDownMax
                select _optimize;

            return _optimizeQuery.FirstOrDefault();
        }

        // fetch market data
        private void GetAskBid(List<AskBid> listAskBid, DateTime dateFrom)
        {
            var _bars = CurrentRobot.MarketData.GetBars(CurrentChart.TimeFrame, CurrentChart.Symbol.Name);
            var i = _bars.Count;
            while ((_bars[0].OpenTime >= dateFrom) && (i > 0))
            {
                i = _bars.LoadMoreHistory();
            }

            var _count = _bars.Count;
            if (_count > 0)
            {
                foreach (var _bar in _bars)
                {
                    if (_bar.OpenTime < dateFrom) // prob. change to LINQ (?)
                    {
                        continue;
                    }
                    listAskBid.Add(new AskBid(_bar.OpenTime, _bar.Open + CurrentChart.Symbol.Spread, _bar.Open));
                }
            }
        }
    }
}

As you can see both classes are using the same logic to create open (buy) and close (sell) signals. The "real" logic is more complex and in addition to that will get more complex with the planned extensions.

The goal is now to merge this into some kind of strategy class with the central logic and use it for both optimization and real trading! Therefore, it would be easier and safer if something else needs to be added or adapted.

Many thanks and regards,
Christian


@ctid2032775