Today we take a look at pyramiding on Tradingview. We will discuss what it is, how it affects your strategy and take a look at some common issues/misunderstandings that might arise when trying to use the feature.
What Is Pyramiding
To simplify strategy development, when using strategy.entry()
, Tradingview limit the number of entries to one at any given time. This means that if you are long and your buy entry conditions are true
again whilst you are still in a long position, it won’t buy again. The backtest engine will wait until you have exited (or reversed) your position before considering a long position again.
Pyramiding allows your strategy to enter multiple times in the same direction. This can be known as “Averaging Into” or “Scaling Into” a position. It can be especially useful for implementing complex entry and exit strategies. To access the Pyramiding setting, ho to the “strategy properties” tab of your strategy like so:
Note: The screenshot above shows the strategy properties of the default strategy script template. This is the template that Tradingview provides when you select “New –> Strategy”. As you will see, Pyramiding is enabled by default but the order size is limited to one. This means that we are limited to one buy or sell in any given position. Perhaps confusingly, there is no difference between having Pyramiding on with 1 order and actually switching it off!
Changing the default
Should you wish to, you can change the default pyramiding setting in the code using the strategy()
function call. Here we can use a variable called pyramiding
. Set this variable to zero and pyramiding will be switched off. Feed any other number value to it and it will change how many orders are allowed.
strategy("My Strategy", overlay=true, pyramiding=100)
The example above will set your order limit to 100. A fully working example of setting this in the code will be provided later.
Pyramiding with strategy.order()
It is worth pointing out that Pyramiding does not affect the strategy.order()
function. Tradingview’s wiki explains why succinctly:
this command places both entry and exit orders. It is not affected by pyramiding setting and by strategy.risk.allow_entry_in keyword. It allows you to create complex enter and exit order constructions when capabilities of the strategy.entry and strategy.exit are not enough.
As such, if you call strategy.order() it will be executed regardless of the Pyramiding setting you select. Pyramiding will only effect strategy.entry()
.
Reference: https://www.tradingview.com/wiki/Strategies
Code Example
Now we put the theory into practice! The following code example will create a simple script that allows us to average down whenever our portfolio is down x%. The idea will be to bring our average cost down so that we can still exit with a profit when conditions improve. With this in mind, the strategy shall also have a simple take profit exit at x% above our average price.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
//@version=3 // ########################################################################## // // // This scipt is intended to demonstrate how pyramiding can be used to average // down a position. // // We will buy when a stock closes above its 20 day MA and Average down if // the trade does not go in our favor. We will hold until a profit is made. // (which could mean we hold forever) // // ########################################################################## // strategy("Average Down", overlay=true, pyramiding=4, default_qty_type=strategy.percent_of_equity, default_qty_value=10) // Date Ranges from_month = input(defval = 1, title = "From Month", minval = 1, maxval = 12) from_day = input(defval = 1, title = "From Day", minval = 1, maxval = 31) from_year = input(defval = 2010, title = "From Year") to_month = input(defval = 1, title = "To Month", minval = 1, maxval = 12) to_day = input(defval = 1, title = "To Day", minval = 1, maxval = 31) to_year = input(defval = 9999, title = "To Year") start = timestamp(from_year, from_month, from_day, 00, 00) // backtest start window finish = timestamp(to_year, to_month, to_day, 23, 59) // backtest finish window window = time >= start and time <= finish ? true : false // create function "within window of time" // Strategy Inputs target_perc = input(-10, title='Target Loss to Average Down (%)', maxval=0)/100 take_profit = input(10, title='Target Take Profit', minval=0)/100 target_qty = input(50, title='% Of Current Holdings to Buy', minval=0)/100 sma_period = input(20, title='SMA Period') // Get our SMA, this will be used for our first entry ma = sma(close,sma_period) // Calculate our key levels pnl = (close - strategy.position_avg_price) / strategy.position_avg_price take_profit_level = strategy.position_avg_price * (1 + take_profit) // First Position first_long = crossover(close, ma) and strategy.position_size == 0 and window if (first_long) strategy.entry("Long", strategy.long) // Average Down! if (pnl <= target_perc) qty = floor(strategy.position_size * target_qty) strategy.entry("Long", strategy.long, qty=qty) // Take Profit! strategy.exit("Take Profit", "Long", limit=take_profit_level) // Plotting plot(ma, color=blue, linewidth=2, title='SMA') plot(strategy.position_avg_price, style=linebr, color=red, title='Average Price') |
Code Commentary
Since this is not the first post in our getting started series, we won’t go through the code line by line. If you are new to pine script programming, you can start at our First Script post.
Before we discuss how to calcuate our PnL or how we determine when to enter in the code, we should give an overview of the main inputs and what they are supposed to do. This will allow readers to understand how to operate the script.
Our strategy has 4 main inputs:
target_perc
: This is the target percentage level will trigger us to average down. In other words, if we have a close below this level from our average buying price, we will average down.take_profit
: A standard take profit percentage level. Use this to set how much profit you will target.target_qty
: Is the number of shares/contracts we will aim to buy when we average down. 50 will mean we buy 50% of our current holdings. So if we have 100 shares, then we buy 50 when we average down.sma_period
: Defines our SMA lookback period. Our strategy will enter the first/initial position when we have a close above our SMA level.
PnL
When we are averaging down, we must know what our current profit or loss is. Only then can we determine if we are ready to average down. Furthermore, we want to know our profit and loss in percentage terms rather than in monetory units (e.g. $).
The calculation for a percentage change is as follows:
(New Value – Old Value) / Old Value
(Close – Average Price) / (Average Price)
Which is then written in the code as:
pnl = (close - strategy.position_avg_price) / strategy.position_avg_price
Why not entry price? Well, during the first entry, the strategy.position_avg_price
is our entry price! On each subsequent entry, our average price lowers. At this point, we need to adjust both our take profit and the next average down target level from the strategy.position_avg_price
. This is because the average price always represents the point which our entire combined entries will swing into profit or loss.
Entries
Our entries are nice and straightforward. For the first entry, we just check whether we have a crossover the SMA and place a market order. We create a separate condition for this as we only want this condition to be true if our position size is zero (i.e strategy.position_size == 0
).
For the subsequent trades, we simply check if our PnL value is less than or equal to our target percentage. If it is, we place a market entry order. That simple.
if (pnl <= target_perc)
On the Charts
After running the script you should have something that looks like this. You can see that we plot both the SMA and the average price line in order to help with verifying the strategy is working as expected.
Now go ahead and switch the number of pyramiding levels, you will see that the number of positions taken for some trades will increase and decrease with this number. (During trades when price moves significantly against us)
A word of caution
This strategy can look great on paper, but it can quickly eat up all of your capital in the real world. As such, it is recommended to start your initial position size at a small percentage of your account size. Also considering limiting the maximum number of pyramid trades or the subsequent position sizes of your averaged down trades using the target_qty
input.
Finally, be aware of commissions. The more entries you put on, the more commissions you will end up paying unless you have a commission-free broker.
Potential Issues
Now we know how to use Pyramiding, it is worth looking at some examples of issues that can crop
Pyramiding doesn’t work!
There are certain types of strategies that Pyramiding will never work on. For example, if you take a simple moving average crossover strategy, it can only crossover one time before crossing back down and generating a short signal. As such, if you increase the number of pyramiding orders allowed, your results will not change at all.
Note the code snippet below is taken from Tradingview default Strategy template. You get this when selecting a “Blank strategy script” in the pine script editor.
1 2 3 4 5 6 7 8 9 10 |
//@version=3 strategy("My Strategy", overlay=true) longCondition = crossover(sma(close, 14), sma(close, 28)) if (longCondition) strategy.entry("My Long Entry Id", strategy.long) shortCondition = crossunder(sma(close, 14), sma(close, 28)) if (shortCondition) strategy.entry("My Short Entry Id", strategy.short) |
Load up this code, enter the “strategy properties” tab and toggle the number of pyramiding orders allowed. You will see our returns don’t change.
Or there are too many entries!
Another potential cause of confusion might be that you are seeing too many entries on the chart. In this case check whether you are using strategy.order()
calls in your code. To see why this might cause you to have more entries than your Pyramiding setting allows, take a look back up towards the start of the page.
Hello, Backtest Rookies. Nice site and introduction to Pine Script. I am enjoying your excellent tutorials.
I like the concept of averaging down and wonder if it could be expanded to allow for multiple stop orders with different price targets submitted with the initial market entry order. The goal would be to improve Tradingview backtesting accuracy by accounting for dollar-cost averaging and scaling into a position without pyramiding.
Here’s a non-working code sketch of the concept:
step_qty = 100 // base quantity for stop limit DCA orders
step_distance = 100 // base unit distance from entry price
strategy.entry(“Buy”, strategy.long, when = long and strategy.position_size == 0) // market order
strategy.order(“DCA 1”, true, qty = step_qty, stop = strategy.position_avg_price – step_distance, when = strategy.position_size > 0)
strategy.order(“DCA 2”, true, qty = step_qty * 1.02, stop = strategy.position_avg_price – step_distance – 100, when = strategy.position_size > 0)
strategy.order(“DCA 3”, true, qty = step_qty * 1.03, stop = strategy.position_avg_price – step_distance – 200, when = strategy.position_size > 0)
etc.
Rather than submitting in a FIFO manner, the intention would be to submit the initial entry and DCA stop orders at the same time with the DCA orders remaining pending until filled. Another goal would be to prevent each strategy.order DCA from submitting more than one stop order while the initial entry remains open.
Ideally, when each of the DCA stop limit orders is filled, strategy.position_avg_price variable would be updated with the latest averaged-down price. Then the current pending take profit order would be cancelled and resubmitted with the updated take profit price. How this would look is less clear to me.
long_take_profit_price = strategy.position_avg_price + 100 // how to update value of position_avg_price with each filled DCA?
strategy.exit(“Take Profit”, stop = long_take_profit_price)
strategy.cancel_all(“DCA”) // to cancel open/unfilled DCA orders
How would you code this in Pine Script?
How can I write a pine script for inverse pyramiding to average up instead average down with trailing stop?