Backtesting providing tick or only bar data

Created at 21 Dec 2020, 13:14
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!
KE

ketos.energy

Joined 19.02.2019

Backtesting providing tick or only bar data
21 Dec 2020, 13:14


Dear community

I have a quick question regarding Backtesting.

I have a difference when I run a bot in live or if I go back in backtesting.

My question is:

If I choose "Backtesting Settings->Data-> Tick data from Server (accurate)"

Is the backtesting providing data on every tick or only the bars data?

 

Thank you so much for your help.


@ketos.energy
Replies

PanagiotisCharalampous
21 Dec 2020, 13:27

Hi ketos.energy,

It provides data for every tick.

Best Regards,

Panagiotis 

Join us on Telegram


@PanagiotisCharalampous

ketos.energy
21 Dec 2020, 16:28

RE:

PanagiotisCharalampous said:

Hi ketos.energy,

It provides data for every tick.

Best Regards,

Panagiotis 

Join us on Telegram

Thank your for your quick response Panagiotis

How come that I have a shift in every trade of between 4 seconds up to almost one minute?
In backtesting, the positions open at the exact minute (e.g. 04/12/2020 06:00, 04/12/2020 06:04 etc.)
In the live trading however, they start at the same minute, but not the same second apparently (e.g. 04/12/2020 06:00:05.770, 04/12/2020 06:04:12.554 etc.)
 


@ketos.energy

PanagiotisCharalampous
21 Dec 2020, 16:56

Hi ketos.energy,

In general, backtesting cannot simulate latency. In backtesting, the entry time is the time at which the tick was generated on the server. In live trading, it takes some time from that moment to the moment your trade is actually executed, since the quote has to travel to your PC, the code needs to be executed, the order needs to be send and then executed on the server. However I cannot know what happens in your case, since the information you provided is very limited.

Best Regards,

Panagiotis 

Join us on Telegram


@PanagiotisCharalampous

ketos.energy
21 Dec 2020, 17:03 ( Updated at: 21 Dec 2023, 09:22 )

RE:

PanagiotisCharalampous said:

Hi ketos.energy,

In general, backtesting cannot simulate latency. In backtesting, the entry time is the time at which the tick was generated on the server. In live trading, it takes some time from that moment to the moment your trade is actually executed, since the quote has to travel to your PC, the code needs to be executed, the order needs to be send and then executed on the server. However I cannot know what happens in your case, since the information you provided is very limited.

Best Regards,

Panagiotis 

Join us on Telegram

Hello Panagiotis

I completely understand that there is a latency. But I count with a latency of a few hundert milliseconds, maybe a few secondes in the worst case, but not almost a minute.

Here is the history log of the live trading and the backtesting in the same timeframe.

Live:

Live

 

Backtesting:

Backtesting


@ketos.energy

PanagiotisCharalampous
22 Dec 2020, 08:28

Hi ketos.energy,

Thanks for the screenshots but they are not useful for me. To check this further I need to reproduce this behavior. Therefore you need to share with me the complete cBot code as well as cBot parameters so that I can perform the experiment too.

Best Regards,

Panagiotis 

Join us on Telegram


@PanagiotisCharalampous

PanagiotisCharalampous
22 Dec 2020, 08:29

Also I cannot see where do you see a minute difference. Can you please point me to the deal you referring to? 


@PanagiotisCharalampous

ketos.energy
22 Dec 2020, 16:05

RE:

PanagiotisCharalampous said:

Also I cannot see where do you see a minute difference. Can you please point me to the deal you referring to? 

Hello dear Panagiotis

 

Please find the code attached here.

The time-frame I tested was: 2020/12/04-2020/12/17

Trading-Pair: GBPUSD

Params:
Volume: 10000
Buy-Start-Pips: 1
Buy TP Pips: 60
Buy SL Pips: 15
Sell-Start-Pips: 1
Sell TP Pips: 60
Sell SL Pips: 15
Spread-Toleranz: 0.5
Start UTC: 6
End-Stunde UTC: 21
End-Minute UTC: 54
Logs: Yes

 

Code:

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

