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.

examples/market_neutral_nocosts.py result figure

This figure is made by the cvxportfolio.result.BacktestResult.plot() method.