Skip to content

mpt ​

How do you turn a window of returns into portfolio weights inside a script? Collect each day's returns into a var array, then pass a window of it to mpt.weights with a method such as "min_variance" or "sortino". mpt.read and mpt.score measure names and portfolios with the same words, so changing a portfolio's objective means changing one word. The functions read nothing but their arguments, so they can't see a later bar.

ameno
indicator("Least-variance weights of three coins")
var array<float> R = array.new_float(0)
var array<float> basis = array.new_float(0)
// One return per name per day, in the same order every day.
R.push(nz(close / close[1] - 1))
R.push(nz(request.security("ETH-USD", "1D", close / close[1] - 1)))
R.push(nz(request.security("SOL-USD", "1D", close / close[1] - 1)))
int days = math.floor(R.size() / 3)
int lo = math.max(0, days - 90)
array<bool> on = array.from(true, true, true)
array<float> w = mpt.weights("min_variance", R, 3, lo, days, on, basis,
      upper = 0.6)
plot(w.get(0), "Weight of the chart's coin")
plot(mpt.score("vol", R, 3, lo, days, w), "Daily volatility")

Every call reads the same window. returns holds n numbers a day, one per name, day after day. The call reads days lo to hi - 1, up to 1,000 names, and on says which names count. A measure is per day and isn't annualised. Volatility and covariance divide by the days read, not by one fewer. A ratio with nothing under it, a name that's off and a name with no reading all read na. An option a word can't honour stops the script and names the option. See mpt.

mpt.read ​

AmenoScript only
ameno
mpt.read(measure, returns, n, lo, hi, on, threshold, alpha, benchmark, order, compound) → array<float>

One number per name: the measure of each name's returns over the window.

MeasureReadsTakes
mean, volThe mean, the standard deviation
downsideThe root mean square of the losses below thresholdthreshold
sharpe, sortinoThe mean over threshold, divided by vol or by downsidethreshold
omega, gain_to_painGains over losses around threshold, or the sum over the lossesthreshold
kappaThe mean over threshold, divided by the root of the lower partial moment of orderthreshold, order
var, cvarThe loss at the alpha quantile, and the mean loss past italpha
starr, rachevThe mean over threshold divided by cvar, and the gain tail's mean over the loss tail'sthreshold, alpha
max_drawdown, cdar, ulcerThe deepest drawdown, the mean of the worst 1 - alpha of them, and their root mean squarecompound, and alpha for cdar
calmar, martinThe mean over threshold, divided by the deepest drawdown or by ulcerthreshold, compound
beta, alpha, treynorAgainst benchmark, each on the excess over thresholdbenchmark, threshold
tracking_error, informationThe active return's volatility, and its mean over thatbenchmark
ParameterDefaultDescription
measure
series string
A word from the table above.
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name. A name that's off reads na.
threshold
series float
0The return a ratio's excess is measured from, per day.
alpha
series float
0.95The confidence of var, cvar, cdar, starr and rachev.
benchmark
array<float>
One benchmark return per day, at least hi of them.
order
series float
2The moment kappa reads.
compound
series bool
falsetrue reads drawdowns on the compounded curve a chart draws, rather than on summed returns.

mpt.score ​

AmenoScript only
ameno
mpt.score(measure, returns, n, lo, hi, weights, threshold, alpha, benchmark, order, compound) → series float

One number for a portfolio: the measure of the returns weights would have earned over the window.

ParameterDefaultDescription
measure
series string
A word from mpt.read's table.
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
weights
array<float>
One weight per name.
threshold
series float
0As for mpt.read.
alpha
series float
0.95As for mpt.read.
benchmark
array<float>
As for mpt.read.
order
series float
2As for mpt.read.
compound
series bool
falseAs for mpt.read.

mpt.weights ​

AmenoScript only
ameno
mpt.weights(method, returns, n, lo, hi, on, basis, lower, upper, gross, groups, group_lower, group_upper, target_return, target_risk, turnover, max_turnover, held, threshold, alpha, benchmark, order, risk_aversion, estimator, mean, cov, budgets, compound) → array<float>

Weights that sum to 1, chosen by method over the window, long only unless lower says otherwise.

basis is a var array<float> that starts empty and that the call owns: it keeps the last answer, so the next day's solve starts from it. Give each method its own, and leave it alone, since a basis the function didn't write stops the script. When no ordinary answer exists, the call falls back and says so through mpt.status: no name counts gives all zeros, one name takes everything, no variance holds equal weights, and limits that can't be met give all zeros. Everything is deterministic, with no random draw. See weights and limits.