/*
Kurzbeschreibung der Logik:
- Es werden je 1 Buy und 1 Sell Order automatisch gesetzt. Pro Order können die Parameter XStartPips TakeProfit-Pips und StopLoss-Pips gesetzt werden..
- Bei einem TP passiert keine weitere Logik
- Bei einem Loss einer Position wird erneut derselbe Order eröffnet.
- Beim Stoppen des Bots werden nur alle Order gestoppt. Die offenen Positionen bleiben offen.
  Der Bot kann danach neu gestartet werden und es werden nur die Orders eröffnet, wo nicht bereits eine Position offen ist.

Feinheiten:
- Der Bot ist nur zur Running-Time aktiv. Parametrisierbar sind Strat- und Endzeit.
- Zur Startzeit werden Buy- und Sell-Order automatisch gesetzt entsprechend mit xStartPips Differenz zum aktullen Ask / Bid-Preis.
- Nach Ende der Running-Time werden alle Orders und Positionen automatisch geschlossen und der Bot wartet erneut bis zur Start-Zeit.
- Zusätzlich kontrollieren wir beim Wiedereröffnen nach einem SL den aktuellen Spread. Ist er grösser als das SL, wird kein Order eröffnet.
  Dies sollte durch die Pause oben (fast) nie passieren. Mit der Logik haben wir aber einen Backup-Plan.
*/
namespace cAlgo
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ExcellenceOld : Robot
    {
        // ###### PARAMS ######
        [Parameter(DefaultValue = 10000)]
        public double Volume { get; set; }
        [Parameter("Buy Start-Pips", DefaultValue = 2)]
        public int BuyStartPips { get; set; }
        [Parameter("Buy TP Pips", DefaultValue = 250)]
        public double BuyTpPips { get; set; }
        [Parameter("Buy SL Pips", DefaultValue = 3)]
        public double BuySlPips { get; set; }
        [Parameter("Sell Start-Pips", DefaultValue = 2)]
        public int SellStartPips { get; set; }
        [Parameter("Sell TP Pips", DefaultValue = 250)]
        public double SellTpPips { get; set; }
        [Parameter("Sell SL Pips", DefaultValue = 3)]
        public double SellSlPips { get; set; }

        [Parameter("Spread-Toleranz", DefaultValue = 0.5)]
        public double SpreadToleranz { get; set; }

        [Parameter("Start-Stunde UTC", DefaultValue = 6)]
        public int StartStunde { get; set; }
        [Parameter("End-Stunde UTC", DefaultValue = 21)]
        public int EndStunde { get; set; }
        [Parameter("End-Minute UTC", DefaultValue = 54)]
        public int EndMinute { get; set; }

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

        private const string BUY_PREFIX = "_buy_";
        private const string SELL_PREFIX = "_sell_";

        private double buyEntryPrice;
        private double sellEntryPrice;

        private bool runningMode = false;
        private bool openCheckDone = false;
        private int openCheckMinute = 0;
        // Braucht es, um zu merken, dass der Tag gelaufen ist, wenn ein TP in der ersten Stunde nach dem Start geschiet.
        private bool tpDoneForToday = false;

        protected override void OnStart()
        {
            if (StartStunde >= EndStunde)
            {
                Print("{0} / ERROR: Start nicht möglich, weil Start-Stunde={1} kleiner als End-Stunde={2} sein muss. Bitte Parameter checken.", SymbolName, StartStunde, EndStunde);
                Stop();
            }

            Positions.Closed += OnPositionClosed;
            Positions.Opened += OnPositionOpened;

            if (ActivateLog)
            {
                Print("{0} / ##Start OldButGoldBot @ bid={1}, ask={2}. Bid is used.", SymbolName, Symbol.Bid, Symbol.Ask);


                if (PendingOrders.Where(p => p.SymbolName == SymbolName).ToList().Count > 0)
                {
                    Print("{0} / INFO->OnStart: PendingOrders at start", SymbolName);
                    foreach (PendingOrder order in PendingOrders.Where(p => p.SymbolName == SymbolName).ToList())
                    {
                        Print("{0} / Still open PendingOrder {1} @ {2}, StopLoss={3}, Volume={4}", SymbolName, order.Label, order.TargetPrice, order.StopLoss, order.VolumeInUnits);
                    }
                }

                if (Positions.Where(p => p.SymbolName == SymbolName).ToList().Count > 0)
                {
                    Print("{0} / INFO->OnStart: Open Positions at start", SymbolName);
                    foreach (Position position in Positions.Where(p => p.SymbolName == SymbolName).ToList())
                    {
                        Print("{0} / Still open Position {1} @ {2}, StopLoss={3}, Pips={4}, Volume={5}", SymbolName, position.Label, position.EntryPrice, position.StopLoss, position.Pips, position.VolumeInUnits);
                    }
                }
            }

            Print("{0} / Params: Volume={1}, Ask={2}, BuyStartPips={3}, Buy-TP={4}, Buy-SL={5}, Bid={6}, SellStartPips={7}, Sell-TP={8}, Sell-SL={9}, Spread={10}, Start-Stunde={11}, End-Stunde={12}, End-Minute={13}", SymbolName, Volume, Symbol.Ask, BuyStartPips, BuyTpPips, BuySlPips, Symbol.Bid, SellStartPips, SellTpPips,
            SellSlPips, GetSpreadPips(), StartStunde, EndStunde, EndMinute);
            Print("{0} / INFO->OnStart: ==============END==================", SymbolName);
        }

        protected override void OnTick()
        {
            // Start des Tages um 06:00 (default). Ist parametrisierbar
            // Ende des Tages um 21:54 (wegen Gold, welches um 21:55 schliesst) (default). Alle offenen Order und Positionen schliessen.

            if (!runningMode && tpDoneForToday)
            {
                // Reset des tpDoneForToday, sobald gestoppt wurde.
                // Braucht es in folgendem Szanario: Die Laufzeit ist zu Ende (Stoptime erreicht) und zur gleichen Zeit gibt es aber noch ein TP.
                // Durch das Ende der Laufzeit wird runningMode = false gesetzt und tpDoneForToday auf false.
                // Weil es aber noch ein TP gibt, wird tpDoneForToday erneut auf true gesetzt und so wird nie mehr gestartet.
                // Daher hier dieses Reset, sobald running = false ist.
                tpDoneForToday = false;
            }

            if (!tpDoneForToday && !runningMode && Server.TimeInUtc.Hour >= StartStunde && Server.TimeInUtc.Hour < EndStunde)
            {
                buyEntryPrice = Symbol.Ask + BuyStartPips * Symbol.PipSize;
                sellEntryPrice = Symbol.Bid - SellStartPips * Symbol.PipSize;

                Print("{0} / INFO->OnTick: Start des Tages. Eröffne neue Orders. Ask={1}, Start-BuyPrice={2}, Bid={3}, Start-SellPrice={4}, Spread={5}", SymbolName, Symbol.Ask, buyEntryPrice, Symbol.Bid, sellEntryPrice, GetSpreadPips());

                runningMode = true;
                // Reinitialisieren.
                openCheckDone = false;
                tpDoneForToday = false;
                // WICHTIG: Sonst geht er direkt in's letzte else if rein und versucht nochmals zu öffnen.
                openCheckMinute = Server.TimeInUtc.Minute;

                OpenOrders();
            }
            else if (runningMode && ((Server.TimeInUtc.Hour == EndStunde && Server.TimeInUtc.Minute >= EndMinute) || (Server.TimeInUtc.Hour == EndStunde + 1)))
            {
                // Weil es ab und zu Tage gibt, wo während mehreresn Minuten kein Tick kommt, darf hier nich tnur auf Hour = EndStunde getestet werden, sondern es muss zusätzlich EndStunde+1 genommen werden...
                // Ist unschön, aber sonst geht das Backtesting nicht
                Print("{0} / INFO->OnTick: Ende des Tages. Schliessen aller offenen Orders und Positionen.", SymbolName);

                // WICHTIG: running setzten, befor Positionen geschlossen werden. Ansonsten würde sie direkt wieder eröffnet werden!!!!!
                runningMode = false;
                tpDoneForToday = false;

                CancelOpenOrders();

                foreach (Position order in Positions.Where(p => p.SymbolName == SymbolName).ToList())
                {
                    order.Close();
                }
            }
            // Wenn nach dem Start keine neuen Order eröffnet werden konnte, weil z.B. der Spread zu diesem Zeitpunkt 
            // zu hoch war, wird hier alle Minuten nochmals versucht, diese Orders zu öffnen.
            // Nur jede Minute und nicht jeden Bar, damit es wegen Async nicht zu Überschneidungen kommt.
            else if (runningMode && !openCheckDone && Server.TimeInUtc.Second == 0 && Server.TimeInUtc.Minute != openCheckMinute)
            {
                bool bothOrdersOpen = true;
                openCheckMinute = Server.TimeInUtc.Minute;

                if (!HasOpenOrderOrPosition(TradeType.Buy))
                {
                    bothOrdersOpen = false;
                    Print("{0} / WARNING->OnTick: Dies sollte eigentlich nie passieren. Der Buy-Order konnten innerhalb einer Minute nicht eröffnet werden. Versuche es erneut.", SymbolName);
                    if ((GetSpreadPips() + SpreadToleranz) >= GetSlPips(TradeType.Buy))
                    {
                        Print("{0} / INFO->OnTick: Spread ist zu gross (Spread={1}). Öffne keinen neuen Order im Moment. Versuche in 1 Minute wieder", SymbolName, GetSpreadPips());
                    }
                    else
                    {
                        OpenOrders();
                    }
                }

                if (!HasOpenOrderOrPosition(TradeType.Sell))
                {
                    bothOrdersOpen = false;
                    Print("{0} / WARNING->OnTick: Dies sollte eigentlich nie passieren. Der Sell-Order konnten innerhalb einer Minute nicht eröffnet werden. Versuche es erneut.", SymbolName);
                    if ((GetSpreadPips() + SpreadToleranz) >= GetSlPips(TradeType.Sell))
                    {
                        Print("{0} / INFO->OnTick: Spread ist zu gross (Spread={1}). Öffne keinen neuen Order im Moment. Versuche in 1 Minute wieder", SymbolName, GetSpreadPips());
                    }
                    else
                    {
                        OpenOrders();
                    }
                }

                // Wichtig: Wenn beide Orders (oder Position) gesetzt sind, den Check stoppen.
                openCheckDone = bothOrdersOpen;
            }
        }


        protected override void OnStop()
        {

            CancelOpenOrders();

            if (ActivateLog)
            {
                double totalOpenPosition = 0;
                foreach (Position position in Positions.Where(p => p.SymbolName == SymbolName).ToList())
                {
                    Print("{0} / INFO->OnStop: Still open position {1} @ {2}, StopLoss={3}, Pips={4}, Volume={5}", SymbolName, position.Label, position.EntryPrice, position.StopLoss, position.Pips, position.VolumeInUnits);
                    totalOpenPosition += position.NetProfit;
                }
                Print("{0} / INFO ###### Total offene Positionen: {1}. Total-Verlust aus noch offenen Positionen: {2}", SymbolName, Positions.Where(p => p.SymbolName == SymbolName).ToList().Count, totalOpenPosition);
            }
        }

        private void CancelOpenOrders()
        {
            if (PendingOrders.Where(p => p.SymbolName == SymbolName).ToList().Count > 0)
            {
                Print("{0} / INFO->CancelOpenOrders: Cancel PendingOrders of current Symbol.", SymbolName);
                foreach (PendingOrder order in PendingOrders.Where(p => p.SymbolName == SymbolName).ToList())
                {
                    order.Cancel();
                }
            }
        }

        private void OpenOrders()
        {
            // Open Orders, wenn es nicht bereits die dazugehörige Position oder den Order gibt
            if (!HasOpenOrderOrPosition(TradeType.Buy))
            {
                PlaceBuyOrderAsync(buyEntryPrice, Volume, BuyTpPips, BuySlPips);
            }

            if (!HasOpenOrderOrPosition(TradeType.Sell))
            {
                PlaceSellOrderAsync(sellEntryPrice, Volume, SellTpPips, SellSlPips);
            }
        }

        private bool HasOpenOrderOrPosition(TradeType tradeType)
        {
            foreach (PendingOrder order in PendingOrders.Where(p => p.SymbolName == SymbolName).ToList())
            {
                if (tradeType == order.TradeType)
                {
                    return true;
                }
            }

            foreach (Position pos in Positions.Where(p => p.SymbolName == SymbolName).ToList())
            {
                if (tradeType == pos.TradeType)
                {
                    return true;
                }
            }

            return false;
        }

        private void PlaceSellOrderAsync(double targetPrice, double volume, double tpPips, double slPips)
        {
            string label = SymbolName + SELL_PREFIX + targetPrice;

            if (targetPrice < Symbol.Ask)
            {
                // Wenn der startValue unter dem aktuellen Kurs ist
                PlaceStopOrderAsync(TradeType.Sell, SymbolName, volume, targetPrice, label, slPips, tpPips, OnAsyncOrderPlaced);
            }
            else
            {
                // Wenn der startValue über dem aktuellen Kurs oder genau gleich ist
                PlaceLimitOrderAsync(TradeType.Sell, SymbolName, volume, targetPrice, label, slPips, tpPips, OnAsyncOrderPlaced);
            }
        }

        private void PlaceBuyOrderAsync(double targetPrice, double volume, double tpPips, double slPips)
        {
            string label = SymbolName + BUY_PREFIX + targetPrice;

            if (targetPrice < Symbol.Bid)
            {
                // Wenn der startValue unter dem aktuellen Kurs ist
                PlaceLimitOrderAsync(TradeType.Buy, SymbolName, volume, targetPrice, label, slPips, tpPips, OnAsyncOrderPlaced);
            }
            else
            {
                // Wenn der startValue über dem aktuellen Kurs oder genau gleich ist
                PlaceStopOrderAsync(TradeType.Buy, SymbolName, volume, targetPrice, label, slPips, tpPips, OnAsyncOrderPlaced);
            }
        }


        private void OnAsyncOrderPlaced(TradeResult tr)
        {
            if (tr.IsSuccessful)
            {
                if (tr.PendingOrder != null)
                {
                    if (ActivateLog)
                    {
                        PendingOrder pendingOrder = tr.PendingOrder;
                        Print("{0} / INFO->OnAsyncOrderPlaced: Der Order wurde gesetzt: {1} @ {2}, TP={3}, SL={4}", SymbolName, pendingOrder.Label, pendingOrder.TargetPrice, pendingOrder.TakeProfitPips, pendingOrder.StopLossPips);
                    }
                }
                else if (tr.Position != null)
                {
                    // Es wurde direkt eine Position aus dem Order
                    if (ActivateLog)
                    {
                        Print("{0} / INFO->OnAsyncOrderPlaced: Der Order wurde direkt zu einer Position: {1}", SymbolName, tr.Position.Label);
                    }
                }
                else
                {
                    Print("{0} / ERROR->OnAsyncOrderPlaced: Hier lief was schief. Im TradeResult ist weder ein PendingOrder, noch eine Position drin, aber das TradeResult ist Successful...", SymbolName);
                }
            }
            else
            {
                Print("{0} / ERROR->OnAsyncOrderPlaced: {1}", SymbolName, tr.Error);
            }
        }

        private void OnPositionOpened(PositionOpenedEventArgs obj)
        {
            Position openedPosition = obj.Position;

            if (openedPosition.SymbolName != SymbolName)
            {
                // MUY importante. Eine Position mit einem anderen Symbol wurde geöffnet. Hier nichts machen.
                return;
            }

            // ----> Dies sollte nie eintreten, da wir nur zwischen 06:00 und 21:54 traden. Code hier einfach als Sicherheit.
            // Es könnte sein, dass es Positionen gibt, die kein SL gesetzt haben.
            // Dieser Fehler kann auftreten, wenn zwischen dem Zeitpunkt wenn der Auftrag zum Öffnen einer
            // Position an den Server geschickt wurde und dem effektiven Eröffnen der Position der Kurs
            // mehr als SL Pips steigt / sinkt. In diesem Fall kommt es zum Error und das SL kann nicht gesetzt
            // werden. Hier räumen wir diese Fälle auf
            if (openedPosition.StopLoss == null)
            {
                double slPips = GetSlPips(openedPosition.TradeType);
                double startDiff = (GetTargetPrice(openedPosition.TradeType) - openedPosition.EntryPrice) / Symbol.PipSize;
                Print("{0} / WARNING->OnPositionOpened: StoppLoss ist null. Spread={1}, SL={2}. Position={3} sollte Preis={4}, hat aber Preis={5} => DiffPips={6}", SymbolName, GetSpreadPips(), slPips, openedPosition.Label, GetTargetPrice(openedPosition.TradeType), openedPosition.EntryPrice, startDiff);

                double slValue;
                double diff;
                if (openedPosition.TradeType == TradeType.Buy)
                {
                    slValue = openedPosition.EntryPrice - Symbol.PipSize * slPips;
                    diff = Symbol.Ask - slValue;
                }
                else
                {
                    slValue = openedPosition.EntryPrice + Symbol.PipSize * slPips;
                    diff = Symbol.Bid - slValue;
                }
                double diffPips = diff / Symbol.PipSize;

                Print("Symbol.PipSize={0}, SLPips={1}, EntryPrice={2}, SlValue={3}, Diff={4}, DiffPips={5}, Label={6}", Symbol.PipSize, slPips, openedPosition.EntryPrice, slValue, diff, diffPips, openedPosition.Label);
                TradeResult tr = ModifyPosition(openedPosition, slValue, openedPosition.TakeProfit);
                if (tr.IsSuccessful)
                {
                    Print("Neu gesetzter SL. Soll={0}, Ist={1}", slValue, tr.Position.StopLoss);
                }
                else
                {
                    Print("{0} / ERROR->OnPositionOpened: Konnte StoppLoss erneut nicht setzen. Schliesse die Position", SymbolName);
                    openedPosition.Close();
                }
            }
            else if (openedPosition.TakeProfit == null)
            {
                double tpPips = GetTpPips(openedPosition.TradeType);
                Print("{0} / WARNING->OnPositionOpened: TakeProfit ist null. Spread={1} ist wohl höher als gesetzer TP={2}. Position {3} wird direkt wieder geschlossen.", SymbolName, GetSpreadPips(), tpPips, openedPosition.Label);
                openedPosition.Close();
            }
            else if (ActivateLog)
            {
                Print("{0} / INFO->OnPositionOpened label={1}, Entry-Price={2}, TP={3}, SL={4}, Spread={5}, id={6}", SymbolName, openedPosition.Label, openedPosition.EntryPrice, openedPosition.TakeProfit, openedPosition.StopLoss, GetSpreadPips(), openedPosition.Id);
            }
        }

        private void OnPositionClosed(PositionClosedEventArgs obj)
        {
            Position closedPosition = obj.Position;

            if (closedPosition.SymbolName != SymbolName)
            {
                // MUY importante. Eine Position mit einem anderen Symbol wurde geschlossen. Hier nichts machen.
                return;
            }

            if (ActivateLog)
            {
                if (closedPosition.GrossProfit >= 0)
                {
                    Print("{0} / $$$ WIN TP->{1} with net profit= {2}", SymbolName, closedPosition.Label, closedPosition.NetProfit);
                    Print("{0} / INFO->OnPositionClosed: Schluss für heute. Cancel von anderem offenen Order.", SymbolName);

                    // ACHTUNG: Hier darf das Flag running NICHT auf false gesetzt werden. Dafür haben wir das tpDoneForToday
                    tpDoneForToday = true;

                    CancelOpenOrders();

                    return;
                }
                else
                {
                    Print("{0} / ------ LOSS / VERLUST SL->{1} with net profit= {2}. Free margin-> {3}", SymbolName, closedPosition.Label, closedPosition.NetProfit, Account.FreeMargin);
                }
            }


            // Bei SL direkt wieder eröffnen
            if (closedPosition.GrossProfit < 0)
            {
                if (!runningMode || tpDoneForToday)
                {
                    // Wenn hoher Spread ist, soll hier kein neuer Order eröffnet werden.
                    Print("{0} / INFO->OnPositionClosed: Wir sind nicht mehr im Running-Mode. Es wird kein automatischer Order mehr für den Loss eröffnet.", SymbolName);
                    return;
                }

                double targetPrice = GetTargetPrice(closedPosition.TradeType);
                double tpPips = GetTpPips(closedPosition.TradeType);
                double slPips = GetSlPips(closedPosition.TradeType);

                if ((GetSpreadPips() + SpreadToleranz) >= slPips)
                {
                    Print("{0} / INFO->OnPositionClosed: Spread ist zu gross (Spread={1}). Öffne keinen neuen Order im Moment. Versuche es in 1 Minute wieder.", SymbolName, GetSpreadPips());
                    openCheckDone = false;
                    return;
                }

                // Genau denselben Order erneut eröffnen, wenn ein Loss eingefahren wurde
                if (closedPosition.TradeType == TradeType.Buy)
                {
                    PlaceBuyOrderAsync(targetPrice, Volume, tpPips, slPips);
                }
                else
                {
                    PlaceSellOrderAsync(targetPrice, Volume, tpPips, slPips);
                }
            }
        }

        private double GetSpreadPips()
        {
            return Symbol.Spread / Symbol.PipSize;
        }

        private double GetTargetPrice(TradeType tradeType)
        {
            return tradeType == TradeType.Buy ? buyEntryPrice : sellEntryPrice;
        }

        private double GetTpPips(TradeType tradeType)
        {
            return tradeType == TradeType.Buy ? BuyTpPips : SellTpPips;
        }

        private double GetSlPips(TradeType tradeType)
        {
            return tradeType == TradeType.Buy ? BuySlPips : SellSlPips;
        }

    }

}

 

