One feature of TradingView strategies is a calculation that happens immediately after an order fills. Such an intra-bar calculation makes our script quicker. But how does it affect the strategy’s behaviour during backtesting and real-time trading?

#### In this article:

## Intra-bar calculation after an order fills: real-time and historical behaviour

We configure a TradingView strategy programmatically with the `strategy()`

function, and that function has to be added to every strategy script (*Pine Script Language Tutorial*, n.d.). Its `title`

argument, which names the strategy, is also something we always need to set (TradingView, n.d.).

Another, optional argument of that function is `calc_on_order_fills`

. When set to `true`

, the strategy performs one additional intra-bar calculation after an order fills (TradingView, n.d.). In contrast, the default behaviour of TradingView strategies is to only calculate on the close of historical and real-time price bars (*Pine Script Language Tutorial*, n.d.).

The intra-bar calculation with `calc_on_order_fills`

enabled happens after an order fills and before the price bar closes. With this we have the opportunity to perform an additional action in this time window, like submitting another order or updating the profit target. And so with this feature we can create a more responsive script, especially on high chart resolutions where there’s a lot of time between filling an order and the bar closing.

However, the `calc_on_order_fills`

argument does have a disadvantage. During backtesting, TradingView calculates with the bar’s open, high, low, and close price; but in real-time trading, the additional intra-bar calculation can happen with every real-time price update (*Pine Script Language Tutorial*, n.d.). Consequently, on historical data at most 4 orders can be filled per bar (2 on the open, 1 on the high, and 1 on the low; *Pine Script Language Tutorial*, n.d.),. But during real-time trading there can be as many additional intra-bar calculations as there are real-time price updates.

A strategy that uses `calc_on_order_fills`

therefore behaves differently on historical bars than with real-time prices. This has three important consequences:

- On real-time data the additional calculations happen with a price that’s close to the order’s fill price. After all, two subsequent real-time price updates have practically the same price while the range between a historical price bar’s open and high is much bigger. This means:
- When we submit an additional market order during the extra intra-bar calculation, this order fills at a different price in real time compared to its fill price in backtesting.
- And that additional order also fills in real time with much less slippage than the backtest suggests.

- With real-time data there are more opportunities for an intra-bar calculation than on historical price bars. Let’s say we use the additional intra-bar calculation after an order fills to submit a market order. On historical bars, at most 4 of those orders can be filled (
*Pine Script Language Tutorial*, n.d.). But with real-time data, dozens of orders can fill during that same bar, simply because each real-time price update provides yet another opportunity to submit an order. This also means that:- The higher the time frame, the bigger the difference between historical and real-time performance. This happens because a high chart resolution gives more real-time price updates per bar (and thus more opportunities for additional intra-bar calculations), whereas historical price bars have the same number of intra-bar calculations regardless of the chart’s time frame.

- When we use the additional intra-bar calculation to submit yet another order, a cascading effect can happen that causes a big difference between backtest and real-time performance. That is, when each intra-bar calculation triggers another order which, when filled, causes yet another intra-bar calculation, then the strategy calculates a lot of times (and fills a lot of orders). But on historical bars the number of intra-bar calculations remain limited to less than a handful while in real time dozens of orders can fill per bar.

For a more practical understanding of how the `calc_on_order_fills`

argument influences a TradingView strategy’s behaviour, let’s look at a programming example that uses this argument. Then we’ll have this example strategy run on real-time data and perform a historical backtest to see how the script’s behaviour differs.

## Example: submitting an extra order after the previous order fills

The basic TradingView strategy below goes long when the bar’s close crosses above the 12-bar EMA (Exponential Moving Average) and goes short if the bar drops below that moving average. Once there’s an open position, we increase the position up to a maximum of 20 entries in the same direction as long as the bar remains above or below the EMA.

A quick view of the strategy’s behaviour is given below: the first chart shows the script’s behaviour in real time while the second chart shows the backtest of the same price bars. After discussing the code, we’ll look at more charts that show the difference between real-time and historical performance.

```
strategy(title="Intra-bar calculations - example", overlay=true,
pyramiding=20, calc_on_order_fills=true)
// Calculate & plot moving average
emaValue = ema(close, 12)
plot(series=emaValue, color=orange, linewidth=2)
// Determine trading conditions
timeFilter = (year == 2016) and (month == 7) and (dayofmonth > 10)
enterLong = timeFilter and crossover(close, emaValue)
addToLong = (strategy.position_size > 0) and (close > emaValue)
enterShort = timeFilter and crossunder(close, emaValue)
addToShort = (strategy.position_size < 0) and (close < emaValue)
// Submit orders
strategy.entry(id="Enter Long", long=true, when=enterLong)
strategy.entry(id="Extra Long Order", long=true, when=addToLong)
strategy.entry(id="Enter Short", long=false, when=enterShort)
strategy.entry(id="Extra Short Order", long=false, when=addToShort)
```