MethodFindsHow
equal, inverse_volEqual shares, or shares by inverse volatilityClosed form
risk_parityEqual risk contributions, or budgetsCyclical coordinate descent
hrpHierarchical risk parityClosed form, single linkage
min_variance (or vol)The least varianceExact quadratic program
utilityThe mean less half risk_aversion times the varianceExact quadratic program
sharpe, information, diversificationThe highest ratioExact, after Charnes and Cooper
mean, alphaThe highest linear scoreExact linear program
beta, treynorThe lowest absolute beta, or the highest Treynor ratioExact linear program
omega, gain_to_painThe highest ratioAn exact vertex walk with no limits, a linear program with them
cvar, cdar, max_drawdownThe least of itExact linear program
starr, calmarThe highest ratioExact linear program
downside, sortinoThe least downside, or the highest ratioExact, by finite Newton
kappaThe highest ratioNewton, or Omega's answer at order = 1
ulcer, martinThe least ulcer index, or the highest ratioA bundle method to a gap of 1e-8
rachevThe highest ratioA local search, since it isn't convex

With compound = true, the drawdown words start from the exact answer on summed returns and then move shares between pairs of names while the compounded measure improves, so that answer is local.

ParameterDefaultDescription
method
series string
A word from the table above.
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name. A name that's off gets 0.
basis
array<float>
A var array<float>, empty at the start, that this call owns.
lower
series float/array<float>
0The least weight per name: one number for every name, or an array with one per name. A negative value allows shorts.
upper
series float/array<float>
The most weight per name, one number or an array with one per name. Left out, each name's is 1 less the other names' least weights, which limits nothing.
gross
series float
A cap on the sum of absolute weights.
groups
array<int>
One group number per name, for group_lower and group_upper.
group_lower
array<float>
The least total weight per group.
group_upper
array<float>
The most total weight per group.
target_return
series float
The mean a risk word must reach.
target_risk
series float
The volatility mean may take.
turnover
series float
A charge per unit of change from held or from the last weights, on min_variance, utility, cvar, cdar, max_drawdown and ulcer.
max_turnover
series float
A cap on the sum of absolute changes from held or from the last weights. A ratio word takes this instead of turnover.
held
array<float>
The weights held now. Left out, the last weights in basis.
threshold
series float
0As for mpt.read.
alpha
series float
0.95As for mpt.read.
benchmark
array<float>
As for mpt.read.
order
series float
2As for mpt.read.
risk_aversion
series float
utility's weight on the variance, above 0. utility needs it.
estimator
series string
"sample"The covariance, "sample" or "ledoit_wolf".
mean
array<float>
Your own mean per name, from any estimator, mpt.black_litterman included.
cov
matrix<float>
Your own covariance matrix. A method that solves on the covariance stops the script on one that isn't positive semi-definite: mpt.psd repairs it.
budgets
array<float>
risk_parity's share of risk per name.
compound
series bool
falseAs for mpt.read.

mpt.omega_weights ​

AmenoScript only
ameno
mpt.omega_weights(returns, n, lo, hi, on, basis) → array<float>

The weights with the highest Omega over the window, found exactly by a simplex over Omega's kinks.

A name that's off counts as nothing. Where no mix has a positive mean, the name with the highest Omega of its own takes everything, and names with gains and no losses share equally. Returns within 1e-10 of each other compare as equal, as the language's own compares do.

ParameterDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.
basis
array<float>
A var array<float>, empty at the start, where the call keeps yesterday's vertex.

mpt.inverse_vol ​

AmenoScript only
ameno
mpt.inverse_vol(returns, n, lo, hi, on) → array<float>

Each name's inverse volatility over the window, as shares that sum to 1.

Here na is no reading rather than a zero. A name with no volatility is left out, and if none has any, the names that are on share equally.

ParameterDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.

mpt.mean ​

AmenoScript only
ameno
mpt.mean(returns, n, lo, hi, on, estimator) → array<float>

Each name's mean return over the window.

ParameterDefaultDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.
estimator
series string
"sample""sample", or "bayes_stein" to shrink each mean toward the common one.

mpt.vol ​

AmenoScript only
ameno
mpt.vol(returns, n, lo, hi, on) → array<float>

Each name's volatility over the window, dividing by the days read.

ParameterDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.

mpt.cov ​

AmenoScript only
ameno
mpt.cov(returns, n, lo, hi, on, estimator) → matrix<float>

The names' covariance matrix over the window.

ParameterDefaultDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.
estimator
series string
"sample""sample" or "ledoit_wolf".

mpt.corr ​

AmenoScript only
ameno
mpt.corr(returns, n, lo, hi, on, estimator) → matrix<float>

The names' correlation matrix over the window.

ParameterDefaultDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.
estimator
series string
"sample""sample" or "ledoit_wolf".

mpt.psd ​

AmenoScript only
ameno
mpt.psd(cov, on) → matrix<float>

A covariance matrix that mpt.weights and mpt.frontier can solve on.

A covariance you build pair by pair, each pair over the days both names have, may not be positive semi-definite: no set of returns could have made it. Given one as cov, a method that solves on the covariance stops the script. mpt.psd returns a matrix that already is one unchanged. Otherwise it sets its correlation's negative eigenvalues to 0 and its diagonal back to 1, as Rebonato and Jaeckel (1999) do, and gives each name back its own variance.

