Edgoro
Strategy · Strategies

Pine Script strategy position sizing: equity, cash, risk

Size Pine Script v6 strategy orders four ways: percent of equity, fixed cash, fixed units, and a fixed risk per trade from the stop distance, with the formulas.

Short answer

Leave qty out of strategy.entry() and the order uses default_qty_type and default_qty_value from strategy(), such as 10% of equity with strategy.percent_of_equity; pass qty and it overrides them. To risk a fixed share of equity per trade, divide the cash you accept to lose by the stop distance times syminfo.pointvalue.

The script

One strategy, five sizing modes chosen in the settings, each computing the quantity it sends, with the cash at risk shown in the Data Window. Paste it into a new strategy in the Pine Editor, then save and add it to the chart.

Pine Script v6
//@version=6
strategy("Position sizing modes", overlay = true, initial_capital = 10000, default_qty_type = strategy.percent_of_equity, default_qty_value = 10, commission_type = strategy.commission.percent, commission_value = 0.05, slippage = 2, margin_long = 100, margin_short = 100)

sizing    = input.string("Risk % per trade", "Sizing", options = ["Properties (default qty)", "% of equity", "Fixed cash", "Fixed units", "Risk % per trade"])
equityPct = input.float(10, "% of equity", minval = 0.1, maxval = 100)
cashAmt   = input.float(1000, "Cash per trade (account currency)", minval = 1)
units     = input.float(1, "Units per trade", minval = 0.000001)
riskPct   = input.float(1, "Risk per trade (% of equity)", minval = 0.1, maxval = 10, step = 0.1)
stopMult  = input.float(2, "Stop distance (x ATR)", minval = 0.5, step = 0.5)
targetR   = input.float(2, "Target (x stop distance)", minval = 0.5, step = 0.5)
wholeOnly = input.bool(false, "Round down to whole units")

atr  = ta.atr(14)
fast = ta.sma(close, 20)
slow = ta.sma(close, 50)
longSignal = ta.crossover(fast, slow)

float stopDist = stopMult * atr
// Equity in the symbol's currency, and what one unit is worth
float equity    = strategy.convert_to_symbol(strategy.equity)
float unitValue = close * syminfo.pointvalue

float rawQty = switch sizing
    "% of equity"      => equity * equityPct / 100 / unitValue
    "Fixed cash"       => strategy.convert_to_symbol(cashAmt) / unitValue
    "Fixed units"      => units
    "Risk % per trade" => equity * riskPct / 100 / (stopDist * syminfo.pointvalue)
    => na
// Never more than the equity can pay for at 100% margin
float qty = math.min(rawQty, equity / unitValue)
qty := wholeOnly ? math.floor(qty) : qty

var float stopPrice   = na
var float targetPrice = na
if longSignal and strategy.position_size == 0
    stopPrice   := close - stopDist
    targetPrice := close + targetR * stopDist
    if sizing == "Properties (default qty)"
        // No qty: the order uses default_qty_type and default_qty_value
        strategy.entry("Long", strategy.long)
    else if qty > 0
        strategy.entry("Long", strategy.long, qty = qty)
if not na(stopPrice)
    strategy.exit("Exit", "Long", stop = stopPrice, limit = targetPrice)

// Cash lost if the stop is hit, in the symbol's currency, for the size this bar would send
float riskCash = qty * stopDist * syminfo.pointvalue
plot(strategy.position_size > 0 ? stopPrice : na, "Stop", color.red, style = plot.style_linebr)
plot(strategy.position_size > 0 ? targetPrice : na, "Target", color.teal, style = plot.style_linebr)
plot(qty, "Quantity this bar", display = display.data_window)
plot(riskCash, "Risk if stopped", display = display.data_window)
plot(strategy.position_size, "Position size", display = display.data_window)

