Portfolio optimization

Portfolio allocation vector

In this example we show how to do portfolio optimization using CVXPY. We begin with the basic definitions. In portfolio optimization we have some amount of money to invest in any of $n$ different assets. We choose what fraction $w_i$ of our money to invest in each asset $i$, $i=1, \ldots, n$.

We call $w\in {\bf R}^n$ the portfolio allocation vector. We of course have the constraint that ${\mathbf 1}^T w =1$. The allocation $w_i<0$ means a short position in asset $i$, or that we borrow shares to sell now that we must replace later. The allocation $w \geq 0$ is a long only portfolio. The quantity $$ \|w \|_1 = {\mathbf 1}^T w_+ + {\mathbf 1}^T w_- $$ is known as leverage.

Asset returns

We will only model investments held for one period. The initial prices are $p_i > 0$. The end of period prices are $p_i^+ >0$. The asset (fractional) returns are $r_i = (p_i^+-p_i)/p_i$. The porfolio (fractional) return is $R = r^Tw$.

A common model is that $r$ is a random variable with mean ${\bf E}r = \mu$ and covariance ${\bf E{(r-\mu)(r-\mu)^T}} = \Sigma$. It follows that $R$ is a random variable with ${\bf E}R = \mu^T w$ and ${\bf var}(R) = w^T\Sigma w$. ${\bf E}R$ is the (mean) return of the portfolio. ${\bf var}(R)$ is the risk of the portfolio. (Risk is also sometimes given as ${\bf std}(R) = \sqrt{{\bf var}(R)}$.)

Portfolio optimization has two competing objectives: high return and low risk.

Classical (Markowitz) portfolio optimization

Classical (Markowitz) portfolio optimization solves the optimization problem

\begin{array}{ll} \mbox{maximize} & \mu^T w - \gamma w^T\Sigma w\\ \mbox{subject to} & {\bf 1}^T w = 1, \quad w \in {\cal W}, \end{array}

where $w \in {\bf R}^n$ is the optimization variable, $\cal W$ is a set of allowed portfolios (e.g., ${\cal W} = {\bf R}_+^n$ for a long only portfolio), and $\gamma >0$ is the risk aversion parameter.

The objective $\mu^Tw - \gamma w^T\Sigma w$ is the risk-adjusted return. Varying $\gamma$ gives the optimal risk-return trade-off. We can get the same risk-return trade-off by fixing return and minimizing risk.

Example

In the following code we compute and plot the optimal risk-return trade-off for $10$ assets, restricting ourselves to a long only portfolio.

In [1]:
# Generate data for long only portfolio optimization.
import numpy as np
import scipy.sparse as sp

np.random.seed(1)
n = 10
mu = np.abs(np.random.randn(n, 1))
Sigma = np.random.randn(n, n)
Sigma = Sigma.T.dot(Sigma)
In [2]:
# Long only portfolio optimization.
import cvxpy as cp


w = cp.Variable(n)
gamma = cp.Parameter(nonneg=True)
ret = mu.T @ w
risk = cp.quad_form(w, Sigma)
prob = cp.Problem(cp.Maximize(ret - gamma * risk), [cp.sum(w) == 1, w >= 0])
In [3]:
# Compute trade-off curve.
SAMPLES = 100
risk_data = np.zeros(SAMPLES)
ret_data = np.zeros(SAMPLES)
gamma_vals = np.logspace(-2, 3, num=SAMPLES)
for i in range(SAMPLES):
    gamma.value = gamma_vals[i]
    prob.solve()
    risk_data[i] = cp.sqrt(risk).value
    ret_data[i] = ret.value
In [4]:
# Plot long only trade-off curve.
import matplotlib.pyplot as plt

%matplotlib inline
%config InlineBackend.figure_format = 'svg'

markers_on = [29, 40]
fig = plt.figure()
ax = fig.add_subplot(111)
plt.plot(risk_data, ret_data, "g-")
for marker in markers_on:
    plt.plot(risk_data[marker], ret_data[marker], "bs")
    ax.annotate(
        r"$\gamma = %.2f$" % gamma_vals[marker],
        xy=(risk_data[marker] + 0.08, ret_data[marker] - 0.03),
    )
for i in range(n):
    plt.plot(cp.sqrt(Sigma[i, i]).value, mu[i], "ro")
plt.xlabel("Standard deviation")
plt.ylabel("Return")
plt.show()
2022-06-04T10:33:09.059339 image/svg+xml Matplotlib v3.5.2, https://matplotlib.org/

We plot below the return distributions for the two risk aversion values marked on the trade-off curve. Notice that the probability of a loss is near 0 for the low risk value and far above 0 for the high risk value.

In [5]:
# Plot return distributions for two points on the trade-off curve.
import scipy.stats as spstats


