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:
- Theory: setting the
calc_on_order_fillsargument of the
- Tips when using the additional intra-bar order calculation
- Calculating when an order fills versus calculating on every tick
- Manually configuring the additional intra-bar calculation
- Example strategy
- Zooming in: the difference between
calc_on_order_fillsenabled versus disabled
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
Another argument of
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.
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=truedo 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_fillsargument 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_all()function do require that there’s an open position before these commands come into effect (TradingView, n.d.).
calc_on_order_fillsargument 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
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.):
calc_on_every_tickis 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.
calc_on_order_fillsargument 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
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:
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:
calc_on_order_fillsargument 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.
[email protected]=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)
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 (
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 (
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:
The other two true/false variables that we make are
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 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 also and
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
enterShort is assigned
We then plot the EMA on the chart so we can visually confirm the strategy’s trades:
plot(series=emaValue, color=#1E90FF, linewidth=2)
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.).
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,
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:
When the example strategy is added to the chart, it behaves like this:
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
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:
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:
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.
calc_on_order_fills set to
true, that same position opens as follows:
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
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:
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.
- To learn more about the
calc_on_order_fillsargument, see why
calc_on_order_fillsdoesn’t always affect a strategy and backtesting with
calc_on_order_fillsgives inaccurate results.
- A related argument is
calc_on_every_tick, which enables a TradingView strategy to calculate with every real-time tick.
- For the other arguments of the
strategy()function, see configuring a TradingView strategy programmatically. In configuring a strategy manually we discuss how the arguments of
strategy()relate to the manual settings.
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/