Market-Neutral Portfolio (Without Costs)¶
This is a simple example of a market (and dollar) neutral strategy.
We use a target volatility risk penalty, which is an alternative to the standard described in the paper (where risk penalization is applied in the objective function).
We this example for simplicity we don’t include transaction nor holding costs, neither in the simulation nor in the optimization, so its results may be unattainable in practice. See the market neutral example script for a version which does include transaction and holding costs.
Note
Running this may take some time. It is a single back-test and we use
a single-threaded solver, so it won’t occupy all system resources
like parallel back-tests, or automatic hyper-parameter optimization, can.
(You may still see some multi-threaded operations coming from CVXPY
compilation routines, or some vectorized Numpy operation we use for
estimating covariances.)
The first time you run this, the covariance matrices for each day are
estimated and factorized. They are then saved on disk (in the
~/cvxportfolio_data
folder). The second time you run it, for example
changing some constraint, it will be noticeably faster.
import numpy as np
import cvxportfolio as cvx
from .universes import DOW30, NDX100, SP500
# these are a little more than 500 names, all large cap US stocks
UNIVERSE = sorted(set(DOW30 + NDX100 + SP500))
target_volatility = 0.05 / np.sqrt(252) # annual std
# you can try different risk models (also combining some)
# the full covariance makes this example quite expensive to run
risk_model = cvx.FullCovariance()
constraints = [
risk_model <= target_volatility**2,
cvx.DollarNeutral(),
cvx.MarketNeutral(),
cvx.LeverageLimit(7),
cvx.MaxWeights(0.05),
cvx.MinWeights(-0.05),
]
policy = cvx.SinglePeriodOptimization(
cvx.ReturnsForecast(),
constraints = constraints,
# CVXPY's matrix caching can't be used here, its implementation is
# only designed to work with small problem instances
ignore_dpp=True,
solver='ECOS')
sim = cvx.MarketSimulator(UNIVERSE)
result = sim.backtest(policy, start_time='2000-01-01')
print('RESULT:')
print(result)
figure = result.plot()
This is the output printed to screen when executing this script.
Updating data.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
RESULT:
#################################################################
Universe size 518
Initial timestamp 2000-01-03 14:30:00+00:00
Final timestamp 2024-03-20 13:30:00+00:00
Number of periods 6092
Initial value (USDOLLAR) 1.000e+06
Final value (USDOLLAR) 2.454e+07
Profit (USDOLLAR) 2.354e+07
Avg. return (annualized) 15.1%
Volatility (annualized) 19.2%
Avg. excess return (annualized) 13.3%
Avg. active return (annualized) 13.3%
Excess volatility (annualized) 19.2%
Active volatility (annualized) 19.2%
Avg. growth rate (annualized) 13.2%
Avg. excess growth rate (annualized) 11.5%
Avg. active growth rate (annualized) 11.5%
Sharpe ratio 0.69
Information ratio 0.69
Avg. drawdown -7.6%
Min. drawdown -32.2%
Avg. leverage 700.0%
Max. leverage 791.1%
Avg. turnover 16.5%
Max. turnover 350.0%
Avg. policy time 1.503s
Avg. simulator time 0.320s
Of which: market data 0.013s
Total time 11108.088s
#################################################################
And this is the figure that is plotted.