One TradingView strategy setting that we can configure is whether the script should perform an additional intra-bar calculation after an order fills. But at times enabling or disabling that option has no effect at all on our strategy. Why is that and how to fix that?

In this article:

Calculating a strategy an extra time after an order fills

We specify the settings of a TradingView strategy either by hand or programmatically. With this latter approach our script is always added to the chart with the right settings. Specifying a strategy’s settings with code is done with the strategy() function, which has to be present in every strategy script (Pine Script Language Tutorial, n.d.).

One argument of that function is calc_on_order_fills. This true/false argument makes the strategy perform, when set to true, an additional intra-bar calculation after an order fills (Pine Script Language Tutorial, n.d.). We set that argument like this:

strategy(title="Example - calculating an extra time when an order fills", 
     calc_on_order_fills=true)

The default value of calc_on_order_fills is false. With that value the strategy doesn’t perform an extra calculation immediately after an order fills, but just calculates on the close of each bar (the default behaviour) or with every real-time price update (Pine Script Language Tutorial, n.d.). For more on that extra calculation when an order fills, see an additional intra-bar calculation after a TradingView order fills.

The benefit of calc_on_order_fills enabled is that our strategy becomes quicker. For instance, on the additional intra-bar calculation after an order fills, the strategy can immediately submit a trailing stop-loss based on the entry price. With the default settings, however, our script would have to wait till the bar closes before submitting that stop.

A confusing characteristic of calc_on_order_fills is that it sometimes doesn’t seem to work. That is, enabling or disabling this argument doesn’t affect the strategy’s behaviour at all. This tells us that, in some cases, we also need to make code changes so that our script actually does something on that additional intra-bar calculation after an order fills.

To fully explore that TradingView behaviour, we’ll create a programming example that isn’t affected by calc_on_order_fills. Then we’ll look at which code changes are needed before our example strategy can take advantage of the additional intra-bar order calculation after an order fills.

Example strategy: trading outside bars in TradingView

The example strategy trades long and short positions with outside bars and a smoothed, offset EMA (Exponential Moving Average). Such bars have a trading range that totally encompasses the previous bar’s range (Pring, 2002). In the strategy, we go long when there’s an outside bar with prices above the EMA, and initiate a short when an outside bar happens while prices are below the EMA.

The chart below gives a quick peek of the strategy; after discussing the code we’ll explore its trades in detail and examine the influence (and lack thereof) of the calc_on_order_fills argument.

Example of the TradingView strategy with the calc_on_order_fills argument disabled
//@version=2
strategy(title="Outside bars - example strategy", overlay=true,
     pyramiding=10, calc_on_order_fills=false)

// Inputs
priceData = input(title="Price Data", type=source, defval=hl2)
emaLen    = input(title="EMA Length", type=integer, defval=12)
smoothLen = input(title="EMA Smoothing", type=integer, defval=3, minval=1)

// Compute values
emaValue  = ema(priceData[10], emaLen)
emaSmooth = ema(emaValue, smoothLen)

// Determine trading conditions
outsideBar = (high > high[1]) and (low < low[1])

enterLong = (priceData > emaSmooth) and outsideBar
enterShort = (priceData < emaSmooth) and outsideBar

// Plot data
bgcolor(color=outsideBar ? teal : na, transp=85)

