- ago
There are already many threads where many users wonder about the different results of strategies within a meta strategy. Much of this comes from the shared capital (equity curves) within the meta strategy and other different settings.

I wanted to find out if the backtest of a meta strategy with a single child strategy match the results of the backtest of the child strategy (non meta). Unfortunately, this is not the case.

The biggest deviation seems to be the position count, and all the deviations that result from it. See below for all details for reproducing.











0
692
Solved
30 Replies

Reply

Bookmark

Sort
Glitch8
 ( 13.17% )
- ago
#1
Getting an exact match here.

0
Cone8
 ( 24.02% )
- ago
#2
Here's a difference -



Thank you for posting the images!
0
- ago
#3
@Glitch
Max Entry Signals must be set to 10. Without this setting, I also get a perfect match.

@Cone
This is set in the child strategy already. So there is no need to limit the entries in the meta strategy globally. But even If set to 10, no change in the results.
1
Glitch8
 ( 13.17% )
- ago
#4
You're right! We'll have to document this as an incompatibility with the MetaStrategy. Thanks for the report!
0
- ago
#5
Would be nice to see this fixed in the near future. :)
0
Glitch8
 ( 13.17% )
- ago
#6
We have a backlog of higher priority items for the time being. We do have the issue logged.
0
- ago
#7
Thanks! Any workaround to analyse/trade multiple strategies on a portfolio level in the meantime?
0
- ago
#8
In the meantime, the workaround is to make sure the option is disabled.
0
- ago
#9
How to do proper exposure/risk management with Max Entry Signals disabled? With this setting disabled, Transaction Weight would have no effect anymore, because all entry signals would be processed and would be send to the broker.

The only way I see for execution is running all child strategies in the S. Monitor. But any meta analysis like shared equity curve, correlation and so on is not possible that way.

Glitch said
QUOTE:
I only use strategies that will submit a limited number of signals. For my dip-buyer for example, it's coded to only submit the top 6 ranked signals for the Nasdaq 100, not the full 100 signals.

With those limits in place, it's easier to control the actual trading of the Meta Strategy. On the days where it might be possible that the strategy will still exceed the maximum, I use the Quotes tool and monitor things, only allowing the desired number of signals to get through to the broker.


https://www.wealth-lab.com/Discussion/Max-Open-Positions-Backtest-vs-Live-Trading-9975
0
Cone8
 ( 24.02% )
- ago
#10
QUOTE:
How to do proper exposure/risk management with Max Entry Signals disabled?
Add the logic to the coded strategy.
0
Glitch8
 ( 13.17% )
- ago
#11
Another workaround would be to use PreExecute, perform a sort there, and identify which signals to submit. This is what I use in my MetaStrategy.
0
- ago
#12
QUOTE:
Another workaround would be to use PreExecute, perform a sort there, and identify which signals to submit. This is what I use in my MetaStrategy.


Alright, I thought a general limit of entry signals is not compatible with MetaStrategy.
Could you provide a code snippet for the PreExecute and sorting logic?

Thanks a lot guys!
0
Glitch8
 ( 13.17% )
- ago
#13
Here is a minimal example, buys the 4 stocks that have the lowest RSI at limit 2% below close. Sells at limit at previous bar's high.