plt.figure()
for midx, idx in enumerate(markers_on):
    gamma.value = gamma_vals[idx]
    prob.solve()
    x = np.linspace(-2, 5, 1000)
    plt.plot(
        x,
        spstats.norm.pdf(x, ret.value, risk.value),
        label=r"$\gamma = %.2f$" % gamma.value,
    )

plt.xlabel("Return")
plt.ylabel("Density")
plt.legend(loc="upper right")
plt.show()
2022-06-04T10:33:09.450579 image/svg+xml Matplotlib v3.5.2, https://matplotlib.org/

Portfolio constraints

There are many other possible portfolio constraints besides the long only constraint. With no constraint (${\cal W} = {\bf R}^n$), the optimization problem has a simple analytical solution. We will look in detail at a leverage limit, or the constraint that $\|w \|_1 \leq L^\mathrm{max}$.

Another interesting constraint is the market neutral constraint $m^T \Sigma w =0$, where $m_i$ is the capitalization of asset $i$. $M = m^Tr$ is the market return, and $m^T \Sigma w = {\bf cov}(M,R)$. The market neutral constraint ensures that the portfolio return is uncorrelated with the market return.

Example

In the following code we compute and plot optimal risk-return trade-off curves for leverage limits of 1, 2, and 4. Notice that more leverage increases returns and allows greater risk.

In [6]:
# Portfolio optimization with leverage limit.
Lmax = cp.Parameter()
prob = cp.Problem(
    cp.Maximize(ret - gamma * risk), [cp.sum(w) == 1, cp.norm(w, 1) <= Lmax]
)
In [7]:
# Compute trade-off curve for each leverage limit.
L_vals = [1, 2, 4]
SAMPLES = 100
risk_data = np.zeros((len(L_vals), SAMPLES))
ret_data = np.zeros((len(L_vals), SAMPLES))
gamma_vals = np.logspace(-2, 3, num=SAMPLES)
w_vals = []
for k, L_val in enumerate(L_vals):
    for i in range(SAMPLES):
        Lmax.value = L_val
        gamma.value = gamma_vals[i]
        prob.solve(solver=cp.SCS)
        risk_data[k, i] = cp.sqrt(risk).value
        ret_data[k, i] = ret.value
In [8]:
# Plot trade-off curves for each leverage limit.
for idx, L_val in enumerate(L_vals):
    plt.plot(risk_data[idx, :], ret_data[idx, :], label=r"$L^{\max}$ = %d" % L_val)
for w_val in w_vals:
    w.value = w_val
    plt.plot(cp.sqrt(risk).value, ret.value, "bs")
plt.xlabel("Standard deviation")
plt.ylabel("Return")
plt.legend(loc="lower right")
plt.show()
2022-06-04T10:33:10.581595 image/svg+xml Matplotlib v3.5.2, https://matplotlib.org/

We next examine the points on each trade-off curve where $w^T\Sigma w = 2$. We plot the amount of each asset held in each portfolio as bar graphs. (Negative holdings indicate a short position.) Notice that some assets are held in a long position for the low leverage portfolio but in a short position in the higher leverage portfolios.

In [9]:
# Portfolio optimization with a leverage limit and a bound on risk.
prob = cp.Problem(cp.Maximize(ret), [cp.sum(w) == 1, cp.norm(w, 1) <= Lmax, risk <= 2])
In [10]:
# Compute solution for different leverage limits.
for k, L_val in enumerate(L_vals):
    Lmax.value = L_val
    prob.solve()
    w_vals.append(w.value)
In [11]:
# Plot bar graph of holdings for different leverage limits.
colors = ["b", "g", "r"]
indices = np.argsort(mu.flatten())
for idx, L_val in enumerate(L_vals):
    plt.bar(
        np.arange(1, n + 1) + 0.25 * idx - 0.375,
        w_vals[idx][indices],
        color=colors[idx],
        label=r"$L^{\max}$ = %d" % L_val,
        width=0.25,
    )
plt.ylabel(r"$w_i$", fontsize=16)
plt.xlabel(r"$i$", fontsize=16)
plt.xlim([1 - 0.375, 10 + 0.375])
plt.xticks(np.arange(1, n + 1))
plt.show()
2022-06-04T10:33:10.858156 image/svg+xml Matplotlib v3.5.2, https://matplotlib.org/

Variations

There are many more variations of classical portfolio optimization. We might require that $\mu^T w \geq R^\mathrm{min}$ and minimize $w^T \Sigma w$ or $\|\Sigma ^{1/2} w\|_2$. We could include the (broker) cost of short positions as the penalty $s^T (w)_-$ for some $s \geq 0$. We could include transaction costs (from a previous portfolio $w^\mathrm{prev}$) as the penalty

$$ \kappa ^T |w-w^\mathrm{prev}|^\eta, \quad \kappa \geq 0. $$

Common values of $\eta$ are $\eta =1, ~ 3/2, ~2$.

Factor covariance model