I hope I didn't forget anything.

 

In backtesting, everything logs at the exact minute. In the real life, there are seconds. I hope you understand my question.

 

Thank you so much for your help.


@ketos.energy

PanagiotisCharalampous
22 Dec 2020, 16:16

Hi ketos.energy,

Backtesting is rounding the entry time to the minute, if this is what is confusing you. 

Best Regards,

Panagiotis 

Join us on Telegram


@PanagiotisCharalampous

ketos.energy
28 Dec 2020, 23:15

RE:

PanagiotisCharalampous said:

Hi ketos.energy,

Backtesting is rounding the entry time to the minute, if this is what is confusing you. 

Best Regards,

Panagiotis 

Join us on Telegram

Hi Panagiotis

Thank you for your reply. However, I don't understand: If you have the correct data to the tick, why would you round to the minute? So the data for every tick is there but you adapt the GUI to the minute? Don't understand why you would do that, if the data is there anyway... Could you explain?


@ketos.energy

linkersx
29 Dec 2020, 00:33

RE:

PanagiotisCharalampous said:

Hi ketos.energy,

In general, backtesting cannot simulate latency. In backtesting, the entry time is the time at which the tick was generated on the server. In live trading, it takes some time from that moment to the moment your trade is actually executed, since the quote has to travel to your PC, the code needs to be executed, the order needs to be send and then executed on the server. However I cannot know what happens in your case, since the information you provided is very limited.

