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 apd.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 thelong_fees
anddividends
parameters and keeps onlyshort_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 inStockMarketSimulator
, 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 parameterrolling
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 parameterrolling
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
andvolume_hat
parameters to support user-provided values as well as forecasters with various parameters. We deprecated the old way (window_sigma_est
andwindow_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
andTransactionCost
.This class derives from
Cost
andcvxportfolio.estimator.SimulatorEstimator
. It implements thesimulate()
method (which is abstract incvxportfolio.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 fromcvxportfolio.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 acvxportfolio.estimator.SimulatorEstimator
which implements differentvalues_in_time()
andsimulate()
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