A particularly common and useful variation is to model the covariance matrix $\Sigma$ as a factor model

$$ \Sigma = F \tilde \Sigma F^T + D, $$

where $F \in {\bf R}^{n \times k}$, $k \ll n$ is the factor loading matrix. $k$ is the number of factors (or sectors) (typically 10s). $F_{ij}$ is the loading of asset $i$ to factor $j$. $D$ is a diagonal matrix; $D_{ii}>0$ is the idiosyncratic risk. $\tilde \Sigma > 0$ is the factor covariance matrix.

$F^Tw \in {\bf R}^k$ gives the portfolio factor exposures. A portfolio is factor $j$ neutral if $(F^Tw)_j=0$.

Portfolio optimization with factor covariance model

Using the factor covariance model, we frame the portfolio optimization problem as

\begin{array}{ll} \mbox{maximize} & \mu^T w - \gamma \left(f^T \tilde \Sigma f + w^TDw \right) \\ \mbox{subject to} & {\bf 1}^T w = 1, \quad f=F^Tw\\ & w \in {\cal W}, \quad f \in {\cal F}, \end{array}

where the variables are the allocations $w \in {\bf R}^n$ and factor exposures $f\in {\bf R}^k$ and $\cal F$ gives the factor exposure constraints.

Using the factor covariance model in the optimization problem has a computational advantage. The solve time is $O(nk^2)$ versus $O(n^3)$ for the standard problem.

Example

In the following code we generate and solve a portfolio optimization problem with 50 factors and 3000 assets. We set the leverage limit $=2$ and $\gamma=0.1$.

We solve the problem both with the covariance given as a single matrix and as a factor model. Using CVXPY with the OSQP solver running in a single thread, the solve time was 173.30 seconds for the single matrix formulation and 0.85 seconds for the factor model formulation. We collected the timings on a MacBook Air with an Intel Core i7 processor.

In [12]:
# Generate data for factor model.
n = 3000
m = 50
np.random.seed(1)
mu = np.abs(np.random.randn(n, 1))
Sigma_tilde = np.random.randn(m, m)
Sigma_tilde = Sigma_tilde.T.dot(Sigma_tilde)
D = sp.diags(np.random.uniform(0, 0.9, size=n))
F = np.random.randn(n, m)
In [13]:
# Factor model portfolio optimization.
w = cp.Variable(n)
f = cp.Variable(m)
gamma = cp.Parameter(nonneg=True)
Lmax = cp.Parameter()
ret = mu.T @ w
risk = cp.quad_form(f, Sigma_tilde) + cp.sum_squares(np.sqrt(D) @ w)
prob_factor = cp.Problem(
    cp.Maximize(ret - gamma * risk),
    [cp.sum(w) == 1, f == F.T @ w, cp.norm(w, 1) <= Lmax],
)

# Solve the factor model problem.
Lmax.value = 2
gamma.value = 0.1
prob_factor.solve(verbose=True)
===============================================================================
                                     CVXPY                                     
                                     v1.2.1                                    
===============================================================================
(CVXPY) Jun 04 10:33:10 AM: Your problem has 3050 variables, 3 constraints, and 2 parameters.
(CVXPY) Jun 04 10:33:10 AM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Jun 04 10:33:10 AM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
-------------------------------------------------------------------------------
                                  Compilation                                  
-------------------------------------------------------------------------------
(CVXPY) Jun 04 10:33:10 AM: Compiling problem (target solver=OSQP).
(CVXPY) Jun 04 10:33:11 AM: Reduction chain: FlipObjective -> CvxAttr2Constr -> Qp2SymbolicQp -> QpMatrixStuffing -> OSQP
(CVXPY) Jun 04 10:33:11 AM: Applying reduction FlipObjective
(CVXPY) Jun 04 10:33:11 AM: Applying reduction CvxAttr2Constr
(CVXPY) Jun 04 10:33:11 AM: Applying reduction Qp2SymbolicQp
(CVXPY) Jun 04 10:33:11 AM: Applying reduction QpMatrixStuffing
(CVXPY) Jun 04 10:33:11 AM: Applying reduction OSQP
(CVXPY) Jun 04 10:33:11 AM: Finished problem compilation (took 6.259e-02 seconds).
(CVXPY) Jun 04 10:33:11 AM: (Subsequent compilations of this problem, using the same arguments, should take less time.)
-------------------------------------------------------------------------------
                                Numerical solver                               
-------------------------------------------------------------------------------
(CVXPY) Jun 04 10:33:11 AM: Invoking solver OSQP  to obtain a solution.
-----------------------------------------------------------------
           OSQP v0.6.2  -  Operator Splitting QP Solver
              (c) Bartolomeo Stellato,  Goran Banjac
        University of Oxford  -  Stanford University 2021
-----------------------------------------------------------------
problem:  variables n = 9050, constraints m = 9052
          nnz(P) + nnz(A) = 178325
