financialkit.portfolio#

Markowitz’s efficient frontier, closed form and long-only through scipy.optimize.minimize; Merton’s analytic constants; Tobin’s tangency portfolio and two-fund separation; the Kelly criterion, Merton’s portfolio problem (wealth simulated through financialkit.integrators.gbm_paths()) and CPPI; Sharpe’s single-index model and Ledoit-Wolf shrinkage; Black-Litterman and Michaud resampling; Rockafellar-Uryasev CVaR optimization (scipy.optimize.linprog), the most-diversified portfolio, risk parity and hierarchical risk parity (scipy.cluster.hierarchy); the Sharpe, Sortino and information ratios, Grinold’s fundamental law and rolling out-of-sample tests against 1/N.

financialkit.portfolio: mean-variance optimization, growth-optimal strategies, covariance estimation and risk-based allocation.

Markowitz’s efficient frontier (closed form and long-only through scipy.optimize.minimize()), Merton’s analytic constants, Tobin’s tangency portfolio and two-fund separation; Kelly’s criterion, Merton’s portfolio problem and CPPI; Sharpe’s single-index model and Ledoit-Wolf shrinkage; Black-Litterman and Michaud resampling; Rockafellar-Uryasev CVaR optimization (scipy.optimize.linprog()), the most-diversified portfolio, risk parity and hierarchical risk parity; and the Sharpe, Sortino and information ratios, Grinold’s fundamental law and rolling out-of-sample tests against 1/N. Returns are simple and per period, weights are fractions of wealth.

class financialkit.portfolio.ActiveManagementResult(active_returns, ir, ir_se, predicted_ir, realized_ic, breadth)[source]#

Bases: object

A simulated active manager, for checking Grinold’s fundamental law.

Parameters:
active_returns: ndarray#

Active return of each period, scaled to unit predicted IR.

Type:

ndarray, shape (n_periods,)

breadth: int#

Independent forecasts per period.

ir: float#

Realized information ratio per period.

ir_se: float#

Standard error of ir.

predicted_ir: float#

Grinold’s \(IC \sqrt{BR}\).

realized_ic: float#

Average cross-sectional correlation of forecasts and outcomes.

class financialkit.portfolio.BlackLittermanResult(prior_returns, posterior_returns, posterior_cov, weights, market_weights, tau, delta)[source]#

Bases: object

Black and Litterman’s blend of market equilibrium and investor views.

Parameters:
delta: float#

Market risk aversion.

market_weights: ndarray#

Capitalisation weights the prior is reverse-engineered from.

Type:

ndarray, shape (d,)

posterior_cov: ndarray#

Covariance of returns after the views, \(\Sigma + M\).

Type:

ndarray, shape (d, d)

posterior_returns: ndarray#

Expected returns after the views.

Type:

ndarray, shape (d,)

prior_returns: ndarray#

Implied equilibrium returns \(\Pi = \delta \Sigma w_{mkt}\).

Type:

ndarray, shape (d,)

tau: float#

Uncertainty of the prior relative to returns.

weights: ndarray#

Unconstrained optimal weights \((\delta(\Sigma + M))^{-1} \mu_{BL}\).

Type:

ndarray, shape (d,)

class financialkit.portfolio.CPPIResult(times, prices, wealth, floor, exposure, multiplier)[source]#

Bases: object

Wealth paths of a constant proportion portfolio insurance strategy.

Parameters:
property breached: ndarray#

Paths whose wealth ever fell below the floor (gap risk).

Type:

ndarray of bool, shape (n_paths,)

property cushion: ndarray#

Wealth above the floor.

Type:

ndarray, shape (n_paths, n_steps + 1)

exposure: ndarray#

Amount held in the risky asset over each step.

Type:

ndarray, shape (n_paths, n_steps)

floor: ndarray#

Floor, growing at the riskless rate.

Type:

ndarray, shape (n_steps + 1,)

multiplier: float#

Exposure per unit of cushion.

prices: ndarray#

Risky asset prices.

Type:

ndarray, shape (n_paths, n_steps + 1)

times: ndarray#

Rebalancing dates.

Type:

ndarray, shape (n_steps + 1,)

wealth: ndarray#

Portfolio value.

Type:

ndarray, shape (n_paths, n_steps + 1)

class financialkit.portfolio.CVaRResult(weights, var, cvar, expected_return, alpha)[source]#

Bases: object

A portfolio minimizing conditional value at risk over scenarios.

Parameters:
alpha: float#

Confidence level.

cvar: float#

Minimized conditional value at risk (a positive loss).

