Appearance
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. Withoutgaps_on, Saturday and Sunday would repeat Friday's return, and the window would count it three times.Rgrows by four numbers every day, in the order ofnames. That flat layout is what everympt.*call reads.days - window, daysis the window: the last 60 days,lotohi - 1.onsays which assets count: all four. A na return is no reading, so a weekend doesn't move the shares' volatility.basisis 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.