plot(series=emaSmooth, color=#F4A460, linewidth=2)

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

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

We start with the @version=2 comment. This makes the script use the second version of Pine Script, which makes features like if statements possible (Pine Script Language Tutorial, n.d.).

Next we configure the strategy’s properties with strategy(). We use this function’s title argument to specify the strategy’s name and with overlay=true the strategy displays on the chart’s instrument (TradingView, n.d.).

With pyramiding set to 10 the strategy’s pyramiding setting allows up to 10 entries in the same direction (TradingView, n.d.). We set the calc_on_order_fills argument to its default value of false. This way we explicitly configure that the strategy should not perform an additional intra-bar calculation after an order fills (TradingView, n.d.). (Later on in this we’ll explore the effect of setting this argument to true.)

Then we make several input options:

priceData = input(title="Price Data", type=source, defval=hl2)
emaLen    = input(title="EMA Length", type=integer, defval=12)
smoothLen = input(title="EMA Smoothing", type=integer, defval=3, minval=1)

The input() function creates user-configurable input options, and also returns the input’s current value (Pine Script Language Tutorial, n.d.). When we store these values in a variable with the assignment operator (=), we can access the input’s value later on in the code by referring to the variable.

Our first input option is a so-called ‘source’ data input option. This kind of input creates a pull-down menu with different kinds of price data from the chart’s instrument, and we make it by setting the type argument of the input() function to source (Pine Script Language Tutorial, n.d.). With the title argument we name this input “Price Data”, and that name displays before the option in the script’s settings. The default value (defval) of this input is hl2, which are the bar’s midpoint values (that is, [high + low] / 2). The current setting of this input is stored in priceData, which we use later on when computing the EMA.

The next two inputs are numerical integer inputs. These accept whole numbers only, and are made by setting the type argument of the input() function to integer (Pine Script Language Tutorial, n.d.). We name the first “EMA Length” and give it a default value (defval) of 12. Its value is tracked with the emaLen input variable, which we’ll calculate the EMA with later on. The third and last input, “EMA Smoothing”, defaults to 3 with a minimum value (minval) of 1. We’ll use this input when smoothing the EMA.

Calculating the exponential moving average is done next:

emaValue  = ema(priceData[10], emaLen)
emaSmooth = ema(emaValue, smoothLen)

To calculate our smoothed EMA, we use ema() twice. This function requires two arguments: the series of values to process and the integer that sets the number of bars to calculate on (TradingView, n.d.).

The first ema() function call calculates on the values of the priceData input variable. But instead of using the current bar value of the “Price Data” input, we offset the values 10 bars to the past. This gives the EMA an interesting lagging effect. For this we use the history referencing operator ([]) with a value of 10: this way priceData[10] returns the value of 10 bars ago (see Pine Script Language Tutorial, n.d.). The other argument of the ema() function is emaLen, the input variable that we gave a default value of 12. We store the computed 12-bar EMA in the emaValue variable.

That variable is then used to perform the smoothing. We again use the ema() function, but now have it calculate on the values of the emaValue variable with a length set by smoothLen (the input variable which starts off with a value of 3). The value returned by ema() is assigned to the emaSmooth variable. We use this variable with the 3-bar EMA of the 12-period EMA later with the script’s trading conditions.

Those trading conditions are what we determine next:

outsideBar = (high > high[1]) and (low < low[1])

enterLong = (priceData > emaSmooth) and outsideBar
enterShort = (priceData < emaSmooth) and outsideBar

We create three true/false variables here. Each is assigned a value based on two expressions combined with the and logical operator. This operator only returns true when both the expression on its left and the expression on its right are true too. When one or both expressions are false, then and returns false too (Pine Script Language Tutorial, n.d.).

For the first true/false variable, outsideBar, we verify if the current bar is an outside bar. To evaluate that, we first check if the current bar’s high (high) is greater than (>) the previous bar high, which we access with the history referencing operator ([]) and the value of 1 between its square brackets. The other expression that affects the value of outsideBar evaluates whether the current bar’s low (low) is less than (<) the previous bar low (low[1]). Since we combine both expressions with and, outsideBar only holds true when the current bar’s high is above the previous bar with the low being below the preceding bar. Whenever this pattern didn’t occur, outsideBar holds false.

The next variable that we create here is enterLong. Its first expression uses priceData, the input variable with the value of the “Price Data” source input option. Here we evaluate whether the current value of that price data is greater than (>) the smoothed EMA (emaSmooth). Since that smoothed EMA is calculated with the same price data, we’re in fact evaluating whether the current value of the “Price Data” input option is above its smoothed moving average.

The other expression that we use to determine the value of enterLong is simply outsideBar, the true/false variable that’s true whenever the current bar is an outside bar. Combined, these two true/false expressions mean that enterLong holds true whenever the input’s price data is trending above its smoothed EMA and the current bar is an outside bar.

We set the value of the third true/false variable (enterShort) in the same way, although now we evaluate whether the priceData input variable is less than (<) its 3-bar smoothed EMA of 12-period EMA values (emaSmooth) while at the same time an outside bar happens. When both situations occur, we assign the enterShort variable true (and false otherwise).

Next we plot the strategy’s values on the chart to visually confirm the script’s trades:

bgcolor(color=outsideBar ? teal : na, transp=85)

plot(series=emaSmooth, color=#F4A460, linewidth=2)

We first colour the chart’s background from top to bottom with the bgcolor() function (Pine Script Language Tutorial, n.d.; TradingView, n.d.). We use two of this function’s arguments: color and transp. That latter sets the transparency of the filled background, and ranges from 0 (no transparency) to 100 (fully invisible) (TradingView, n.d.). The color argument specifies the background’s colour, and we set that argument here to a value returned by the conditional (ternary) operator (?:).

That operator works with three values. The first is a true/false expression that, when true, makes the operator return its second value. If the true/false expression evaluates to false, then the conditional operator returns its third and last value (Pine Script Language Tutorial, n.d.). This makes this operator work like an if/else statement: ‘if this is true, return A; otherwise, return B’.

For our true/false expression we use outsideBar, the variable that’s true when the current bar is an outside bar and false when it isn’t. When this variable holds true, then the conditional operator returns the teal basic TradingView colour. That colour is then used with the color argument of the bgcolor() function.

When outsideBar is false, the conditional operator returns its third value: na. That built-in variable represents a “not a number” value (TradingView, n.d.) which, when used as a colour, has a transparent effect (Pine Script Language Tutorial, n.d.). So when outsideBar is false, the na ‘colour’ makes the background transparent. And when outsideBar is true, the chart’s background is coloured teal to highlight the outside bars.

We also display the EMA values on the chart, and use plot() for that. This function displays the data of its series argument as line by default (TradingView, n.d.), and here we set that argument to the emaSmooth variable. Those 3-bar EMA values of the 12-period EMA are coloured with the #F4A460 hexadecimal colour value of sandy brown. The linewidth argument, which specifies the width of the line starting with 1 as the default line size (TradingView, n.d.), is set to 2 to make this line a bit bigger than default.

We conclude the example by submitting the strategy’s orders:

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

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

The first if statement evaluates the enterLong variable, which holds true when the price is above its smoothed EMA while the current bar is an outside bar. In that case, we call the strategy.entry() function.

That function opens a position with a market order by default and respects the strategy’s pyramiding setting. Furthermore, when there’s already an open position in the other direction, then strategy.entry() will reverse that position (TradingView, n.d.).

We use this function with two arguments here: id and long. The first sets the order identifier, and this name displays on the chart and in the ‘Strategy Tester’ window. The long argument sets the market direction: true has the function submit long orders and false short orders (TradingView, n.d.). Here we name the order “Long Entry” alongside long=true.

The second if statement is much like the first. Now we evaluate the enterShort variable, which holds true when the price is below its smoothed EMA and the current bar is an outside bar. In that situation, we call strategy.entry() to submit an enter short (long=false) order that’s named “Short Entry”.

Example: trading outside bars in TradingView

Now let’s see at what our script’s code does. First, the above example strategy has the following input options:

Example of the TradingView strategy's input options

The strategy generates the following trades:

Example of the trades generated by the TradingView strategy

Here we see orders generating with an outside bar (whose background is highlighted), but those orders fill at the open of the next bar. This happens because TradingView strategies calculate on bar close by default (Pine Script Language Tutorial, n.d.). And so when the script calculates and generates the order on bar close, then this order can fill at the next bar’s open at the soonest.

Another chart that shows how our strategy trades outside bars is:

Example: TradingView strategy generating trades

Since it’s clear that our strategy behaves like it should, let’s see how changing the calc_on_order_fills argument affects its behaviour.

Enabling an extra intra-bar calculation when an order fills

In the above programming example, we configured the strategy() function as follows:

strategy(title="Outside bars - example strategy", overlay=true,
     pyramiding=10, calc_on_order_fills=false)

To enable an additional intra-bar calculation after an order fills, we change the calc_on_order_fills argument to true. Then we save the script, after which TradingView reloads the strategy and the chart changes to:

Example of the TradingView strategy behaves the same despite the calc_on_order_fills setting

Here we see that the strategy still trades like it did in the previous chart. The same thing happens when we look at the other time period, where the trades are also still the same:

Example of the identical behaviour of the TradingView trading strategy

So even though we changed a setting that apparently impacts the script’s calculations, we don’t see the script behaving differently than it did before. Let’s discuss why this happens and then change the programming example so that enabling the calc_on_order_fills does make the strategy respond quicker and scale into positions more often.

Discussion: what’s required for an extra intra-bar calculation?

To understand why the above example didn’t perform differently when we enabled calc_on_order_fills, we need to take into account the behaviour of TradingView strategies. First, when we enable that argument (that is, calc_on_order_fills=true), then the strategy performs an additional intra-bar calculation after filling an order (TradingView, n.d.).

But another feature of TradingView strategies is that they calculate on the bar’s close by default during both real-time trading and historical backtesting (Pine Script Language Tutorial, n.d.). So when a strategy calculates, and during that calculation the order condition is true, then the script generates an order. But when the price bar already closed when that order generates, the first opportunity for that order to fill is at the next bar’s open.

Now let’s say that our order indeed fills immediately at the bar’s open. Then with calc_on_order_fills enabled, the strategy performs an additional script calculation. But at that time the script calculates one bar after the one on which the order condition happened. And now the logic for submitting another order – or performing whichever action – isn’t valid anymore.

This reasoning can be visually explained as follows:

Visual explanation of why the TradingView strategy doesn't generate additional orders

For our example strategy, we required an outside bar to generate an order. And so when the bar closes and that bar is an outside bar, then the strategy can generate an order. That order then fills at the next bar’s open as soonest. But that next bar doesn’t necessarily have to be another outside bar. And so the additional intra-bar calculation that happens after the order fills, doesn’t generate an additional order. This way it looks like our strategy doesn’t do anything and that the calc_on_order_fills argument doesn’t work.

And so if we want to use calc_on_order_fills to scale into positions earlier up to the maximum position size as set by the strategy’s pyramiding setting, then we need to make sure that on those additional intra-bar calculations the entry condition is still valid. Now let’s turn to our programming example to see how to do just that.

Example: multiple orders per bar with calc_on_order_fills

To take advantage of the calc_on_order_fills argument so that our strategy builds up a position quicker, we need to make a minor adjustment to the strategy’s code. That tweak allows the strategy to still submit orders when the initial order has been filled and the additional intra-bar script calculation happens. Because each filled order triggers yet another intra-bar script calculation, the strategy’s maximum position size is reached much quicker.

The full code of the adjusted strategy script is:

//@version=2
strategy(title="Outside bars - example strategy", overlay=true,
     pyramiding=10, calc_on_order_fills=true)

// Inputs
priceData = input(title="Price Data", type=source, defval=hl2)
emaLen    = input(title="EMA Length", type=integer, defval=12)
smoothLen = input(title="EMA Smoothing", type=integer, defval=3, minval=1)

// Compute values
emaValue  = ema(priceData[10], emaLen)
emaSmooth = ema(emaValue, smoothLen)

// Determine trading conditions
outsideBar = (high > high[1]) and (low < low[1])

enterLong  = (priceData > emaSmooth) and (outsideBar or outsideBar[1])
enterShort = (priceData < emaSmooth) and (outsideBar or outsideBar[1])

// Plot data
bgcolor(color=outsideBar ? teal : na, transp=85)

plot(series=emaSmooth, color=#F4A460, linewidth=2)

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

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

Since practically all statements are the same as in the previous version, let’s discuss what did change. Previously, we set the values of the enterLong and enterShort variables like this:

enterLong = (priceData > emaSmooth) and outsideBar
enterShort = (priceData < emaSmooth) and outsideBar

But as we already note above, this way multiple entry orders weren’t quickly submitted after each other – despite using the calc_on_order_fills argument in an attempt to get a more responsive script. To change that, we alter the conditions of these variables to:

enterLong  = (priceData > emaSmooth) and (outsideBar or outsideBar[1])
enterShort = (priceData < emaSmooth) and (outsideBar or outsideBar[1])

So previously our order conditions required that the current bar was an outside bar (outsideBar); now the previous bar may be an outside bar too. We evaluate that by placing the history referencing operator ([]) with a value of 1 behind the outsideBar variable to retrieve the previous bar’s value.

We combine those current and previous values together with the or logical operator. This operator returns true when either the value on its left or the value on its right are true too, and or only returns false when both values are false also (Pine Script Language Tutorial, n.d.). And so our strategy’s order conditions require, besides filtering with the EMA, that either the current or previous bar was an outside bar.

Including the previous bar value of outsideBar has the following benefit. When the first order fills on the bar after the outside bar, then on that second bar our order condition can still be true (after all, outsideBar[1] returns true then). That means when the additional intra-bar script calculation happens on that second bar, the order condition is likely still true and the script can submit its next order.

So to have our example strategy still submit an order on the bar after the outside bar, we change the order conditions. Now the previous bar may also be an outside bar. With the previous approach, whereby only the current bar had to be an outside bar, the strategy couldn’t take advantage of the additional intra-bar script calculation (as we saw above).

When we save the script after the change to the enterLong and enterShort variables, the strategy now trades like this:

Example of the TradingView strategy generating additional orders with calc_on_order_fills

On the other chart period we also see the strategy pyramid into a position much quicker:

Example of the TradingView strategy with quicker orders due to calc_on_order_fills

Summary

The strategy() function needs to be added to the code of every TradingView strategy. This function has several arguments to configure the script’s settings, including calc_on_order_fills. That argument defaults to false when omitted, but when enabled the strategy performs an additional intra-bar script calculation each time an order fills. That makes the script more responsive and allows for performing additional task (like submitting another order) immediately after an order fills. Especially on higher time frames, the script can then scale into positions much quicker since each filled order causes yet another intra-bar calculation. Note that, when we enable calc_on_order_fills, the order conditions have to remain valid on the additional intra-bar calculation before an extra order can be submitted. This means we need to change the order logic of some strategy scripts before calc_on_order_fills makes quickly building positions possible.

Learn more:


References

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

Pring, M.J. (2002). Technical Analysis Explained (4th edition). New York, NY: McGraw-Hill.

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