Cost models

This module defines cost models for both simulation and optimization.

We implement two types of costs, as discussed in the paper: TransactionCost, defined in section 2.3, and HoldingCost, defined in section 2.4. We also provide versions of each that are specialized to the stock market, StocksTransactionCost and StocksHoldingCost: these have a default values that are typical for liquid stocks in the US.

The latter two are included by default in cvxportfolio.StockMarketSimulator, the market simulator specialized to the stock market, which is used throughout the examples. So, the example back-tests all include these costs, unless otherwise specified.

The cost objects have the same user interface when you use them as simulator costs or optimization costs. For example, to include an annualized borrowing cost of 10% (as opposed to the default 5%) in a back-test, you do

simulator = cvx.MarketSimulator(
    universe, costs=[cvx.HoldingCost(short_fees=10)])
backtest_result_with_borrow_fee = simulator.backtest(policy)

And to include the same borrow cost penalty in an optimization-based policy, you do

policy = cvx.SinglePeriodOptimization(
    objective = cvx.ReturnsForecast()
        - 0.5 * cvx.FullCovariance()
        - cvx.HoldingCost(short_fees=10),
    constraints = [cvx.LeverageLimit(3)])

As done throughout the library, you can pass data in the form of a Python scalar (like 10), a Pandas Series indexed by time, by the assets, or a Pandas DataFrame indexed by time and with the assets as columns; see the manual page on passing data.

Costs Documentation

class cvxportfolio.HoldingCost(short_fees=None, long_fees=None, dividends=None, periods_per_year=None)View on GitHub

Generic holding cost model.

This is a generalization of the model described in section 2.4 of the paper (which instead corresponds to StocksHoldingCost).

This represents the following objective term, expressed in terms of the post-trade dollar positions:

\[s^T_t {(h^+_t)}_- + l^T_t {(h^+_t)}_+ - d^T_t h^+_t\]

where \(s_t\) are the (short) borrowing fees, \(l_t\) are the fees on long positions, and \(d_t\) are dividend rates (their sign is flipped because the costs are deducted from the cash account at each period).

Parameters:
  • short_fees (float, pd.Series, pd.DataFrame or None) – Short borrowing fees expressed as annual percentage; you can provide them as a float (constant for all times and all assets), a pd.Series indexed by time (constant for all assets but varying in time) or by assets’ names (constant in time but varying across assets), or a pd.DataFrame indexed by time and whose columns are the assets’ names, if varying both in time and across assets. See the manual page on passing data. If None (the default) the term is ignored.

  • long_fees (float, pd.Series, pd.DataFrame or None) – Fees on long positions expressed as annual percentage; same convention as above applies.

  • dividends (float, pd.Series, pd.DataFrame or None) – Dividend rates per period. Same conventions as above. Our default data interface already includes the dividend rates in the market returns (i.e., uses total returns, from the adjusted prices). If, however, you provide your own market returns that do not include dividends, you may use this. Default None, which disables the term.

  • periods_per_year (float or None) – Number of trading period per year, used to obtain the holding cost per-period from the annualized percentages. Only relevant in optimization, since in simulation we know the exact duration of each period. If left to the default, None, uses the estimated average length of each period from the historical data. Note that, in simulation, the holding cost is applied to the actual length of the period between trading times, so for example it will be higher over a weekend than between weekdays.

class cvxportfolio.StocksHoldingCost(short_fees=5)View on GitHub

Holding cost specialized to stocks (only borrow fee).

This implements the simple model described in section 2.4 of the paper, refer to that for more details. The cost is, in terms of the post-trade dollar positions:

\[s^T_t {(h^+_t)}_-,\]

i.e., a simple borrow fee applies to the short positions. This class is a simplified version of HoldingCost: it drops the long_fees and dividends parameters and keeps only short_fees with a default value of 5, i.e., \(5\%\) annualized borrowing fee. That is a rough (optimistic) approximation of the cost of shorting liquid US stocks. This cost is included by default in StockMarketSimulator, the market simulator specialized to US (liquid) stocks.

Parameters:

short_fees (float, pd.Series, pd.DataFrame or None) – Same as in HoldingCost: annualized borrow fee in percent, can be asset- and/or period-specific. Default 5.