Only names that are on and have a variance above 0 count. Every other name keeps its row and column as given. A matrix that isn't square, one of more than 1,000 names, or a na between two names that count stops the script.

ParameterDescription
cov
matrix<float>
The covariance, n rows by n columns.
on
array<bool>
One bool per name. Left out, every name counts.
ameno
indicator("A covariance repaired")
// Names 0 and 1 each move with name 2, yet against each other.
// No set of returns can do that, so the matrix isn't positive semi-definite.
matrix<float> C = matrix.new<float>(3, 3, 1.0)
matrix.set(C, 0, 1, -0.9)
matrix.set(C, 1, 0, -0.9)
matrix.set(C, 0, 2, 0.9)
matrix.set(C, 2, 0, 0.9)
matrix.set(C, 1, 2, 0.9)
matrix.set(C, 2, 1, 0.9)
matrix<float> Q = mpt.psd(C)
plot(matrix.get(Q, 0, 1), "Names 0 and 1, repaired") // about -0.5
plot(matrix.get(Q, 0, 2), "Names 0 and 2, repaired") // about 0.5
plot(matrix.get(Q, 0, 0), "Name 0's variance, kept") // 1

mpt.bets ​

AmenoScript only
ameno
mpt.bets(returns, n, lo, hi, on) → array<float>

How many names' worth of bet each name carries: the sum of its positive correlations with the names that are on, itself included. A name that moves with no other reads 1, and one that moves with every other reads close to the count of names on.

ParameterDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.

mpt.returns_of ​

AmenoScript only
ameno
mpt.returns_of(returns, n, lo, hi, weights) → array<float>

The portfolio's own return on each day of the window, for weights held throughout.

ParameterDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
weights
array<float>
One weight per name.

mpt.contributions ​

AmenoScript only
ameno
mpt.contributions(kind, returns, n, lo, hi, weights) → array<float>

Each name's share of the portfolio's volatility or mean. The shares sum to the whole.

ParameterDescription
kind
series string
"risk" for volatility or "return" for the mean.
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
weights
array<float>
One weight per name.

mpt.running_mean ​

AmenoScript only
ameno
mpt.running_mean(target, state) → array<float>

Moves weights toward target by a step of their own mean one-way turnover so far, so no length has to be chosen.

It stays at zero until the target holds a weight. A target with a na stops the script.

ParameterDescription
target
array<float>
The weights to move toward.
state
array<float>
A var array<float>, empty at the start, that the call owns.

mpt.frontier ​

AmenoScript only
ameno
mpt.frontier(returns, n, lo, hi, on, points, lower, upper, gross, groups, group_lower, group_upper, estimator, mean, cov) → matrix<float>

The efficient frontier as a matrix, one row per point: the weights, then the volatility, then the mean, from the least risk to the highest mean, spaced evenly by mean.

ParameterDefaultDescription
returns
array<float>
The window's returns, n a day.
n
series int
Names per day.
lo
series int
The first day to read.
hi
series int
One past the last day to read.
on
array<bool>
One bool per name.
points
series int
How many points, a whole number from 2 to 1,000.
lower
series float/array<float>
0As for mpt.weights.
upper
series float/array<float>
As for mpt.weights.
gross
series float
As for mpt.weights.
groups
array<int>
As for mpt.weights.
group_lower
array<float>
As for mpt.weights.
group_upper
array<float>
As for mpt.weights.
estimator
series string
"sample"As for mpt.cov.
mean
array<float>
Your own mean per name.
cov
matrix<float>
Your own covariance matrix. One that isn't positive semi-definite stops the script: mpt.psd repairs it.

mpt.black_litterman ​

AmenoScript only
ameno
mpt.black_litterman(cov, prior, views, targets, tau, risk_aversion, view_variance) → array<float>

The Black-Litterman posterior mean per name, to pass to mpt.weights as mean.

ParameterDescription
cov
matrix<float>
The names' covariance matrix.
prior
array<float>
The market weights the prior mean is implied from.
views
matrix<float>
One row per view, one column per name.
targets
array<float>
The return each view expects.
tau
series float
The uncertainty of the prior, above 0.
risk_aversion
series float
The market's risk aversion, above 0.
view_variance
array<float>
One variance per view. Left out, He and Litterman's diagonal.

mpt.capped ​

AmenoScript only
ameno
mpt.capped(basis) → series int

How many times a solve on basis stopped short of the optimum at one of its limits on steps or precision, summed over every call that used it. One call can count more than once. A correct run reads 0.

ParameterDescription
basis
array<float>
The basis an mpt.weights or mpt.omega_weights call owns.

mpt.status ​

AmenoScript only
ameno
mpt.status(basis) → series string

What the last call on basis found, as one of: "solved", "stopped short", "limits can't be met", "no mix has a positive excess mean", "no risk to reduce", "target out of reach" or "singular: ties went to the most equal weights".

ParameterDescription
basis
array<float>
The basis an mpt.weights call owns.

See also ​

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