I came across this paper describing the ORB day trading strategy and it seemed quite intriguing. The results mitigated much of 2022's losses and it's profits are fairly consistent.
I think it would be cool if the WL community could build our own version of this strategy.
I have attempted to code the strategy in WealthLab's C# code but have hit some road blocks. I receive an object reference error at line 71. Also, I am struggling to create end of day market sell and cover orders with 5-minute bars. Maybe some WL veterans can critique and correct my code? I have also attached the paper and it's trading rules.
6590-ORB-Strategy-pdf
I think it would be cool if the WL community could build our own version of this strategy.
I have attempted to code the strategy in WealthLab's C# code but have hit some road blocks. I receive an object reference error at line 71. Also, I am struggling to create end of day market sell and cover orders with 5-minute bars. Maybe some WL veterans can critique and correct my code? I have also attached the paper and it's trading rules.
6590-ORB-Strategy-pdf
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using WealthLab.Candlesticks; namespace WealthScript8 { public class MyStrategy : UserStrategyBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { PlotStopsAndLimits(3); cp = CandleGeneDecoder.FindPattern("Neutral Doji"); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { // Grabbing OHLC of first candle if (bars.IntradayBarNumber(idx) == 0) { first_Open = bars.Open[idx]; first_High = bars.High[idx]; first_Low = bars.Low[idx]; first_Close = bars.Close[idx]; } // Code our buy conditions here if (!HasOpenPosition(bars, PositionType.Long) && !HasOpenPosition(bars, PositionType.Short)) { if (bars.IntradayBarNumber(idx) == 1) { // do not want to place trades if there is a neutral doji as the first bar if (!CandleGeneDecoder.DetectPattern(bars, idx - 1, cp)) { // Buy if first bar's Close greater than Open if (first_Close > first_Open) { PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } // Short if first bar's Close less than Open if (first_Close < first_Open) { PlaceTrade(bars, TransactionType.Short, OrderType.Market); } } } } else { //code your sell conditions here //If we have a long position if (HasOpenPosition(bars, PositionType.Long)) { openPosition = FindOpenPosition(0); // Stop Loss at Low of first bar if (bars.Close[idx] <= first_Low) { ClosePosition(openPosition, OrderType.Market); WriteToDebugLog("Stop Limit Market Order Triggered."); } // Selling at Profit Target long_Dollar_Distance = openPosition.EntryPrice - first_Low; long_Order_Target = openPosition.EntryPrice + (long_Dollar_Distance * 10); ClosePosition(openPosition, OrderType.Limit, long_Order_Target); } //If we have a Short Position if (HasOpenPosition(bars, PositionType.Short)) { openPosition = FindOpenPosition(0); // "Stop loss" for the short position if (bars.Close[idx] >= first_High) { ClosePosition(openPosition, OrderType.Market); } // Covering at profit target short_Dollar_Distance = first_High - openPosition.EntryPrice; short_Dollar_Target = openPosition.EntryPrice - (short_Dollar_Distance * 10); ClosePosition(openPosition, OrderType.Limit, short_Dollar_Target); } } } //declare private variables below double first_Open; double first_High; double first_Low; double first_Close; private CandleGenePattern cp; double profitTarget; double long_Dollar_Distance; double long_Order_Target; double short_Dollar_Distance; double short_Dollar_Target; Position openPosition; } }
Rename
There are 2 mistakes.
1. If you want to enter at the open of the 2nd bar -
2. PositionTag will be -1, not 0, If you don't assign it to 0 in PlaceTrade():
1. If you want to enter at the open of the 2nd bar -
CODE:
// THIS if (bars.IntradayBarNumber(idx) == 1) // SHOULD BE: if (bars.IntradayBarNumber(idx) == 0) //and as a consequence... if (!CandleGeneDecoder.DetectPattern(bars, idx, cp))
2. PositionTag will be -1, not 0, If you don't assign it to 0 in PlaceTrade():
CODE:
//THIS openPosition = FindOpenPosition(-1); //SHOULD BE: (2 places) openPosition = FindOpenPosition(-1);
What Cone said, plus you're not closing out at end-of-day when your targets have not been signaled. Here's the updated code...
Edit: check for end-of-day using the Market of the bars.
Edit 2: Use MarketClose instead of Market to close position at end-of-day.
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using WealthLab.Candlesticks; namespace WealthScript8 { public class MyStrategy : UserStrategyBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { PlotStopsAndLimits(3); cp = CandleGeneDecoder.FindPattern("Neutral Doji"); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { // Grabbing OHLC of first candle if (bars.IntradayBarNumber(idx) == 0) { first_Open = bars.Open[idx]; first_High = bars.High[idx]; first_Low = bars.Low[idx]; first_Close = bars.Close[idx]; } // Code our buy conditions here if (!HasOpenPosition(bars, PositionType.Long) && !HasOpenPosition(bars, PositionType.Short)) { if (bars.IntradayBarNumber(idx) == 0) { // do not want to place trades if there is a neutral doji as the first bar if (!CandleGeneDecoder.DetectPattern(bars, idx - 1, cp)) { // Buy if first bar's Close greater than Open if (first_Close > first_Open) { PlaceTrade(bars, TransactionType.Buy, OrderType.Market, signalName: "Long"); } // Short if first bar's Close less than Open if (first_Close < first_Open) { PlaceTrade(bars, TransactionType.Short, OrderType.Market, signalName: "Short"); } } } } else { //code your sell conditions here var openPosition = FindOpenPosition(-1); if (openPosition == null) { return; } // sell at and of day (or close to it) var td = bars.DateTimes[idx].TimeOfDay; var mclose = bars.Market.GetCloseTimeMarketTime().TimeOfDay.Subtract(TimeSpan.FromMinutes(5)); if (td >= mclose) { ClosePosition(openPosition, OrderType.MarketClose, exitSignalName: "End-of-day"); WriteToDebugLog("Closing at end of day"); } else if (openPosition.PositionType == PositionType.Long) { // Stop Loss at Low of first bar if (bars.Close[idx] <= first_Low) { ClosePosition(openPosition, OrderType.Market, exitSignalName: "Stop Loss Long"); WriteToDebugLog("Stop Limit Market Order Triggered."); } // Selling at Profit Target long_Dollar_Distance = openPosition.EntryPrice - first_Low; long_Order_Target = openPosition.EntryPrice + (long_Dollar_Distance * 10); ClosePosition(openPosition, OrderType.Limit, long_Order_Target, exitSignalName: "Profit Target Long"); } else { // "Stop loss" for the short position if (bars.Close[idx] >= first_High) { ClosePosition(openPosition, OrderType.Market, exitSignalName: "Stop Loss Short"); } // Covering at profit target short_Dollar_Distance = first_High - openPosition.EntryPrice; short_Dollar_Target = openPosition.EntryPrice - (short_Dollar_Distance * 10); ClosePosition(openPosition, OrderType.Limit, short_Dollar_Target, "Profit Target Short"); } } } //declare private variables below private double first_Open; private double first_High; private double first_Low; private double first_Close; private CandleGenePattern cp; private double profitTarget; private double long_Dollar_Distance; private double long_Order_Target; private double short_Dollar_Distance; private double short_Dollar_Target; private Position openPosition; } }
Edit: check for end-of-day using the Market of the bars.
Edit 2: Use MarketClose instead of Market to close position at end-of-day.
Code below includes the edits from Post #2 and adds parameter for the profit factor.
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using WealthLab.Candlesticks; namespace WealthScript8 { public class MyStrategy : UserStrategyBase { public MyStrategy() { ParamProfitFactor = AddParameter("Profit Factor", ParameterType.Double, 10.0, 2.0, 20.0, 0.5); } //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { PlotStopsAndLimits(3); cp = CandleGeneDecoder.FindPattern("Neutral Doji"); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { // Grabbing OHLC of first candle if (bars.IntradayBarNumber(idx) == 0) { first_Open = bars.Open[idx]; first_High = bars.High[idx]; first_Low = bars.Low[idx]; first_Close = bars.Close[idx]; } // Code our buy conditions here if (!HasOpenPosition(bars, PositionType.Long) && !HasOpenPosition(bars, PositionType.Short)) { if (bars.IntradayBarNumber(idx) == 0) { // do not want to place trades if there is a neutral doji as the first bar if (!CandleGeneDecoder.DetectPattern(bars, idx - 1, cp)) { // Buy if first bar's Close greater than Open if (first_Close > first_Open) { PlaceTrade(bars, TransactionType.Buy, OrderType.Market, signalName: "Long"); } // Short if first bar's Close less than Open if (first_Close < first_Open) { PlaceTrade(bars, TransactionType.Short, OrderType.Market, signalName: "Short"); } } } } else { //code your sell conditions here var openPosition = FindOpenPosition(-1); if (openPosition == null) { return; } // sell at and of day (or close to it) var td = bars.DateTimes[idx].TimeOfDay; var mclose = bars.Market.GetCloseTimeMarketTime().TimeOfDay.Subtract(TimeSpan.FromMinutes(5)); if (td >= mclose) { ClosePosition(openPosition, OrderType.MarketClose, exitSignalName: "End-of-day"); WriteToDebugLog("Closing at end of day"); } else if (openPosition.PositionType == PositionType.Long) { // Stop Loss at Low of first bar if (bars.Close[idx] <= first_Low) { ClosePosition(openPosition, OrderType.Market, exitSignalName: "Stop Loss Long"); WriteToDebugLog("Stop Limit Market Order Triggered."); } // Selling at Profit Target long_Dollar_Distance = openPosition.EntryPrice - first_Low; long_Order_Target = openPosition.EntryPrice + (long_Dollar_Distance * ParamProfitFactor.AsDouble); ClosePosition(openPosition, OrderType.Limit, long_Order_Target, exitSignalName: "Profit Target Long"); } else { // "Stop loss" for the short position if (bars.Close[idx] >= first_High) { ClosePosition(openPosition, OrderType.Market, exitSignalName: "Stop Loss Short"); } // Covering at profit target short_Dollar_Distance = first_High - openPosition.EntryPrice; short_Dollar_Target = openPosition.EntryPrice - (short_Dollar_Distance * ParamProfitFactor.AsDouble); ClosePosition(openPosition, OrderType.Limit, short_Dollar_Target, "Profit Target Short"); } } } //declare private variables below private double first_Open; private double first_High; private double first_Low; private double first_Close; private CandleGenePattern cp; private double profitTarget; private double long_Dollar_Distance; private double long_Order_Target; private double short_Dollar_Distance; private double short_Dollar_Target; private Position openPosition; private Parameter ParamProfitFactor; } }
Thanks you guys! Appreciate the help. After comparing the two backtests, the paper's seems over bullish.
Definitely. They're showing something like a 100% return every 2 years with that strategy - nope!
I ran a few backtests with it, and it didn't perform well. It looks like one of those "shot in the dark" strategies based solely on the opening 5 minute candle going one way or the other. There's no confluence, no consideration of overnight gapping, no consideration of condensed timeframe action, volume etc. It just "goes for it." No thanks.
Okay, here's another difference - they're using 1% Max Risk sizing -
QUOTE:
The trading size was calibrated such that if a stop was hit, we lost 1% of our capital. We used a 1% risk budget per trade as the historical average daily move on QQQ is 1%.
Here's a promising modification that:
1. allows Max Risk sizing,
2. implements same-bar stop,
3. and corrects the bar on which the Doji is detected.
Use 1% Max Risk Sizing, increase margin to 4:1, and run it on TQQQ.
With 0 commission, this modification gooses the strategy to 40+% APR.
Note!
Your broker may not allow you to short TQQQ.
TQQQ is the benchmark. $0 Commission
1. allows Max Risk sizing,
2. implements same-bar stop,
3. and corrects the bar on which the Doji is detected.
Use 1% Max Risk Sizing, increase margin to 4:1, and run it on TQQQ.
With 0 commission, this modification gooses the strategy to 40+% APR.
Note!
Your broker may not allow you to short TQQQ.
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using WealthLab.Candlesticks; namespace WealthScript82 { public class MyStrategy : UserStrategyBase { public MyStrategy() { ParamProfitFactor = AddParameter("Profit Factor", ParameterType.Double, 10.0, 2.0, 20.0, 0.5); } //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { PlotStopsAndLimits(3); cp = CandleGeneDecoder.FindPattern("Neutral Doji"); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { // Grabbing OHLC of first candle if (bars.IntradayBarNumber(idx) == 0) { first_Open = bars.Open[idx]; first_High = bars.High[idx]; first_Low = bars.Low[idx]; first_Close = bars.Close[idx]; } // Code our buy conditions here if (!HasOpenPosition(bars, PositionType.Long) && !HasOpenPosition(bars, PositionType.Short)) { if (bars.IntradayBarNumber(idx) == 0) { // do not want to place trades if there is a neutral doji as the first bar if (!CandleGeneDecoder.DetectPattern(bars, idx, cp)) { // Buy if first bar's Close greater than Open if (first_Close > first_Open) { Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, signalName: "Long"); t.AutoStopLossPrice = first_Low; } // Short if first bar's Close less than Open if (first_Close < first_Open) { Transaction t = PlaceTrade(bars, TransactionType.Short, OrderType.Market, signalName: "Short"); t.AutoStopLossPrice = first_High; } } } } else { //code your sell conditions here var openPosition = FindOpenPosition(-1); if (openPosition == null) { return; } // sell at and of day (or close to it) var td = bars.DateTimes[idx].TimeOfDay; var mclose = bars.Market.GetCloseTimeMarketTime().TimeOfDay.Subtract(TimeSpan.FromMinutes(5)); if (td >= mclose) { ClosePosition(openPosition, OrderType.MarketClose, exitSignalName: "End-of-day"); } else if (openPosition.PositionType == PositionType.Long) { // Stop Loss at Low of first bar ClosePosition(openPosition, OrderType.Stop, first_Low, exitSignalName: "Stop Loss Long"); // Selling at Profit Target long_Dollar_Distance = openPosition.EntryPrice - first_Low; long_Order_Target = openPosition.EntryPrice + (long_Dollar_Distance * ParamProfitFactor.AsDouble); ClosePosition(openPosition, OrderType.Limit, long_Order_Target, exitSignalName: "Profit Target Long"); } else { // "Stop loss" for the short position ClosePosition(openPosition, OrderType.Stop, first_High, exitSignalName: "Stop Loss Short"); // Covering at profit target short_Dollar_Distance = first_High - openPosition.EntryPrice; short_Dollar_Target = openPosition.EntryPrice - (short_Dollar_Distance * ParamProfitFactor.AsDouble); ClosePosition(openPosition, OrderType.Limit, short_Dollar_Target, "Profit Target Short"); } } } public override double GetMaxRiskStopLevel(BarHistory bars, PositionType pt, int idx) { if (pt == PositionType.Long) return bars.Low[idx]; else return bars.High[idx]; } //declare private variables below private double first_Open; private double first_High; private double first_Low; private double first_Close; private CandleGenePattern cp; private double profitTarget; private double long_Dollar_Distance; private double long_Order_Target; private double short_Dollar_Distance; private double short_Dollar_Target; private Position openPosition; private Parameter ParamProfitFactor; } }
TQQQ is the benchmark. $0 Commission
I just noticed that the graphs in the paper are in log scale.
Although this TQQQ result doesn't come close to theirs, but it's rhymes a lot with the paper's QQQ result. This is actually the same as the graph above, but in log scale (and doesn't show the negative borrowed equity).
Although this TQQQ result doesn't come close to theirs, but it's rhymes a lot with the paper's QQQ result. This is actually the same as the graph above, but in log scale (and doesn't show the negative borrowed equity).
Hi Cone, what intraday data provider are you using to receive the TQQQ data? I have TD Ameritrade, but my 5 minute graphs look nothing like yours. See below graph and strategy parameters
IQFeed.
You should check your source because it's probably not TDA.
I don't think it's possible to get more than 18 months of intraday data from TDA.
Also, check commission and slippage settings. What are they? (Mine are none)
You should check your source because it's probably not TDA.
I don't think it's possible to get more than 18 months of intraday data from TDA.
Also, check commission and slippage settings. What are they? (Mine are none)
Your Response
Post
Edit Post
Login is required