Best Regards,

Panagiotis 

Join us on Telegram

Why not? I've seen simulation of all kind of latencies in back testing. Of course these algorithms really run on 100% tick Back test!

I really don't understand what`s the big fuss about back testing in general.

 


@linkersx

PanagiotisCharalampous
29 Dec 2020, 08:25

Hi linkersx,

Why not? I've seen simulation of all kind of latencies in back testing. Of course these algorithms really run on 100% tick Back test!

Because it is not possible to know how the network and LP would have behaved in real time in past date e.g. at a point in time in 2017. If you are referring to some random addition of latency in the trading process, then this is something different and it is definitely not realistic and not accurate.

 Best Regards,

Panagiotis 

Join us on Telegram


@PanagiotisCharalampous

ketos.energy
29 Dec 2020, 08:46

RE:

PanagiotisCharalampous said:

Hi linkersx,

Why not? I've seen simulation of all kind of latencies in back testing. Of course these algorithms really run on 100% tick Back test!

Because it is not possible to know how the network and LP would have behaved in real time in past date e.g. at a point in time in 2017. If you are referring to some random addition of latency in the trading process, then this is something different and it is definitely not realistic and not accurate.

 Best Regards,

Panagiotis 

Join us on Telegram

I completey understood that it is not possible to know the latency etc. The thing I don't understand is: The real tick will not be at the exact minute with second and millisecond to 0. Since you have the exact tick at let's say 10:04:03.345, why do you not show that but instead you round to the minute in backtesting as you mentionned before ("Backtesting is rounding the entry time to the minute, if this is what is confusing you. ") . So this means backtesting whould show 10:04 instead of 10:04:03.345.

