EfficientFrontier

class amplpyfinance.EfficientFrontier(expected_returns, cov_matrix, weight_bounds=(0, 1), tickers=None, solver='gurobi', solver_options='', verbose=False)[source]

An EfficientFrontier object contains multiple optimization methods that can be called (corresponding to different objective functions) with various parameters.

AMPL version of pypfopt.EfficientFrontier with similar interface. This class is also available under the alias amplpyfinance.EfficientFrontierWithAMPL in order to distinguish from pypfopt.EfficientFrontier if used together.

Instance variables:

  • Inputs:

    • n_assets - int

    • tickers - str list

    • bounds - float tuple OR (float tuple) list

    • cov_matrix - np.ndarray

    • expected_returns - np.ndarray

    • solver - str

    • solver_options - str

  • Output: weights - np.ndarray

Public methods:

  • min_volatility() optimizes for minimum volatility

  • max_sharpe() optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)

  • max_quadratic_utility() maximizes the quadratic utility, given some risk aversion.

  • efficient_risk() maximizes return for a given target risk

  • efficient_return() minimizes risk for a given target returns

  • portfolio_performance() calculates the expected return, volatility and Sharpe ratio for the optimized portfolio.

  • clean_weights() rounds the weights and clips near-zeros.

  • save_weights_to_file() saves the weights to csv, json, or txt.

__init__(expected_returns, cov_matrix, weight_bounds=(0, 1), tickers=None, solver='gurobi', solver_options='', verbose=False)[source]

Corresponding AMPL code:

set A ordered;
param S{A, A};
param mu{A} default 0;
param lb default 0;
param ub default 1;
var w{A} >= lb <= ub;
ampl.set["A"] = tickers
ampl.param["S"] = pd.DataFrame(
    cov_matrix, index=tickers, columns=tickers
).unstack(level=0)
ampl.param["mu"] = expected_returns
ampl.param["lb"] = weight_bounds[0]
ampl.param["ub"] = weight_bounds[1]
Parameters
  • expected_returns (pd.Series, list, np.ndarray) – expected returns for each asset. Can be None if optimizing for volatility only (but not recommended).

  • cov_matrix (pd.DataFrame or np.array) – covariance of returns for each asset. This must be positive semidefinite, otherwise optimization will fail.

  • weight_bounds (tuple OR tuple list, optional) – minimum and maximum weight of each asset OR single min/max pair if all identical, defaults to (0, 1). Must be changed to (-1, 1) for portfolios with shorting.

  • tickers (str list, optional) – asset labels.

  • solver (str) – name of the AMPL solver to use.

  • solver_options (str) – options for the given solver

  • verbose (bool, optional) – whether performance and debugging info should be printed, defaults to False

Raises
  • TypeError – if expected_returns is not a series, list or array

  • TypeError – if cov_matrix is not a dataframe or array

min_volatility()[source]

Minimize volatility.

Corresponding AMPL code:

set A ordered;
param S{A, A};

param lb default 0;
param ub default 1;
var w{A} >= lb <= ub;

minimize portfolio_variance:
    sum {i in A, j in A} w[i] * S[i, j] * w[j];
s.t. portfolio_weights:
    sum {i in A} w[i] = 1;
ampl.solve()

AMPL version of pypfopt.EfficientFrontier.min_volatility() with the same interface:

Returns

asset weights for the volatility-minimising portfolio

Return type

OrderedDict

efficient_risk(target_volatility, market_neutral=False)[source]

Maximize return for a target risk. The resulting portfolio will have a volatility less than the target (but not guaranteed to be equal).

Corresponding AMPL code:

param target_volatility;
param market_neutral default 0;

set A ordered;
param S{A, A};
param mu{A} default 0;

param lb default 0;
param ub default 1;
var w{A} >= lb <= ub;

maximize portfolio_return:
    sum {i in A} mu[i] * w[i];
s.t. portfolio_variance:
    sum {i in A, j in A} w[i] * S[i, j] * w[j] <= target_volatility^2;
s.t. portfolio_weights:
    sum {i in A} w[i] = if market_neutral then 0 else 1;
ampl.param["target_volatility"] = target_volatility
ampl.param["market_neutral"] = market_neutral
ampl.solve()

AMPL version of pypfopt.EfficientFrontier.efficient_risk() with the same interface:

Parameters
  • target_volatility (float) – the desired maximum volatility of the resulting portfolio.

  • market_neutral – whether the portfolio should be market neutral (weights sum to zero), defaults to False. Requires negative lower weight bound.

  • market_neutral – bool, optional

Raises
  • ValueError – if target_volatility is not a positive float

  • ValueError – if no portfolio can be found with volatility equal to target_volatility

  • ValueError – if risk_free_rate is non-numeric

Returns

asset weights for the efficient risk portfolio

Return type

OrderedDict

efficient_return(target_return, market_neutral=False)[source]

Calculate the ‘Markowitz portfolio’, minimising volatility for a given target return.

Corresponding AMPL code:

param target_return;
param market_neutral default 0;

set A ordered;
param S{A, A};
param mu{A} default 0;

param lb default 0;
param ub default 1;
var w{A} >= lb <= ub;

minimize portfolio_variance:
    sum {i in A, j in A} w[i] * S[i, j] * w[j];
s.t. portfolio__return:
    sum {i in A} mu[i] * w[i] >= target_return;
s.t. portfolio_weights:
    sum {i in A} w[i] = if market_neutral then 0 else 1;
ampl.param["target_return"] = target_return
ampl.param["market_neutral"] = market_neutral
ampl.solve()

AMPL version of pypfopt.EfficientFrontier.efficient_return() with the same interface:

