Skip to content

Risk parity ​

How do you size a portfolio so each asset adds the same risk, from returns your script collects? Push each day's returns into one flat array, then ask mpt.weights for "risk_parity" over the last 60 days, once a month.

ameno
indicator("Risk parity, monthly")
array<string> names = array.from("SPY", "TLT", "GLD", "BTC-USD")
int window = input.int(60, "Days of returns", minval = 10)

// gaps_on: a day an asset doesn't trade reads na, not the day before
array<float> r = request.each(names, close / close[1] - 1,
  gaps = barmerge.gaps_on)
// Every day's returns, name after name: 4 numbers a day
var array<float> R = array.new_float(0)
var array<float> basis = array.new_float(0)
var int days = 0
for x in r
    array.push(R, x)
days += 1

if days > window and timeframe.change("1M")
    array<bool> on = array.new_bool(array.size(names), true)
    array<float> w = mpt.weights("risk_parity", R, array.size(names),
      days - window, days, on, basis)
    book.target(names, w, reason = "monthly risk parity")
plot(book.equity(), "Equity")
plot(mpt.capped(basis), "Solves stopped short",
  display = display.data_window)

Run it on a daily chart of a coin that trades every day, such as CRYPTO:BTCUSD. R grows only on the chart's bars, and Bitcoin's weekend returns arrive only on weekend bars, so a stock's chart, which has none, would never collect them. request.each, mpt.* and the book.* calls are AmenoScript additions, so the script is AmenoScript and runs leak-safe. It is a teaching example, not a recommendation.

How it works ​

  • request.each(names, close / close[1] - 1, gaps = barmerge.gaps_on) gives one daily return per asset. On a day an asset doesn't trade, such as a weekend for the shares, its return is na. Without gaps_on, Saturday and Sunday would repeat Friday's return, and the window would count it three times.
  • R grows by four numbers every day, in the order of names. That flat layout is what every mpt.* call reads.
  • days - window, days is the window: the last 60 days, lo to hi - 1.
  • on says which assets count: all four. A na return is no reading, so a weekend doesn't move the shares' volatility.
  • basis is the call's own state. It starts empty, belongs to this one call, and the script never writes to it.
  • mpt.capped(basis) counts each time a solve stopped short of its optimum. It should stay at 0.

Change one word ​

Swap "risk_parity" for another method and nothing else changes: "min_variance", "hrp", "inverse_vol", "sharpe", "cvar". Options go by name, such as upper = 0.4 to cap each weight. mpt.* lists them.

Traps ​

  • The names were chosen today. The run warns that SPY, TLT, GLD and BTC were picked knowing how they turned out. Read the result with that in mind.
  • Split-adjusted share prices. The run also warns that a share's bars hold every split since, so its early prices are ones nobody quoted. Returns are unaffected, levels are not.

See also ​

AmenoScript is compatible with Pine Script v6. Pine Script is a trademark of TradingView, Inc., which has no part in AmenoScript.