Why? Why not showing the exact time if you have it?

This is what I don't understand @Panagiotis


@ketos.energy

PanagiotisCharalampous
29 Dec 2020, 08:54

Hi ketos.energy,

There is no specific intention behind this except the fact that it takes less space and looks cleaner. It is just that AFAIK nobody ever requested this information. If you think it is important, post a suggestion and if it gathers enough votes, we will consider it.

Best Regards,

Panagiotis 

Join us on Telegram


@PanagiotisCharalampous

ketos.energy
29 Dec 2020, 09:12

RE:

PanagiotisCharalampous said:

Hi ketos.energy,

There is no specific intention behind this except the fact that it takes less space and looks cleaner. It is just that AFAIK nobody ever requested this information. If you think it is important, post a suggestion and if it gathers enough votes, we will consider it.

Best Regards,

Panagiotis 

Join us on Telegram

Thank you so much for your super fast answers :) For me, it was just very confusing and this gave my the input to think that in backtesting, there is only bar-data and not tick-data used. This was the reason in the first place I asked. When I do backtesting, I'd like to have the closest results to the real world. And if I compare the times, I see a difference. So in my world, I think that there is a difference. If data is here, I'd prefer having the closes look as possible. But this is just my opinion. Just confusing like this :)

 

Enjoy the holidays.


