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:
objectA simulated active manager, for checking Grinold’s fundamental law.
- Parameters:
- class financialkit.portfolio.BlackLittermanResult(prior_returns, posterior_returns, posterior_cov, weights, market_weights, tau, delta)[source]#
Bases:
objectBlack and Litterman’s blend of market equilibrium and investor views.
- Parameters:
- market_weights: ndarray#
Capitalisation weights the prior is reverse-engineered from.
- Type:
ndarray, shape (d,)
- class financialkit.portfolio.CPPIResult(times, prices, wealth, floor, exposure, multiplier)[source]#
Bases:
objectWealth paths of a constant proportion portfolio insurance strategy.
- Parameters:
- class financialkit.portfolio.CVaRResult(weights, var, cvar, expected_return, alpha)[source]#
Bases:
objectA portfolio minimizing conditional value at risk over scenarios.
- class financialkit.portfolio.CovarianceResult(cov, method, n_params, shrinkage=0.0, target=None, betas=None)[source]#
Bases:
objectAn estimated covariance matrix.
- Parameters:
- class financialkit.portfolio.FrontierConstants(A, B, C, D)[source]#
Bases:
objectMerton’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.
- class financialkit.portfolio.FrontierResult(returns, volatilities, weights, mu, cov, long_only)[source]#
Bases:
objectPoints along a mean-variance efficient frontier.
- Parameters:
- class financialkit.portfolio.HRPResult(weights, order, linkage)[source]#
Bases:
objectLopez de Prado’s hierarchical risk parity allocation.
- linkage: ndarray#
scipy.cluster.hierarchy.linkage()matrix of the correlation distance.- Type:
ndarray, shape (d - 1, 4)
- class financialkit.portfolio.OutOfSampleResult(returns, weights=<factory>, name='')[source]#
Bases:
objectReturns of a portfolio rule re-estimated on a rolling window.
- class financialkit.portfolio.PortfolioResult(weights, expected_return, volatility, method)[source]#
Bases:
objectOne portfolio and its first two moments.
- class financialkit.portfolio.ResampledFrontierResult(weights, sample_weights, returns, volatilities, frontier)[source]#
Bases:
objectMichaud’s resampled frontier: rank-averaged weights from resampled inputs.
- Parameters:
weights (ndarray)
sample_weights (ndarray)
returns (ndarray)
volatilities (ndarray)
frontier (FrontierResult)
- 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)
- 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:
- 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:
prices (
ArrayLike) – Risky asset paths, e.g. fromfinancialkit.integrators.gbm_paths().times (
ArrayLike)floor (
float) – Initial floor as a fraction ofw0.multiplier (
float) – Exposure per unit of cushion, \(m\).r (
float) – Riskless rate.max_leverage (
float) – Cap \(\ell\) on exposure as a multiple of wealth (1forbids borrowing).w0 (
float) – Initial wealth.
- Return type:
- 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:
- 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:
- Return type:
- 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:
- Returns:
float – At least one for long-only weights;
sqrt(d)forduncorrelated 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:
- Return type:
- 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 tomax(mu)long-only and to1.5 * max(mu)otherwise.
- Return type:
- 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:
- Return type:
- 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.
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:
- Return type:
- 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:
- Return type:
- 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.
Cluster the assets with
scipy.cluster.hierarchy.linkage()on the distance \(\sqrt{(1 - \rho_{ij})/2}\).Reorder them by the dendrogram’s leaves (quasi-diagonalization).
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:
- Return type:
- 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:
- Return type:
- 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:
- 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:
- 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:
- Return type:
- 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:
f (
ArrayLike) – Fraction staked, below1 / a.p (
ArrayLike) – As inkelly_fraction().b (
ArrayLike) – As inkelly_fraction().a (
ArrayLike) – As inkelly_fraction().
- Return type:
- 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:
- Return type:
- 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
fon repeated bets.- Parameters:
f (
float) – Fraction staked and the bet, as inkelly_growth_rate().p (
float) – Fraction staked and the bet, as inkelly_growth_rate().b (
float) – Fraction staked and the bet, as inkelly_growth_rate().a (
float) – Fraction staked and the bet, as inkelly_growth_rate().n_bets (
int)n_paths (
int)w0 (
float) – Initial wealth.
- Return type:
- 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:
- Return type:
- 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
piin 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:
- 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:
- 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:
- Return type:
- 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)
- Return type:
- 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:
- Return type:
- 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:
- 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
windowperiods and holding for one.- Parameters:
- Return type:
- 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:
- 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_resampleshistories ofn_obsnormal returns with the given moments, computes each history’s frontier atn_pointsequally 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:
- Return type:
- 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:
- 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:
- 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:
- Return type:
- 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:
returns (
ArrayLike) – As insharpe_ratio().rf (
float) – As insharpe_ratio().periods_per_year (
float) – As insharpe_ratio().
- Return type:
- 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
icmakingbreadthindependent 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\).
- 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:
- Returns:
CovarianceResult – With
betasset andtargetholding 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:
- Return type:
- 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:
- 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:
- Returns:
PortfolioResult – Risky weights (not summing to one);
expected_returnincludes 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)