- ago
For limit orders, when there is a tie on the intraday/granular time level (while running a strategy on Daily or higher scale), if the strategy assigns transaction weights please use those as the tie-breaker to determine which trade to take (rather than choosing randomly). This would ensure consistent backtest results and also match trades actually taken in real trading. This is critical to get backtest results that can be relied on for limit orders. This feature is currently implemented in WL6.

5
1,550
Solved
25 Replies

Reply

Bookmark

Sort
- ago
#1
Wasn't this already completed?
0
Glitch8
 ( 10.65% )
- ago
#2
Yes, this is how it currently works.
0
- ago
#3
QUOTE:
Yes, this is how it currently works.


As per this topic, I was asked to post a new feature request (this thread).
https://www.wealth-lab.com/Discussion/Granular-Limit-Stop-Processing-6088

Maybe I missed when it was implemented? Please confirm.
0
Glitch8
 ( 10.65% )
- ago
#4
Oh, I misunderstood this request, didn't catch the "Intraday" part in the title. I changed to title to reflect "Granular Processing" and re-activated it, thanks.
0
- ago
#5
Thanks. Any thoughts on when this might be implemented? I know it's down on the list, but it's critical for my strategy backtesting (and I believe for all those that want to use Limit entries throughout the day not just based on EOD or at Open). It's the only reason I haven't been able to sign up for WL7 as yet. Hoping it's a relatively minor change to take user-assigned transaction weights into account in the granular processing logic you have already implemented. But you know best what it will take.
[Specifically, I'm looking for the 5 minute intraday granularity with RSI (or any indicator picked by user) weights for the tie-breaker between symbols that hit their limit price within the 5 minutes. This would match what's in WL6.]
0
Glitch8
 ( 10.65% )
- ago
#6
Let's slot it in for Build 29, it should be a relatively minor piece of development. Hoping to get Build 28 out today.
0
- ago
#7
That would be awesome. Thanks!
0
Glitch8
 ( 10.65% )
- ago
#8
It's ready to go for Build 29.
0
Best Answer
- ago
#9
Much appreciated!
0
- ago
#10
Was this implemented with user-assigned weights for the tie-breaker?

In my backtests, the results don't change regardless of whether the weight is assigned with a negative (lowest) or positive (highest) value.

Also, the release notes for Build 29 state "Original Transaction Weight will be used to break ties when using Granular Processing". Does that mean it is using the internal assigned ("original"?) weight and not the user-assigned weight?
0
Glitch8
 ( 10.65% )
- ago
#11
Yes, it was implemented.
0
- ago
#12
Here's what I'm seeing using a slightly modified version of the canned example code for transaction weights -