@ketos.energy

linkersx
29 Dec 2020, 17:33 ( Updated at: 29 Dec 2020, 17:39 )

RE: RE:

ketos.energy said:

PanagiotisCharalampous said:

Hi ketos.energy,

There is no specific intention behind this except the fact that it takes less space and looks cleaner. It is just that AFAIK nobody ever requested this information. If you think it is important, post a suggestion and if it gathers enough votes, we will consider it.

Best Regards,

Panagiotis 

Join us on Telegram

Thank you so much for your super fast answers :) For me, it was just very confusing and this gave my the input to think that in backtesting, there is only bar-data and not tick-data used. This was the reason in the first place I asked. When I do backtesting, I'd like to have the closest results to the real world. And if I compare the times, I see a difference. So in my world, I think that there is a difference. If data is here, I'd prefer having the closes look as possible. But this is just my opinion. Just confusing like this :)

 

Enjoy the holidays.

Please check out this-> 

it is trully 100% tick & event driven bar types, even minute bars are derived from ticks which normal btw,

since in the market we got only 2 things price and time, everything else is abstraction from that.,

With BarX bars there is no problem in back testing even in a mix of tick and time based bars

Fully Market profile bars.

Happy holydays


@linkersx

linkersx
29 Dec 2020, 17:33 ( Updated at: 29 Dec 2020, 17:37 )

