We can configure several TradingView settings with code or by hand, including options that affect how the script calculates itself. How do we make a TradingView strategy perform an additional intra-bar calculation whenever an order fills, and why would we want to?

In this article:

Performing a one-time script calculation when an order fills

We configure the settings of a TradingView strategy either by hand or with code. This latter is done with the strategy() function, and every strategy script needs to use this function (Pine Script Language Tutorial, n.d.). This function also always sets the strategy name (TradingView, n.d.), but things like the number of entries in the same direction are also configured with strategy().

Another argument of strategy() is calc_on_order_fills. When we set this argument to true, the strategy performs one additional intra-bar calculation after an order fills (Pine Script Language Tutorial, n.d.; TradingView, n.d.). This argument defaults to false when we don’t set it, and in that case the strategy doesn’t perform such an additional calculation when an order fills during backtesting or real-time trading (TradingView, n.d.).

We set the calc_on_order_fills argument like this:

strategy(title="Example - calc_on_order_fills", calc_on_order_fills=true)

Now what’s the point of this argument? We use calc_on_order_fills when our strategy should execute any additional actions before the bar closes whenever an order fills (TradingView Support, personal communication, April 7, 2016). To better understand the motivation for using this feature, let’s consider the standard behaviour of strategies.

By default, during backtesting and real-time trading, TradingView calculates a strategy when the price bar closes (Pine Script Language Tutorial, n.d.). The strategy can generate an order during that script calculation, after which this order is submitted and can be filled sometime during the next price bar. But before the strategy can take an action based on how or when this order filled, it has to wait till the price bar closes. This can lead to a relatively long period in which the script is inactive, ‘waiting’ till the bar closes before it calculates again.

Now with calc_on_order_fills=true, the strategy still calculates on bar close for real-time and historical bars (Pine Script Language Tutorial, n.d.). But now the script also calculates one additional time whenever an order fills. This way the script can take immediate action as soon as the order fills, for example to manage the position, place another entry order, or take whichever action is needed.

And so the advantage of calc_on_order_fills is that it makes the strategy more responsive: due to that extra script calculation each time an order fills, the strategy can react quicker and take actions sooner. This is especially helpful when the chart has a high time frame and it takes a long time before bars close.

Tips and insights for an additional intra-bar calculation

Several meaningful characteristics of the calc_on_order_fills argument of the strategy() function are:

  • Most importantly, the backtest results when using calc_on_order_fills=true do not indicate how the strategy performs in real-time trading. This is because, during backtesting, the additional intra-bar script calculation is performed on the bar’s open, high, low, or close price; but during real-time trading, the next price update is used (see Pine Script Language Tutorial, n.d.). This has two important consequences:
    • On real-time data, the additional intra-bar calculation is done with a price that’s much closer to the previous calculation. After all, two subsequent real-time price updates have a closer range than, for example, the difference between the open and high of a historical bar.
    • On real-time data there can be a lot more intra-bar calculations than just 4 per bar. So when for example the strategy submits an additional order during the intra-bar calculation, then on historical data at most 4 orders are filled per bar – but on real-time data the number of orders filled would depend on the amount of price updates that happen inside that bar.
    • For more on these points, see TradingView tip: backtest results are inaccurate when using calc_on_order_fills.
  • The calc_on_order_fills argument isn’t needed to make sure that an open position immediately has a stop-loss and/or profit target attached to it. This is because exit orders generated by strategy.exit() will wait till the entry order is filled before being submitted (TradingView, n.d.). And so when we call that function during the same script calculation that submits the entry order, TradingView will ensure that the exit order is in place when the entry order fills. Note that the strategy.close() and strategy.close_all() function do require that there’s an open position before these commands come into effect (TradingView, n.d.).
  • The calc_on_order_fills argument can be used to quickly build up a position since each filled order triggers a script calculation, during which we can submit another order. However, since orders are typically generated on bar close, during the subsequent intra-bar calculation that happens a bar later the entry or exit condition may not be valid anymore. This means we need to adjust our code so that those intra-bar calculations can also trigger orders. For more, see ‘why hasn’t my order been filled during an intra-bar calculation?’.

Calculate when orders fill versus on every tick

