In TradingView we can configure several strategy settings with code, like the number of decimals used by the script’s values. But how do we configure which price scale those values use?

In this article:

Programmatically setting a TradingView strategy’s price scale

We programmatically set the properties of a TradingView strategy, like its initial backtest capital and the fill assumptions of limit orders, with the strategy() function (TradingView, n.d.). Each strategy needs to use this function (Pine Script Language Tutorial, n.d.), and its title argument (which names the strategy) always needs to be set (TradingView, n.d.).

Another argument of the strategy() function is scale. This argument sets which price scale the strategy’s plotted values attach to (TradingView, n.d.). scale is an optional argument; when we don’t use it in the strategy() function, the strategy defaults to using the chart’s right price scale (TradingView, n.d.).

Most scripts don’t need to use the scale argument since their values display correctly on the right price scale. Some scripts, however, plot values that are much higher or lower than the range of the right price axis. In that case, we can use scale to attach the strategy to a different price scale than the right in order to prevent distorting the price axis. (We’ll see an example of this later on.)

Using the scale argument of the strategy() function looks like:

strategy(title="My example strategy", scale=scale.left)

The scale argument can be set to the following values (TradingView, n.d.):

Value Meaning
scale.right Attaches the strategy to the chart’s right price scale. This is the standard behaviour: when we don’t specify the scale argument, its implicit value is scale.right.
scale.left Attaches the strategy to the left price scale. This feature is typically used to prevent skewing the right price axis when plotting more than one script in the same chart panel or when overlaying the strategy on the chart’s instrument.
scale.none Doesn’t attach the strategy to any price scale. This way the plotted values cannot be read from a price scale, but another consequence is that the strategy can use the full chart when plotting – and sometimes even plots beyond the chart’s margins.
Tip: The scale argument set to scale.none only works when the strategy overlays on the chart’s instrument (TradingView, n.d.). When we use scale=scale.none without setting the overlay argument to true, the following error message is generated:
TradingView programming error of the 'overlay' and 'scale' arguments

A couple of other features of scale are:

Now let’s take a look at the different scaling options that scale makes possible.

The different scaling options of a TradingView script

As we saw above, the scale argument can be set to three values: scale.right (default), scale.left, and scale.none.

On the Euro-Bund CFD charts below we’ve plotted the standard Bollinger Bands script as well as the volume histogram. The latter uses the left price scale (which ranges from 0 to 900) while the Bollinger Bands are attached to the right price scale (which has a much narrower range around 165):

TradingView example chart with the Bollinger Bands indicator

When we change the scaling of the Bollinger Bands to the left axis (scale=scale.left), the chart looks as follows:

Plotting the Bollinger Bands indicator on the left price scale

Now the Bollinger Bands display as a horizontal line because the chart’s left scale, which is already used by the volume histogram, has notably different values than the right price scale has (0 till 900 versus 164.80 till 165.40).

When we plot the Bollinger Bands with scale.none, its script doesn’t attach to any price scale but uses the full chart area instead:

Plotting the Bollinger Bands in TradingView without any scaling

Here the Bollinger Bands look the same as when they were attached to the right price scale. However, the Euro Bund price action is better visible now since the right price scale doesn’t need to accommodate the Bollinger Band values anymore.

Example: setting a strategy’s price scale programmatically

The programming example below uses the scale argument of the strategy() function to attach three Exponential Moving Average (EMA) plots to one of the chart’s axes. The strategy enters a position when two moving averages cross and optionally submits a stop-loss. We first overlay the strategy on the chart’s instrument (see the image below) and then examine how different values of scale affect the appearance of this moving average strategy. But first, let’s discuss the code.

Example of plotting multiple moving averages in a TradingView strategy
//@version=2
strategy(title="MA cross with stops", overlay=true, precision=1, 
     scale=scale.none)

// Inputs
ma1Len  = input(title="MA 1 Length", type=integer, defval=14)
ma2Len  = input(title="MA 2 Length", type=integer, defval=25)
ma3Len  = input(title="MA 3 Length", type=integer, defval=38)
useStop = input(title="Use Stop?", type=bool, defval=true)

// Compute values
firstMA  = ema(close, ma1Len)
secondMA = ema(close, ma2Len)
thirdMA  = ema(close, ma3Len)

// Plot values
plot(series=firstMA, color=navy, linewidth=2)
plot(series=useStop ? secondMA : na, color=orange, linewidth=2)
plot(series=thirdMA, color=purple, linewidth=2)

// Submit enter market orders
if (crossover(firstMA, thirdMA))
    strategy.entry(id="Enter Long", long=true)

if (crossunder(firstMA, thirdMA))
    strategy.entry(id="Enter Short", long=false)

// Submit stops (if enabled)
strategy.exit(id="Long Stop", from_entry="Enter Long", 
     stop=secondMA, when=useStop)
    
strategy.exit(id="Short Stop", from_entry="Enter Short", 
     stop=secondMA, when=useStop)

We begin with a comment saying @version=2. This way we specify that the script should use the second version of Pine Script, which makes the use of if statements possible (Pine Script Language Tutorial, n.d.).