RE: RE:

...


@linkersx

linkersx
30 Dec 2020, 06:55 ( Updated at: 30 Dec 2020, 06:58 )

RE:

PanagiotisCharalampous said:

Hi ketos.energy,

There is no specific intention behind this except the fact that it takes less space and looks cleaner. It is just that AFAIK nobody ever requested this information. If you think it is important, post a suggestion and if it gathers enough votes, we will consider it.

Best Regards,

Panagiotis 

Join us on Telegram

You mean, saving memory? wee, that's cool, however i doubt that having dedicated DataSeries for 

Weighted, Median and Typical prices is a space saver!

Once I have read on this forum, someone from cTrader support said, that the Tick has an Open, High, Close and low price.

He was talking about tick and not bar !

        //     Gets the Weighted prices data.
        DataSeries WeightedPrices { get; }
        //
        // Summary:
        //     Gets the Median prices data.
        DataSeries MedianPrices { get; }
        //
        // Summary:
        //     Gets the Typical prices data.
        DataSeries TypicalPrices { get; }


@linkersx

... Deleted by UFO ...

heinrich.munz
19 Jan 2021, 18:17

In Backtesting, Ticks are just backward SIMULATED from Minute Date in cTrader and MT4

 

Please note that on cTrader as well as on MT4 in in Backtesting/Simulation the single Ticks you download did not really happen this way. To save download bandwidth, the smallest timeframe which you get for backtest download data from the cTrader and MT4 servers are minute bars with its four values of OHLC (open, high, low and close). From this minute bars, the ticks are backward simulated to build the correct minute bar. This is the reason why you never should trust backtests with the “Tick data from server (accurate)” mode for cBots which are sensitive to small tick changes (i.e. scalper).

This is the reason why there are 3rd party products out there for MT4 like https://eareview.net/tick-data-suite

For cTrader I am not aware of a product to buy, so I created my own solution: Getting REAL tick data from reliable sources (i.e. Dukascopy or Birt’s TDS) and converting them to the backtesting cache format of cTrader. The cache files for cTrader can be found under: C:\Users\USERNAME\AppData\Roaming\BROKER\BacktestingCache\ACCOUNT\SYMBOL\Ticks and they have the filename format of 2021.01.18.tdbc34, one for each day.

 


@heinrich.munz