Besides calc_on_order_fills, there’s also another argument of the strategy() function that affects how often a script calculates: calc_on_every_tick. The difference between these two arguments is (Pine Script Language Tutorial, n.d.; TradingView, n.d.):

  • When calc_on_every_tick is set to true, the strategy calculates on every real-time tick instead of just on bar close. This, however, only changes how the strategy behaves on real-time data and has no effect on historical data.
  • With calc_on_order_fills argument set to true, the strategy performs an additional intra-bar calculation when an order fills, and this affects the real-time behaviour as well as the historical backtest. But with real-time data that calculation happens on the next price update, whereas with historical data it happens on the bar’s open, high, low, or close.

Now let’s continue discussing the calc_on_order_fills argument by seeing how we can enable this argument manually. After that we’ll look at a full programming example.

Configuring a strategy’s one-time intra-bar calculation by hand

Besides using calc_on_order_fills to configure whether a strategy performs one additional intra-bar calculation when an order fills, we can also manually change this setting. That way various instances of the same script can have different settings, and we wouldn’t have that flexibility if we could only configure this setting programmatically.

To manually change the strategy’s calculation settings, we click on the gear icon ( ) that’s displayed to the right of the strategy’s name:

Opening the strategy settings window in TradingView

This brings up a window with the strategy’s settings. Here we select the ‘Properties’ tab, and there we can enable (or disable) whether the strategy performs an additional calculation when an order fills with the “Recalculate After Order filled’ checkbox:

Manually changing whether a TradingView strategy calculates after an order fills
Note: While the manual setting defaults to the value that we’ve set the calc_on_order_fills argument to, the ‘Recalculate After Order filled’ checkbox takes precedence over that programmatic value. So changing the manual setting overrides whichever value we set in the code.

Example: recalculating a strategy each time an order fills

In the example strategy below we trade a 20-bar EMA (Exponential Moving Average): we go short whenever the bar closes below that moving average, and go long when the bar is above that moving average. With this deliberately simple strategy we can focus our discussion on the calc_on_order_fills argument instead of the strategy’s details.

The image below shows how the finished strategy script behaves when added to the chart. After the code discussion, we examine how the strategy trades when calc_on_order_fills disabled and enabled.

Example of the TradingView strategy without calculation after order fills
//@version=2
strategy(title="Calc_on_order_fills - example", overlay=true, pyramiding=5, 
     calc_on_order_fills=false)

// Input
emaLen = input(title="EMA Length", type=integer, defval=20)

// Determine values & conditions
emaValue = ema(close, emaLen)

timeFilter = (year == 2016)

enterLong  = (close > emaValue) and timeFilter
enterShort = (close < emaValue) and timeFilter