(Edit - see Post #18 for corrected code)

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; namespace WealthScript1 { 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) {          rsi4 = RSI.Series(bars.Close, 4);          bars.UserData = rsi4; } //pre-execute, sort candidates by RSI, keep lowest 4 public override void PreExecute(DateTime dt, List<BarHistory> participants) {          symbolsToTrade.Clear();          participants.Sort((a, b) =>          {             RSI rsiA = (RSI)a.UserData;             RSI rsiB = (RSI)b.UserData;             int idxA = GetCurrentIndex(a);             int idxB = GetCurrentIndex(b);             return rsiA[idxA].CompareTo(rsiB[idxB]);          });          for (int n = 0; n < 4; n++)             if (n < participants.Count)                symbolsToTrade.Add(participants[n]); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) {          bool tradeIt = symbolsToTrade.Contains(bars); if (!HasOpenPosition(bars, PositionType.Long)) { //code your buy conditions here     if (tradeIt)                PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx] * 0.98); } else {             //code your sell conditions here             PlaceTrade(bars, TransactionType.Sell, OrderType.Limit, bars.High[idx]); } }       //declare private variables below       private RSI rsi4;       private static List<BarHistory> symbolsToTrade = new List<BarHistory>(); } }
0
- ago
#14
Thanks Glitch! Any idea why it doesn't match the building blocks strategy?


0
Glitch8
 ( 13.17% )
- ago
#15
No, I’d have to painstakingly compare the trades. I will examine this when I work on the main issue.
1
Best Answer
- ago
#16
Even your Benchmark profit is different between the strategies which suggest your settings are not equal.
0
- ago
#17
QUOTE:
Even your Benchmark profit is different between the strategies which suggest your settings are not equal.


I think this little deviation is caused by different StartIndex.
0
Glitch8
 ( 13.17% )
- ago
#18
I actually was able to make some progress on this. I discovered that the entries aren't being sorted properly when processing the max entries and am fixing this for B43. Furthermore, there was a mistake in my code which caused the number of entries to sometimes be less than the expected 4. Here's an update,

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; namespace WealthScript1 {    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)       {          rsi4 = RSI.Series(bars.Close, 4);          bars.UserData = rsi4;          StartIndex = 4;       }       //pre-execute, sort candidates by RSI, keep lowest 4       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          symbolsToTrade.Clear();          participants.Sort((a, b) =>          {             RSI rsiA = (RSI)a.UserData;             RSI rsiB = (RSI)b.UserData;             int idxA = GetCurrentIndex(a);             int idxB = GetCurrentIndex(b);             return rsiA[idxA].CompareTo(rsiB[idxB]);          });          string s = "";          int entries = 0;          foreach (BarHistory bh in participants)          {             if (!HasOpenPosition(bh, PositionType.Long))             {                symbolsToTrade.Add(bh);                s += bh.Symbol + ", ";                entries++;                if (entries >= 4)                   break;             }          }          WriteToDebugLog(dt.ToShortDateString() + ": " + s, false);       }       //execute the strategy rules here, this is executed once for each bar in the backtest history       public override void Execute(BarHistory bars, int idx)       {          bool tradeIt = symbolsToTrade.Contains(bars);          if (!HasOpenPosition(bars, PositionType.Long))          {             //code your buy conditions here             if (tradeIt)                PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx] * 0.98);          }          else          {             //code your sell conditions here             PlaceTrade(bars, TransactionType.Sell, OrderType.Limit, bars.High[idx]);          }       }       //declare private variables below       private RSI rsi4;       private static List<BarHistory> symbolsToTrade = new List<BarHistory>();    } }
0
Glitch8
 ( 13.17% )
- ago
#20
With these in place things are looking almost exact,

1
- ago
#21
I'm confused. The PreExecute code in Reply# 13 is counting 4 entry signals regardless of whether they are currently owned or not. In contrast, the code in Reply# 18 is counting 4 entry signals only if they are not currently owned. I'm assuming the Reply# 18 code has it right when counting the "4 entry signals"; the entry signals must all be for stocks not currently owned.

To control risk, however, shouldn't we to trying to limit the number of total positions the strategy owns and not try to limit the number of "entry signals"? If so, then Reply# 13's approach may be better because the number of stocks owned will continue to grow uncontrollably with Reply# 18's approach.
0
Cone8
 ( 24.02% )
- ago
#22
QUOTE:
The PreExecute code in Reply# 13 is counting 4 entry signals regardless of whether they are currently owned or not
That's not correct. It sorts by RSI and then adds BarHistories that don't currently have an Long position to the symbolsToTrade list.
1
- ago
#23
QUOTE:
That's not correct. It sorts by RSI and then adds BarHistories that don't currently have an Long position to the symbolsToTrade list.

What program line in Reply# 13 is checking if each BarHistory is owned or not before adding it to the symbolsToTrade list? I don't see it. It's missing the
CODE:
if (!HasOpenPosition(bh, PositionType.Long))
in it's symbolsToTrade.Add(bh) For loop.
0
Cone8
 ( 24.02% )
- ago
#24
CODE:
foreach (BarHistory bh in participants) { if (!HasOpenPosition(bh, PositionType.Long)) { symbolsToTrade.Add(bh);
0
Glitch8
 ( 13.17% )
- ago
#25
Look at the CORRECTED code in post 18, NOT 13.
0
Glitch8
 ( 13.17% )
- ago
#26
Just verifying these changes for Build 42, the Strategy and MetaStrategy are now matching exactly even when Max Entry Signals is non-zero.

1
- ago
#27
CODE:
   foreach (BarHistory bh in participants)    {       if (!HasOpenPosition(bh, PositionType.Long))       {          symbolsToTrade.Add(bh);       }

Yes, but Reply# 13 is missing the ForEach statement. That's my point.
QUOTE:
Look at the CORRECTED code in post 18, NOT 13.
Okay, so Reply# 13 is in error and Reply# 18 has it right. Thanks for that clarification.

Now my other issue discussed in Reply# 21 has to do with controlling risk. What's to prevent the Reply# 18 code from taking on more and more positions (and risk) as new unowned stocks are constantly introduced into the symbolsToTrade list over time (with each new bar)? Perhaps you define "risk" different than me; I think the more open positions the strategy has (and greater drawdown) the greater the risk.
0
- ago
#28
QUOTE:
Now my other issue discussed in Reply# 21 has to do with controlling risk. What's to prevent the Reply# 18 code from taking on more and more positions (and risk) as new unowned stocks are constantly introduced into the symbolsToTrade list over time (with each new bar)? Perhaps you define "risk" different than me; I think the more open positions the strategy has (and greater drawdown) the greater the risk.


I think I get your point here. Max Entry Signals or the coded sorting logic just limit the entry signals each bar. Right? This doesn't prevent from having more than (in this example) 10 open positions? In order to to this, Max Entry Signals should be reduced by the number of already open positions. I'm not sure, if it's already programmed to work like this.

Example
Max Entry Signals = 10
Works more like "Max Open Positions"

Day 1
open position count: 0
potential entry signals: 23
limited and sorted entry signals: 10 (10 - open position count)
assumption: 7 get filled on that day

Day 2
open position count: 7
potential entry signals: 24
limited and sorted entry signals: 3 (10 - open position count)
assumption: 3 get filled on that day

Day 3
open position count: 10
potential entry signals: 15
limited and sorted entry signals: 0 (10 - open position count)
assumption: there are no entry signals, so 0 fills

Day 4
open position count: 10
...
0
- ago
#29
QUOTE:
Just verifying these changes for Build 42, the Strategy and MetaStrategy are now matching exactly even when Max Entry Signals is non-zero.


If the sorting and limitation logic of entry signals is coming straight from the code, Max Entry Signals should have no effect anymore? Right?
0
- ago
#30
QUOTE:
Max Entry Signals or the coded sorting logic just limit the entry signals each bar. Right?

For the example in Reply# 18, that appears correct to me. However, the Reply# 13 code limits the total number of entries, so that would reduce risk more.

But understand, the Reply 18 code "probably" performs better than the Reply 13 code because it takes on more risk, which is what you would expect. So taking on more risk can be a good thing as long as it's done in a smart way.

"Risk" is a personal choice of the investor. And how one defines risk is a personal choice as well. There's no right or wrong way.
0

Reply

Bookmark

Sort