Then we configure the strategy with strategy(). With this function’s title argument we set the strategy’s name and, with overlay set to true, display the strategy on the instrument (TradingView, n.d.). The precision argument set to 1 sets the strategy’s number of decimals to 1, and that nicely matches the instrument that the script is added to (see images further down below). We set the scale argument to scale.none, and so the price scale initially doesn’t use a price scale but the whole chart area instead.

Next we add several input options to the script:

ma1Len  = input(title="MA 1 Length", type=integer, defval=14)
ma2Len  = input(title="MA 2 Length", type=integer, defval=25)
ma3Len  = input(title="MA 3 Length", type=integer, defval=38)
useStop = input(title="Use Stop?", type=bool, defval=true)

The input() function creates user-configurable options that appear in the ‘Inputs’ tab of the strategy settings window (see image below). This function not only creates the input, but also returns the input’s current value (TradingView, n.d.). Here we assign each of those values to a variable with the assignment operator (=), and this makes using the input’s current value later on in the code possible.

The first three 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.). Each is used later on when we compute the moving averages (MAs).

With the input() function’s title argument we name the first integer input “MA 1 Length”. That name is displayed before the input option in the script’s settings (see image further down below). We set its default value (defval) to 14 and store its current value in the ma1Len variable.

The other two integer inputs are named “MA 2 Length” and “MA 3 Length”. These have standard values of 25 and 38, and we track their values with the ma2Len and ma3Len variables.

The last input is a Boolean true/false input. That input is displayed as a checkbox in the script’s settings which can be turned on or off, and such inputs are made by setting the type argument of the input() function to bool (Pine Script Language Tutorial, n.d.). We name this setting “Use Stop?” and enable it by default (defval=true). The input’s current value is stored in the useStop variable that we use later on to determine whether the strategy should submit a stop-loss order or not.

Then we compute the moving averages:

firstMA  = ema(close, ma1Len)
secondMA = ema(close, ma2Len)
thirdMA  = ema(close, ma3Len)

The ema() function calculates exponential moving averages, and for this it requires two arguments: a series of values to process and an integer that sets the average’s length (TradingView, n.d.). Here each ema() function call uses the instrument’s closing prices (close).

The length of the moving averages is set with the ma1Len, ma2Len, and ma3Len input variables. This way we calculate EMAs with a length of 14, 25, and 38 bars, and we store those calculated values in the firstMA, secondMA, and thirdMA variables.

After that we plot the averages on the chart:

plot(series=firstMA, color=navy, linewidth=2)
plot(series=useStop ? secondMA : na, color=orange, linewidth=2)
plot(series=thirdMA, color=purple, linewidth=2)

The plot() function displays data on the chart as a connected line by default (TradingView, n.d.). With the first plot() statement we plot the 12-bar EMA (stored in the firstMA variable). This line’s colour is set to the navy standard TradingView colour. And with the linewidth argument, which sets the plot’s size starting from 1 for the default size (TradingView, n.d.), we make the line a bit bigger than default (linewidth=2).

The third plot() function call is much like the first. We use it to display the 38-bar EMA (series=thirdMA) with the purple basic TradingView colour. This plot’s size is, just like the first, also set to 2.

The second plot() statement differs from the other two. Instead of downright using a variable with the series argument, we use the conditional (ternary) operator (?:) here. This operator works on three values, with the first being a true/false expression. Now when that expression is true, the conditional operator returns its second value; and when that expression is false, the operator’s third value is returned (Pine Script Language Tutorial, n.d.). This makes the conditional operator work like an if/else statement: ‘if this is true, do A; otherwise, perform B’.

We use the conditional operator to plot the 25-bar EMA conditionally. For this we evaluate the value of the useStop input variable, which holds true when we’ve enabled the “Use Stop?” input option and returns false when that checkbox is unchecked. When that input variable is true, the conditional operator returns the secondMA variable. The value of that 25-bar EMA is then used with the series argument of the plot() function in order to plot that moving average on the chart.

Now when the useStop input variable is false, the conditional operator returns its third value. That value is the na built-in variable which represents a “not a number” value (TradingView, n.d.). That special value, when used with plotting, has the effect of ‘turning off’ the plot (see Pine Script Language Tutorial, n.d.).

And so when the useStop variable is false the conditional operator returns na to the series argument of the plot() function, and the 25-bar EMA won’t be plotted on the chart then. By using the conditional operator in this way, we conditionally plot the EMA on the chart depending on the “Use Stop?” input option. We do this because that middle EMA acts as the stop-loss level later on in the code; and so when the stop-loss is disabled with the input option, we have no need to plot the EMA.

After plotting the moving averages we generate the strategy’s entry orders:

if (crossover(firstMA, thirdMA))
    strategy.entry(id="Enter Long", long=true)

if (crossunder(firstMA, thirdMA))
    strategy.entry(id="Enter Short", long=false)

The orders are submitted with the help of two if statements here. The first checks whether the value that’s returned by the crossover() function is true and, when it is, submits an enter long order.