// Plot data
plot(series=emaValue, color=#1E90FF, linewidth=2)

// Submit orders
if (enterLong)
    strategy.entry(id="Long Entry", long=true)

if (enterShort)
    strategy.entry(id="Short Entry", long=false)

We begin with the @version=2 comment. This specifies the script uses the second version of TradingView Pine, and one thing that version makes possible are if statements (Pine Script Language Tutorial, n.d.), and we use those later on in the script.

Next we configure the strategy properties with strategy(). With the title argument we specify the script’s name and with overlay set to true the strategy displays in the instrument’s chart panel (TradingView, n.d.). pyramiding is set to 5 to allow up to five entries in the same direction.

We also include the calc_on_order_fills argument here and explicitly set it to its false default value. This way our strategy does not perform an intra-bar calculation after an order is filled (TradingView, n.d.). We’ll change this argument later on in the article to see how that affects the strategy’s behaviour.

Then we add an input option to the script:

emaLen = input(title="EMA Length", type=integer, defval=20)

The input() function adds inputs to the settings of a TradingView script and also returns the input’s current value (Pine Script Language Tutorial, n.d.; TradingView, n.d.). Here we assign that returned value to the emaLen variable. That way we can refer to the input’s current value later on in the script by using the variable.

The input that we make is a numerical integer input option. Such an input option accepts whole numbers only and is made by setting the type argument of the input() function to integer (Pine Script Language Tutorial, n.d.). With this function’s title argument we name the input “EMA Length”, and this name is what’s placed before the input option in the script’s settings (see image further down below). The standard value of this input is 20 (defval=20).

After that we determine the strategy’s values and conditions:

emaValue = ema(close, emaLen)

timeFilter = (year == 2016)

enterLong  = (close > emaValue) and timeFilter
enterShort = (close < emaValue) and timeFilter

We first compute the exponential moving average with ema(). This function requires two arguments: a series of values to process and an integer that specifies the moving average’s length (TradingView, n.d.). We set the first to the bar’s closing prices (close) and the second to emaLen, our input variable that we gave a default value of 20. The value that’s computed by ema() is assigned here to the emaValue variable, and that variable is used later on when determining the long and short conditions.

timeFilter is the next variable that we create, and we assign this variable the value of a true/false expression. That expression evaluates whether the year of the current bar in the exchange’s time zone (which is returned by the year built-in variable; TradingView, n.d.) equals (==) 2016.

This is indeed an odd condition to include since it doesn’t relate to our entry and exit logic. However, we use the timeFilter variable to limit the strategy’s orders by only having it trade in 2016. We do this because currently, TradingView doesn’t allow a strategy to generate more than 2,000 orders. And without the timeFilter limit, the script runs into that ‘order limit was reached’ error message:

TradingView error: order limit was reached

The other two true/false variables that we make are enterLong and enterShort. As their name suggest, these are used when opening positions. Both have their value set with two true/false expressions that are combined with the and logical operator. That operator returns true when both the value on its left and the value on its right are true too. Otherwise, when one or both values are false, then and returns false too (Pine Script Language Tutorial, n.d.).

The first expression that affects the value of the enterLong variable is whether the bar’s closing price (close) is greater than (>) the 20-bar EMA (which we stored in the emaValue variable). The second expression is the timeFilter variable to have the strategy only enter trades during 2016. When both expressions are true, enterLongis assigned true also and false otherwise.

The enterShort variable is given its value in much the same way. Here the first expression evaluates whether the closing price is less than (<) the EMA, while the second checks whether this short condition happens in 2016. Since we combine the expressions with the and logical operator, when both are true then enterShort is assigned true also.

We then plot the EMA on the chart so we can visually confirm the strategy’s trades:

plot(series=emaValue, color=#1E90FF, linewidth=2)

The plot() function displays the data of its series argument as a line by default (TradingView, n.d.). Here we set that argument to the emaValue variable with the 20-bar EMA values. We set the colour of the line to the #1E90FF hexadecimal colour value of dodger blue. And the linewidth argument, which sets the plot’s size starting from 1 as the default size (TradingView, n.d.), is set to 2 here to make the EMA plot a bit bigger than default.

The last part of the example submits the orders:

if (enterLong)
    strategy.entry(id="Long Entry", long=true)

if (enterShort)
    strategy.entry(id="Short Entry", long=false)

We submit our orders conditionally with two if statements. The first checks if the enterLong variable is true. When the bar closed above the 20-bar EMA during 2016, we submit an enter long order with strategy.entry(). That function opens a position with a market order by default and, when there’s already an open position in the other direction, reverses that existing position (TradingView, n.d.).

We use strategy.entry() with two arguments. The first, id, specifies the order identifier. This name is displayed on the chart and in the ‘Strategy Tester’ window, but also used by other order functions to reference this order. The second argument, long, makes strategy.entry() open a long position when set to true and a short position when long=false (TradingView, n.d.).

The second if statement evaluates the enterShort variable. When this variable is true, which it is when the bar closes below the 20-bar EMA during 2016, then the strategy.entry() function is called. With that function we open a short order (long=false) that’s named “Short Entry”. Since we submit no other orders than these two entry orders, the strategy is always in the market and continuously goes from long to short and vice versa.

Example: a TradingView strategy without intra-bar calculations

Our above example strategy has this user-configurable input option:

Example of the TradingView strategy's input option

When the example strategy is added to the chart, it behaves like this:

Example of the TradingView strategy with calc_on_order_fills turned off

Here we see that entry orders are filled with one per price bar and that there are 5 entries in the same direction. Both observations can be explained with how we’ve configured the strategy() function of our example:

strategy(title="Calc_on_order_fills - example", overlay=true, pyramiding=5, 
     calc_on_order_fills=false)

Since we’ve set the pyramiding argument to 5, the strategy may enter up to 5 times in the same direction. That there’s only one order filled per bar is due to setting the calc_on_order_fills argument to its default value of false. Given that TradingView strategies calculate on bar close by default (Pine Script Language Tutorial, n.d.), the script is only calculated once per bar and so only one entry order can also be generated on that bar.

Thus when the bar closes above or below the 20-bar EMA, the script calculates on that bar’s close and generates the first entry order. That market order is then filled on the next bar’s open. On the close of that next bar the script calculates again, and since the entry condition is still valid, it generates an additional market order. That order is then filled on the open of the next bar, and when that bar closes, our strategy calculates again. This continues as long as the entry condition remains valid and we haven’t reached the limit of the pyramiding setting yet.

The benefit of the calc_on_order_fills argument set to true is that it makes the strategy more responsive. On the above 120 minute GBP/USD chart we used a strategy with that argument set to false. Since the strategy then only calculates once per bar, when the first bar closed above the EMA and generated a market order, the strategy ‘waited’ 2 hours before next calculation happened and the second order could be submitted.

So let’s look at how we can make our strategy act quicker with calc_on_order_fills.

Example: additional intra-bar calculations of a TradingView strategy

To make our strategy perform an intra-bar calculation after an order fills, we set the calc_on_order_fills argument to true. So our strategy() function call becomes:

strategy(title="Calc_on_order_fills - example", overlay=true, pyramiding=5, 
     calc_on_order_fills=true)

After saving that change, the strategy reloads and looks like this:

Example of the TradingView strategy with calc_on_order_fills enabled

Now with calc_on_order_fills set to true, the strategy doesn’t only calculate on bar close but also performs an additional intra-bar calculation after an order fills.

In our case, when the bar closes above the EMA and the long entry triggers, the strategy submits the first market order. When that order fills at the next bar’s open, the strategy performs an intra-bar calculation. If the entry condition is still valid then, that extra calculation submits the second order – which in turn triggers another intra-bar calculation when it fills. This process continues until the entry condition becomes invalid or the strategy reaches its pyramiding limit.

Due to those additional intra-bar calculations, with calc_on_order_fills=true our positions require 2 bars instead of 5 to be at their maximum position size: four entries on the first bar and one additional entry on the second bar.

Difference between calc_on_order_fills on and off

To really examine the strategy’s behaviour, let’s zoom in on its long and short orders. With calc_on_order_fills, a long position is opened with one order per bar:

Zooming in on the TradingView strategy's orders with calc_on_order_fills disabled

When the bar closes above the 20-bar EMA, the long condition triggers and the strategy submits a market order. That long order fills on the next bar’s open. When that bar closes, the script calculates again and submits the next market order. That order fills on the open of the next bar, and on that bar the script performs the next calculation. This continues until there are 5 entries in the same direction.

Now with calc_on_order_fills set to true, that same position opens as follows:

Examining the TradingView strategy's behaviour with calc_on_order_fills enabled

Here the first bar that closes above the 20-bar EMA still acts as the long signal bar. On this bar’s close the script calculates and generates a market order (which is filled at the next bar’s open). An intra-bar calculation doesn’t happen on this bar yet since calc_on_order_fills=true requires that an order fills first. And so the first entry order is not generated any sooner when we use calc_on_order_fills; the script calculation that generates that order is still done on bar close.

What does go quicker is building the position. On the bar on which the first order fills, several intra-bar calculations happen. How often the strategy calculates on historical bars is limited, however; on those bars, TradingView takes the high, low, open, and close into consideration. As a consequence, a strategy can fill up to 4 orders on a historical bar with calc_on_order_fills=true: 2 on the open (one generated on the previous bar’s close and another generated at the open itself), 1 on the high, and 1 on the low (Pine Script Language Tutorial, n.d.). That’s why our fifth enter long order is filled at the next bar’s open.

We see the same thing happening with short orders. First, this is how the strategy fills short orders with calc_on_order_fills disabled:

A short trade with the strategy's calc_on_order_fills argument disabled

When we enable calc_on_order_fills, the first 4 orders fill on the bar immediately after the short signal generates. And the 5th order fills on the bar after that:

The same short trade in TradingView, now with calc_on_order_fills enabled

Summary

The strategy() function configures a strategy’s settings, and we need to add this function to the code of every strategy. One of this function’s arguments is calc_on_order_fills. When we don’t specify this argument, it defaults to false. But when we set it to true, the strategy performs an intra-bar script calculation after an order fills. That gives the strategy the opportunity to perform an additional task, like submitting another order or calculating an exit price based on the position’s fill price. This feature makes the strategy more responsive and makes it scale into positions quicker. 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); on real-time data, how many orders fill per bar depends on the number of price updates. And so with calc_on_order_fills=true, the strategy fills more orders and has less slippage between these orders during real-time trading than when backtesting. This can cause a considerable discrepancy between backtest results and real-time performance.

Learn more:


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 May 3, 2016, from https://www.tradingview.com/study-script-reference/