class cvxportfolio.TransactionCost(a=0.0, b=None, volume_hat=<class 'cvxportfolio.forecast.HistoricalMeanVolume'>, sigma=<class 'cvxportfolio.forecast.HistoricalStandardDeviation'>, exponent=1.5, c=None)View on GitHub

This is a generic model for transaction cost of financial assets.

Added in version 1.2.0: This was significantly improved with new options; it was undocumented before.

It is described in section 2.3 of the paper.

The model is, separated on a single asset (equation 2.2 in the paper)

\[a | x | + b \sigma \frac{{ | x |}^{3/2}}{V^{1/2}} + c x\]

where \(x\) is the dollar traded quantity, \(a\) is a coefficient representing fees proportional to the absolute value traded, like half the bid-ask spread, \(b\) is a coefficient that multiplies the market impact term, typically of the order of 1, \(\sigma\) is an estimate of the volatility of the asset returns over recent periods, \(V\) is the market volume traded over the period for the asset and \(c\) is a coefficient used to introduce bias in the model, for example the negative of open-to-close return (if transactions are executed at close), or the negative of the open-to-VWAP return (if transactions are executed at the volume-weighted average price).

In optimization the realized market volume \(V\) is not known and we use its forecast \(\hat V\) instead.

As done throughout the library, this implementation accepts either user-provided data for the various parts of the model, or uses built-in forecaster classes to do the heavy-lifting.

Parameters:
  • a (float, pd.Series, pd.DataFrame, or None) – Coefficients of the first term of the transaction cost model, for example half the bid-ask spread, brokerage fees proportional to the size of each trade, …. Usual conventions on passing data apply. Default None, which disables the term.

  • b (float, pd.Series, pd.DataFrame, or None) – Coefficients of the second term of the transaction cost model, typically of the order of 1. Same conventions. Default None, which disables the term and invalidates the following three parameters.

  • volume_hat (float, pd.Series, pd.DataFrame, cvx.forecast.BaseForecast class or instance) – Forecast of the market volumes, has only effect in optimization (in simulation the actual volumes are used). You can pass a DataFrame of externally computed forecasts, or use the default cvxportfolio.forecast.HistoricalMeanVolume (or another forecaster) to compute the forecasts at each point in time. If you pass a class, like the default, it is instantiated with parameter rolling equal to 1 year, if you prefer a different estimation lenght you can instantiate the forecaster and pass the instance.

  • sigma (float, pd.Series, pd.DataFrame, cvx.forecast.BaseForecast class or instance) – Externally computed forecasts, or forecaster, of the market returns’ volatilities \(\sigma\). The default is cvxportfolio.forecast.HistoricalStandardDeviation. If you pass a class, like the default, it is instantiated with parameter rolling equal to 1 year, if you prefer a different estimation lenght you can instantiate the forecaster and pass the instance.

  • exponent (float) – Exponent of the second term of the model, default \(3/2\). (In the model above, this exponent applies to \(|z|\), and this exponent minus 1 applies to the denominator term \(V\)). You can use any float larger than 1.

  • c (float, pd.Series, pd.DataFrame, or None) – Coefficients of the third term of the transaction cost model. If None, the default, the term is ignored.

class cvxportfolio.StocksTransactionCost(a=0.0, pershare_cost=0.005, b=1.0, volume_hat=<class 'cvxportfolio.forecast.HistoricalMeanVolume'>, sigma=<class 'cvxportfolio.forecast.HistoricalStandardDeviation'>, exponent=1.5, c=None, window_sigma_est=None, window_volume_est=None)View on GitHub

Simplified version of TransactionCost for stocks.

Added in version 1.2.0: We added the sigma and volume_hat parameters to support user-provided values as well as forecasters with various parameters. We deprecated the old way (window_sigma_est and window_volume_est) in which that was done before.

This is included as a simulator cost by default (with default arguments) in cvxportfolio.StockMarketSimulator.

