Navigation: Backtest Engine Details >

Split Handling





RealTest internally keeps price and volume data split-unadjusted. Any time any formula refers to "Close", the value returned is the real as-traded close for the current bar being evaluated.

To avoid potential distortions when using bar offsets or calculating multi-bar indicators, RealTest temporarily split-adjusts all past bars to the current bar being evaluated while making such calculations. In other words, RealTest always adjusts for past splits, but never adjusts for future splits that could not have been known in advance.

This method of past-only split adjustment makes all price and indicator values automatically "as-traded" in all situations, avoids distortions across past splits, and avoids subtle look-ahead bias which can occur when using data that is adjusted for future splits.

As a simple example, consider TSLA stock in 2020.

There was a 5:1 split on August 31, as shown in this unadjusted chart (charts can optionally be shown either adjusted or unadjusted):

Using the RealTest Debug Panel, we can see how any formula would be evaluated for a specific stock on a specific date in a backtest.

Here are two examples of very simple formulas, evaluated first for the day before the split, then the day after:

The first is simply "C", which returns the as-traded close of both dates.

The second is the 5-day moving average, which returns a correct as-traded split-adjusted calculation for each date, thus avoiding the split distortion.

While this all may sound complex, it is complexity that RealTest handles so that you don't have to. Every formula that you use anywhere in a strategy definition simply returns the correct past-only split-adjusted value for each date in the test, as if you had been trading on that day using the latest adjusted data, with no knowledge of future splits.

You may have noticed in the above log output that each result includes both a value (e.g. 441.81) and a type (e.g. "[price]"). In order to correctly handle splits in any formula expression, RealTest needs to keep track of the "type" of each value, i.e., whether it is a price, a volume, or neither. An average of prices needs to be adjusted one way, an average of volume the other way, and an average of price*volume (approximate turnover) does not require adjustment.

This knowledge of value type during formula evaluation ensures that split-adjustment is correct even when a formula refers to previously calculated data items. Those items know what the final type was when they were calculated, thus enabling their correct adjustment in any other formula.

While this mechanism works 99% of the time, it is not perfect. Given the flexibility of RealTest in allowing any formula to be evaluated and have its result stored in the Data Section, and then referenced later by another formula, it is possible to find examples where as-needed split adjustment is not correct for previously-stored data items.

One such example is Extern symbol values. Using the TSLA scenario above, if you were to create a data item called "TSLA_CLOSE" with Extern($TSLA, C) as its formula, and then later calculate MA(TSLA_CLOSE, 5) on 8/31/20, the result would not be correct.

Values returned by Extern never have a "price" or "volume" type, so storing them in Data and then referencing them later with an offset will not provide correct split adjustment.

Another example would be to store a derivative of price that depends on split adjustment in a Data item, then refer to it with an offset. For example, during an "exponential slope" calculation such as Slope(Log(C), 100), each of the past 99 values of C will be split-adjusted to the current bar before being passed to the Log() function. The end result will be correctly split-adjusted for the current bar, as usual with any multi-bar indicator.

However, if you calculate a separate Data item like this: LogC: Log(C), and then change the Slope formula to Slope(LogC, 100), this will produce a different result if used across a split boundary. This is because Log(C), as a stored data value, can no longer be considered split-adjustable.

The simple rule of thumb to follow, to avoid these rare and obscure potential adjustment errors, is this: do not STORE a price or volume value that can no longer be split-adjusted in a Data Section item, and then refer to it using an offset, or as an argument to a multi-bar function or indicator.

If you would prefer to "keep it simple" and always work with split-adjusted data, there is a way to do so: just add KeepAdjusted: True to your Import definition

This will remove the benefits of using unadjusted data, such as realistic as-traded prices and share quantities, and will introduce a risk of look-ahead bias (since adjustments indicate future splits), but the option exists if you need it for some reason.




Copyright © 2020-2024 Systematic Solutions, LLC