We start by configuring the strategy’s settings programmatically with `strategy()`

. With the `title`

argument we name the strategy and with `overlay`

set to `true`

our strategy displays on the chart’s instrument (TradingView, n.d.). To allow multiple entries in the same direction we set `pyramiding`

to 20. And, with `calc_on_order_fills=true`

, our strategy performs an extra intra-bar calculation when an order fills (TradingView, n.d.).

Then we calculate and plot the exponential moving average:

```
emaValue = ema(close, 12)
plot(series=emaValue, color=orange, linewidth=2)
```

We calculate the average with `ema()`

, a function that requires two arguments: a series of values to calculate on and the length of the moving average (TradingView, n.d.). Here we set those arguments to the bar’s closing prices (`close`

) and 12. With the assignment operator (`=`

) we store that 12-bar EMA in the `emaValue`

variable for use later.

Next we plot the average with `plot()`

. That function displays the values of its `series`

argument as a line by default on the chart (TradingView, n.d.). We set that argument to the `emaValue`

variable. The function’s `color`

argument is set to the `orange`

basic TradingView colour and `linewidth`

is given the value of 2. That latter argument specifies the plot’s size starting from 1 as the default size (TradingView, n.d.), and so `2`

makes the line a bit bigger than normal.

After that we create a time filter:

```
timeFilter = (year == 2016) and (month == 7) and (dayofmonth > 10)
```

This `timeFilter`

variable is determined quite arbitrarily and doesn’t necessarily add something to the strategy’s long and short logic. So why did we add it to the script? TradingView currently allows strategy scripts to generate at most 2,000 orders, and triggers an error when we cross that limit:

Since our strategy allows up to 20 entries in the same direction, it generates a lot of orders and that makes the script reach the order limit quickly. To limit the amount of bars that are backtested (and thereby limiting the number of orders), we use a time filter here that only makes the strategy trades from July 8, 2016, onward.

To implement that filter programmatically, we set the `timeFilter`

variable to the value of three true/false expressions combined with the `and`

logical operator. That operator returns `true`

whenever the value on its left *and* the value on its right are both `true`

too; when one or both values are `false`

, then `and`

returns `false`

too (*Pine Script Language Tutorial*, n.d.). This means each of the three true/false expressions has to be `true`

before the `timeFilter`

variable is assigned `true`

too.

The expressions checks whether the `year`

built-in variable (which returns the current bar’s year in exchange time zone; TradingView, n.d.) equals (`==`

) 2016. The second expression checks whether the `month`

variable (that returns the current bar’s month in exchange time zone; TradingView, n.d.) is equal to (`==`

) 7. And then the last expression evaluates whether `dayofmonth`

returns a value greater than (`>`

) 10. This latter variable returns the current bar’s date in the exchange’s time zone (TradingView, n.d.).

So combined, these first three true/false expressions require that the bar’s date is in July 2016 while the day is 11 or later. When each of these three situations is indeed the case, `timeFilter`

is set to `true`

; otherwise, this variable is assigned `false`

.

With the time filter made, we create the long trading conditions:

```
enterLong = timeFilter and crossover(close, emaValue)
addToLong = (strategy.position_size > 0) and (close > emaValue)
```

Both `enterLong`

and `addToLong`

variables are set to a value based on the combination of two true/false expressions with the `and`

logical operator. And so each has to be `true`

before the variable is assigned `true`

too, and when just one of the expressions is `false`

, then the variable becomes `false`

too.

The first of those two expressions is the `timeFilter`

variable, and this way we incorporate our time filter into the strategy’s enter long logic. The second expression that sets the value of `enterLong`

checks whether the bar’s close crossed above the 12-bar EMA. We evaluate this with `crossover()`

. That function works on two arguments and returns `true`

when, on the current bar, the value of the first argument is greater than the second while, on the previous bar, the first argument’s value was less than the second argument (TradingView, n.d.). Since we use `crossover()`

with `close`

and `emaValue`

, the function returns `true`

when the bar’s closing price crossed above the 12-bar EMA and returns `false`

when such a crossover didn’t happen.

For the `addToLong`

variable we first evaluate whether the strategy is long already. We use `strategy.position_size`