CODE:
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript2 {    public class MyStrategy : UserStrategyBase    {       public override void Initialize(BarHistory bars)       {          _sma1 = SMA.Series(bars.Close, 8);          _sma2 = SMA.Series(bars.Close, 20);          _rsi = RSI.Series(bars.Close, 10);          PlotIndicator(_sma1);          PlotIndicator(_sma2);       }       public override void Execute(BarHistory bars, int idx)       {          if (!HasOpenPosition(bars, PositionType.Long))          {                         Transaction t = null;             if (_sma1.CrossesOver(_sma2, idx))                t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market);             // Assign the highest priority to the lowest rsi (most oversold) by negation             if (t != null)             {                t.Weight = - _rsi[idx];                t.Tag = t.Weight;             }          }          else          {             if (_sma1.CrossesUnder(_sma2, idx))                PlaceTrade(bars, TransactionType.Sell, OrderType.Market);          }       }       //private variables       SMA _sma1;       SMA _sma2;       RSI _rsi;    } }


Run 1 - Executed on the "DOW 30" dataset with Most Recent 2 years, and $1M starting capital with Fixed position size of $5000 -



Run 2 - Executed on the "DOW 30" dataset with Most Recent 2 years, and $1M starting capital with 100% position size (and Margin 1.1) -



Run 3 - Changed the weight to "+" value instead of "-" in code above, and reexecuted on the "DOW 30" dataset with Most Recent 2 years, and $1M starting capital with 100% position size (and Margin 1.1) -



I have granular processing set to on at 5 min. Both UNH and HON trades are triggered at open on 11/9/2020 (since it is a Market order for the next day). Yet, the result doesn't change between Run 2 and Run 3 - i.e. UNH is taken on 11/9/2020 regardless of its lower weight compared to HON even when the weight is set to highest in Run 3.

Please let me know if I'm missing something.
0
Glitch8
 ( 10.65% )
- ago
#13
Of course, it will take me some time to sit down and mock up a minimal example. Time to get to work :)
0
- ago
#14
Of course! Or to cut down on the time/effort you could perhaps use the example code/scenario in my post and let me know if you see different results or what I'm missing there. :)
0
Glitch8
 ( 10.65% )
- ago
#15
OK, here is my minimal example proof that it's working ... take the code below and create a new Strategy ...

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript1 { public class MyStrategy : UserStrategyBase { //Initialize public override void Initialize(BarHistory bars) {          rsi = RSI.Series(bars.Close, 4);          PlotIndicator(rsi); } //Execute public override void Execute(BarHistory bars, int idx) {          DateTime dt = new DateTime(2021, 10, 28);          if (bars.DateTimes[idx] == dt)          {             Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx]);             //t.Weight = rsi[idx];          } }       //private members       private RSI rsi; } }


Next, create a new DataSet with AAPL and AMZN.

Activate Granular Processing in Advanced Settings, and set the rest of the settings like this ...



If you run it like this, with line 24 commented out, you should get a random choice each time, the backtest will switch between holding AAPL and AMZN.

But if you uncomment line 24, recompile, and run, you should always get AAPL as the position. This is because the Weight we assigned, RSI4, is breaking the granular processing tie.

Both positions could have been bought on the first 10-minute bar because of the gap down. But because AAPL had a higher RSI on 10/28/2021 (86.04) than AMZN did (72.20), AAPL is always selected.
1
Glitch8
 ( 10.65% )
- ago
#16
I think I see the difference here, Granular Processing (as described in the Help) is applied to Limit/Stop orders only, not Market orders. It doesn't make sense to turn Granular Processing on for Market orders.
1
Glitch8
 ( 10.65% )
- ago
#17
Everything is still behaving as expected though if I change this to a market order, even if leaving Granular Processing on. When I change the Weight to negative, it buys AMZN, and when I switch it back to positive it buys AAPL.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript1 { public class MyStrategy : UserStrategyBase { //Initialize public override void Initialize(BarHistory bars) {          rsi = RSI.Series(bars.Close, 4);          PlotIndicator(rsi); } //Execute public override void Execute(BarHistory bars, int idx) {          DateTime dt = new DateTime(2021, 10, 28);          if (bars.DateTimes[idx] == dt)          {             Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, bars.Close[idx]);             t.Weight = -rsi[idx];          } }       //private members       private RSI rsi; } }
1
- ago
#18
Thanks, it does work in that minimal example. :)

Took me a bit to expand the scope just enough to show a case where it still doesn't seem to work.

Modifying the date in your example ...

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript1 { public class MyStrategy : UserStrategyBase { //Initialize public override void Initialize(BarHistory bars) {           rsi = RSI.Series(bars.Close, 4);           PlotIndicator(rsi); } //Execute public override void Execute(BarHistory bars, int idx) {           DateTime dt = new DateTime(2020, 12, 23);           if (bars.DateTimes[idx] == dt)           {             Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx]);             t.Weight = rsi[idx];             //t.Weight = - rsi[idx];           } } //private members private RSI rsi; } }


... And expanding the dataset to "AAPL AMZN BA BAC INTC PFE", it enters PFE (RSI=37.78) even though BAC has the highest RSI (73.09) on 12/23/2020.

If we flip the weight to use lowest RSI instead (uncomment/comment weight lines above), it enters BA (RSI=28.62) even though INTC has the lowest RSI (23.83) on 12/23/2020.

It does seem to work in some cases and not in others if we run it on larger datasets and timeframes. Something seems erratic, or it could still be me missing something. Hope the example above works the same for you, so you can see the issue. I can send other examples if it helps.
0
Glitch8
 ( 10.65% )
- ago
#20
PFE was entered instead of BAC because PFE opened below the limit price of the order and BAC didn’t. The WL7 backtester correctly gives this case priority because that's how it would go down in real world trading.
1
- ago
#21
Thanks, that helps clarify! If I'm understanding you correctly, since BAC opened above its limit and PFE opened below its limit (on 12/24/2020), so PFE would be triggered right at open while BAC wouldn't until later (although still in the first 5 mins), therefore PFE would be taken in real world trading. Is that right?

However, here's a case where both open below the limit -

Change the date in the code to 6/17/2021.
Run as before, this time for dataset "AXP UNH XOM CVX HWM HPQ AIG TRV GS BAC JPM DOW".
Gap down morning and all of these opened below their limits on 6/18/2021.
Yet AXP (RSI=33.97) gets taken rather than XOM(RSI=39.28).
On reversal to lowest weights, UNH(RSI=27.80) gets taken rather than DOW(RSI=7.55).

Please help understand this too. (I think you already answered my first question above in an edit to your previous post, while I was still typing!)
0
Glitch8
 ( 10.65% )
- ago
#22
On my run, XOM is taken, maybe there is some data issue on your end?
0
Glitch8
 ( 10.65% )
- ago
#23
0
- ago
#24
Looks like it. Truncated and reloaded both Daily and 5 min data, and XOM does get taken as expected.

That was the key difference (new in WL7) - Limit Trades that trigger right at open get priority over other limit trades that might trigger later in the same bar (based on granular processing setting). Thanks for helping walk through it.
1
Glitch8
 ( 10.65% )
- ago
#25
Yet another reason why WL7 is so superior ;)
1

Reply

Bookmark

Sort