Cross Function and Range

If I use the following code for the symbol Spy:

Buy = C>MA(C,30);

I get a Buy signal every day this is true from the first date in the Backtester From-To Range. I.e. if I set my range to 2020-08-02T23:00:00Z2020-08-30T23:00:00Z I get a Buy signal on the first day which is what I would expect. However, I would prefer to us the Cross function in my code:

Buy = Cross(C,MA(C,30));

as I understand that it is best practice and will make it easier to review buy signals in explorer. However, even though Spy is higher than the 30MA on the first day of the From-To Range because the most recent cross of Spy to the 30MA occurs before the first day in the From-To Range it doesn't create a Buy signal on the first day.

How do people normally work around this for the purpose of back testing? Do you create a different check for the first day that uses ">" code or is there a setting that allows you specify the cross should only start checking from the first day in the From-To Range? Apologies if this is in the manual somewhere but I haven't been able to find it.

There is a major difference between what these two pieces of code are actually doing.
Buy = C>MA(C,30); always gives a buy signal if Close is greater than your MA. It's always "ON" or "True".

Buy = Cross(C,MA(C,30)); on the other hand is only true once. At the time of the cross. It is essentially a shortcut to doing: Buy = C >MA(C,30) && Ref(C <MA(C,30),-1);.

IMO there isn't a "correct" way, as there are strategies that could benefit from an always True signal. But you need to realize they will be doing completely different things and pick what makes sense for your particular strategy.

If you backtest crossing only, than no other entries should be introduced. Just wait till crossing happens and open position. Otherwise your backtest stat will be incorrect.

Thanks for responding Pinecone and ab-trader. I totally agree that the two approaches do different things and, in some strategies, only one of the approaches will be appropriate.

For my strategy I want to be invested when spy is above the MA and not when it is below. Trades to reflect this will normally happen when spy crosses the MA so Cross() would make sense. However, when I deploy my capital if, on day one, spy is above MA I also want to be invested. Looking at the options to code this:

  1. Buy = C>MA(C,30); This achieves the goal of my strategy as if on day one if it is above the MA signal it will trigger the buy. Furthermore, the backtest will remove any repeated buy signals until a sell signal occurs and as such basically works like Cross(). However, it means my explorer shows buys everyday it is above and sells everyday it is below and therefore is difficult to debug / visualize what is going on.

  2. Buy = Cross(C,MA(C,30)); This will only show the buys/sells in the explorer when a cross occurs which is great. However, as ab-trader rightly points out a true cross doesn’t occur on day one of the back test and so when I backtest the strategy it doesn’t deploy my capital from day one even if spy is above the MA.

  3. Buy = C>MA(C,30); Buy = ExRem(Buy,Sell); This will only show the buys/sells in the explorer the first time they occur. But due to the fact the first buy occurs before the backtest/explorer period it effectively creates the same output as the Cross() function.

So, unless I am missing something it sounds like I use ExRem() to clean up my Buy()/Sell() but only apply it if it is not the first bar in range which gives me the following code:

Buy = iif(Status("FirstBarInRange"),Buy,ExRem(Buy,Sell));

Sell = iif(Status("FirstBarInRange"),Sell,ExRem(Sell,Buy));

This make sense to you?

It is simple:

  1. Your formula uses all data and Raw Signals are generated using ENTIRE data set (that you have in your DB), regardless of range set (unless QuickAFL has been turned on)
  2. Range is applied to backtesting (where backtest starts/ends), not to signal generation

Raw signals are those generated by the formula and placed in Buy/Sell/Short/Cover arrays BEFORE backtesting begins.

Why is that so? Because if you changed the way signals are generated then values of many indicators that are cumulative or recursive would change and would generate different signals. And you don't want that. When you move testing window you are expecting past signals to stay in place (that is required for example for walk-forward testing).

If you for some reason, want to filter out raw SIGNALS only to "Range" you can do so in the formula.

In STATE form it would be:

inrange = Status("barinrange");
Buy = inrange AND C > MA( C, 30 );

In the IMPULSE form it would be:

inrange = Status("barinrange");
Buy = inrange AND Cross( C, MA( C, 30 ) );

To go from STATE to IMPULSE form you can use ExRem(), to go into opposite direction (IMPULSE to STATE) you can use Flip().

STATE form is typically used to combine signals using AND operator because if multiple conditions must be be present, they very rarely happen on exactly same bar, so STATE form allows to combine signals that are out of perfect sync.

Note that backtester by default REMOVES excess signals, so it essentially converts everything internally to IMPULSE form. See Scan vs Exploration vs Backtest - summary of differences

Exploration should NOT be used as "viewing signals for backtest". That is wrong use. Use backtester to view signals for backtest. Exploration is not backtest.

For more information, examples:


Thanks Tomasz. This is very useful.

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.