settings: linear system solver = qdldl,
          eps_abs = 1.0e-05, eps_rel = 1.0e-05,
          eps_prim_inf = 1.0e-04, eps_dual_inf = 1.0e-04,
          rho = 1.00e-01 (adaptive),
          sigma = 1.00e-06, alpha = 1.60, max_iter = 10000
          check_termination: on (interval 25),
          scaling: on, scaled_termination: off
          warm start: on, polish: on, time_limit: off

iter   objective    pri res    dua res    rho        time
   1  -2.1363e+03   7.63e+00   3.73e+02   1.00e-01   7.06e-02s
 200  -4.2091e+00   2.19e-03   7.84e-03   3.56e-01   4.62e-01s
 400  -4.6228e+00   3.07e-04   5.76e-04   3.56e-01   7.46e-01s
 600  -4.6427e+00   2.23e-04   8.76e-04   3.56e-01   1.00e+00s
 800  -4.6215e+00   1.14e-04   4.97e-04   3.56e-01   1.30e+00s
1000  -4.6214e+00   8.78e-05   1.06e-04   3.56e-01   1.54e+00s
1200  -4.6204e+00   8.58e-05   9.26e-06   3.56e-01   1.81e+00s
1400  -4.6138e+00   6.70e-05   2.37e-04   3.56e-01   2.14e+00s
1600  -4.6067e+00   2.86e-05   1.19e-04   3.56e-01   2.43e+00s
1675  -4.6051e+00   1.91e-05   3.72e-05   3.56e-01   2.52e+00s

status:               solved
solution polish:      unsuccessful
number of iterations: 1675
optimal objective:    -4.6051
run time:             2.59e+00s
optimal rho estimate: 3.20e-01

-------------------------------------------------------------------------------
                                    Summary                                    
-------------------------------------------------------------------------------
(CVXPY) Jun 04 10:33:13 AM: Problem status: optimal
(CVXPY) Jun 04 10:33:13 AM: Optimal value: 4.605e+00
(CVXPY) Jun 04 10:33:13 AM: Compilation took 6.259e-02 seconds
(CVXPY) Jun 04 10:33:13 AM: Solver (including time spent in interface) took 2.592e+00 seconds
Out[13]:
4.605102865930337
In [ ]:
from cvxpy.atoms.affine.wraps import psd_wrap

# Standard portfolio optimization with data from factor model.
risk = cp.quad_form(w, psd_wrap(F.dot(Sigma_tilde).dot(F.T) + D))
prob = cp.Problem(
    cp.Maximize(ret - gamma * risk), [cp.sum(w) == 1, cp.norm(w, 1) <= Lmax]
)

# Uncomment to solve the problem.
# WARNING: this will take many minutes to run.
prob.solve(verbose=True, max_iter=100_000)
===============================================================================
                                     CVXPY                                     
                                     v1.2.1                                    
===============================================================================
(CVXPY) Jun 04 10:33:13 AM: Your problem has 3000 variables, 2 constraints, and 2 parameters.
(CVXPY) Jun 04 10:33:13 AM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Jun 04 10:33:13 AM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
-------------------------------------------------------------------------------
                                  Compilation                                  
-------------------------------------------------------------------------------
(CVXPY) Jun 04 10:33:13 AM: Compiling problem (target solver=OSQP).
(CVXPY) Jun 04 10:33:13 AM: Reduction chain: FlipObjective -> CvxAttr2Constr -> Qp2SymbolicQp -> QpMatrixStuffing -> OSQP
(CVXPY) Jun 04 10:33:13 AM: Applying reduction FlipObjective
(CVXPY) Jun 04 10:33:13 AM: Applying reduction CvxAttr2Constr
(CVXPY) Jun 04 10:33:13 AM: Applying reduction Qp2SymbolicQp
(CVXPY) Jun 04 10:33:13 AM: Applying reduction QpMatrixStuffing
(CVXPY) Jun 04 10:33:17 AM: Applying reduction OSQP
(CVXPY) Jun 04 10:33:17 AM: Finished problem compilation (took 3.845e+00 seconds).
(CVXPY) Jun 04 10:33:17 AM: (Subsequent compilations of this problem, using the same arguments, should take less time.)
-------------------------------------------------------------------------------
                                Numerical solver                               
-------------------------------------------------------------------------------
(CVXPY) Jun 04 10:33:17 AM: Invoking solver OSQP  to obtain a solution.
-----------------------------------------------------------------
           OSQP v0.6.2  -  Operator Splitting QP Solver
              (c) Bartolomeo Stellato,  Goran Banjac
        University of Oxford  -  Stanford University 2021
-----------------------------------------------------------------
problem:  variables n = 6000, constraints m = 6002
          nnz(P) + nnz(A) = 4519500