expected_return: float#

Mean scenario return of the optimal portfolio.

var: float#

Value at risk of the optimal portfolio, the LP’s auxiliary variable \(\zeta\) (a positive loss).

weights: ndarray#

Optimal weights.

Type:

ndarray, shape (d,)

class financialkit.portfolio.CovarianceResult(cov, method, n_params, shrinkage=0.0, target=None, betas=None)[source]#

Bases: object

An estimated covariance matrix.

Parameters:
betas: ndarray | None = None#

Market betas (single-index model only).

Type:

ndarray, shape (d,)

property condition_number: float#

Ratio of the largest to the smallest eigenvalue.

property corr: ndarray#

The implied correlation matrix.

Type:

ndarray, shape (d, d)

cov: ndarray#

The estimate.

Type:

ndarray, shape (d, d)

method: str#

"sample", "single_index" or "ledoit_wolf".

n_params: int#

Number of free parameters the estimator fits.

shrinkage: float = 0.0#

Weight on the shrinkage target (Ledoit-Wolf only).

target: ndarray | None = None#

Shrinkage target, or the single-index model’s structured part.

Type:

ndarray, shape (d, d)

class financialkit.portfolio.FrontierConstants(A, B, C, D)[source]#

Bases: object

Merton’s (1972) four scalars that pin down the unconstrained frontier.