How it works

  • Properties. default_qty_type = strategy.percent_of_equity, default_qty_value = 10 is what strategy.entry() uses when it gets no qty. It also fills the "Order size" field of the strategy's Properties tab, where the user can change it. The other types are strategy.fixed (units) and strategy.cash (an amount of money).
  • % of equity buys units worth that share of the equity: equity × pct / 100 / (close × pointvalue). That sizes the position's value, not its risk.
  • Fixed cash and fixed units give the same exposure on every trade, whatever the account does.
  • Risk % per trade divides the cash to lose (equity × riskPct / 100) by what one unit loses if the stop is hit (stopDist × pointvalue). A wide stop gets a small position and a tight stop a large one, so a stop-out always costs about the same share of equity.
  • syminfo.pointvalue is the value of one full point of price for one unit: 1 for most stocks, forex and crypto, larger for futures (50 for the E-mini S&P 500). Leaving it out sizes futures many times too large.
  • strategy.convert_to_symbol() converts an amount from the strategy's account currency to the symbol's currency, for a strategy in USD trading a symbol quoted in EUR, for example.
  • The cap keeps any mode from buying more than the equity can pay for, which matters with tight stops in risk mode.

Pitfalls

  • Percent of equity isn't risk. 10% of equity in a position with a stop 2% away risks 0.2% of equity; with a stop 10% away, 1%. If you meant "risk 1% per trade", use the risk formula, not strategy.percent_of_equity.
  • Forgetting pointvalue. On a futures contract, one point is worth syminfo.pointvalue in money. A risk formula without it treats the E-mini S&P 500 as if one point were worth 1, and sizes the position 50 times too large.
  • Margin. In v6 margin_long and margin_short default to 100%: a position worth more than the equity isn't funded, and the Strategy Tester simulates a margin call that closes part of it. Cap the size, as the script does, or set the margin your broker really requires.
  • Whole units. Stocks and futures trade in whole units. math.floor(qty) rounds down, and on a small account it can round down to 0: the script then skips the trade instead of sending an order of zero.
  • qty overrides the Properties tab. Once strategy.entry() gets a qty, the "Order size" in the Properties tab no longer does anything for that order, which surprises users who change it and see no difference.

Variations

Short trades. The formulas are the same with the stop above the entry: stopPrice := close + stopDist and strategy.entry("Short", strategy.short, qty = qty).

Risk from the equity at the start of the day. Sizing from strategy.equity includes the open trade's unrealized profit or loss. To size every trade of a day from the same base, store the equity when the day starts:

// Equity at the first bar of each day, used for every trade of that day
var float dayEquity = strategy.initial_capital
if timeframe.change("D")
    dayEquity := strategy.equity
plot(dayEquity, "Equity at the day's start", display = display.data_window)

The strategy template wraps risk sizing, an exposure cap, filters and exits into one reusable strategy, and the free position size calculator gives the same numbers by hand.

Questions

What does default_qty_value do if I pass qty to strategy.entry?

Nothing for that order: an explicit qty always wins. default_qty_type and default_qty_value only size the entries that are called without a qty argument.

How do I risk 1% per trade in a Pine Script strategy?

Compute the stop distance first, then qty = strategy.equity * 0.01 / (stopDistance * syminfo.pointvalue), converting the equity with strategy.convert_to_symbol() if the account and symbol currencies differ. Pass it as strategy.entry(..., qty = qty) and use the same stop in strategy.exit().

Does strategy.equity include open profit?

Yes: it's the initial capital plus the closed trades' net profit plus the open position's unrealized profit or loss. Use strategy.initial_capital + strategy.netprofit to leave the open trade out.

Every script on this page was checked with Edgoro Pine Check (Pine Script v6 rules). Pine Check is a static checker, not TradingView's compiler: TradingView compiles the script when you save it in the Pine Editor. The scripts are examples to learn from, not trading systems. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

Need a different version of this script?

Describe the change in plain English and Edgoro writes the Pine Script v6 for you, checked with Pine Check. For example:

“Write a strategy that risks 1% of equity per trade with a 2 ATR stop and a 2R target, rounds the quantity down to whole shares and never puts more than the equity into one position.”