settings: linear system solver = qdldl,
          eps_abs = 1.0e-05, eps_rel = 1.0e-05,
          eps_prim_inf = 1.0e-04, eps_dual_inf = 1.0e-04,
          rho = 1.00e-01 (adaptive),
          sigma = 1.00e-06, alpha = 1.60, max_iter = 100000
          check_termination: on (interval 25),
          scaling: on, scaled_termination: off
          warm start: on, polish: on, time_limit: off

iter   objective    pri res    dua res    rho        time
   1  -1.1774e+04   2.65e+02   1.51e+04   1.00e-01   5.67e+00s
 200  -4.1080e+02   2.42e-01   8.86e-04   1.00e-01   7.92e+00s
 400  -1.9413e+02   1.13e-01   2.51e-04   1.00e-01   1.05e+01s
 600  -1.2345e+02   6.40e-02   1.09e-04   1.00e-01   1.28e+01s
 800  -8.7560e+01   4.67e-02   5.29e-05   1.00e-01   1.51e+01s
1000  -6.5202e+01   3.49e-02   2.99e-05   1.00e-01   1.73e+01s
1200  -5.0118e+01   2.68e-02   1.91e-05   1.00e-01   1.96e+01s
1400  -3.9737e+01   2.09e-02   1.41e-05   1.00e-01   2.18e+01s
1600  -3.2445e+01   1.72e-02   1.06e-05   1.00e-01   2.41e+01s
1800  -2.6947e+01   1.42e-02   8.27e-06   1.00e-01   2.63e+01s
2000  -2.2700e+01   1.17e-02   6.57e-06   1.00e-01   2.93e+01s
2200  -1.9294e+01   9.74e-03   5.29e-06   1.00e-01   3.15e+01s
2400  -1.6616e+01   8.26e-03   4.32e-06   1.00e-01   3.38e+01s
2600  -1.4460e+01   7.01e-03   3.56e-06   1.00e-01   3.60e+01s
2800  -1.2704e+01   5.95e-03   2.93e-06   1.00e-01   3.83e+01s
3000  -1.1267e+01   5.06e-03   2.43e-06   1.00e-01   4.05e+01s
3200  -1.0092e+01   4.25e-03   2.00e-06   1.00e-01   4.27e+01s
3400  -9.1244e+00   3.58e-03   1.66e-06   1.00e-01   4.53e+01s
3600  -8.3286e+00   3.04e-03   1.38e-06   1.00e-01   4.75e+01s
3800  -7.6760e+00   2.60e-03   1.14e-06   1.00e-01   4.97e+01s
4000  -7.1409e+00   2.26e-03   9.40e-07   1.00e-01   5.20e+01s
4200  -6.7000e+00   2.04e-03   7.81e-07   1.00e-01   5.42e+01s
4400  -6.3366e+00   1.85e-03   6.50e-07   1.00e-01   5.64e+01s
4600  -6.0382e+00   1.69e-03   5.41e-07   1.00e-01   5.87e+01s
4800  -5.7969e+00   1.58e-03   4.54e-07   1.00e-01   6.10e+01s
5000  -5.5953e+00   1.46e-03   3.83e-07   1.00e-01   6.34e+01s
5200  -5.4277e+00   1.37e-03   3.24e-07   1.00e-01   6.58e+01s
5400  -5.2885e+00   1.28e-03   2.73e-07   1.00e-01   6.81e+01s
5600  -5.1729e+00   1.20e-03   2.30e-07   1.00e-01   7.03e+01s
5800  -5.0768e+00   1.13e-03   1.94e-07   1.00e-01   7.26e+01s
6000  -4.9968e+00   1.08e-03   1.63e-07   1.00e-01   7.49e+01s
6200  -4.9301e+00   1.02e-03   1.37e-07   1.00e-01   7.71e+01s
6400  -4.8746e+00   9.80e-04   1.18e-07   1.00e-01   7.98e+01s
6600  -4.8281e+00   9.40e-04   1.09e-07   1.00e-01   8.20e+01s
6800  -4.7893e+00   9.04e-04   1.01e-07   1.00e-01   8.43e+01s
7000  -4.7568e+00   8.72e-04   9.40e-08   1.00e-01   8.66e+01s
7200  -4.7295e+00   8.44e-04   8.75e-08   1.00e-01   8.88e+01s
7400  -4.7372e+00   8.63e-04   2.54e-07   1.00e-01   9.11e+01s
7600  -4.7339e+00   8.57e-04   1.41e-07   1.00e-01   9.33e+01s
7800  -4.7278e+00   8.25e-04   8.93e-08   1.00e-01   9.58e+01s
8000  -4.7195e+00   7.99e-04   5.47e-08   1.00e-01   9.82e+01s
8200  -4.7100e+00   7.75e-04   4.25e-08   1.00e-01   1.00e+02s
8400  -4.7002e+00   7.59e-04   3.67e-08   1.00e-01   1.03e+02s
8600  -4.6909e+00   7.51e-04   3.23e-08   1.00e-01   1.05e+02s
8800  -4.6824e+00   7.42e-04   3.05e-08   1.00e-01   1.07e+02s
9000  -4.6749e+00   7.35e-04   2.86e-08   1.00e-01   1.09e+02s
9200  -4.6684e+00   7.27e-04   2.66e-08   1.00e-01   1.12e+02s
9400  -4.6627e+00   7.21e-04   2.47e-08   1.00e-01   1.14e+02s
9600  -4.6577e+00   7.17e-04   2.29e-08   1.00e-01   1.16e+02s
9800  -4.6534e+00   7.13e-04   2.15e-08   1.00e-01   1.19e+02s
10000  -4.6496e+00   7.10e-04   2.03e-08   1.00e-01   1.21e+02s
10200  -4.6463e+00   7.06e-04   1.91e-08   1.00e-01   1.23e+02s
10400  -4.6434e+00   7.03e-04   1.81e-08   1.00e-01   1.25e+02s
10600  -4.6335e+00   6.88e-04   3.27e-07   5.04e-01   1.33e+02s
10800  -4.6280e+00   6.75e-04   2.51e-07   5.04e-01   1.35e+02s
11000  -4.6248e+00   6.64e-04   1.95e-07   5.04e-01   1.37e+02s
11200  -4.6228e+00   6.55e-04   1.54e-07   5.04e-01   1.39e+02s
11400  -4.6218e+00   6.45e-04   8.79e-08   5.04e-01   1.41e+02s
11600  -4.6207e+00   6.44e-04   9.50e-08   5.04e-01   1.44e+02s
11800  -4.6198e+00   6.43e-04   9.48e-08   5.04e-01   1.46e+02s
12000  -4.6190e+00   6.42e-04   9.17e-08   5.04e-01   1.49e+02s
12200  -4.6182e+00   6.41e-04   8.76e-08   5.04e-01   1.51e+02s
12400  -4.6175e+00   6.39e-04   8.33e-08   5.04e-01   1.53e+02s
12600  -4.6175e+00   6.20e-04   3.63e-07   5.04e-01   1.55e+02s
12800  -4.6158e+00   6.17e-04   2.08e-07   5.04e-01   1.57e+02s
13000  -4.6153e+00   6.13e-04   1.47e-07   5.04e-01   1.60e+02s
13200  -4.6148e+00   6.10e-04   1.09e-07   5.04e-01   1.62e+02s
13400  -4.6454e+00   5.54e-04   2.45e-06   5.04e-01   1.64e+02s
13600  -4.6464e+00   5.27e-04   7.63e-07   5.04e-01   1.67e+02s
13800  -4.6382e+00   5.07e-04   5.38e-07   5.04e-01   1.69e+02s
14000  -4.6332e+00   4.89e-04   4.15e-07   5.04e-01   1.71e+02s
14200  -4.6304e+00   4.65e-04   3.03e-07   5.04e-01   1.73e+02s
14400  -4.6286e+00   4.52e-04   2.31e-07   5.04e-01   1.76e+02s
14600  -4.6274e+00   4.41e-04   1.90e-07   5.04e-01   1.78e+02s
14800  -4.6263e+00   4.36e-04   1.57e-07   5.04e-01   1.80e+02s
15000  -4.6254e+00   4.31e-04   1.31e-07   5.04e-01   1.83e+02s
15200  -4.6247e+00   4.27e-04   1.10e-07   5.04e-01   1.85e+02s
15400  -4.6240e+00   4.24e-04   9.37e-08   5.04e-01   1.87e+02s
15600  -4.6234e+00   4.22e-04   8.00e-08   5.04e-01   1.89e+02s
15800  -4.6229e+00   4.21e-04   6.87e-08   5.04e-01   1.92e+02s
16000  -4.6224e+00   4.21e-04   5.93e-08   5.04e-01   1.94e+02s
16200  -4.6220e+00   4.21e-04   5.14e-08   5.04e-01   1.96e+02s
16400  -4.6217e+00   4.21e-04   4.48e-08   5.04e-01   1.99e+02s
16600  -4.6213e+00   4.20e-04   3.92e-08   5.04e-01   2.01e+02s
16800  -4.6210e+00   4.20e-04   3.44e-08   5.04e-01   2.03e+02s
17000  -4.6208e+00   4.20e-04   2.84e-08   5.04e-01   2.05e+02s
17200  -4.6207e+00   4.19e-04   2.37e-08   5.04e-01   2.08e+02s
17400  -4.6205e+00   4.18e-04   2.00e-08   5.04e-01   2.10e+02s
17600  -4.6203e+00   4.18e-04   1.82e-08   5.04e-01   2.12e+02s
17800  -4.6202e+00   4.17e-04   1.72e-08   5.04e-01   2.15e+02s
18000  -4.6200e+00   4.16e-04   1.64e-08   5.04e-01   2.17e+02s
18200  -4.6256e+00   4.14e-04   9.55e-07   5.04e-01   2.19e+02s
18400  -4.6227e+00   4.15e-04   3.35e-07   5.04e-01   2.21e+02s
18600  -4.6224e+00   4.16e-04   1.95e-07   5.04e-01   2.24e+02s
18800  -4.6226e+00   4.16e-04   1.27e-07   5.04e-01   2.26e+02s
19000  -4.6229e+00   4.15e-04   8.84e-08   5.04e-01   2.28e+02s
19200  -4.6231e+00   4.15e-04   6.51e-08   5.04e-01   2.31e+02s
19400  -4.6233e+00   4.14e-04   5.14e-08   5.04e-01   2.33e+02s
19600  -4.6235e+00   4.14e-04   4.14e-08   5.04e-01   2.35e+02s
19800  -4.6236e+00   4.14e-04   3.39e-08   5.04e-01   2.37e+02s
20000  -4.6236e+00   4.13e-04   2.83e-08   5.04e-01   2.40e+02s
20200  -4.6237e+00   4.13e-04   2.40e-08   5.04e-01   2.42e+02s
20400  -4.6279e+00   4.19e-04   1.10e-06   5.04e-01   2.44e+02s
20600  -4.6328e+00   4.14e-04   4.43e-07   5.04e-01   2.47e+02s
20800  -4.6348e+00   4.09e-04   3.19e-07   5.04e-01   2.49e+02s
21000  -4.6360e+00   4.06e-04   2.50e-07   5.04e-01   2.51e+02s
21200  -4.6368e+00   4.03e-04   2.00e-07   5.04e-01   2.53e+02s
21400  -4.6375e+00   4.00e-04   1.62e-07   5.04e-01   2.56e+02s
21600  -4.6380e+00   3.99e-04   1.40e-07   5.04e-01   2.58e+02s
21800  -4.6386e+00   3.98e-04   1.18e-07   5.04e-01   2.60e+02s
22000  -4.6392e+00   3.98e-04   1.00e-07   5.04e-01   2.62e+02s
22200  -4.6396e+00   3.97e-04   9.12e-08   5.04e-01   2.65e+02s
22400  -4.6402e+00   3.88e-04   1.72e-06   5.04e-01   2.67e+02s
22600  -4.6481e+00   3.69e-04   6.62e-07   5.04e-01   2.69e+02s
22800  -4.6513e+00   3.68e-04   3.75e-07   5.04e-01   2.72e+02s
23000  -4.6578e+00   3.65e-04   1.26e-06   5.04e-01   2.74e+02s
23200  -4.6606e+00   3.71e-04   3.81e-07   5.04e-01   2.76e+02s
23400  -4.6574e+00   3.69e-04   1.74e-07   5.04e-01   2.78e+02s
23600  -4.6563e+00   3.55e-04   3.46e-07   5.04e-01   2.81e+02s
23800  -4.6543e+00   3.48e-04   1.62e-07   5.04e-01   2.83e+02s
24000  -4.6534e+00   3.46e-04   9.43e-08   5.04e-01   2.85e+02s
24200  -4.6530e+00   3.45e-04   7.96e-08   5.04e-01   2.87e+02s
24400  -4.6575e+00   3.44e-04   6.80e-07   5.04e-01   2.90e+02s
24600  -4.6604e+00   3.40e-04   4.12e-07   5.04e-01   2.92e+02s
24800  -4.6596e+00   3.36e-04   2.92e-07   5.04e-01   2.94e+02s
25000  -4.6582e+00   3.30e-04   2.22e-07   5.04e-01   2.97e+02s
25200  -4.6570e+00   3.26e-04   1.74e-07   5.04e-01   2.99e+02s
25400  -4.6560e+00   3.21e-04   1.40e-07   5.04e-01   3.01e+02s
25600  -4.6552e+00   3.18e-04   1.21e-07   5.04e-01   3.03e+02s
25800  -4.6545e+00   3.15e-04   1.09e-07   5.04e-01   3.07e+02s
26000  -4.6540e+00   3.12e-04   9.83e-08   5.04e-01   3.10e+02s
26200  -4.6536e+00   3.10e-04   8.83e-08   5.04e-01   3.13e+02s
26400  -4.6644e+00   2.98e-04   8.13e-07   5.04e-01   3.15e+02s
26600  -4.6618e+00   2.91e-04   3.47e-07   5.04e-01   3.18e+02s
26800  -4.6579e+00   2.86e-04   2.42e-07   5.04e-01   3.20e+02s
27000  -4.6555e+00   2.84e-04   2.09e-07   5.04e-01   3.22e+02s
27200  -4.6539e+00   2.85e-04   1.89e-07   5.04e-01   3.25e+02s
27400  -4.6529e+00   2.85e-04   1.70e-07   5.04e-01   3.28e+02s
27600  -4.6522e+00   2.85e-04   1.53e-07   5.04e-01   3.31e+02s
27800  -4.6517e+00   2.84e-04   1.38e-07   5.04e-01   3.33e+02s
28000  -4.6513e+00   2.84e-04   1.24e-07   5.04e-01   3.36e+02s
28200  -4.6510e+00   2.84e-04   1.12e-07   5.04e-01   3.39e+02s
28400  -4.6507e+00   2.83e-04   1.01e-07   5.04e-01   3.41e+02s
28600  -4.6504e+00   2.82e-04   9.15e-08   5.04e-01   3.44e+02s
28800  -4.6502e+00   2.82e-04   8.31e-08   5.04e-01   3.46e+02s
29000  -4.6499e+00   2.81e-04   7.57e-08   5.04e-01   3.48e+02s
29200  -4.6497e+00   2.80e-04   6.92e-08   5.04e-01   3.51e+02s
29400  -4.6495e+00   2.80e-04   6.34e-08   5.04e-01   3.54e+02s
29600  -4.6493e+00   2.79e-04   5.82e-08   5.04e-01   3.56e+02s
29800  -4.6491e+00   2.79e-04   5.35e-08   5.04e-01   3.58e+02s
30000  -4.6489e+00   2.78e-04   4.94e-08   5.04e-01   3.61e+02s
30200  -4.6487e+00   2.77e-04   4.57e-08   5.04e-01   3.64e+02s
30400  -4.6485e+00   2.77e-04   4.23e-08   5.04e-01   3.68e+02s
30600  -4.6483e+00   2.76e-04   3.93e-08   5.04e-01   3.70e+02s
30800  -4.6487e+00   2.74e-04   5.85e-07   5.04e-01   3.72e+02s
31000  -4.6476e+00   2.68e-04   3.31e-07   5.04e-01   3.75e+02s
31200  -4.6467e+00   2.63e-04   2.20e-07   5.04e-01   3.77e+02s
31400  -4.6460e+00   2.60e-04   1.63e-07   5.04e-01   3.79e+02s
31600  -4.6454e+00   2.57e-04   1.29e-07   5.04e-01   3.81e+02s
31800  -4.6449e+00   2.57e-04   1.09e-07   5.04e-01   3.84e+02s
32000  -4.6444e+00   2.56e-04   1.03e-07   5.04e-01   3.86e+02s
32200  -4.6441e+00   2.56e-04   9.75e-08   5.04e-01   3.88e+02s
32400  -4.6437e+00   2.55e-04   9.23e-08   5.04e-01   3.91e+02s
32600  -4.6434e+00   2.55e-04   8.76e-08   5.04e-01   3.93e+02s
32800  -4.6431e+00   2.54e-04   8.33e-08   5.04e-01   3.95e+02s
33000  -4.6428e+00   2.53e-04   7.93e-08   5.04e-01   3.98e+02s
33200  -4.6443e+00   2.53e-04   2.76e-07   5.04e-01   4.00e+02s
33400  -4.6446e+00   2.52e-04   1.90e-07   5.04e-01   4.03e+02s
33600  -4.6446e+00   2.50e-04   1.48e-07   5.04e-01   4.05e+02s
33800  -4.6444e+00   2.48e-04   1.22e-07   5.04e-01   4.07e+02s
34000  -4.6442e+00   2.46e-04   1.11e-07   5.04e-01   4.10e+02s
34200  -4.6439e+00   2.45e-04   1.05e-07   5.04e-01   4.12e+02s
34400  -4.6437e+00   2.43e-04   9.84e-08   5.04e-01   4.14e+02s
34600  -4.6434e+00   2.42e-04   9.15e-08   5.04e-01   4.16e+02s
34800  -4.6435e+00   2.41e-04   4.91e-08   5.04e-01   4.19e+02s
35000  -4.6439e+00   2.40e-04   3.71e-08   5.04e-01   4.21e+02s
35200  -4.6440e+00   2.40e-04   3.12e-08   5.04e-01   4.24e+02s
35400  -4.6439e+00   2.39e-04   2.61e-08   5.04e-01   4.26e+02s
35600  -4.6439e+00   2.39e-04   2.20e-08   5.04e-01   4.29e+02s
35800  -4.6439e+00   2.37e-04   2.52e-08   5.04e-01   4.31e+02s
36000  -4.6438e+00   2.37e-04   2.04e-08   5.04e-01   4.33e+02s
36200  -4.6437e+00   2.36e-04   1.75e-08   5.04e-01   4.36e+02s
36400  -4.6436e+00   2.35e-04   1.55e-08   5.04e-01   4.38e+02s
In [ ]:
print("Factor model solve time = {}".format(prob_factor.solver_stats.solve_time))
print("Single model solve time = {}".format(prob.solver_stats.solve_time))