Strategy Advanced#

The previous chapter covered basic entry and exit commands. This chapter dives deeper into settings that affect backtest accuracy: commissions, slippage, position sizing, and how to avoid common backtesting pitfalls.

Commission Settings#

Commission is an unavoidable cost in backtesting. Set it in the strategy() function:

//@version=6
strategy("Strategy with Commission",
         overlay=true,
         initial_capital=100000,
         commission_type=strategy.commission.percent,  // percentage commission
         commission_value=0.1)                          // 0.1% per trade

commission_type options:

ConstantDescription
strategy.commission.percentCharged as a percentage of trade value
strategy.commission.cash_per_contractFixed amount per contract
strategy.commission.cash_per_orderFixed amount per order

Slippage Settings#

Slippage simulates the difference between the actual execution price and the signal price:

strategy("Strategy with Slippage",
         overlay=true,
         slippage=2)  // Slip 2 minimum tick units per execution

Position Sizing#

Fixed Quantity#

strategy("Fixed Size",
         default_qty_type=strategy.fixed,
         default_qty_value=1)  // Trade 1 contract/share each time

Fixed Cash Amount#

strategy("Fixed Cash",
         default_qty_type=strategy.cash,
         default_qty_value=10000)  // Invest $10,000 each time

Percentage of Equity#

strategy("Percent of Equity",
         default_qty_type=strategy.percent_of_equity,
         default_qty_value=10)  // Invest 10% of total equity each time

Dynamic Quantity Calculation#

Specify qty directly in strategy.entry to dynamically calculate size based on current equity:

//@version=6
strategy("Dynamic Position Size", overlay=true, initial_capital=100000)

riskPct = input.float(2.0, "Risk per Trade (%)") / 100
atrMult = input.float(2.0, "Stop-Loss ATR Multiplier")

atrValue   = ta.atr(14)
stopAmount = atrValue * atrMult                        // Stop distance per unit
riskAmount = strategy.equity * riskPct                 // Max acceptable loss per trade
qty        = math.floor(riskAmount / stopAmount)       // Calculate position size

ma20 = ta.sma(close, 20)

if ta.crossover(close, ma20) and strategy.position_size == 0
    strategy.entry("Long", strategy.long, qty=qty)
    strategy.exit("Exit", "Long", stop=close - stopAmount)

pyramiding — Adding to a Position#

The pyramiding parameter sets the maximum number of open orders in the same direction:

strategy("Pyramiding Strategy", overlay=true, pyramiding=3)  // Up to 3 long orders at once

// Add to the position each time price pulls back to the MA
ma = ta.sma(close, 20)
if close > ma and close < ma * 1.02
    strategy.entry("Long", strategy.long, qty=1)

Avoiding Backtesting Pitfalls#

1. Lookahead Bias#

Pine Script’s strategy.entry defaults to filling on the open of the next bar after the signal bar closes, which is the correct behavior.

However, some approaches can introduce “peeking into the future”:

// ❌ Wrong: uses the current bar's high as the entry price
//    (that price existed before the signal was generated)
if close > ma
    strategy.entry("Long", strategy.long, limit=high)

// ✅ Correct: fills on the next bar's open (default behavior)
if close > ma
    strategy.entry("Long", strategy.long)

2. Overfitting#

Avoid repeatedly tuning parameters on the same historical dataset. Recommendations:

  • Reserve an “out-of-sample” test period (e.g., the most recent 20% of data)
  • Use fewer parameters; simpler strategies are more robust

3. barmerge.lookahead Setting#

When fetching data from other timeframes using request.security (see CH15), always use barmerge.lookahead_off to avoid peeking at the close of a future bar.

Common strategy.* Query Variables#

VariableDescription
strategy.equityCurrent total equity (including unrealized P&L)
strategy.netprofitRealized net profit/loss
strategy.position_sizeCurrent position size (positive = long, negative = short)
strategy.position_avg_priceAverage entry price of the position
strategy.opentradesNumber of currently open trades
strategy.closedtradesTotal number of closed trades
strategy.wintradesNumber of winning trades
strategy.losstradesNumber of losing trades

Practical Example: RSI Strategy with Full Risk Management#

//@version=6
strategy("RSI Strategy", overlay=false,
         initial_capital=100000,
         default_qty_type=strategy.percent_of_equity,
         default_qty_value=10,
         commission_type=strategy.commission.percent,
         commission_value=0.1)

// Parameters
rsiLen     = input.int(14,  "RSI Period")
oversold   = input.int(30,  "Oversold Threshold")
overbought = input.int(70,  "Overbought Threshold")
stopPct    = input.float(3.0, "Stop Loss (%)",   step=0.5) / 100
targetPct  = input.float(6.0, "Take Profit (%)", step=0.5) / 100

rsiValue = ta.rsi(close, rsiLen)

// Entry: RSI crosses back up from oversold
buySignal  = ta.crossover(rsiValue,  oversold)
sellSignal = ta.crossunder(rsiValue, overbought)

if buySignal
    strategy.entry("Long", strategy.long)

if strategy.position_size > 0
    entryPx = strategy.position_avg_price
    strategy.exit("Exit Long", "Long",
                  stop=entryPx * (1 - stopPct),
                  limit=entryPx * (1 + targetPct))

// Plotting
plot(rsiValue, "RSI", color=color.purple)
hline(overbought, color=color.red,   linestyle=hline.style_dashed)
hline(oversold,   color=color.green, linestyle=hline.style_dashed)
hline(50, color=color.gray, linestyle=hline.style_dotted)

Reference#