
Dekodierung von Intraday-Handelsstrategien des Opening Range Breakout
Einführung
Die Strategien des Opening Range Breakout (ORB) basieren auf der Idee, dass die erste Handelsspanne, die sich kurz nach der Markteröffnung bildet, wichtige Preisniveaus widerspiegelt, bei denen sich Käufer und Verkäufer auf einen Wert einigen. Durch die Identifizierung von Ausbrüchen über oder unter einer bestimmten Spanne können Händler von der Dynamik profitieren, die oft folgt, wenn die Marktrichtung klarer wird.
In diesem Artikel werden wir drei ORB-Strategien untersuchen, die aus den Papieren der Concretum Group übernommen wurden. Zunächst werden wir den Forschungshintergrund, einschließlich der Schlüsselkonzepte und der verwendeten Methodik, behandeln. Dann werden wir für jede Strategie erklären, wie sie funktioniert, ihre Signalregeln auflisten und ihre Leistung statistisch analysieren. Schließlich werden wir sie aus einer Portfolioperspektive betrachten und uns dabei auf das Thema Diversifizierung konzentrieren.
Dieser Artikel geht nicht näher auf die Programmierung ein, sondern konzentriert sich stattdessen auf den Forschungsprozess, einschließlich der Nachbildung, Analyse und Prüfung der Strategien aus diesen drei Arbeiten. Dieses Buch ist für Leser geeignet, die auf der Suche nach potenziellen Handelsvorteilen sind oder sich dafür interessieren, wie diese Strategien untersucht und repliziert wurden. Dennoch wird der gesamte MQL5-Code für die EAs offengelegt werden. Die Leserinnen und Leser sind eingeladen, den Rahmen selbst zu erweitern.
Hintergrund der Forschung
Dieser Abschnitt behandelt die Forschungsmethodik, die wir zur Analyse der Strategien anwenden werden, sowie Schlüsselkonzepte, die später im Artikel auftauchen werden.
Die Concretum Group ist eines der wenigen akademischen Forschungsteams, das Intraday-Handelsstrategien entwickelt. In den Studien, die wir übernehmen, konzentrieren sie sich auf Strategien, die zwischen Marktöffnung und -schluss (9:30 Uhr bis 16:00 Uhr Eastern Time) gehandelt werden. Da unser Broker UTC+2/3 verwendet, bedeutet dies 18:30-24:00 Serverzeit - stellen Sie sicher, dass Sie beim Testen die Zeitzone Ihres Brokers berücksichtigen.
Die ursprüngliche Untersuchung handelt mit QQQ, einem ETF, der den Nasdaq-100-Index abbildet. Es ist wichtig zu wissen, dass der Nasdaq-100 die Performance von 100 großen, technologieorientierten Unternehmen an der Nasdaq-Börse darstellt. Der Index selbst ist eigentlich nicht handelbar, sondern nur seine Derivate. QQQ ermöglicht Anlegern ein Engagement in diesen Unternehmen über eine einzige Aktie. Für unsere Tests werden wir mit USTEC (einem CFD auf den Nasdaq-100) handeln, der die Spekulation auf Kursbewegungen ermöglicht, ohne die zugrunde liegenden Vermögenswerte zu besitzen, wobei häufig eine Hebelwirkung eingesetzt wird, um Gewinne oder Verluste zu vergrößern.
Wir werden in diesem Artikel zwei wichtige Kennzahlen vorstellen: Alpha und Beta. Im Handel steht Alpha für die Überschussrendite, die eine Anlage im Vergleich zu einer Benchmark wie einem Marktindex erzielt. Sie zeigt, ob die Investition die Erwartungen übertrifft und im Wesentlichen den Vorsprung widerspiegelt. Beta misst die Empfindlichkeit einer Anlage gegenüber Marktbewegungen. Ein Beta von 1 bedeutet, dass er die Schwankungen des Marktes widerspiegelt. Ein Wert über 1 deutet auf eine höhere Volatilität hin, ein Wert unter 1 auf eine geringere Volatilität. Diese Kennzahlen sind wichtig, um zu verstehen, inwieweit Ihre Strategie von Markttrends abhängt und nicht von Ihrem einzigartigen Vorteil. Dieses Wissen hilft Ihnen bei der Einschätzung potenzieller direktionaler Verzerrungen in Trendwerten wie Indizes oder Kryptowährungen.
Alpha und Beta werden wie folgt berechnet:
Ri ist die Anlagerendite, Rf ist der risikofreie Zinssatz, der häufig auf der Rendite von Staatsanleihen basiert oder ignoriert wird, und Rm ist die Marktrendite. Kovarianz und Varianz werden in der Regel anhand von Tagesrenditen berechnet.
Ein Schlüsselindikator, der später in diesem Artikel verwendet wird, ist der volumengewichtete Durchschnittspreis (VWAP). Er wird berechnet als:
Die Intuition hinter dem VWAP besteht darin, den durchschnittlichen Preis zu messen, zu dem ein Wertpapier gehandelt wird, gewichtet nach dem Volumen, was die „wahren“ Kosten des Handels über einen bestimmten Zeitraum widerspiegelt. Im Gegensatz zu einem einfachen Durchschnitt werden Kurse mit höherer Handelsaktivität stärker gewichtet, was sie zu einer faireren Benchmark macht.
Er wird häufig im algorithmischen Handel eingesetzt:
- Er dient als Trendfilter.
- Er dient als Trailing-Stopp.
- Er dient als Signalgeber (z. B. Einstieg bei Überschreiten des VWAP).
In der Regel beginnen wir mit der Berechnung des VWAP ab der ersten Kerze bei Markteröffnung. In der obigen Gleichung steht Pi für den Kurs der i-ten Kerze, in der Regel der Schlusskurs, und Vi für das Handelsvolumen der i-ten Kerze. Das Handelsvolumen kann aufgrund unterschiedlicher Liquiditätsanbieter von CFD-Broker zu CFD-Broker variieren, aber die relative Gewichtung sollte im Allgemeinen bei allen Brokern gleich sein.
Dieser Artikel implementiert das Leverage-Space-Modell. Bei dieser Methode wird ein bestimmter Prozentsatz unseres Guthabens pro Handel riskiert, der ausgelöst wird, wenn der Stop-Loss erreicht wird. Der Stop-Loss-Bereich wird ein fester Prozentsatz des Vermögens sein, um sich an dessen Wertentwicklung und Volatilität anzupassen. Das Risiko pro Handel wird mit runden Zahlen festgelegt, um der Einfachheit halber einen maximalen Drawdown von etwa 20 % zu erreichen. Wir werden jede Strategie über einen Zeitraum von fünf Jahren, vom 1. Januar 2020 bis zum 1. Januar 2025, testen, um genügend aktuelle Daten zur Bewertung der aktuellen Rentabilität zu sammeln. Eine gründliche statistische Analyse wird Vergleiche mit „Kaufen und Halten“ auf der Grundlage kumulativer prozentualer Gewinne und individueller Leistungskennzahlen umfassen.
Strategie Eins: Richtung der Eröffnungskerze
Die erste Strategie, die wir uns ansehen werden, ist eine klassische Eröffnungsausbruchsstrategie, die in dem Papier „Can Day Trading Really Be Profitable?“ von der Concretum Group vorgestellt wird. Die Motivation hinter den Regeln für die Strategiesignale liegt in der Erfassung kurzfristiger Preisdynamik bei gleichzeitiger Abwägung von Praktikabilität und Risikomanagement für Daytrader. Die Autoren wählten den ORB-Ansatz, um die erhöhte Volatilität und die Richtungsdynamik auszunutzen, die häufig bei der Markteröffnung zu beobachten sind. Dieser Zeitraum gilt als kritisches Zeitfenster, in dem institutionelle Anleger häufig aktiv sind, und Privatanleger können die Kursrichtung dieses Zeitraums als Anhaltspunkt für die Bestimmung des Trends für den gesamten Tag nutzen.
Nach Durchsicht des Papiers haben wir mehrere Möglichkeiten zur Verbesserung der ursprünglichen Strategie gefunden. Der ursprüngliche Ansatz verwendete das Hoch oder Tief der ersten Fünf-Minuten-Kerze als Stop-Loss und einen Take-Profit von 10R. Diese Methode war zwar profitabel, aber für Kleinhändler im Live-Handel unpraktisch. Der enge Stop-Loss der ersten Fünf-Minuten-Kerze erhöhte die relativen Handelskosten. Außerdem war der Take-Profit von 10R unnötig, da wir alle Handelsgeschäfte bis zum Ende des Tages schließen und er nur selten erreicht wurde. Schließlich fehlte der ursprünglichen Strategie ein Regimefilter, sodass das Hinzufügen eines gleitenden Durchschnitts die Strategie verbessern könnte, indem er als Regimefilter dient.
Unsere modifizierten Signalregeln lauten wie folgt:
- Kaufe fünf Minuten nach Markteröffnung, wenn die fünfminütige Eröffnungskerze eine Aufwärtskerze ist und der Schlusskurs über dem gleitenden Durchschnitt der 350er-Periode liegt.
- Verkaufe fünf Minuten nach Markteröffnung, wenn die fünfminütige Eröffnungskerze eine Aufwärtskerze ist und der Schlusskurs unter dem gleitenden Durchschnitt der 350er-Periode liegt.
- Schließe fünf Minuten vor Börsenschluss die bestehende Positionen.
- Stopp-Loss bei 1 % des Kurses vom Einstiegsniveau.
- 2% Risiko pro Handel.
Vollständiger MQL5-Code für den EA:
//USTEC-M5 #include <Trade/Trade.mqh> CTrade trade; input int startHour = 18; input int startMinute = 35; input int endHour = 23; input int endMinute = 55; input double risk = 2.0; input double slp = 0.01; input int MaPeriods = 350; input int Magic = 0; int barsTotal = 0; int handleMa; double lastClose=0; double lastOpen = 0; double lot = 0.1; //+------------------------------------------------------------------+ //|Initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(Magic); handleMa = iMA(_Symbol,PERIOD_CURRENT,MaPeriods,0,MODE_SMA,PRICE_CLOSE); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //|Deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //|On tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol, PERIOD_CURRENT); if(barsTotal != bars){ barsTotal=bars; double ma[]; CopyBuffer(handleMa,0,1,1,ma); if(MarketOpen()){ lastClose = iClose(_Symbol,PERIOD_CURRENT,1); lastOpen = iOpen(_Symbol,PERIOD_CURRENT,1); if(lastClose<lastOpen&&lastClose<ma[0])executeSell(); if (lastClose>lastOpen&&lastClose>ma[0]) executeBuy(); } if(MarketClose()){ for(int i = PositionsTotal()-1; i>=0; i--){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos); } } } } //+------------------------------------------------------------------+ //| Detect if market is opened | //+------------------------------------------------------------------+ bool MarketOpen() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min; if (currentHour == startHour &¤tMinute==startMinute)return true; else return false; } //+------------------------------------------------------------------+ //| Detect if market is closed | //+------------------------------------------------------------------+ bool MarketClose() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min; if (currentHour == endHour && currentMinute == endMinute)return true; else return false; } //+------------------------------------------------------------------+ //| Sell execution function | //+------------------------------------------------------------------+ void executeSell() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); double sl = bid*(1+slp); sl = NormalizeDouble(sl, _Digits); lot = calclots(bid*slp); trade.Sell(lot,_Symbol,bid,sl); } //+------------------------------------------------------------------+ //| Buy execution function | //+------------------------------------------------------------------+ void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); double sl = ask*(1-slp); sl = NormalizeDouble(sl, _Digits); lot = calclots(ask*slp); trade.Buy(lot,_Symbol,ask,sl); } //+------------------------------------------------------------------+ //| Calculate lot size based on risk and stop loss range | //+------------------------------------------------------------------+ double calclots(double slpoints) { double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * risk / 100; double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double lotstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep; double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep; lots = MathMin(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)); lots = MathMax(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)); return lots; }
Ein typischer Handel würde folgendermaßen aussehen:
Backtest-Ergebnisse:
Ohne den Filter des gleitenden Durchschnitts würden die ursprünglichen Strategieregeln einen Handel pro Handelstag erzeugen. Durch den Filter wurde der Handel um die Hälfte reduziert. Da sich die durchschnittliche Haltedauer über die gesamte Handelssitzung erstreckt, spiegeln die Ergebnisse in gewisser Weise den Makrotrend wider, wobei Kaufpositionen häufiger vorkommen und eine höhere Gewinnquote aufweisen. Insgesamt erreicht die Strategie einen Gewinnfaktor von 1,23 und eine Sharpe Ratio von 2,81, was eine starke Performance widerspiegelt. Diese einfachen Regeln sind weniger anfällig für eine Überanpassung, was darauf hindeutet, dass solide Backtest-Ergebnisse wahrscheinlich auch im realen Handel Bestand haben werden.
Der EA übertrifft den Buy-and-Hold-Ansatz für USTEC über diesen Fünfjahreszeitraum eindrucksvoll, während der maximale Drawdown mit 18 % nur halb so hoch ist wie der der Benchmark. Die Kapitalkurve bleibt glatt, mit nur einer kurzen Stagnation von Ende 2022 bis Anfang 2023, einer Zeit, in der die USTEC mit einer größeren Inanspruchnahme konfrontiert war.
Alpha: 1.6017 Beta: 0.0090
Ein Beta von 0,9 % zeigt, dass die tägliche Rendite nur zu 0,9 % mit dem Basiswert korreliert. Drawdowns und Renditen bleiben konstant, was darauf hindeutet, dass der Fonds auch in extremen Zeiten wie dem COVID-Absturz 2020 stabil bleibt. Die meisten Monate sind gewinnbringend, und die Rückschläge sind gering, wobei der schlechteste bei 10,2 % liegt. Insgesamt handelt es sich um eine handelbare und rentable Strategie.
Strategie zwei: VWAP Trendfolge
Die zweite Strategie, die wir uns ansehen werden, ist eher eine marktoffene Trendfolgestrategie, die in dem Papier „Volume Weighted Average Price (VWAP) The Holy Grail for Day Trading Systems“ vorgestellt wurde. Die Motivation hinter den Signalregeln besteht darin, den VWAP als klare, volumengewichtete Benchmark für die Identifizierung von Intraday-Trends zu nutzen. Eine Kaufposition wird ausgelöst, wenn der Kurs über dem VWAP schließt, und eine Verkaufsposition, wenn er darunter schließt, um das bestätigte Momentum zu erfassen und gleichzeitig das Rauschen herauszufiltern. Diese Einfachheit gewährleistet umsetzbare, reproduzierbare Signale für Daytrader. Dieser klassische Trendfolge-Ansatz funktioniert am besten bei hoher Volatilität, fängt langlaufende Trends ein und bringt hohe Gewinne im Verhältnis zum Risiko. Während der fünf Stunden, in denen die Börse geöffnet ist, erfährt der Index erhebliche Bewegungen, was eine ausgezeichnete zeitliche Liquidität für den Erfolg der Strategie bietet.
Das ursprüngliche Papier befasste sich mit einem einminütigen Zeitrahmen und behauptete, dieser sei unter den verschiedenen Zeitrahmen am effektivsten. Meine persönlichen Tests haben jedoch ergeben, dass ein 15-Minuten-Zeitrahmen für diese Strategie besser geeignet ist, was wahrscheinlich auf die höheren Handelskosten von CFDs im Vergleich zu ETFs zurückzuführen ist, die einen häufigen Handel weniger rentabel machen. Auch ein Stop-Loss wurde in dem Papier nicht berücksichtigt. In unserem Ansatz werden wir einen einbeziehen, da wir einen höheren Zeitrahmen verwenden. Dieser Zusatz dient als Unfallschutz und liefert eine Referenzspanne für die Risikoberechnung. Schließlich haben wir einen gleitenden Durchschnittstrendfilter hinzugefügt, wie wir es zuvor getan haben.
Unsere modifizierten Signalregeln lauten wie folgt:
- Kaufe nach der Markteröffnung, wenn keine aktuelle Position eröffnet ist und der letzte 15-Minuten-Schlusskurs über dem VWAP und dem gleitenden 300-Perioden-Durchschnitt liegt.
- Verkaufe nach der Markteröffnung, wenn keine aktuelle Position eröffnet wurde und der letzte 15-Minuten-Schlusskurs unter dem VWAP und dem gleitenden 300-Perioden-Durchschnitt liegt.
- Stopp-Loss wird bei 0,8 % des Kurses vom Einstiegsniveau gesetzt.
- 2% Risiko pro Handelsgeschäft.
Der vollständige MQL5-Code für den EA:
//USTEC-M15 #include <Trade/Trade.mqh> CTrade trade; input int startHour = 18; input int startMinute = 35; input int endHour = 23; input int endMinute = 45; input double risk = 2.0; input double slp = 0.008; input int MaPeriods = 300; input int Magic = 0; int barsTotal = 0; int handleMa; double lastClose=0; double lot = 0.1; //+------------------------------------------------------------------+ //|Initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(Magic); handleMa = iMA(_Symbol,PERIOD_CURRENT,MaPeriods,0,MODE_SMA,PRICE_CLOSE); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //|Deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //|On tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol, PERIOD_CURRENT); if(barsTotal != bars){ barsTotal=bars; bool NotInPosition = true; double ma[]; CopyBuffer(handleMa,0,1,1,ma); if(MarketOpened()&&!MarketClosed()){ lastClose = iClose(_Symbol,PERIOD_CURRENT,1); int startIndex = getSessionStartIndex(); double vwap = getVWAP(startIndex); for(int i = PositionsTotal()-1; i>=0; i--){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){ if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&lastClose<vwap)||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&lastClose>vwap))trade.PositionClose(pos); else NotInPosition=false; } } if(lastClose<vwap&&NotInPosition&&lastClose<ma[0])executeSell(); if(lastClose>vwap&&NotInPosition&&lastClose>ma[0]) executeBuy(); } if(MarketClosed()){ for(int i = PositionsTotal()-1; i>=0; i--){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos); } } } } //+------------------------------------------------------------------+ //| Detect if market is opened | //+------------------------------------------------------------------+ bool MarketOpened() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min; if (currentHour >= startHour &¤tMinute>=startMinute)return true; else return false; } //+------------------------------------------------------------------+ //| Detect if market is closed | //+------------------------------------------------------------------+ bool MarketClosed() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min; if (currentHour >= endHour && currentMinute >= endMinute)return true; else return false; } //+------------------------------------------------------------------+ //| Sell execution function | //+------------------------------------------------------------------+ void executeSell() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); double sl = bid*(1+slp); sl = NormalizeDouble(sl, _Digits); lot = calclots(bid*slp); trade.Sell(lot,_Symbol,bid,sl); } //+------------------------------------------------------------------+ //| Buy execution function | //+------------------------------------------------------------------+ void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); double sl = ask*(1-slp); sl = NormalizeDouble(sl, _Digits); lot = calclots(ask*slp); trade.Buy(lot,_Symbol,ask,sl); } //+------------------------------------------------------------------+ //| Get VWAP function | //+------------------------------------------------------------------+ double getVWAP(int startCandle) { double sumPV = 0.0; // Sum of (price * volume) long sumV = 0.0; // Sum of volume // Loop from the starting candle index down to 1 (excluding current candle) for(int i = startCandle; i >= 1; i--) { // Calculate typical price: (High + Low + Close) / 3 double high = iHigh(_Symbol, PERIOD_CURRENT, i); double low = iLow(_Symbol, PERIOD_CURRENT, i); double close = iClose(_Symbol, PERIOD_CURRENT, i); double typicalPrice = (high + low + close) / 3.0; // Get volume and update sums long volume = iVolume(_Symbol, PERIOD_CURRENT, i); sumPV += typicalPrice * volume; sumV += volume; } // Calculate VWAP or return 0 if no volume if(sumV == 0) return 0.0; double vwap = sumPV / sumV; // Plot the dot datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); string objName = "VWAP" + TimeToString(currentBarTime, TIME_MINUTES); ObjectCreate(0, objName, OBJ_ARROW, 0, currentBarTime, vwap); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen); // Green dot ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DOT); // Dot style ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1); // Size of the dot return vwap; } //+------------------------------------------------------------------+ //| Find the index of the candle corresponding to the session open | //+------------------------------------------------------------------+ int getSessionStartIndex() { int sessionIndex = 1; // Loop over bars until we find the session open for(int i = 1; i <=1000; i++) { datetime barTime = iTime(_Symbol, PERIOD_CURRENT, i); MqlDateTime dt; TimeToStruct(barTime, dt); if(dt.hour == startHour && dt.min == startMinute-5) { sessionIndex = i; break; } } return sessionIndex; } //+------------------------------------------------------------------+ //| Calculate lot size based on risk and stop loss range | //+------------------------------------------------------------------+ double calclots(double slpoints) { double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * risk / 100; double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double lotstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep; double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep; lots = MathMin(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)); lots = MathMax(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)); return lots; }
Ein typischer Handel würde folgendermaßen aussehen:
Backtest-Ergebnisse:
Verglichen mit der ersten Strategie, dem Opening Range Breakout, wird bei dieser Strategie häufiger gehandelt, im Durchschnitt mehr als ein Handel pro Tag. Dieser Anstieg ist darauf zurückzuführen, dass ein Wiedereinstieg immer dann möglich ist, wenn der Kurs den VWAP während der Marktzeit erneut überschreitet. Die Gewinnquote ist mit 42 % niedriger und liegt unter 50 %, was für einen Trendfolgeansatz mit dynamischem Trailing-Stop typisch ist. Dieses Setup begünstigt Handelsgeschäfte mit höherem Rendite-Risiko-Verhältnis, erhöht aber auch die Chance, ausgestoppt zu werden. Die Sharpe Ratio und der Gewinnfaktor sind mit 3,57 bzw. 1,26 außergewöhnlich hoch.
Mit einer Rendite von 501 % über fünf Jahre übertrifft die Strategie die Buy-and-Hold-Strategie deutlich. Dies geschieht mit einem maximalen Drawdown von 16 %, wobei die schlechteste Periode Ende 2021 liegt und sich von der schlechtesten Phase von USTEC unterscheidet, was auf eine unkorrelierte Performance hindeutet.
Alpha: 4.8714 Beta: 0.0985
Das Beta entspricht dem der ersten Strategie, was ebenfalls auf eine geringe Korrelation mit dem Basiswert hindeutet. Bemerkenswert ist, dass das Alpha dieser Strategie dreimal höher ist als das der ersten Strategie, während der maximale Drawdown ähnlich hoch ist. Dieser Vorteil beruht wahrscheinlich auf häufigerem Handel, kürzeren Haltedauern und einer größeren internen Diversifizierung durch Long- und Short-Möglichkeiten am selben Tag. Die monatliche Tabelle bestätigt die solide Performance, mit gleichmäßig verteilten und konsistenten Drawdowns und Renditen über die Monate hinweg.
Strategie Drei: Concretum Bands Breakout
Die dritte Strategie ist ein Ausbruch aus dem Rauschbereich, die während der Marktöffnungszeit gehandelt wird. Sie wurde erstmals in „Beat the Market An Effective Intraday Momentum Strategy for S&P500 ETF (SPY)“ vorgestellt und ging später über X/Twitter viral. Die Motivation hinter den Signalregeln der Concretum Bands Breakout-Strategie liegt in dem Ziel, signifikante Preisbewegungen zu identifizieren, die durch Ungleichgewichte zwischen Angebot und Nachfrage im Intraday-Handel ausgelöst werden. Die Strategie verwendet volatilitätsbasierte Bandbreiten, die aus dem Schlusskurs des Vortages oder dem Eröffnungswert des aktuellen Tages berechnet und mit einem Volatilitätsmultiplikator angepasst werden, um einen „Rauschbereich“ zu definieren, in dem zufällige Kursschwankungen auftreten. Die Regeln zielen darauf ab, Marktrauschen herauszufiltern, aus wahrscheinlichen Schwankungen Kapital zu schlagen und sich an die schwankende Volatilität anzupassen, um sicherzustellen, dass die Handelsgeschäfte auf echte Trendanfänge und nicht auf flüchtige Schwankungen ausgerichtet sind.
Hier sind die Berechnungen der Bänder.
Da die Signalregeln aus dem Originalbeitrag gut durchdacht sind, werden wir in diesem Artikel nicht viel ändern. Der Einfachheit halber werden wir denselben Handelsinstrument (USTEC) und denselben Risikomanagementansatz beibehalten, was zu anderen Ergebnissen führen kann als der Ansatz in der Studie. Die Signalregeln lauten wie folgt:
- Kaufe nach der Markteröffnung, wenn der 1-Minuten-Balken das obere Band überschreitet.
- Verkaufe nach der Markteröffnung, wenn der 1-Minuten-Balken unter das untere Band fällt.
- Schließe alle Positionen, wenn der Markt geschlossen ist.
- Der Stop-Loss wird auf 1% des Preises der Einstiegsposition gesetzt, zusammen mit dem VWAP als Trailing-Stop.
- 4% Risiko pro Handel.
Vollständiger MQL5-Code des EA:
//USTEC-M1 #include <Trade/Trade.mqh> CTrade trade; input int startHour = 18; input int startMinute = 35; input int endHour = 23; input int endMinute = 55; input double risk = 4.0; input double slp = 0.01; input int Magic = 0; input int maPeriod = 400; int barsTotal = 0; int handleMa; double lastClose=0; double lastOpen = 0; double lot = 0.1; //+------------------------------------------------------------------+ //|Initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(Magic); handleMa = iMA(_Symbol,PERIOD_CURRENT,maPeriod,0,MODE_SMA,PRICE_CLOSE); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //|Deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //|On tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol, PERIOD_CURRENT); if(barsTotal != bars){ barsTotal=bars; bool NotInPosition = true; double ma[]; CopyBuffer(handleMa,0,1,1,ma); if(MarketOpened()&&!MarketClosed()){ lastClose = iClose(_Symbol,PERIOD_CURRENT,1); lastOpen = iOpen(_Symbol,PERIOD_CURRENT,1); int startIndex = getSessionStartIndex(); double vwap = getVWAP(startIndex); for(int i = PositionsTotal()-1; i>=0; i--){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol){ if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY&&lastClose<vwap)||(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL&&lastClose>vwap))trade.PositionClose(pos); else NotInPosition=false; } } double lower = getLowerBand(); double upper = getUpperBand(); if(NotInPosition&&lastOpen>lower&&lastClose<lower&&lastClose<ma[0])executeSell(); if(NotInPosition&&lastOpen<upper&&lastClose>upper&&lastClose>ma[0]) executeBuy(); } if(MarketClosed()){ for(int i = PositionsTotal()-1; i>=0; i--){ ulong pos = PositionGetTicket(i); string symboll = PositionGetSymbol(i); if(PositionGetInteger(POSITION_MAGIC) == Magic&&symboll== _Symbol)trade.PositionClose(pos); } } } } //+------------------------------------------------------------------+ //| Detect if market is opened | //+------------------------------------------------------------------+ bool MarketOpened() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min; if (currentHour >= startHour &¤tMinute>=startMinute)return true; else return false; } //+------------------------------------------------------------------+ //| Detect if market is closed | //+------------------------------------------------------------------+ bool MarketClosed() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; int currentMinute = timeStruct.min; if (currentHour >= endHour && currentMinute >= endMinute)return true; else return false; } //+------------------------------------------------------------------+ //| Sell execution function | //+------------------------------------------------------------------+ void executeSell() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); double sl = bid*(1+slp); sl = NormalizeDouble(sl, _Digits); lot = calclots(bid*slp); trade.Sell(lot,_Symbol,bid,sl); } //+------------------------------------------------------------------+ //| Buy execution function | //+------------------------------------------------------------------+ void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); double sl = ask*(1-slp); sl = NormalizeDouble(sl, _Digits); lot = calclots(ask*slp); trade.Buy(lot,_Symbol,ask,sl); } //+------------------------------------------------------------------+ //| Get VWAP function | //+------------------------------------------------------------------+ double getVWAP(int startCandle) { double sumPV = 0.0; // Sum of (price * volume) long sumV = 0.0; // Sum of volume // Loop from the starting candle index down to 1 (excluding current candle) for(int i = startCandle; i >= 1; i--) { // Calculate typical price: (High + Low + Close) / 3 double high = iHigh(_Symbol, PERIOD_CURRENT, i); double low = iLow(_Symbol, PERIOD_CURRENT, i); double close = iClose(_Symbol, PERIOD_CURRENT, i); double typicalPrice = (high + low + close) / 3.0; // Get volume and update sums long volume = iVolume(_Symbol, PERIOD_CURRENT, i); sumPV += typicalPrice * volume; sumV += volume; } // Calculate VWAP or return 0 if no volume if(sumV == 0) return 0.0; double vwap = sumPV / sumV; // Plot the dot datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); string objName = "VWAP" + TimeToString(currentBarTime, TIME_MINUTES); ObjectCreate(0, objName, OBJ_ARROW, 0, currentBarTime, vwap); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGreen); // Green dot ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DOT); // Dot style ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1); // Size of the dot return vwap; } //+------------------------------------------------------------------+ //| Find the index of the candle corresponding to the session open | //+------------------------------------------------------------------+ int getSessionStartIndex() { int sessionIndex = 1; // Loop over bars until we find the session open for(int i = 1; i <=1000; i++) { datetime barTime = iTime(_Symbol, PERIOD_CURRENT, i); MqlDateTime dt; TimeToStruct(barTime, dt); if(dt.hour == startHour && dt.min == 30) { sessionIndex = i; break; } } return sessionIndex; } //+------------------------------------------------------------------+ //| Get the number of bars from now to market open | //+------------------------------------------------------------------+ int getBarShiftForTime(datetime day_start, int hour, int minute) { MqlDateTime dt; TimeToStruct(day_start, dt); dt.hour = hour; dt.min = minute; dt.sec = 0; datetime target_time = StructToTime(dt); int shift = iBarShift(_Symbol, PERIOD_M1, target_time, true); return shift; } //+------------------------------------------------------------------+ //| Get the upper Concretum band value | //+------------------------------------------------------------------+ double getUpperBand() { // Get the time of the current bar datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0); MqlDateTime current_dt; TimeToStruct(current_time, current_dt); int current_hour = current_dt.hour; int current_min = current_dt.min; // Find today's opening price at 9:30 AM datetime today_start = iTime(_Symbol, PERIOD_D1, 0); int bar_at_930_today = getBarShiftForTime(today_start, 9, 30); if (bar_at_930_today < 0) return 0; // Return 0 if no 9:30 bar exists double open_930_today = iOpen(_Symbol, PERIOD_M1, bar_at_930_today); if (open_930_today == 0) return 0; // No valid price // Calculate sigma based on the past 14 days double sum_moves = 0; int valid_days = 0; for (int i = 1; i <= 14; i++) { datetime day_start = iTime(_Symbol, PERIOD_D1, i); int bar_at_930 = getBarShiftForTime(day_start, 9, 30); int bar_at_HHMM = getBarShiftForTime(day_start, current_hour, current_min); if (bar_at_930 < 0 || bar_at_HHMM < 0) continue; // Skip if bars don't exist double open_930 = iOpen(_Symbol, PERIOD_M1, bar_at_930); double close_HHMM = iClose(_Symbol, PERIOD_M1, bar_at_HHMM); if (open_930 == 0) continue; // Skip if no valid opening price double move = MathAbs(close_HHMM / open_930 - 1); sum_moves += move; valid_days++; } if (valid_days == 0) return 0; // Return 0 if no valid data double sigma = sum_moves / valid_days; // Calculate the upper band double upper_band = open_930_today * (1 + sigma); // Plot a blue dot at the upper band level string obj_name = "UpperBand_" + TimeToString(current_time, TIME_DATE|TIME_MINUTES|TIME_SECONDS); ObjectCreate(0, obj_name, OBJ_ARROW, 0, current_time, upper_band); ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 159); // Dot symbol ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 2); return upper_band; } //+------------------------------------------------------------------+ //| Get the lower Concretum band value | //+------------------------------------------------------------------+ double getLowerBand() { // Get the time of the current bar datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0); MqlDateTime current_dt; TimeToStruct(current_time, current_dt); int current_hour = current_dt.hour; int current_min = current_dt.min; // Find today's opening price at 9:30 AM datetime today_start = iTime(_Symbol, PERIOD_D1, 0); int bar_at_930_today = getBarShiftForTime(today_start, 9, 30); if (bar_at_930_today < 0) return 0; // Return 0 if no 9:30 bar exists double open_930_today = iOpen(_Symbol, PERIOD_M1, bar_at_930_today); if (open_930_today == 0) return 0; // No valid price // Calculate sigma based on the past 14 days double sum_moves = 0; int valid_days = 0; for (int i = 1; i <= 14; i++) { datetime day_start = iTime(_Symbol, PERIOD_D1, i); int bar_at_930 = getBarShiftForTime(day_start, 9, 30); int bar_at_HHMM = getBarShiftForTime(day_start, current_hour, current_min); if (bar_at_930 < 0 || bar_at_HHMM < 0) continue; // Skip if bars don't exist double open_930 = iOpen(_Symbol, PERIOD_M1, bar_at_930); double close_HHMM = iClose(_Symbol, PERIOD_M1, bar_at_HHMM); if (open_930 == 0) continue; // Skip if no valid opening price double move = MathAbs(close_HHMM / open_930 - 1); sum_moves += move; valid_days++; } if (valid_days == 0) return 0; // Return 0 if no valid data double sigma = sum_moves / valid_days; // Calculate the lower band double lower_band = open_930_today * (1 - sigma); // Plot a red dot at the lower band level string obj_name = "LowerBand_" + TimeToString(current_time, TIME_DATE|TIME_MINUTES|TIME_SECONDS); ObjectCreate(0, obj_name, OBJ_ARROW, 0, current_time, lower_band); ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 159); // Dot symbol ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrRed); ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 2); return lower_band; } //+------------------------------------------------------------------+ //| Calculate lot size based on risk and stop loss range | //+------------------------------------------------------------------+ double calclots(double slpoints) { double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * risk / 100; double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double lotstep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double moneyperlotstep = slpoints / ticksize * tickvalue * lotstep; double lots = MathFloor(riskAmount / moneyperlotstep) * lotstep; lots = MathMin(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)); lots = MathMax(lots, SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)); return lots; }
Ein typischer Handel würde folgendermaßen aussehen:
Backtest-Ergebnisse:
Die Strategie wird mit einer ähnlichen Häufigkeit wie die erste ORB-Strategie gehandelt, im Durchschnitt etwa alle zwei Handelstage. Er wird nicht täglich gehandelt, da die Kursbewegungen manchmal innerhalb einer Geräuschspanne verbleiben und es nicht gelingt, aus den Bändern auszubrechen. Die Gewinnquote liegt unter 50 %, was auf die Verwendung des VWAP als dynamischen Trailing-Stop zurückzuführen ist. Ein Gewinnfaktor von 1,3 und eine Sharpe Ratio von 5,9 deuten auf hohe Renditen im Verhältnis zum Drawdown hin.
Die Strategie übertrifft die „Buy-and-Hold“-Strategie leicht, während der maximale Drawdown nur halb so groß ist. Allerdings kommt es bei dieser Strategie häufiger zu signifikanten Drawdowns als bei der vorherigen Strategie. Dies deutet darauf hin, dass die Strategie trotz ihrer überragenden Performance häufig längere Rückschläge hinnehmen muss, bevor sie neue Höchststände bei den Aktien erreicht.
Alpha: 1.6562 Beta: -0.1183
Diese Strategie hat ein Beta von -11%, was auf eine leicht negative Korrelation mit dem Basiswert hinweist. Dies ist ein günstiges Ergebnis für Händler, die einen Vorteil suchen, der sich entgegen den Markttrends bewegt. Im Vergleich zu den beiden anderen Strategien hat diese Strategie mehr Drawdown-Monate, etwa 50 %, liefert aber in den profitablen Monaten höhere Erträge. Dieses Muster deutet darauf hin, dass sich Händler auf längere Drawdown-Phasen im Live-Handel einstellen und geduldig auf die größeren Ertragsphasen warten sollten. Mit einer beträchtlichen Stichprobengröße über einen soliden Zeitrahmen bleibt diese Strategie handelbar.
Überlegungen
In unserem letzten Artikel haben wir uns mit dem Aufbau von Modellsystemen anstelle von Einzelstrategien beschäftigt. Wir haben das gleiche Konzept in diesem Artikel angewandt. Alle drei Strategien basieren auf Ausbrüchen aus der offenen Zeitspanne des Aktienmarktes, wobei sich verschiedene Varianten als profitabel erwiesen haben. Wir tauschten auch Erkenntnisse darüber aus, wie man durch die Anpassung akademischer Arbeiten an unser eigenes Wissen und unsere Intuition einen strategischen Vorsprung erzielen kann. Dieser Ansatz eignet sich hervorragend, um robuste Handelskonzepte aufzudecken und unser Verständnis zu erweitern.
Da wir nun drei rentable Strategien in der Hand haben, sollten wir nun eine Portfolioperspektive in Betracht ziehen. Wir müssen ihre kombinierten Ergebnisse, Korrelationen und den maximalen Gesamtverlust untersuchen, bevor wir sie gleichzeitig handeln. Im algorithmischen Handel ist die Diversifizierung der wahre heilige Gral. Sie hilft, die Verluste verschiedener Strategien über verschiedene Zeiträume hinweg auszugleichen. Bis zu einem gewissen Grad wird Ihre maximale Rendite durch den Rückschlag, den Sie zu tolerieren bereit sind, begrenzt. Durch die Kombination verschiedener Strategien können Sie Ihr Engagement erhöhen und gleichzeitig einen ähnlichen Drawdown beibehalten, was die Rendite steigert. Allerdings kann das Risiko auf diese Weise nicht unendlich skaliert werden, da das minimale Risiko immer über dem der einzelnen Handelsgeschäfte liegt.
Einige gängige Wege zur Diversifizierung sind:
- Handel mit ein und demselben Strategiemodell und dessen Verteilung auf verschiedene unkorrelierte Vermögenswerte.
- Handel mit verschiedenen Strategiemodellen für ein und denselben Vermögenswert.
- Verteilen Sie das Kapital auf verschiedene Handelsansätze wie Optionen, Arbitrage und Aktienauswahlen.
Es ist wichtig zu verstehen, dass mehr Diversifizierung nicht immer besser ist, sondern dass es auf eine unkorrelierte Diversifizierung ankommt. So ist es beispielsweise nicht ideal, dieselbe Strategie auf alle Kryptomärkte anzuwenden, da die meisten Krypto-Assets auf breiterer Ebene stark korreliert sind. Darüber hinaus kann es irreführend sein, sich ausschließlich auf die Backtest-Diversifizierung zu verlassen, da die Korrelation vom Zeitraum abhängt, z. B. von täglichen oder monatlichen Renditen. Darüber hinaus können die Korrelationen zwischen den Strategien bei starken Marktveränderungen unerwartet verzerrt und verzerrt werden. Aus diesem Grund ziehen es einige Händler vor, die Korrelationen der Live-Handelsergebnisse im Vergleich zu den Korrelationen der Backtest-Ergebnisse zu verwenden, um zu beurteilen, ob die Vorteile ihrer Strategien abgenommen haben.
Mit diesem Wissen im Hinterkopf sind hier die Backtest-Statistiken der kombinierten Performance der drei Strategien.
Die Kapital- und Drawdown-Kurven zeigen anschaulich, wie die verschiedenen Strategien ihre Drawdowns über verschiedene Zeiträume hinweg ausgleichen. Der maximale Drawdown liegt nun bei etwa 10 % und damit deutlich unter den maximalen Drawdowns der einzelnen Strategien, die alle über 15 % liegen.
Drawdowns und Renditen scheinen gleichmäßig über die Monate verteilt zu sein, was darauf hindeutet, dass keine extremen Regimeperioden die Backtest-Performance unverhältnismäßig stark beeinflussen. Dies ist bei über 3000 Stichproben und einer konsistenten Risikoallokation pro Handelsgeschäft sinnvoll.
Die Korrelation misst, wie ähnlich die Kapitalkurven der Backtests der einzelnen Strategien sind. Sie reicht von -1 für entgegengesetztes Verhalten bis 1 für identisches Verhalten und vergleicht in der Regel zwei Subjekte. Wir berechnen sie, indem wir x für die Zeitachse der Aktienkurve und y für die Renditeachse verwenden.
Schlussfolgerung
In diesem Artikel wurden drei Intraday-Strategien des Opening Range Breakout aus den akademischen Papieren der Concretum Group untersucht. Wir begannen damit, den Forschungshintergrund zu skizzieren und die wichtigsten Konzepte und Methoden zu erläutern. Anschließend haben wir die Beweggründe für die drei Strategien untersucht, Verbesserungsmöglichkeiten aufgezeigt, klare Signalregeln, MQL5-Code und statistische Backtest-Analysen bereitgestellt. Schließlich haben wir den Prozess reflektiert, eine Diversifizierung eingeführt und die kombinierten Ergebnisse analysiert.
Der Artikel bietet Einblicke in die wahre Robustheit der Strategieentwicklung. Eine tiefer gehende statistische Analyse bietet einen breiteren Überblick über die Leistung einer Strategie und ihre Rolle innerhalb eines Portfolios. Alle Bemühungen zielen darauf ab, das Verständnis zu vertiefen und Vertrauen zu schaffen, bevor der Live-Handel beginnt. Die Leser werden ermutigt, den Forschungsprozess nachzuvollziehen und anhand des vorgestellten Rahmens Fachberater zu entwickeln.
Datei-Tabelle
Datei Name | Dateiverwendung |
---|---|
ORB1.mq5. | Das MQL5 EA-Skript für die erste Strategie |
ORB2.mq5 | Das MQL5 EA-Skript für die zweite Strategie |
ORB3.mq5 | Das MQL5 EA-Skript für die dritte Strategie |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17745
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Ps. Für ORB3 habe ich die Zeit für die Markteröffnung auf 9:30 Uhr fest einprogrammiert. Sie können diese Funktionen so ändern, dass sie mit der Serverzeit der New Yorker Marktöffnungszeit übereinstimmen.
Die Änderung der Berechnungszeit könnte ein Weg sein, die Strategie weiter zu optimieren :)
OMG, ich kann nicht glauben, dass ich das übersehen habe. Es sollte sein:
Ich entschuldige mich aufrichtig für den Flüchtigkeitsfehler. Danke, dass Sie so aufmerksam gelesen und mich darauf hingewiesen haben.
Keine Sorge, ich habe den offenen und den geschlossenen Markt bereits in einer Funktion zusammengeführt.
Noch eine Sache: Sie verwenden die OHLC-Daten des Brokers für Backtests, ohne Verzögerung. Diese Backtests scheinen etwas optimistisch zu sein, verglichen mit Backtests, die mit echten Tickdaten mit zufälliger Verzögerung für Slippage und Requotes durchgeführt werden.
Nochmals vielen Dank für Ihre Bemühungen!
Keine Sorge, ich habe den offenen und den geschlossenen Markt bereits in einer Funktion zusammengefasst.
Eine weitere Sache: Sie verwenden Broker OHLC Daten für Backtesting, keine Verzögerung. Diese Backtests scheinen ein bisschen optimistisch im Vergleich zu Backtests auf echte Tick-Daten mit zufälliger Verzögerung für Slippage und requotes getan.
Nochmals vielen Dank für Ihre Bemühungen!
Gut gemacht mit Ihrer Änderung! Ich habe den gesamten Code in meinem Github aktualisiert.
Für Ihr Anliegen, die Handelslogik tritt jede neue Bar und beinhaltet nicht Tick-Bewegung. Außerdem beträgt die durchschnittliche Haltedauer nur ein paar Stunden, was meiner Meinung nach kein signifikantes Slippage-Problem darstellen wird. Ich würde sagen, nur sehr wenige Makler bieten echte Tick-Daten für mehr als 5 Jahre, und 1 min OHLC ist genug.