- ago
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

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; } }
0
433
Solved
12 Replies

Reply

Bookmark

Sort
Cone8
 ( 25.44% )
- ago
#1
There are 2 mistakes.

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);
0
- ago
#2
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...

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.
1
- ago
#3
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;    } }
0
- ago
#4
Thanks you guys! Appreciate the help. After comparing the two backtests, the paper's seems over bullish.


0
Cone8
 ( 25.44% )
- ago
#5
Definitely. They're showing something like a 100% return every 2 years with that strategy - nope!
0
- ago
#6
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.
0
Cone8
 ( 25.44% )
- ago
#7
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%.
0
Cone8
 ( 25.44% )
- ago
#8
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.

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
0
Best Answer
Cone8
 ( 25.44% )
- ago
#9
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).

0
- ago
#10
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
0
- ago
#11


0
Cone8
 ( 25.44% )
- ago
#12
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)
1

Reply

Bookmark

Sort