That crossover() function returns true when, on the current bar, the value of the first argument is above the value of the second argument, while on the previous bar the first argument’s value was below that of the second (TradingView, n.d.). Since we use the function here with the firstMA and thirdMA variables, this means crossover() returns true whenever the 14-bar EMA crosses over the 38-bar EMA. When that happens, we call the strategy.entry() function.

That functions opens a position with a market order by default and, when there’s already a position in the other direction, reverses that open position (TradingView, n.d.). We use strategy.entry() here with two arguments: id to specify the order identifier (which shows up on the chart and in the ‘Strategy Tester’ window) and long. That latter argument, when set to true, makes strategy.entry() enter a long position while setting long to false will have it open a short position.

Similar to the first if statement is the second. This one evaluates if crossunder() returns true. This function does so when, on the current bar, the value of its first argument has dropped below the second argument, while on the previous bar the first argument was still above the second (TradingView, n.d.). Since we use the function with the firstMA and thirdMA variables, it returns true whenever the 14-bar EMA has dropped below the 38-bar EMA and false in all other cases.

Now whenever the 14-bar moving average crossed under the 38-bar moving average, we call the strategy.entry() function to enter a short position (long=false) that’s named “Enter Short”.

In the example’s last part we submit stop orders depending on the value of the “Use Stop?” input option:

strategy.exit(id="Long Stop", from_entry="Enter Long", 
     stop=secondMA, when=useStop)
    
strategy.exit(id="Short Stop", from_entry="Enter Short", 
     stop=secondMA, when=useStop)

Closing a specific entry is done with strategy.exit(), and this function can submit either a limit or stop order (TradingView, n.d.). Both of our strategy.exit() statements use the same arguments. With id we set the order identifier while from_entry defines which entry order the exit order should apply to. With stop we specify the stop-loss price and when places the order conditionally: when the value of this argument is false, the order isn’t placed (TradingView, n.d.).

In the first strategy.exit() function call we name the order “Long Stop” and specify that it should exit the “Enter Long” order. The stop argument sets the stop-loss price to secondMA, the variable with the 25-bar EMA. This makes our stop move alongside the price and not a fixed value. We set the when argument to the useStop input variable, which holds true when the “Use Stop?” input option is checked and false when that option is disabled. This way we can have our strategy submit stop-loss orders based on whether or not we’ve enabled that manual option.

The second strategy.exit() function call is much the same. Here we submit an order that’s named “Short Stop” and apply it to the “Enter Short” entry. The stop’s price is the secondMA variable, and the strategy uses this stop whenever the useStop input variable is true.

Attaching a TradingView strategy to a specific price scale

We configured the strategy properties of the above example with the strategy() function, and set its scale argument to scale.none to have the script not attach to any price scale:

strategy(title="MA cross with stops", overlay=true, precision=1, 
     scale=scale.none)

Now when we add this strategy to a S&P 500 Index CFD chart, it looks like:

Example of plotting multiple moving averages in a TradingView strategy

Here we see that, when scale is set to scale.none, the script’s plotted values make full use of the chart. Some lines are even plotted beyond the visible chart area and display behind the name of the instrument.

When we examine the chart, it appears that the “Long Stop” and “Short Stop” orders are not triggered with the middle moving average line:

Odd visual effect due to scaling in TradingView

This is somewhat confusing; after all, we’ve programmed these exit orders to make them use the price of that middle moving average.

However, we did set the scale argument to scale.none and so the strategy doesn’t attach to a price scale. A consequence of this is that our moving averages aren’t plotted in line with the instrument’s price scale. We can see that in the image above, where the moving averages rise above the price bars during an uptrend – which cannot happen due to how moving averages work.

So let’s attach the strategy to the right price scale by changing the strategy() function to:

strategy(title="MA cross with stops", overlay=true, precision=1, 
     scale=scale.right)

We then save the script without making other changes. To see the updated code, we first need to remove the script from the chart and then re-add it. The strategy then looks like:

Example of a TradingView strategy that plots with the right price scale

Now our moving averages are plotted below the bars and, more importantly, the stop prices are indeed the middle moving average’s value:

Highlighted trades on the TradingView chart: plotting goes correctly now

The input options that the strategy makes are the following:

Input options of the TradingView example strategy

When we disable the “Use Stop?” input option, the previous chart changes to:

Disabling the stops of the TradingView example strategy

Instead of closing the position with the middle moving average, the strategy is now always in the market by going from long to short (and vice versa) based on the two plotted moving averages.

Summary

The strategy() function needs to be added to every TradingView strategy. This function has several arguments to configure the strategy’s properties, including title (a required argument that sets the strategy’s name). With the optional scale argument we specify which (if any) price scale the strategy should attach to. We can set this argument to scale.right to have the strategy use the right price scale and scale.left to have the plots attach to the left axis. This scale argument can also be set to scale.none, in which case the strategy is independent from both price scales and uses the full chart area to plot its value. This latter scaling option does require that the script is overlaid on the chart’s instrument, and we can do that by setting the overlay argument of the strategy() function to true. When we don’t use the scale argument in our strategy() function, the script defaults to using the right price scale.

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