Skip to content

Commit

Permalink
managed to reproduce pre-2023 hello world exactly
Browse files Browse the repository at this point in the history
  • Loading branch information
enzbus committed Dec 21, 2023
1 parent c5c0670 commit 1f7b2ef
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 41 deletions.
117 changes: 93 additions & 24 deletions examples/paper_examples/hello_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,106 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""This is a simple example of back-tests with Cvxportfolio.
This is a close translation of what was done in `this notebook
<https://github.com/cvxgrp/cvxportfolio/blob/0.0.X/examples/HelloWorld.ipynb>`_.
In fact, you can see that the results are identical.
The approach used here is not recommended; in particular we download
data externally (it is done better now by the automatic data download
and cleaning code we include in Cvxportfolio). The returns used here
are close-to-close total returns, while our interface computes correctly
the open-to-open total returns.
In this example returns and covariances are forecasted externally,
while today this can be done automatically using the default forecasters
used by :class:`cvxportfolio.ReturnsForecast` and
:class:`cvxportfolio.FullCovariance`.
Nevertheless, you can see by running this that we are still able to
reproduce exactly the behavior of the early development versions
of the library.
.. note ::
To run this, you need to install ``yfinance`` and
``pandas_datareader``.
"""

import cvxportfolio as cvx
import pandas as pd
import matplotlib.pyplot as plt
import pandas as pd

import yfinance
import pandas_datareader as pdr

import cvxportfolio as cvx

# Download market data
tickers = ['AMZN', 'GOOGL', 'TSLA', 'NKE']

returns = pd.DataFrame(dict([(ticker,
yfinance.download(ticker)['Adj Close'].pct_change())
for ticker in tickers]))

returns["USDOLLAR"]=pdr.get_data_fred(
'DFF', start="1900-01-01",
end=pd.Timestamp.today())['DFF']/(252*100)

returns = returns.fillna(method='ffill').iloc[1:]

# Download market data.
market_data = cvx.DownloadedMarketData(
universe = ['AMZN', 'GOOGL', 'TSLA', 'NKE'],
print('Returns')
print(returns)

# Create market data server.
market_data = cvx.UserProvidedMarketData(
returns = returns,
cash_key = 'USDOLLAR')

print('Historical open-to-open total returns:')

# Today we'd do all the above by (no external packages needed):
# market_data = cvx.DownloadedMarketData(
# universe = ['AMZN', 'GOOGL', 'TSLA', 'NKE'],
# cash_key = 'USDOLLAR')


print('Historical returns:')
print(market_data.returns)

# Build forecasts of expected returns and covariances.
# Note that we shift so that each day we use ones built
# using past returns only. This is done automatically
# by the forecasters used by default in the stable versions
# of Cvxportfolio.
r_hat_with_cash = market_data.returns.rolling(
window=250).mean().shift(1).dropna()
Sigma_hat_without_cash = market_data.returns.iloc[:,:-1
].rolling(window=250).cov().shift(4).dropna()

r_hat = r_hat_with_cash.iloc[:, :-1]
r_hat_cash = r_hat_with_cash.iloc[:,-1]
print('Expected returns forecast:')
print(r_hat_with_cash)

# Define transaction and holding cost models.

# half spread
HALF_SPREAD = 10E-4
BORROW_FEE = 1E-4 * (252 / 100) # in annualized percentage
tcost_model = cvx.TcostModel(a=HALF_SPREAD)
hcost_model = cvx.HcostModel(short_fees=BORROW_FEE)

# As returns forecast, we simply take the historical means
# computed at each point in the back-test (looking only
# at past returns). That's the default behavior; we
# may as well pass a dataframe here with different predictions.
r_hat = cvx.ReturnsForecast()
# In the 2016 development code borrow fees were expressed per-period.
# In the stable version we require annualized percent.
# This value corresponds to 1 basis point per period,
# which was in the original example.
BORROW_FEE = 2.552

tcost_model = cvx.TcostModel(a=HALF_SPREAD, b=None)
hcost_model = cvx.HcostModel(short_fees=BORROW_FEE)

# As risk model, we choose the full historical covariance.
# It is computed every day using the full past historical
# returns at that point. (We may as well provide it as a
# dataframe.)
risk_model = cvx.FullSigma()
# As risk model, we use the historical covariances computed above.
# Note that the stable version of Cvxportfolio requires the covariance
# matrix to not include cash (as it should). In the development versions
# it was there. It doesn't make any difference in numerical terms.
risk_model = cvx.FullSigma(Sigma_hat_without_cash)

# Constraint.
leverage_limit = cvx.LeverageLimit(3)
Expand All @@ -52,18 +120,19 @@
# objective function is maximized.
gamma_risk, gamma_trade, gamma_hold = 5., 1., 1.
spo_policy = cvx.SinglePeriodOpt(
objective = r_hat
objective = cvx.ReturnsForecast(r_hat) + cvx.CashReturn(r_hat_cash)
- gamma_risk * risk_model
- gamma_trade * tcost_model
- gamma_trade * tcost_model
- gamma_hold * hcost_model,
constraints=[leverage_limit])
constraints=[leverage_limit],
include_cash_return=False)

# Define the market simulator.
market_sim = cvx.MarketSimulator(
market_data = market_data,
costs = [
cvx.TcostModel(a=HALF_SPREAD),
cvx.HcostModel(short_fees=BORROW_FEE)])
cvx.TcostModel(a=HALF_SPREAD, b=None),
cvx.HcostModel(short_fees=BORROW_FEE)])

# Initial portfolio, uniform on non-cash assets.
init_portfolio = pd.Series(
Expand Down
38 changes: 21 additions & 17 deletions examples/risk_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@
# limitations under the License.
"""Test different choices of risk models, which has best performance?
**WORK IN PROGRESS**
.. note::
On the Nasdaq 100 index, daily trading from 2016 to today:
The output of this example is currently (Cvxportfolio ``1.0.3``)
not too easy to read; the ``__repr__`` method of a policy object
with symbolic hyper-parameters is scheduled for improvement. It
does work, though.
On the Dow Jones, daily trading from 2016 to today:
- diagonal risk model
- diagonal risk model with risk forecast error
- full covariance
Expand All @@ -24,28 +29,26 @@
We test on a long-only portfolio and use automatic hyper-parameter
optimization to maximize the information ratio, in back-test,
versus an index ETF.
versus the index ETF.
"""

import os
from pprint import pprint

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import pandas as pd

import cvxportfolio as cvx
from .universes import DOW30 as UNIVERSE
#from .universes import NDX100 as UNIVERSE

from .universes import DOW30 as UNIVERSE

# Index
INDEX = 'DIA' #
#INDEX = 'QQQ'
INDEX = 'DIA'

# Times.
START = '2000-01-01' #'2016-01-01'
END = None #'2017-01-01'
START = '2000-01-01'
END = None # today

# Leverage.
LEVERAGE_LIMIT = 1.
Expand All @@ -60,40 +63,41 @@
all_in_index[INDEX] = 1.
benchmark = cvx.FixedWeights(all_in_index)

# Define hyper-parameter objects:
# Define hyper-parameter objects.
# These will be included in the library in a future release.
class GammaTradeCoarse(cvx.RangeHyperParameter):
"""Transaction cost multiplier, coarse value range."""
def __init__(self):
super().__init__(
values_range=np.arange(1,11),
values_range=np.arange(1, 11),
current_value=1.)

class GammaTradeFine(cvx.RangeHyperParameter):
"""Transaction cost multiplier, fine value range."""
def __init__(self):
super().__init__(
values_range=np.linspace(-1.,1.,51),
values_range=np.linspace(-1., 1., 51),
current_value=0)

class GammaRiskCoarse(cvx.RangeHyperParameter):
"""Risk term multiplier, coarse value range."""
def __init__(self):
super().__init__(
values_range=np.arange(1,21),
values_range=np.arange(1, 21),
current_value=1.)

class GammaRiskFine(cvx.RangeHyperParameter):
"""Risk term multiplier, fine value range."""
def __init__(self):
super().__init__(
values_range=np.linspace(-1.,1.,51),
values_range=np.linspace(-1., 1., 51),
current_value=0)

class Kappa(cvx.RangeHyperParameter):
"""Risk forecast error multiplier, fine value range."""
def __init__(self):
super().__init__(
values_range=np.linspace(0.,0.5),
values_range=np.linspace(0., 0.5),
current_value=0)


Expand Down Expand Up @@ -132,7 +136,7 @@ def __init__(self):
- (GammaTradeCoarse() + GammaTradeFine()
) * cvx.StocksTransactionCost()
- risk_model,
constraints=[cvx.LongOnly(), cvx.LeverageLimit(LEVERAGE_LIMIT)],
constraints=[cvx.LongOnly(), cvx.LeverageLimit(LEVERAGE_LIMIT)],
benchmark=benchmark,
solver='CLARABEL')

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies = ["pandas", "numpy", "matplotlib", "requests", "cvxpy",
docs = ["sphinx", "furo"]
dev = ["build", "twine", "coverage", "diff_cover", "pylint", "isort",
"autopep8", "docformatter", "beautifulsoup4"]
examples = ['beatifulsoup4', 'pandas_datareader', 'yfinance']

[project.urls]
Homepage = "https://www.cvxportfolio.com"
Expand Down

0 comments on commit 1f7b2ef

Please sign in to comment.