Parameters:
  • a (float or pd.Series or pd.DataFrame) – Same as in TransactionCost, default 0.

  • pershare_cost (float or pd.Series or pd.DataFrame) – Per-share cost: cash paid for each share traded. Requires to know the prices of the stocks (they are present in the default market data server cvxportfolio.data.DownloadedMarketData). Default 0.005.

  • b (float or pd.Series or pd.DataFrame) – Same as in TransactionCost, default 1.

  • sigma (float, pd.Series, pd.DataFrame, cvx.forecast.BaseForecast class or instance) – Same as in TransactionCost.

  • window_sigma_est (int or None) – Deprecated, size of rolling window to estimate historical standard deviations. If left to None (the default) has no effect. Use instead the sigma parameter.

  • volume_hat (float, pd.Series, pd.DataFrame, cvx.forecast.BaseForecast class or instance) – Same as in TransactionCost.

  • window_volume_est (int or None) – Deprecated, size of rolling window to estimate the mean of past volumes. If left to None (the default) has no effect. Use instead the volume_hat parameter.

  • exponent (float) – Same as in TransactionCost.

  • c (float or pd.Series or pd.DataFrame or None) – Same as in TransactionCost.

class cvxportfolio.SoftConstraint(constraint)View on GitHub

Soft constraint cost.

This can be applied to most constraint objects, as discussed in its section of the constraints documentation.

Parameters:

constraint (cvxportfolio.constraints.EqualityConstraint or cvxportfolio.constraints.InequalityConstraint) – Cvxportfolio constraint instance whose violation we penalize.

class cvxportfolio.TcostModel(a=0.0, b=None, volume_hat=<class 'cvxportfolio.forecast.HistoricalMeanVolume'>, sigma=<class 'cvxportfolio.forecast.HistoricalStandardDeviation'>, exponent=1.5, c=None)View on GitHub

Alias of TransactionCost.

As it was defined originally in section 6.1 of the paper.

class cvxportfolio.HcostModel(short_fees=None, long_fees=None, dividends=None, periods_per_year=None)View on GitHub

Alias of HoldingCost.

As it was defined originally in section 6.1 of the paper.

Base classes (for defining your costs)

class cvxportfolio.costs.CostView on GitHub

Base class for cost objects (and also risks).

You should derive from this class to define an objective term for optimization-based policies, like a risk model.

The base class itself defines logic used for the algebraic operations, and to take inequalities of a cost object (which results in a Cvxportfolio constraint object).

class cvxportfolio.costs.SimulatorCostView on GitHub

Cost class that can be used by cvxportfolio.MarketSimulator.

This is the base class of both HoldingCost and TransactionCost.

This class derives from Cost and cvxportfolio.estimator.SimulatorEstimator. It implements the simulate() method (which is abstract in cvxportfolio.estimator.SimulatorEstimator). The implementation uses the CVXPY compiled expression of the (optimization) cost to evaluate the cost in simulation, so we’re sure the algebra is (exactly) the same. Of course the CVXPY expression operates on weights, so holdings and trades are divided by the portfolio value, and the result is multiplied by the portfolio value. If you implement a custom simulator’s cost and prefer to implement the cost expression directly you can derive straight from cvxportfolio.estimator.SimulatorEstimator. Look at the code of the cost classes we implement if in doubt. Every operation that is different in simulation and in optimization is wrapped in a cvxportfolio.estimator.SimulatorEstimator which implements different values_in_time() and simulate() respectively.

simulate(t, u, h_plus, past_volumes, past_returns, current_prices, current_weights, current_portfolio_value, **kwargs)View on GitHub

Simulate the cost in the market simulator (not optimization).

Cost classes that are meant to be used in the simulator can implement this. The arguments to this are the same as for cvxportfolio.estimator.Estimator.values_in_time() plus the trades vector and post-trade allocations.

Parameters:
  • t (pandas.Timestamp) – Current timestamp.

  • u (pandas.Series) – Trade vector in cash units requested by the policy. If the market simulator implements rounding by number of shares and/or canceling trades on assets whose volume for the period is zero, this is after those transformations.

  • h_plus (pandas.Series) – Post-trade holdings vector.

  • past_returns (pandas.DataFrame) – Past market returns (including cash).

  • past_volumes (pandas.DataFrame or None) – Past market volumes, or None if not available.

  • current_prices (pandas.Series or None) – Current (open) prices, or None if not available.

  • current_weights (pandas.Series) – Current allocation weights (before trading).

  • current_portfolio_value (float) – Current total value of the portfolio in cash units, before costs.

  • kwargs (dict) – Other unused arguments to cvxportfolio.estimator.SimulatorEstimator.simulate().

Returns:

Simulated realized value of the cost, in cash accounting units (e.g., US dollars). Typically a positive number: it is subtracted from the cash account.

Return type:

float