for that, a built-in variable that returns both the size and direction of the strategy’s current position. That is, when the strategy is long, this variable returns a positive value with the number of open long contracts; if the script is short, it returns a negative value with the short position size; and when the strategy is flat, `strategy.position_size`

returns 0 (TradingView, n.d.).

So that variable returns a value that’s greater than (`>`

) 0 when the strategy is currently long. The second expression evaluates whether the bar’s closing price (`close`

) is above (`>`

) the 12-bar moving average (`emaValue`

). Only when both situations happened – being long and a close above the EMA – will the `addToLong`

variable be assigned a value of `true`

. We’ll use this variable later on in the script to submit an additional enter long order.

The other two true/false variables define our short conditions:

```
enterShort = timeFilter and crossunder(close, emaValue)
addToShort = (strategy.position_size < 0) and (close < emaValue)
```

Both `enterShort`

and `addToShort`

variables are set to the opposite of our long conditions. That is, before `enterShort`

is set to `true`

we require that the `timeFilter`

variable returns `true`

and that the bar’s closing price dropped below the 12-bar EMA.

We evaluate that latter with the `crossunder()`

function, which returns `true`

when the value of its first argument is now below that of the second argument, while on the previous bar the opposite was the case (TradingView, n.d.). And so by using this function with the `close`

and `emaValue`

variables, it returns `true`

whenever the bar’s close fell below the moving average. When both situations (the time filter and cross under) happen, then `enterShort`

holds `true`

and `false`

otherwise.

For the `addToShort`

variable we require that the strategy is short, which it is when `strategy.position_size`

returns a value that’s less than (`<`

) 0. The second requirement before we’ll submit a short scale-in order is that the bar’s closing price is below the 12-bar EMA (`close < emaValue`

). Our `addToShort`

variable is assigned `true`

when both things occurred, and holds `false`

when one or both aren’t the case.

With both long and short conditions set, we generate the strategy’s orders:

```
strategy.entry(id="Enter Long", long=true, when=enterLong)
strategy.entry(id="Extra Long Order", long=true, when=addToLong)
strategy.entry(id="Enter Short", long=false, when=enterShort)
strategy.entry(id="Extra Short Order", long=false, when=addToShort)
```

We submit the orders with `strategy.entry()`

. This function opens a position with a market order by default and scales into a position up to the strategy’s pyramiding setting. And when the strategy already has a position in the opposite direction, reverses that position (TradingView, n.d.).

Each `strategy.entry()`

function call uses the same three arguments. With `id`

we specify the order identifier, and this name displays on the chart and in the ‘Strategy Tester’ window. The `long`

argument, when set to `true`

, makes the function submit an enter long order, whereas `long=false`

has it open a short position. And the `when`

argument accepts a true/false value to define when the order should be submitted: only if this argument is set to a value of `true`

will `strategy.entry()`

submit our order (TradingView, n.d.).

The first `strategy.entry()`

function call creates a long order (`long=true`

) that’s named “Enter Long” and submitted when the `enterLong`

variable is `true`

on the current bar. The next statement uses `strategy.entry()`

to generate an order named “Extra Long Order” and this order is send off whenever the `addToLong`

variable is `true`

.

The last two `strategy.entry()`

function calls are practically the same. The first submits an “Enter Short” order (`long=false`

) whenever the `enterShort`

variable is `true`

. And in the last line of the programming example we submit the “Extra Short Order” when `addToShort`

is `true`

to add to an existing short position.

This ends our discussion of the programming example. Now let’s look at its behaviour on real-time and historical data.

## Example: trading a TradingView strategy on real-time data

With the above example strategy running in real-time, after a while the chart looks like:

Because of the high number (20) of entries in the same direction, the script also has plenty of intra-bar calculation opportunities. After all, since we’ve enabled the `calc_on_order_fills`

argument the strategy calculates an extra time after an order fills. For instance, in the above chart there are several bars with multiple entries per bar. And the last short trade displayed here even had its maximum number of entries reached within a single bar.

For a better visual grasp of how many orders the strategy executed, we can disable the order labels to reduce the clutter on the chart. For that we click on the gear icon ( ) displayed to the right of the strategy name:

This opens the window with the strategy settings. There we go to the ‘Style’ tab and disable the ‘Signal Labels’ option, followed by clicking ‘OK’.

This changes our chart to the following, with each arrow signalling an order:

When we change the price axis’ scaling (by clicking on the price axis and dragging the mouse down) we can see even more of the strategy’s orders:

While this doesn’t make all orders visible, we can already see that our strategy is quite active and submits a bunch of orders quickly after each other. Now how does this strategy with the `calc_on_order_fills`