Parameters
  • target_return (float) – the desired return of the resulting portfolio.

  • market_neutral (bool, optional) – whether the portfolio should be market neutral (weights sum to zero), defaults to False. Requires negative lower weight bound.

Raises
  • ValueError – if target_return is not a positive float

  • ValueError – if no portfolio can be found with return equal to target_return

Returns

asset weights for the Markowitz portfolio

Return type

OrderedDict

max_sharpe(risk_free_rate=0.02)[source]

Maximize the Sharpe Ratio. The result is also referred to as the tangency portfolio, as it is the portfolio for which the capital market line is tangent to the efficient frontier.

This is a convex optimization problem after making a certain variable substitution. See Cornuejols and Tutuncu (2006) for more.

Corresponding AMPL code:

param risk_free_rate default 0.02;

set A ordered;
param S{A, A};
param mu{A} default 0;

var k >= 0;
var z{i in A} >= 0;  # scaled weights
var w{i in A} = z[i] / k;

minimize portfolio_sharpe:
    sum {i in A, j in A} z[i] * S[i, j] * z[j];
s.t. muz:
    sum {i in A} (mu[i] - risk_free_rate) * z[i] = 1;
s.t. portfolio_weights:
    sum {i in A}  z[i] = k;
ampl.param["risk_free_rate"] = risk_free_rate
ampl.solve()

AMPL version of pypfopt.EfficientFrontier.max_sharpe() with the same interface:

Parameters

risk_free_rate (float, optional) – risk-free rate of borrowing/lending, defaults to 0.02. The period of the risk-free rate should correspond to the frequency of expected returns.

Raises

ValueError – if risk_free_rate is non-numeric

Returns

asset weights for the Sharpe-maximising portfolio

Return type

OrderedDict

max_quadratic_utility(risk_aversion=1, market_neutral=False)[source]

Maximize the given quadratic utility, i.e:

\[\max_w w^T \mu - \frac \delta 2 w^T \Sigma w\]

Corresponding AMPL code:

param risk_aversion default 1;
param market_neutral default 0;

set A ordered;
param S{A, A};
param mu{A} default 0;

param lb default 0;
param ub default 1;
var w{A} >= lb <= ub;

maximize quadratic_utility:
    sum {i in A} mu[i] * w[i]
    - 0.5 * risk_aversion * sum {i in A, j in A} w[i] * S[i, j] * w[j];
s.t. portfolio_weights:
    sum {i in A} w[i] = if market_neutral then 0 else 1;
ampl.param["risk_aversion"] = risk_aversion
ampl.param["market_neutral"] = market_neutral
ampl.solve()

AMPL version of pypfopt.EfficientFrontier.max_quadratic_utility() with the same interface:

Parameters
  • risk_aversion (positive float) – risk aversion parameter (must be greater than 0), defaults to 1

  • market_neutral – whether the portfolio should be market neutral (weights sum to zero), defaults to False. Requires negative lower weight bound.

  • market_neutral – bool, optional

Returns

asset weights for the maximum-utility portfolio

Return type

OrderedDict

add_sector_constraints(sector_mapper, sector_lower, sector_upper)[source]

Adds constraints on the sum of weights of different groups of assets. Most commonly, these will be sector constraints e.g portfolio’s exposure to tech must be less than x%:

sector_mapper = {
    "GOOG": "tech",
    "FB": "tech",,
    "XOM": "Oil/Gas",
    "RRC": "Oil/Gas",
    "MA": "Financials",
    "JPM": "Financials",
}

sector_lower = {"tech": 0.1}  # at least 10% to tech
sector_upper = {
    "tech": 0.4, # less than 40% tech
    "Oil/Gas": 0.1 # less than 10% oil and gas
}

Corresponding AMPL code:

param lb default 0;
param ub default 1;
var w{A} >= lb <= ub;

set SECTORS default {};
set SECTOR_MEMBERS{SECTORS};
param sector_lower{SECTORS} default -Infinity;
param sector_upper{SECTORS} default Infinity;

s.t. sector_constraints_lower{s in SECTORS: sector_lower[s] != -Infinity}:
    sum {i in SECTOR_MEMBERS[s]} w[i] >= sector_lower[s];
s.t. sector_constraints_upper{s in SECTORS: sector_upper[s] != Infinity}:
    sum {i in SECTOR_MEMBERS[s]} w[i] <= sector_upper[s];
sectors = set(sector_mapper.values())
ampl.set["SECTORS"] = sectors
for sector in sectors:
    ampl.set["SECTOR_MEMBERS"][sector] = [
        ticker for ticker, s in sector_mapper.items() if s == sector
    ]
ampl.param["sector_lower"] = sector_lower
ampl.param["sector_upper"] = sector_upper

AMPL version of pypfopt.EfficientFrontier.add_sector_constraints() with the same interface:

Parameters
  • sector_mapper ({str: str} dict) – dict that maps tickers to sectors

  • sector_lower ({str: float} dict) – lower bounds for each sector

  • sector_upper ({str:float} dict) – upper bounds for each sector

portfolio_performance(verbose=False, risk_free_rate=0.02)[source]

After optimising, calculate (and optionally print) the performance of the optimal portfolio. Currently calculates expected return, volatility, and the Sharpe ratio.

Parameters
  • verbose (bool, optional) – whether performance should be printed, defaults to False

  • risk_free_rate (float, optional) – risk-free rate of borrowing/lending, defaults to 0.02. The period of the risk-free rate should correspond to the frequency of expected returns.

Raises

ValueError – if weights have not been calcualted yet

Returns

expected return, volatility, Sharpe ratio.

Return type

(float, float, float)

__module__ = 'amplpyfinance.efficient_frontier.efficient_frontier'