With \(A = \mathbf 1'\Sigma^{-1}\mu\), \(B = \mu'\Sigma^{-1}\mu\), \(C = \mathbf 1'\Sigma^{-1}\mathbf 1\) and \(D = BC - A^2\), the least variance at mean \(m\) is \(\sigma^2(m) = (C m^2 - 2 A m + B) / D\), a hyperbola in the mean-volatility plane.

Parameters:
A: float#
B: float#
C: float#
D: float#
asymptote_slope()[source]#

Slope sqrt(D / C) of the hyperbola’s asymptotes.

Return type:

float

property gmv_return: float#

Expected return of the global minimum-variance portfolio, A / C.

property gmv_volatility: float#

Volatility of the global minimum-variance portfolio, 1 / sqrt(C).

variance(m)[source]#

Least portfolio variance with expected return m (broadcasts).

volatility(m)[source]#

Least portfolio volatility with expected return m (broadcasts).

class financialkit.portfolio.FrontierResult(returns, volatilities, weights, mu, cov, long_only)[source]#

Bases: object

Points along a mean-variance efficient frontier.

Parameters:
property asset_volatilities: ndarray#

Volatility of each asset on its own.

Type:

ndarray, shape (d,)

cov: ndarray#

Covariance of the assets.

Type:

ndarray, shape (d, d)

long_only: bool#

Whether short sales were excluded.

mu: ndarray#

Expected returns of the assets.

Type:

ndarray, shape (d,)

returns: ndarray#

Target expected returns.

Type:

ndarray, shape (n_points,)

volatilities: ndarray#

Minimum volatility reaching each target.

Type:

ndarray, shape (n_points,)

weights: ndarray#

The efficient portfolio at each target.

Type:

ndarray, shape (n_points, d)

class financialkit.portfolio.HRPResult(weights, order, linkage)[source]#

Bases: object

Lopez de Prado’s hierarchical risk parity allocation.

Parameters:
linkage: ndarray#

scipy.cluster.hierarchy.linkage() matrix of the correlation distance.

Type:

ndarray, shape (d - 1, 4)

order: ndarray#

Asset indices in quasi-diagonal (dendrogram leaf) order.

Type:

ndarray, shape (d,)

weights: ndarray#

Weights in the original asset order.

Type:

ndarray, shape (d,)

class financialkit.portfolio.OutOfSampleResult(returns, weights=<factory>, name='')[source]#

Bases: object

Returns of a portfolio rule re-estimated on a rolling window.

Parameters:
property mean: float#

Mean out-of-sample return per period.

name: str = ''#

Label of the rule.

returns: ndarray#

Realized out-of-sample returns.

Type:

ndarray, shape (n_periods - window,)

property sharpe: float#

Out-of-sample Sharpe ratio per period (no riskless rate).

property turnover: float#

Average sum of absolute weight changes per rebalance.

weights: ndarray#

Weights held in each period.

Type:

ndarray, shape (n_periods - window, d)

class financialkit.portfolio.PortfolioResult(weights, expected_return, volatility, method)[source]#

Bases: object

One portfolio and its first two moments.

Parameters:
expected_return: float#

Expected return w @ mu (nan when no mu was given).

method: str#

Name of the rule that produced the weights.

sharpe_ratio(rf=0.0)[source]#

Excess return per unit of volatility.

Return type:

float

Parameters:

rf (float)

volatility: float#

Standard deviation sqrt(w @ cov @ w).

weights: ndarray#

Weight of each asset.

Type:

ndarray, shape (d,)

class financialkit.portfolio.ResampledFrontierResult(weights, sample_weights, returns, volatilities, frontier)[source]#

Bases: object

Michaud’s resampled frontier: rank-averaged weights from resampled inputs.

Parameters:
frontier: FrontierResult#

The classical frontier of the true inputs, for comparison.

returns: ndarray#

Expected return of each averaged portfolio under the true inputs.

Type:

ndarray, shape (n_points,)

sample_weights: ndarray#

The frontier of every resample.

Type:

ndarray, shape (n_resamples, n_points, d)

volatilities: ndarray#

Volatility of each averaged portfolio under the true inputs.

Type:

ndarray, shape (n_points,)

weights: ndarray#

Averaged weights at each frontier rank.

Type:

ndarray, shape (n_points, d)

financialkit.portfolio.black_litterman(cov, market_weights, P, Q, omega=None, tau=0.05, delta=2.5)[source]#

Black-Litterman posterior returns and optimal weights.

Parameters:
  • cov (ArrayLike) – Covariance of excess returns.

  • market_weights (ArrayLike)

  • P (ArrayLike) – Each row is a view portfolio (absolute or relative).

  • Q (ArrayLike) – Expected excess return of each view portfolio.

  • omega (ArrayLike | None) – Covariance of the view errors. Defaults to He and Litterman’s \(\mathrm{diag}(P\tau\Sigma P')\), which gives each view the same confidence as the prior.

  • tau (float) – Scale of the prior’s uncertainty.

  • delta (float) – Risk aversion.

Return type:

BlackLittermanResult

Returns:

BlackLittermanResult

Examples

He and Litterman (1999): the view that German equities outperform the rest of Europe by 5% raises Germany’s expected return to about 11%. See implied_equilibrium_returns() and the tests for the full seven-country example.

>>> res = black_litterman([[0.04, 0.0], [0.0, 0.04]], [0.5, 0.5], [[1.0, 0.0]], [0.10])
>>> np.round(res.posterior_returns, 4)
array([0.075, 0.05 ])
financialkit.portfolio.cppi(prices, times, floor=0.8, multiplier=3.0, r=0.0, max_leverage=inf, w0=1.0)[source]#

Constant proportion portfolio insurance rebalanced on a discrete grid.

At each date the strategy holds \(E = \min(m\,\max(W - F, 0), \ell W)\) in the risky asset and the rest at the riskless rate, where the floor \(F_t = F_0 e^{rt}\) grows like a zero-coupon bond. Between dates the risky asset can fall by more than \(1/m\), and wealth then drops through the floor: that is gap risk.

Parameters:
Return type:

CPPIResult

Returns:

CPPIResult

Examples

>>> import numpy as np
>>> res = cppi([[100.0, 90.0, 99.0]], [0.0, 0.5, 1.0], floor=0.8, multiplier=2.0)
>>> np.round(res.wealth, 4)
array([[1.   , 0.96 , 0.992]])
financialkit.portfolio.cppi_continuous(prices, times, sigma, floor=0.8, multiplier=3.0, r=0.0, w0=1.0)[source]#

Black and Perold’s closed form for CPPI under continuous rebalancing.

For geometric Brownian motion the cushion is a power of the price, \(C_t = C_0 (S_t/S_0)^m \exp\left((1 - m)(r + \tfrac12 m\sigma^2)t\right)\), so wealth \(F_t + C_t\) never falls below the floor.

Parameters:
Return type:

ndarray

Returns:

ndarray, shape (n_paths, n_steps + 1) – Wealth along each path.

financialkit.portfolio.cvar_optimization(scenarios, alpha=0.95, target_return=None, long_only=True)[source]#

Rockafellar-Uryasev minimum-CVaR portfolio by linear programming.

Parameters:
  • scenarios (ArrayLike) – Equally likely joint return scenarios.

  • alpha (float) – Confidence level.

  • target_return (float | None) – Lower bound on the mean scenario return.

  • long_only (bool) – Exclude short sales (otherwise weights lie in [-1, 2], keeping the program bounded).

Return type:

CVaRResult

Returns:

CVaRResult

Examples

Two scenarios, one asset safe in each: hedging both equally removes all loss.

>>> res = cvar_optimization([[0.1, -0.1], [-0.1, 0.1]], alpha=0.5)
>>> np.round(res.weights, 6), round(res.cvar, 6) + 0.0
(array([0.5, 0.5]), 0.0)
financialkit.portfolio.diversification_ratio(weights, cov)[source]#

Choueifaty and Coignard’s ratio of weighted average volatility to portfolio volatility.

Parameters:
Return type:

float

Returns:

float – At least one for long-only weights; sqrt(d) for d uncorrelated equal-volatility assets held equally.

Examples

>>> diversification_ratio([0.25] * 4, np.eye(4))
2.0
financialkit.portfolio.downside_deviation(returns, target=0.0, periods_per_year=1.0)[source]#

Root mean square shortfall below a minimum acceptable return.

\(DD = \sqrt{\tfrac1n \sum_t \min(R_t - \tau, 0)^2}\), the square root of the second lower partial moment, annualized by \(\sqrt{q}\).

Parameters:
  • returns (ArrayLike)

  • target (float) – Minimum acceptable return per period, \(\tau\).

  • periods_per_year (float)

Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> downside_deviation([0.03, -0.04, 0.01, -0.02])
0.022360679774997897
financialkit.portfolio.efficient_frontier(mu, cov, n_points=50, long_only=False, max_return=None)[source]#

Efficient portfolios from the minimum-variance portfolio upwards.

Parameters:
  • mu (ArrayLike)

  • cov (ArrayLike)

  • n_points (int) – Number of target returns, equally spaced.

  • long_only (bool) – Exclude short sales; the frontier then ends at the asset with the highest expected return.

  • max_return (float | None) – Highest target. Defaults to max(mu) long-only and to 1.5 * max(mu) otherwise.

Return type:

FrontierResult

Returns:

FrontierResult

Examples

>>> fr = efficient_frontier([0.05, 0.1, 0.15], [[0.01, 0, 0], [0, 0.04, 0], [0, 0, 0.09]], 5)
>>> bool(np.all(np.diff(fr.volatilities) > 0))
True
financialkit.portfolio.efficient_portfolio(mu, cov, target_return, long_only=False)[source]#

The least-variance portfolio with a given expected return.

Unconstrained, Merton’s closed form \(w = \Sigma^{-1}[(C m - A)\mu + (B - A m)\mathbf 1] / D\); long-only, a quadratic program.

Parameters:
  • mu (ArrayLike)

  • cov (ArrayLike)

  • target_return (float) – Required mean return; long-only it must lie within [min(mu), max(mu)].

  • long_only (bool)

Return type:

PortfolioResult

Returns:

PortfolioResult

Examples

>>> res = efficient_portfolio([0.1, 0.2], [[0.04, 0.0], [0.0, 0.09]], 0.15)
>>> res.weights
array([0.5, 0.5])
financialkit.portfolio.equal_weight(n_assets)[source]#

The naive 1/N portfolio.

Parameters:

n_assets (int)

Return type:

ndarray

Returns:

ndarray, shape (n_assets,)

Examples

>>> equal_weight(4)
array([0.25, 0.25, 0.25, 0.25])
financialkit.portfolio.frontier_constants(mu, cov)[source]#

Merton’s constants \(A, B, C, D\) of the unconstrained frontier.

Parameters:
  • mu (ArrayLike) – Expected returns; not all equal.

  • cov (ArrayLike) – Positive definite covariance.

Return type:

FrontierConstants

Returns:

FrontierConstants

Examples

>>> fc = frontier_constants([0.1, 0.2], [[0.04, 0.0], [0.0, 0.09]])
>>> round(fc.gmv_return, 4), round(fc.gmv_volatility, 4)
(0.1308, 0.1664)
financialkit.portfolio.fundamental_law(ic, breadth)[source]#

Grinold’s information ratio \(IC\sqrt{BR}\).

Parameters:
  • ic (ArrayLike) – Information coefficient: correlation of forecasts with outcomes.

  • breadth (ArrayLike) – Independent forecasts per year.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

A stock picker with IC 0.05 on 400 independent bets a year:

>>> fundamental_law(0.05, 400)
1.0
financialkit.portfolio.hierarchical_risk_parity(cov, method='single')[source]#

Lopez de Prado’s hierarchical risk parity.

  1. Cluster the assets with scipy.cluster.hierarchy.linkage() on the distance \(\sqrt{(1 - \rho_{ij})/2}\).

  2. Reorder them by the dendrogram’s leaves (quasi-diagonalization).

  3. Bisect the ordered list recursively, splitting each parent’s weight between its halves in inverse proportion to the variance of each half’s inverse-variance portfolio.

Parameters:
  • cov (ArrayLike)

  • method (str) – Linkage method ("single" in the original paper).

Return type:

HRPResult

Returns:

HRPResult

Examples

With a diagonal covariance, HRP reduces to inverse-variance weights.

>>> res = hierarchical_risk_parity(np.diag([0.01, 0.04, 0.04, 0.16]))
>>> np.round(res.weights * 25, 6)
array([16.,  4.,  4.,  1.])
financialkit.portfolio.implied_equilibrium_returns(cov, market_weights, delta=2.5)[source]#

Reverse optimization: the excess returns that make the market portfolio optimal.

Parameters:
  • cov (ArrayLike)

  • market_weights (ArrayLike) – Capitalisation weights.

  • delta (float) – Market risk aversion, the market’s excess return over its variance.

Return type:

ndarray

Returns:

ndarray, shape (d,) – \(\Pi = \delta\Sigma w_{mkt}\).

Examples

>>> implied_equilibrium_returns([[0.04, 0.01], [0.01, 0.09]], [0.6, 0.4], 2.0)
array([0.056, 0.084])
financialkit.portfolio.information_ratio(returns, benchmark, periods_per_year=1.0)[source]#

Mean active return over tracking error, annualized.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(information_ratio([0.03, 0.01, 0.02], [0.02, 0.01, 0.0]), 4)
1.0
financialkit.portfolio.inverse_volatility_portfolio(cov, mu=None)[source]#

Weights proportional to \(1/\sigma_i\), the naive risk parity rule.

Parameters:
Return type:

PortfolioResult

Returns:

PortfolioResult

Examples

>>> np.round(inverse_volatility_portfolio(np.diag([0.01, 0.04])).weights, 6)
array([0.666667, 0.333333])
financialkit.portfolio.kelly_fraction(p, b=1.0, a=1.0)[source]#

Kelly’s growth-optimal fraction of wealth to stake on a binary bet.

A stake \(f\) returns \(+b f\) with probability \(p\) and \(-a f\) otherwise; \(f^* = p/a - (1 - p)/b\). A negative value means the bet is unfavourable (take the other side, or abstain).

Parameters:
  • p (ArrayLike) – Probability of winning.

  • b (ArrayLike) – Net odds received on a win, per unit staked.

  • a (ArrayLike) – Fraction of the stake lost on a loss.

Return type:

ndarray | float

Returns:

float or ndarray

Examples

Kelly’s even-money bet with a 60% edge:

>>> round(kelly_fraction(0.6), 12)
0.2
financialkit.portfolio.kelly_growth_rate(f, p, b=1.0, a=1.0)[source]#

Expected log-growth per bet, \(p\log(1 + bf) + (1 - p)\log(1 - af)\).

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(kelly_growth_rate(0.2, 0.6), 6)
0.020136
financialkit.portfolio.kelly_portfolio(mu, cov, r=0.0)[source]#

Growth-optimal risky weights for lognormal assets, \(\Sigma^{-1}(\mu - r)\).

The continuous-time Kelly portfolio is the logarithmic investor’s (\(\gamma = 1\)) Merton/Tobin allocation; whatever it leaves over is held in cash (or borrowed, if negative).

Parameters:
  • mu (ArrayLike) – Instantaneous expected returns.

  • cov (ArrayLike) – Instantaneous covariance.

  • r (float) – Riskless rate.

Return type:

ndarray

Returns:

ndarray, shape (d,)

Examples

>>> kelly_portfolio([0.08], [[0.04]], 0.02)
array([1.5])
financialkit.portfolio.kelly_wealth_paths(f, p, n_bets, n_paths, b=1.0, a=1.0, w0=1.0, rng=None)[source]#

Wealth of a gambler who stakes the fixed fraction f on repeated bets.

Parameters:
Return type:

ndarray

Returns:

ndarray, shape (n_paths, n_bets + 1)

financialkit.portfolio.ledoit_wolf(returns, target='identity')[source]#

Ledoit-Wolf shrinkage of the sample covariance towards a structured target.

The estimate is \(\delta F + (1 - \delta) S\), where \(S\) is the sample covariance (divided by \(n\)) and the intensity \(\delta \in [0, 1]\) is the consistent estimate of the loss-minimizing value.

Parameters:
  • returns (ArrayLike)

  • target (str) – "identity": \(F = (\mathrm{tr}\,S / d) I\) (2004a); "constant_correlation": sample variances with every correlation set to the average one (2004b).

Return type:

CovarianceResult

Returns:

CovarianceResult

Examples

>>> import numpy as np
>>> X = np.random.default_rng(0).standard_normal((30, 20))
>>> res = ledoit_wolf(X)
>>> bool(0 < res.shrinkage < 1), bool(res.condition_number < sample_covariance(X).condition_number)
(True, True)
financialkit.portfolio.merton_certainty_equivalent(pi, mu, r, sigma, gamma)[source]#

Certainty-equivalent growth rate of holding the constant fraction pi in the stock.

Wealth is then geometric Brownian motion with drift \(r + \pi(\mu - r)\) and volatility \(\pi\sigma\), and a CRRA investor values it like a sure return of \(r + \pi(\mu - r) - \tfrac{\gamma}{2}\pi^2\sigma^2\) per year, maximized at merton_fraction().

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(merton_certainty_equivalent(0.5, 0.08, 0.02, 0.2, 3.0), 6)
0.035
financialkit.portfolio.merton_consumption_rate(mu, r, sigma, gamma, rho)[source]#

Merton’s optimal consumption-to-wealth ratio over an infinite horizon.

With time preference \(\rho\) and Sharpe ratio \(\theta = (\mu - r)/\sigma\), the investor consumes the constant fraction \(\nu = [\rho - (1 - \gamma)(r + \theta^2 / (2\gamma))]/\gamma\) of wealth per year; it must be positive for the problem to be well posed.

Parameters:
Return type:

float

Returns:

float

Examples

Log utility consumes at the rate of time preference.

>>> merton_consumption_rate(0.08, 0.02, 0.2, 1.0, 0.03)
0.03
financialkit.portfolio.merton_fraction(mu, r, sigma, gamma)[source]#

Merton’s optimal constant fraction of wealth in the stock, \((\mu - r)/(\gamma\sigma^2)\).

Parameters:
  • mu (ArrayLike) – Expected return of the stock.

  • r (ArrayLike) – Riskless rate.

  • sigma (ArrayLike) – Volatility of the stock.

  • gamma (ArrayLike) – Relative risk aversion (1 is log utility, i.e. Kelly).

Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(merton_fraction(0.08, 0.02, 0.2, 3.0), 12)
0.5
financialkit.portfolio.merton_wealth_paths(pi, mu, r, sigma, T, n_steps, n_paths, consumption=0.0, w0=1.0, rng=None)[source]#

Exact wealth paths of the constant-mix strategy, through financialkit.integrators.gbm_paths().

Parameters:
  • pi (float) – Fraction of wealth held in the stock, rebalanced continuously.

  • mu (float) – Stock drift and volatility, riskless rate.

  • r (float) – Stock drift and volatility, riskless rate.

  • sigma (float) – Stock drift and volatility, riskless rate.

  • T (float) – Horizon in years.

  • n_steps (int)

  • n_paths (int)

  • consumption (float) – Fraction of wealth consumed per year.

  • w0 (float)

  • rng (Generator | int | None)

Return type:

tuple[ndarray, ndarray]

Returns:

  • times (ndarray, shape (n_steps + 1,))

  • wealth (ndarray, shape (n_paths, n_steps + 1))

financialkit.portfolio.min_variance_portfolio(cov, mu=None, long_only=False)[source]#

The global minimum-variance portfolio.

Unconstrained, \(w = \Sigma^{-1}\mathbf 1 / \mathbf 1'\Sigma^{-1}\mathbf 1\).

Parameters:
  • cov (ArrayLike)

  • mu (ArrayLike | None) – Expected returns, used only to report the portfolio’s mean.

  • long_only (bool) – Exclude short sales.

Return type:

PortfolioResult

Returns:

PortfolioResult

Examples

With uncorrelated assets the weights are proportional to inverse variances.

>>> res = min_variance_portfolio([[0.04, 0.0], [0.0, 0.01]])
>>> res.weights, round(res.volatility, 4)
(array([0.2, 0.8]), 0.0894)
financialkit.portfolio.most_diversified_portfolio(cov, mu=None, long_only=True)[source]#

The portfolio with the largest diversification ratio.

Maximizing \(D(w)\) is the minimum-variance problem on the correlation matrix followed by dividing each weight by the asset’s volatility and renormalizing (Choueifaty and Coignard 2008); the unconstrained answer is \(w \propto \Sigma^{-1}\sigma\).

Parameters:
Return type:

PortfolioResult

Returns:

PortfolioResult

Examples

Uncorrelated assets: weights proportional to inverse volatility.

>>> np.round(most_diversified_portfolio(np.diag([0.01, 0.04])).weights, 6)
array([0.666667, 0.333333])
financialkit.portfolio.out_of_sample_returns(returns, rule, window, name='')[source]#

Roll a portfolio rule through history, estimating on window periods and holding for one.

Parameters:
  • returns (ArrayLike) – Asset returns.

  • rule (Callable[[ndarray], ArrayLike]) – Maps the (window, d) block of past returns to weights of shape (d,).

  • window (int) – Estimation window length.

  • name (str) – Label stored on the result.

Return type:

OutOfSampleResult

Returns:

OutOfSampleResult

Examples

>>> import numpy as np
>>> R = np.array([[0.01, 0.03], [0.02, -0.01], [0.0, 0.02]])
>>> out_of_sample_returns(R, lambda past: equal_weight(2), window=1).returns
array([0.005, 0.01 ])
financialkit.portfolio.portfolio_moments(weights, mu, cov)[source]#

Expected return and volatility of a portfolio.

Parameters:
Return type:

tuple[float, float]

Returns:

mean, volatility (float)

Examples

>>> portfolio_moments([0.5, 0.5], [0.1, 0.2], [[0.04, 0.0], [0.0, 0.09]])
(0.15000000000000002, 0.18027756377319948)
financialkit.portfolio.resampled_frontier(mu, cov, n_obs, n_resamples=50, n_points=15, long_only=True, rng=None)[source]#

Michaud’s resampled efficient frontier.

Draws n_resamples histories of n_obs normal returns with the given moments, computes each history’s frontier at n_points equally spaced ranks (from its minimum-variance portfolio to its highest-return one) and averages the weights rank by rank. The averaged portfolios are then evaluated under the true inputs.

Parameters:
  • mu (ArrayLike)

  • cov (ArrayLike) – Point estimates, treated as the truth for resampling.

  • n_obs (int) – Length of each simulated history, the amount of data the estimates are as reliable as.

  • n_resamples (int)

  • n_points (int)

  • long_only (bool)

  • rng (Generator | int | None)

Return type:

ResampledFrontierResult

Returns:

ResampledFrontierResult

financialkit.portfolio.risk_parity(cov, budgets=None, mu=None)[source]#

Equal (or budgeted) risk contribution portfolio.

Solves Spinu’s strictly convex program \(\min_{y > 0} \tfrac12 y'\Sigma y - \sum_i b_i \log y_i\), whose first-order condition \(y_i(\Sigma y)_i = b_i\) makes each risk contribution proportional to its budget, by his damped Newton iteration (hand-written: SciPy’s trust-region methods stall on this problem at rounding level), then normalizes \(w = y / \mathbf 1'y\).

Parameters:
Return type:

PortfolioResult

Returns:

PortfolioResult

Examples

With two assets, risk parity is inverse volatility whatever the correlation.

>>> cov = np.array([[0.04, 0.018], [0.018, 0.09]])
>>> np.round(risk_parity(cov).weights, 6)
array([0.6, 0.4])
financialkit.portfolio.sample_covariance(returns)[source]#

The unbiased sample covariance matrix.

Parameters:

returns (ArrayLike)

Return type:

CovarianceResult

Returns:

CovarianceResult

Examples

>>> sample_covariance([[0.01, 0.02], [0.03, 0.0], [-0.01, 0.01]]).cov
array([[ 4.e-04, -1.e-04],
       [-1.e-04,  1.e-04]])
financialkit.portfolio.sharpe_ratio(returns, rf=0.0, periods_per_year=1.0)[source]#

Sharpe ratio: mean excess return over its standard deviation, annualized.

Parameters:
  • returns (ArrayLike) – Periodic returns; each column is a separate series.

  • rf (float) – Riskless return per period.

  • periods_per_year (float) – Scales the per-period ratio by its square root (252 for daily, 12 for monthly).

Return type:

ndarray | float

Returns:

float or ndarray, shape (k,)

Examples

>>> round(sharpe_ratio([0.02, -0.01, 0.03, 0.0], periods_per_year=12), 4)
1.8974
financialkit.portfolio.sharpe_ratio_se(returns, rf=0.0, periods_per_year=1.0)[source]#

Lo’s (2002) standard error of the Sharpe ratio for independent returns.

The per-period estimate has asymptotic standard error \(\sqrt{(1 + \tfrac12 SR^2)/n}\); annualizing multiplies it by \(\sqrt{q}\) like the ratio itself.

Parameters:
Return type:

ndarray | float

Returns:

float or ndarray

Examples

Ten years of monthly returns pin down an annual Sharpe ratio of 0.5 only to about plus or minus 0.32.

>>> import numpy as np
>>> r = np.random.default_rng(0).normal(0.005, 0.04, 120)
>>> round(sharpe_ratio_se(r, periods_per_year=12), 2)
0.32
financialkit.portfolio.simulate_active_management(ic, breadth, n_periods, rng=None)[source]#

Simulate a manager with skill ic making breadth independent bets a period.

Each period the standardized residual returns are \(z_i \sim N(0, 1)\) and the manager’s scores are \(g_i = IC\, z_i + \sqrt{1 - IC^2}\,\varepsilon_i\); the optimal active positions are proportional to the scores, so the active return is \(\sum_i g_i z_i\). Its exact information ratio is \(IC\sqrt{BR}/\sqrt{1 + IC^2}\), which Grinold’s law approximates to first order in \(IC\).

Parameters:
  • ic (float) – Information coefficient in (-1, 1).

  • breadth (int) – Independent bets per period.

  • n_periods (int)

  • rng (Generator | int | None)

Return type:

ActiveManagementResult

Returns:

ActiveManagementResult

financialkit.portfolio.single_index_covariance(returns, market)[source]#

Sharpe’s single-index covariance, \(\sigma_m^2\beta\beta' + \mathrm{diag}(\sigma_\varepsilon^2)\).

Each asset is regressed on the market by least squares, \(R_i = \alpha_i + \beta_i R_m + \varepsilon_i\), and the residuals are taken to be uncorrelated across assets.

Parameters:
Return type:

CovarianceResult

Returns:

CovarianceResult – With betas set and target holding the market part \(\sigma_m^2\beta\beta'\).

Examples

Assets that are exact multiples of the market have no residual risk.

>>> import numpy as np
>>> m = np.array([0.01, -0.02, 0.03, 0.0])
>>> res = single_index_covariance(np.column_stack([m, 2 * m]), m)
>>> np.round(res.betas, 12), bool(np.allclose(res.cov, np.var(m, ddof=1) * np.outer([1, 2], [1, 2])))
(array([1., 2.]), True)
financialkit.portfolio.sortino_ratio(returns, target=0.0, periods_per_year=1.0)[source]#

Sortino ratio: mean return above the target over the downside deviation, annualized.

Parameters:
  • returns (ArrayLike)

  • target (float) – Minimum acceptable return per period.

  • periods_per_year (float)

Return type:

ndarray | float

Returns:

float or ndarray

Examples

>>> round(sortino_ratio([0.03, -0.04, 0.01, -0.02]), 4)
-0.2236
financialkit.portfolio.tangency_portfolio(mu, cov, rf=0.0, long_only=False)[source]#

Tobin’s tangency portfolio, the fully invested maximum-Sharpe portfolio.

Unconstrained, \(w \propto \Sigma^{-1}(\mu - r_f\mathbf 1)\), the same computation as Roy’s safety-first portfolio with the disaster level set to the riskless rate, so it calls financialkit.risk.safety_first_portfolio(). Long-only, it solves \(\min y'\Sigma y\) subject to \((\mu - r_f)'y = 1\), \(y \ge 0\), and rescales \(y\) to sum to one.

Parameters:
Return type:

PortfolioResult

Returns:

PortfolioResult

Examples

>>> res = tangency_portfolio([0.1, 0.2], [[0.04, 0.0], [0.0, 0.09]], rf=0.02)
>>> np.round(res.weights, 4), round(res.sharpe_ratio(0.02), 4)
(array([0.5, 0.5]), 0.7211)
financialkit.portfolio.two_fund_allocation(mu, cov, rf, risk_aversion)[source]#

Tobin’s optimal mix of risky assets and cash for a mean-variance investor.

Maximizing \(w'(\mu - r_f) - \tfrac{\gamma}{2} w'\Sigma w\) gives \(w = \Sigma^{-1}(\mu - r_f\mathbf 1)/\gamma\): the tangency portfolio scaled by the investor’s risk aversion, with \(1 - \mathbf 1'w\) in cash. Separation means the risky mix does not depend on \(\gamma\).

Parameters:
Return type:

PortfolioResult

Returns:

PortfolioResult – Risky weights (not summing to one); expected_return includes the riskless rate earned on the cash.

Examples

>>> res = two_fund_allocation([0.1, 0.2], [[0.04, 0.0], [0.0, 0.09]], 0.02, 5.0)
>>> np.round(res.weights, 4), round(float(1 - res.weights.sum()), 4)
(array([0.4, 0.4]), 0.2)