argument enabled perform on historical price bars?

## Example: computing the TradingView strategy on historical data

To backtest the bars that we previously traded in real time, we remove the strategy from the chart and re-add it. We don’t make any changes to its settings or code; the only difference is that now it calculates on previous bars instead of processing real-time price updates. For the same time period as the previous chart, the script trades as follows:

The first thing you might notice here is that the strategy seems to trade more often. For instance, the last three price bars are now traded with short orders and this didn’t happen on the real-time chart. This occurs because on historical data the strategy can only fill a handful of trades at most (*Pine Script Language Tutorial*, n.d.), while on real-time data the strategy can calculate much more often per bar (and thus generate more orders too).

And so during backtesting we’ll have more historical bars on which orders fill, simply because it takes longer (that is, more price bars) to reach the strategy’s maximum number of entries in the same direction. We can see this more clearly when we turn off the signal labels like we did above:

And when we adjust the price axis again, we see that at most 5 orders were filled per bar:

Here is the same chart period with the orders executed in real time, and the orders are much more clustered here:

Since we added the strategy to a 3-minute stock chart during quiet trading hours, there weren’t a huge amount of price updates with every real-time bar. Should we add the script to a more active instrument and/or chart with a higher resolution, then we see more price updates per real-time bar. That would make our strategy submit orders more often and quicker than it did now on real-time data (and the difference with its historical performance would be even bigger).

## Intra-bar calculations: comparing real-time with historical performance

Now what effect has the additional intra-bar calculation after an order fills on the historical and real-time performance of a TradingView strategy? A couple of insights are:

- Since each order triggers another intra-bar calculation (which in turn can generate another order), the backtest and real-time difference is especially big when we allow multiple entries in the same direction. This is also what we saw in the example above.
- The more volatile the instrument, the bigger the difference between backtest and real-time performance. This is because a more active instrument has more price updates per bar, and with each of those the strategy can perform an additional calculation (provided that an order is filled before that). And the more of these calculation opportunities, the bigger the difference between real-time and historical performance.
- During a backtest, the strategy can trade on bars on which it wouldn’t trade in real time. This happens because the strategy’s maximum position size is reached much sooner during real-time trading (and so the amount of bars on which orders fill is less), while in backtesting the strategy can fill at most 5 orders per bar (and so the strategy trades much more bars to open its maximum position size). Because a strategy during a backtest will fill orders over a longer period of time, the fill prices can also differ significantly.

Besides the difference between backtest and real-time performance of a strategy that calculates an additional time when an order fills, there’s also a discrepancy when the strategy calculates with every real-time tick. That feature makes the backtest versus real-time difference even bigger, and we discuss it in backtest results are inaccurate when calculating on every tick.

### Summary

We configure the settings of a strategy with `strategy()`

, a function that needs to be added to the code of every TradingView strategy. One argument of `strategy()`

is `calc_on_order_fills`

that, when set to `true`

, makes the strategy perform an additional intra-bar calculation after an order fills. This way the strategy can perform another task (like submitting another order or updating the profit target) after an order fills but before the price bar closes, which is the default moment when a TradingView strategy calculates. An effect of `calc_on_order_fills`

is that the strategy can scale into a position quicker. However, there are at most 4 additional intra-bar calculations per historical price bar, while on real-time data there can be as many intra-bar calculations as there are price updates. This can cause a considerable difference between how the strategy performs on real-time data versus backtesting. And this discrepancy will be even more pronounced when trading a high time frame or price bars that have plenty of incoming real-time ticks.

#### Learn more:

- To learn more about the
`calc_on_order_fills`

argument of the`strategy()`

function, see recalculating a TradingView strategy when an order fills. - With the
`calc_on_order_fills`

argument enabled, the strategy performs an additional intra-bar calculation after an order fills. But it’s also possible to have a strategy do an intra-bar calculation with each real-time price update independent from whether an order filled or not. That feature is what we enable with the`calc_on_every_tick`

argument. - However, those script calculations with each price update make the backtest results quite unreliable compared to how the strategy performs in real time. We discuss this in inaccurate backtest results when calculating on every tick.

**References**

*Pine Script Language Tutorial* (n.d.). Retrieved on February 24, 2016, from https://docs.google.com/document/d/1sCfC873xJEMV7MGzt1L70JTStTE9kcG2q-LDuWWkBeY/

TradingView (n.d.). *Script Language Reference Manual*. Retrieved on July 18, 2016, from https://www.tradingview.com/study-script-reference/