From daef24fe39e1e06fed57e7c57d56c6968612e133 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Mon, 11 Dec 2023 13:22:12 +0400 Subject: [PATCH 01/13] examples/universes.py --- examples/__init__.py | 15 ++++ examples/sp500_ndx100.py | 90 ++------------------- examples/timing.py | 61 +------------- examples/universes.py | 169 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 143 deletions(-) create mode 100644 examples/__init__.py create mode 100644 examples/universes.py diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 000000000..3462e96bb --- /dev/null +++ b/examples/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2023 Enzo Busseti +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""This package contains examples of typical usage of Cvxportfolio. +""" diff --git a/examples/sp500_ndx100.py b/examples/sp500_ndx100.py index 9a9b202a2..72b89aefd 100644 --- a/examples/sp500_ndx100.py +++ b/examples/sp500_ndx100.py @@ -1,98 +1,22 @@ import cvxportfolio as cvx import matplotlib.pyplot as plt +from .universes import SP500, NDX100 + ## This is an example of a very large backtest, ~600 names and ~6000 days ## with multi period optimization. It shows that all parts of the system scale ## to such usecases. -SP500 = ['MMM', 'AOS', 'ABT', 'ABBV', 'ACN', 'ADM', 'ADBE', 'ADP', - 'AAP', 'AES', 'AFL', 'A', 'APD', 'AKAM', 'ALK', 'ALB', 'ARE', - 'ALGN', 'ALLE', 'LNT', 'ALL', 'GOOGL', 'GOOG', 'MO', 'AMZN', - 'AMCR', 'AMD', 'AEE', 'AAL', 'AEP', 'AXP', 'AIG', 'AMT', 'AWK', - 'AMP', 'AME', 'AMGN', 'APH', 'ADI', 'ANSS', 'AON', 'APA', - 'AAPL', 'AMAT', 'APTV', 'ACGL', 'ANET', 'AJG', 'AIZ', 'T', 'ATO', - 'ADSK', 'AZO', 'AVB', 'AVY', 'AXON', 'BKR', 'BALL', 'BAC', 'BBWI', - 'BAX', 'BDX', 'WRB', 'BRK-B', - 'BBY', 'BIO', 'TECH', 'BIIB', 'BLK', - 'BK', 'BA', 'BKNG', 'BWA', 'BXP', 'BSX', 'BMY', 'AVGO', 'BR', - 'BRO', 'BF-B', - 'BG', 'CHRW', 'CDNS', 'CZR', 'CPT', 'CPB', 'COF', - 'CAH', 'KMX', 'CCL', 'CARR', 'CTLT', 'CAT', 'CBOE', 'CBRE', 'CDW', - 'CE', 'CNC', 'CNP', 'CDAY', 'CF', 'CRL', 'SCHW', 'CHTR', 'CVX', - 'CMG', 'CB', 'CHD', 'CI', 'CINF', 'CTAS', 'CSCO', 'C', 'CFG', - 'CLX', 'CME', 'CMS', 'KO', 'CTSH', 'CL', 'CMCSA', 'CMA', 'CAG', - 'COP', 'ED', 'STZ', 'CEG', 'COO', 'CPRT', 'CTVA', 'CSGP', - 'COST', 'CTRA', 'CCI', 'CSX', 'CMI', 'CVS', 'DHI', 'DHR', 'DRI', - 'DVA', 'DE', 'DAL', 'XRAY', 'DVN', 'DXCM', 'FANG', 'DLR', 'DFS', - 'DISH', 'DIS', 'DG', 'DLTR', 'D', 'DPZ', 'DOV', 'DOW', 'DTE', - 'DUK', 'DD', 'DXC', 'EMN', 'ETN', 'EBAY', 'ECL', 'EIX', 'EW', 'EA', - 'ELV', 'LLY', 'EMR', 'ENPH', 'ETR', 'EOG', 'EPAM', 'EQT', 'EFX', - 'EQIX', 'EQR', 'ESS', 'EL', 'ETSY', 'EVRG', 'ES', 'EXC', - 'EXPE', 'EXPD', 'EXR', 'XOM', 'FFIV', 'FDS', 'FICO', 'FAST', 'FRT', - 'FDX', 'FITB', 'FSLR', 'FE', 'FIS', 'FLT', 'FMC', 'F', - 'FTNT', 'FTV', 'FOXA', 'FOX', 'BEN', 'FCX', 'GRMN', 'IT', 'GEHC', - 'GEN', 'GNRC', 'GD', 'GE', 'GIS', 'GM', 'GPC', 'GILD', 'GL', 'GPN', - 'GS', 'HAL', 'HIG', 'HAS', 'HCA', 'PEAK', 'HSIC', 'HSY', 'HES', - 'HPE', 'HLT', 'HOLX', 'HD', 'HON', 'HRL', 'HST', 'HWM', 'HPQ', - 'HUM', 'HBAN', 'HII', 'IBM', 'IEX', 'IDXX', 'ITW', 'ILMN', 'INCY', - 'IR', 'PODD', 'INTC', 'ICE', 'IFF', 'IP', 'IPG', 'INTU', 'ISRG', - 'IVZ', 'INVH', 'IQV', 'IRM', 'JBHT', 'JKHY', 'J', 'JNJ', 'JCI', - 'JPM', 'JNPR', 'K', 'KDP', 'KEY', 'KEYS', 'KMB', 'KIM', 'KMI', - 'KLAC', 'KHC', 'KR', 'LHX', 'LH', 'LRCX', 'LW', 'LVS', 'LDOS', - 'LEN', 'LNC', 'LIN', 'LYV', 'LKQ', 'LMT', 'L', 'LOW', 'LYB', 'MTB', - 'MRO', 'MPC', 'MKTX', 'MAR', 'MMC', 'MLM', 'MAS', 'MA', 'MTCH', - 'MKC', 'MCD', 'MCK', 'MDT', 'MRK', 'META', 'MET', 'MTD', 'MGM', - 'MCHP', 'MU', 'MSFT', 'MAA', 'MRNA', 'MHK', 'MOH', 'TAP', 'MDLZ', - 'MPWR', 'MNST', 'MCO', 'MS', 'MOS', 'MSI', 'MSCI', 'NDAQ', 'NTAP', - 'NFLX', 'NWL', 'NEM', 'NWSA', 'NWS', 'NEE', 'NKE', 'NI', 'NDSN', - 'NSC', 'NTRS', 'NOC', 'NCLH', 'NRG', 'NUE', 'NVDA', 'NVR', 'NXPI', - 'ORLY', 'OXY', 'ODFL', 'OMC', 'ON', 'OKE', 'ORCL', 'OGN', 'OTIS', - 'PCAR', 'PKG', 'PARA', 'PH', 'PAYX', 'PAYC', 'PYPL', 'PNR', 'PEP', - 'PFE', 'PCG', 'PM', 'PSX', 'PNW', 'PXD', 'PNC', 'POOL', 'PPG', - 'PPL', 'PFG', 'PG', 'PGR', 'PLD', 'PRU', 'PEG', 'PTC', 'PSA', - 'PHM', 'QRVO', 'PWR', 'QCOM', 'DGX', 'RL', 'RJF', 'RTX', 'O', - 'REG', 'REGN', 'RF', 'RSG', 'RMD', 'RVTY', 'RHI', 'ROK', 'ROL', - 'ROP', 'ROST', 'RCL', 'SPGI', 'CRM', 'SBAC', 'SLB', 'STX', 'SEE', - 'SRE', 'NOW', 'SHW', 'SPG', 'SWKS', 'SJM', 'SNA', #'SEDG', - 'SO', - 'LUV', 'SWK', 'SBUX', 'STT', 'STLD', 'STE', 'SYK', 'SYF', 'SNPS', - 'SYY', 'TMUS', 'TROW', 'TTWO', 'TPR', 'TRGP', 'TGT', 'TEL', 'TDY', - 'TFX', 'TER', 'TSLA', 'TXN', 'TXT', 'TMO', 'TJX', 'TSCO', 'TT', - 'TDG', 'TRV', 'TRMB', 'TFC', 'TYL', 'TSN', 'USB', 'UDR', 'ULTA', - 'UNP', 'UAL', 'UPS', 'URI', 'UNH', 'UHS', 'VLO', 'VTR', 'VRSN', - 'VRSK', 'VZ', 'VRTX', 'VFC', 'VTRS', 'VICI', 'V', 'VMC', 'WAB', - 'WBA', 'WMT', 'WBD', 'WM', 'WAT', 'WEC', 'WFC', 'WELL', 'WST', - 'WDC', 'WRK', 'WY', 'WHR', 'WMB', #'WTW', - 'GWW', 'WYNN', 'XEL', - 'XYL', 'YUM', 'ZBRA', 'ZBH', 'ZION', 'ZTS'] - - -NDX100 = ["AMZN", "AAPL", "MSFT", "GOOGL", "TSLA", "GM", - 'NKE', 'MCD', 'GE', 'CVX', - 'XOM', 'MMM', 'UNH', 'HD', 'WMT', 'ORCL', 'INTC', 'JPM', 'BLK', 'BA', 'NVDA', - 'F', 'GS', 'AMD', 'CSCO', 'KO', 'HON', 'DIS', - 'V', 'ADBE', 'AMGN', 'CAT', 'BA', 'HON', 'JNJ', 'AXP', 'PG', 'JPM', - 'IBM', 'MRK', 'MMM', 'VZ', 'WBA', 'INTC', 'PEP', 'AVGO', - 'COST', 'TMUS', 'CMCSA', 'TXN', 'NFLX', 'SBUX', 'GILD', 'ISRG', 'MDLZ', 'BKNG', 'AMAT', - 'ADI', 'ADP', 'VRTX', 'REGN', 'PYPL', 'LRCX', 'PYPL', - 'MU', 'CSX', 'MELI', 'MNST', 'PANW', 'ORLY', 'ASML', 'SNPS', - 'CDNS', 'MAR', 'KLAC', 'FTNT', 'CHTR', 'CHTR', 'MRNA', - 'KHC', 'CTAS', 'AEP', 'DXCM', 'LULU', 'KDP', - 'AZN', 'BIIB', 'ABNB', 'NXPI', 'ADSK', 'EXC', 'MCHP', 'IDXX', 'CPRT', 'PAYX', - 'PCAR', 'XEL', 'PDD', 'WDAY', 'SGEN', 'ROST', 'DLTR', 'RA', #'MRVL', - 'ODFL', 'VRSK', 'ILMN', 'CTSH', 'FAST', 'CSGP', 'WBD', 'GFS', 'CRWD', - 'BKR', 'WBA', 'CEG', 'ANSS', 'DDOG', 'EBAY', 'FANG','ENPH', 'ALGN', 'TEAM', - 'ZS','JD', 'ZM','SIRI','LCID', 'RIVN', 'SGEN', 'MDLZ', 'NFLX', 'GOOGL', 'DXCM'] - - objective = cvx.ReturnsForecast() -.05 * cvx.ReturnsForecastError() \ - - 5 * (cvx.FactorModelCovariance(num_factors=50) + 0.1 * cvx.RiskForecastError()) \ + - 5 * (cvx.FactorModelCovariance(num_factors=50) + + 0.1 * cvx.RiskForecastError()) \ - cvx.StocksTransactionCost(exponent=2) - cvx.StocksHoldingCost() constraints = [cvx.LeverageLimit(3)] -policy = cvx.MultiPeriodOptimization(objective, constraints, planning_horizon=3, ignore_dpp=True) - +policy = cvx.MultiPeriodOptimization( + objective, constraints, planning_horizon=3, ignore_dpp=True) + universe = sorted(set(SP500 + NDX100)) simulator = cvx.StockMarketSimulator(universe) diff --git a/examples/timing.py b/examples/timing.py index c338eb5bb..e64b0e598 100644 --- a/examples/timing.py +++ b/examples/timing.py @@ -3,66 +3,7 @@ import time import pandas as pd -SP500 = ['MMM', 'AOS', 'ABT', 'ABBV', 'ACN', 'ADM', 'ADBE', 'ADP', - 'AAP', 'AES', 'AFL', 'A', 'APD', 'AKAM', 'ALK', 'ALB', 'ARE', - 'ALGN', 'ALLE', 'LNT', 'ALL', 'GOOGL', 'GOOG', 'MO', 'AMZN', - 'AMCR', 'AMD', 'AEE', 'AAL', 'AEP', 'AXP', 'AIG', 'AMT', 'AWK', - 'AMP', 'AME', 'AMGN', 'APH', 'ADI', 'ANSS', 'AON', 'APA', - 'AAPL', 'AMAT', 'APTV', 'ACGL', 'ANET', 'AJG', 'AIZ', 'T', 'ATO', - 'ADSK', 'AZO', 'AVB', 'AVY', 'AXON', 'BKR', 'BALL', 'BAC', 'BBWI', - 'BAX', 'BDX', 'WRB', 'BRK-B', - 'BBY', 'BIO', 'TECH', 'BIIB', 'BLK', - 'BK', 'BA', 'BKNG', 'BWA', 'BXP', 'BSX', 'BMY', 'AVGO', 'BR', - 'BRO', 'BF-B', - 'BG', 'CHRW', 'CDNS', 'CZR', 'CPT', 'CPB', 'COF', - 'CAH', 'KMX', 'CCL', 'CARR', 'CTLT', 'CAT', 'CBOE', 'CBRE', 'CDW', - 'CE', 'CNC', 'CNP', 'CDAY', 'CF', 'CRL', 'SCHW', 'CHTR', 'CVX', - 'CMG', 'CB', 'CHD', 'CI', 'CINF', 'CTAS', 'CSCO', 'C', 'CFG', - 'CLX', 'CME', 'CMS', 'KO', 'CTSH', 'CL', 'CMCSA', 'CMA', 'CAG', - 'COP', 'ED', 'STZ', 'CEG', 'COO', 'CPRT', 'CTVA', 'CSGP', - 'COST', 'CTRA', 'CCI', 'CSX', 'CMI', 'CVS', 'DHI', 'DHR', 'DRI', - 'DVA', 'DE', 'DAL', 'XRAY', 'DVN', 'DXCM', 'FANG', 'DLR', 'DFS', - 'DISH', 'DIS', 'DG', 'DLTR', 'D', 'DPZ', 'DOV', 'DOW', 'DTE', - 'DUK', 'DD', 'DXC', 'EMN', 'ETN', 'EBAY', 'ECL', 'EIX', 'EW', 'EA', - 'ELV', 'LLY', 'EMR', 'ENPH', 'ETR', 'EOG', 'EPAM', 'EQT', 'EFX', - 'EQIX', 'EQR', 'ESS', 'EL', 'ETSY', 'EVRG', 'ES', 'EXC', - 'EXPE', 'EXPD', 'EXR', 'XOM', 'FFIV', 'FDS', 'FICO', 'FAST', 'FRT', - 'FDX', 'FITB', 'FSLR', 'FE', 'FIS', 'FLT', 'FMC', 'F', - 'FTNT', 'FTV', 'FOXA', 'FOX', 'BEN', 'FCX', 'GRMN', 'IT', 'GEHC', - 'GEN', 'GNRC', 'GD', 'GE', 'GIS', 'GM', 'GPC', 'GILD', 'GL', 'GPN', - 'GS', 'HAL', 'HIG', 'HAS', 'HCA', 'PEAK', 'HSIC', 'HSY', 'HES', - 'HPE', 'HLT', 'HOLX', 'HD', 'HON', 'HRL', 'HST', 'HWM', 'HPQ', - 'HUM', 'HBAN', 'HII', 'IBM', 'IEX', 'IDXX', 'ITW', 'ILMN', 'INCY', - 'IR', 'PODD', 'INTC', 'ICE', 'IFF', 'IP', 'IPG', 'INTU', 'ISRG', - 'IVZ', 'INVH', 'IQV', 'IRM', 'JBHT', 'JKHY', 'J', 'JNJ', 'JCI', - 'JPM', 'JNPR', 'K', 'KDP', 'KEY', 'KEYS', 'KMB', 'KIM', 'KMI', - 'KLAC', 'KHC', 'KR', 'LHX', 'LH', 'LRCX', 'LW', 'LVS', 'LDOS', - 'LEN', 'LNC', 'LIN', 'LYV', 'LKQ', 'LMT', 'L', 'LOW', 'LYB', 'MTB', - 'MRO', 'MPC', 'MKTX', 'MAR', 'MMC', 'MLM', 'MAS', 'MA', 'MTCH', - 'MKC', 'MCD', 'MCK', 'MDT', 'MRK', 'META', 'MET', 'MTD', 'MGM', - 'MCHP', 'MU', 'MSFT', 'MAA', 'MRNA', 'MHK', 'MOH', 'TAP', 'MDLZ', - 'MPWR', 'MNST', 'MCO', 'MS', 'MOS', 'MSI', 'MSCI', 'NDAQ', 'NTAP', - 'NFLX', 'NWL', 'NEM', 'NWSA', 'NWS', 'NEE', 'NKE', 'NI', 'NDSN', - 'NSC', 'NTRS', 'NOC', 'NCLH', 'NRG', 'NUE', 'NVDA', 'NVR', 'NXPI', - 'ORLY', 'OXY', 'ODFL', 'OMC', 'ON', 'OKE', 'ORCL', 'OGN', 'OTIS', - 'PCAR', 'PKG', 'PARA', 'PH', 'PAYX', 'PAYC', 'PYPL', 'PNR', 'PEP', - 'PFE', 'PCG', 'PM', 'PSX', 'PNW', 'PXD', 'PNC', 'POOL', 'PPG', - 'PPL', 'PFG', 'PG', 'PGR', 'PLD', 'PRU', 'PEG', 'PTC', 'PSA', - 'PHM', 'QRVO', 'PWR', 'QCOM', 'DGX', 'RL', 'RJF', 'RTX', 'O', - 'REG', 'REGN', 'RF', 'RSG', 'RMD', 'RVTY', 'RHI', 'ROK', 'ROL', - 'ROP', 'ROST', 'RCL', 'SPGI', 'CRM', 'SBAC', 'SLB', 'STX', 'SEE', - 'SRE', 'NOW', 'SHW', 'SPG', 'SWKS', 'SJM', 'SNA', #'SEDG', - 'SO', - 'LUV', 'SWK', 'SBUX', 'STT', 'STLD', 'STE', 'SYK', 'SYF', 'SNPS', - 'SYY', 'TMUS', 'TROW', 'TTWO', 'TPR', 'TRGP', 'TGT', 'TEL', 'TDY', - 'TFX', 'TER', 'TSLA', 'TXN', 'TXT', 'TMO', 'TJX', 'TSCO', 'TT', - 'TDG', 'TRV', 'TRMB', 'TFC', 'TYL', 'TSN', 'USB', 'UDR', 'ULTA', - 'UNP', 'UAL', 'UPS', 'URI', 'UNH', 'UHS', 'VLO', 'VTR', 'VRSN', - 'VRSK', 'VZ', 'VRTX', 'VFC', 'VTRS', 'VICI', 'V', 'VMC', 'WAB', - 'WBA', 'WMT', 'WBD', 'WM', 'WAT', 'WEC', 'WFC', 'WELL', 'WST', - 'WDC', 'WRK', 'WY', 'WHR', 'WMB', #'WTW', - 'GWW', 'WYNN', 'XEL', - 'XYL', 'YUM', 'ZBRA', 'ZBH', 'ZION', 'ZTS'] +from .universes import SP500 # we test the typical time it takes # to solve and simulate an SPO policy, diff --git a/examples/universes.py b/examples/universes.py new file mode 100644 index 000000000..391feadcb --- /dev/null +++ b/examples/universes.py @@ -0,0 +1,169 @@ +# Copyright 2023 Enzo Busseti +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""This module contains up-to-date universes of stock tickers. + +If you run it attempts to download updated lists from the relevant +Wikipedia pages and it rewrites itself. Be careful when you run it +and check that the results make sense. +""" + +# This was generated on 2023-12-11 09:06:57.144451+00:00 + +SP500 = \ +['A', 'AAL', 'AAPL', 'ABBV', 'ABNB', 'ABT', 'ACGL', 'ACN', 'ADBE', 'ADI', + 'ADM', 'ADP', 'ADSK', 'AEE', 'AEP', 'AES', 'AFL', 'AIG', 'AIZ', 'AJG', 'AKAM', + 'ALB', 'ALGN', 'ALK', 'ALL', 'ALLE', 'AMAT', 'AMCR', 'AMD', 'AME', 'AMGN', + 'AMP', 'AMT', 'AMZN', 'ANET', 'ANSS', 'AON', 'AOS', 'APA', 'APD', 'APH', + 'APTV', 'ARE', 'ATO', 'AVB', 'AVGO', 'AVY', 'AWK', 'AXON', 'AXP', 'AZO', 'BA', + 'BAC', 'BALL', 'BAX', 'BBWI', 'BBY', 'BDX', 'BEN', 'BF.B', 'BG', 'BIIB', + 'BIO', 'BK', 'BKNG', 'BKR', 'BLK', 'BMY', 'BR', 'BRK.B', 'BRO', 'BSX', 'BWA', + 'BX', 'BXP', 'C', 'CAG', 'CAH', 'CARR', 'CAT', 'CB', 'CBOE', 'CBRE', 'CCI', + 'CCL', 'CDAY', 'CDNS', 'CDW', 'CE', 'CEG', 'CF', 'CFG', 'CHD', 'CHRW', 'CHTR', + 'CI', 'CINF', 'CL', 'CLX', 'CMA', 'CMCSA', 'CME', 'CMG', 'CMI', 'CMS', 'CNC', + 'CNP', 'COF', 'COO', 'COP', 'COR', 'COST', 'CPB', 'CPRT', 'CPT', 'CRL', 'CRM', + 'CSCO', 'CSGP', 'CSX', 'CTAS', 'CTLT', 'CTRA', 'CTSH', 'CTVA', 'CVS', 'CVX', + 'CZR', 'D', 'DAL', 'DD', 'DE', 'DFS', 'DG', 'DGX', 'DHI', 'DHR', 'DIS', 'DLR', + 'DLTR', 'DOV', 'DOW', 'DPZ', 'DRI', 'DTE', 'DUK', 'DVA', 'DVN', 'DXCM', 'EA', + 'EBAY', 'ECL', 'ED', 'EFX', 'EG', 'EIX', 'EL', 'ELV', 'EMN', 'EMR', 'ENPH', + 'EOG', 'EPAM', 'EQIX', 'EQR', 'EQT', 'ES', 'ESS', 'ETN', 'ETR', 'ETSY', + 'EVRG', 'EW', 'EXC', 'EXPD', 'EXPE', 'EXR', 'F', 'FANG', 'FAST', 'FCX', 'FDS', + 'FDX', 'FE', 'FFIV', 'FI', 'FICO', 'FIS', 'FITB', 'FLT', 'FMC', 'FOX', 'FOXA', + 'FRT', 'FSLR', 'FTNT', 'FTV', 'GD', 'GE', 'GEHC', 'GEN', 'GILD', 'GIS', 'GL', + 'GLW', 'GM', 'GNRC', 'GOOG', 'GOOGL', 'GPC', 'GPN', 'GRMN', 'GS', 'GWW', + 'HAL', 'HAS', 'HBAN', 'HCA', 'HD', 'HES', 'HIG', 'HII', 'HLT', 'HOLX', 'HON', + 'HPE', 'HPQ', 'HRL', 'HSIC', 'HST', 'HSY', 'HUBB', 'HUM', 'HWM', 'IBM', 'ICE', + 'IDXX', 'IEX', 'IFF', 'ILMN', 'INCY', 'INTC', 'INTU', 'INVH', 'IP', 'IPG', + 'IQV', 'IR', 'IRM', 'ISRG', 'IT', 'ITW', 'IVZ', 'J', 'JBHT', 'JCI', 'JKHY', + 'JNJ', 'JNPR', 'JPM', 'K', 'KDP', 'KEY', 'KEYS', 'KHC', 'KIM', 'KLAC', 'KMB', + 'KMI', 'KMX', 'KO', 'KR', 'KVUE', 'L', 'LDOS', 'LEN', 'LH', 'LHX', 'LIN', + 'LKQ', 'LLY', 'LMT', 'LNT', 'LOW', 'LRCX', 'LULU', 'LUV', 'LVS', 'LW', 'LYB', + 'LYV', 'MA', 'MAA', 'MAR', 'MAS', 'MCD', 'MCHP', 'MCK', 'MCO', 'MDLZ', 'MDT', + 'MET', 'META', 'MGM', 'MHK', 'MKC', 'MKTX', 'MLM', 'MMC', 'MMM', 'MNST', 'MO', + 'MOH', 'MOS', 'MPC', 'MPWR', 'MRK', 'MRNA', 'MRO', 'MS', 'MSCI', 'MSFT', + 'MSI', 'MTB', 'MTCH', 'MTD', 'MU', 'NCLH', 'NDAQ', 'NDSN', 'NEE', 'NEM', + 'NFLX', 'NI', 'NKE', 'NOC', 'NOW', 'NRG', 'NSC', 'NTAP', 'NTRS', 'NUE', + 'NVDA', 'NVR', 'NWS', 'NWSA', 'NXPI', 'O', 'ODFL', 'OKE', 'OMC', 'ON', 'ORCL', + 'ORLY', 'OTIS', 'OXY', 'PANW', 'PARA', 'PAYC', 'PAYX', 'PCAR', 'PCG', 'PEAK', + 'PEG', 'PEP', 'PFE', 'PFG', 'PG', 'PGR', 'PH', 'PHM', 'PKG', 'PLD', 'PM', + 'PNC', 'PNR', 'PNW', 'PODD', 'POOL', 'PPG', 'PPL', 'PRU', 'PSA', 'PSX', 'PTC', + 'PWR', 'PXD', 'PYPL', 'QCOM', 'QRVO', 'RCL', 'REG', 'REGN', 'RF', 'RHI', + 'RJF', 'RL', 'RMD', 'ROK', 'ROL', 'ROP', 'ROST', 'RSG', 'RTX', 'RVTY', 'SBAC', + 'SBUX', 'SCHW', 'SEDG', 'SEE', 'SHW', 'SJM', 'SLB', 'SNA', 'SNPS', 'SO', + 'SPG', 'SPGI', 'SRE', 'STE', 'STLD', 'STT', 'STX', 'STZ', 'SWK', 'SWKS', + 'SYF', 'SYK', 'SYY', 'T', 'TAP', 'TDG', 'TDY', 'TECH', 'TEL', 'TER', 'TFC', + 'TFX', 'TGT', 'TJX', 'TMO', 'TMUS', 'TPR', 'TRGP', 'TRMB', 'TROW', 'TRV', + 'TSCO', 'TSLA', 'TSN', 'TT', 'TTWO', 'TXN', 'TXT', 'TYL', 'UAL', 'UDR', 'UHS', + 'ULTA', 'UNH', 'UNP', 'UPS', 'URI', 'USB', 'V', 'VFC', 'VICI', 'VLO', 'VLTO', + 'VMC', 'VRSK', 'VRSN', 'VRTX', 'VTR', 'VTRS', 'VZ', 'WAB', 'WAT', 'WBA', + 'WBD', 'WDC', 'WEC', 'WELL', 'WFC', 'WHR', 'WM', 'WMB', 'WMT', 'WRB', 'WRK', + 'WST', 'WTW', 'WY', 'WYNN', 'XEL', 'XOM', 'XRAY', 'XYL', 'YUM', 'ZBH', 'ZBRA', + 'ZION', 'ZTS'] + +NDX100 = \ +['AAPL', 'ABNB', 'ADBE', 'ADI', 'ADP', 'ADSK', 'AEP', 'ALGN', 'AMAT', 'AMD', + 'AMGN', 'AMZN', 'ANSS', 'ASML', 'AVGO', 'AZN', 'BIIB', 'BKNG', 'BKR', 'CDNS', + 'CEG', 'CHTR', 'CMCSA', 'COST', 'CPRT', 'CRWD', 'CSCO', 'CSGP', 'CSX', 'CTAS', + 'CTSH', 'DDOG', 'DLTR', 'DXCM', 'EA', 'EBAY', 'ENPH', 'EXC', 'FANG', 'FAST', + 'FTNT', 'GEHC', 'GFS', 'GILD', 'GOOG', 'GOOGL', 'HON', 'IDXX', 'ILMN', 'INTC', + 'INTU', 'ISRG', 'JD', 'KDP', 'KHC', 'KLAC', 'LCID', 'LRCX', 'LULU', 'MAR', + 'MCHP', 'MDLZ', 'MELI', 'META', 'MNST', 'MRNA', 'MRVL', 'MSFT', 'MU', 'NFLX', + 'NVDA', 'NXPI', 'ODFL', 'ON', 'ORLY', 'PANW', 'PAYX', 'PCAR', 'PDD', 'PEP', + 'PYPL', 'QCOM', 'REGN', 'ROST', 'SBUX', 'SGEN', 'SIRI', 'SNPS', 'TEAM', + 'TMUS', 'TSLA', 'TTD', 'TXN', 'VRSK', 'VRTX', 'WBA', 'WBD', 'WDAY', 'XEL', + 'ZM', 'ZS'] + +if __name__ == '__main__': + + from pprint import pprint + + import bs4 as bs + import pandas as pd + import requests + + def get_column_wikipedia_page(page, table_number, column_number): + """Get a column as list of strings from a table on wikipedia. + + This is adapted from: + + https://pythonprogramming.net/sp500-company-price-data-python-programming-for-finance/ + + :param page: Wikipedia URL. + :type page: str + :param table_number: Which table on the page. + :type table_number: int + :param column_number: Which column to extract. + :type column_number: int + + :returns: Sorted strings of the column. + :rtype: list + """ + resp = requests.get(page, timeout=10) + soup = bs.BeautifulSoup(resp.text, 'lxml') + table = soup.find_all( + 'table', {'class': 'wikitable sortable'})[table_number] + column = [] + for row in table.findAll('tr')[1:]: + element = row.findAll('td')[column_number].text + column.append(element.strip()) + return sorted(column) + + def get_sp500_tickers(): + """Get current list of SP500 tickers by parsing wikipedia page. + + :returns: Current SP500 tickers. + :rtype: list + """ + return get_column_wikipedia_page( + "http://en.wikipedia.org/wiki/List_of_S%26P_500_companies", 0, 0) + + def get_ndx100_tickers(): + """Get current list of NDX100 tickers by parsing wikipedia page. + + :returns: Current NDX100 tickers. + :rtype: list + """ + return get_column_wikipedia_page( + "https://en.wikipedia.org/wiki/Nasdaq-100", -1, 1) + + # re-write this file + + with open(__loader__.path, 'r', encoding='utf-8') as f: + this_file_content = f.readlines() + + code_index = this_file_content.index("if __name__ == '__main__':\n") + + with open(__loader__.path, 'w', encoding='utf-8') as f: + + # header + f.writelines(this_file_content[:13]) + + # docstring + f.write('"""' + __doc__ + '"""\n') + + # timestamp + f.write("\n# This was generated on " + str(pd.Timestamp.utcnow()) + "\n") + + # SP500 list + f.write('\nSP500 = \\\n') + pprint( + get_sp500_tickers(), compact=True, width=79, stream=f) + + # NDX100 list + f.write('\nNDX100 = \\\n') + pprint( + get_ndx100_tickers(), compact=True, width=79, stream = f) + + # copy everything in the if __name__ == '__main__' clause + f.write('\n') + f.writelines(this_file_content[code_index:]) From a2de2ac3ed5d3ced23f3c785922b05c713949364 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Wed, 13 Dec 2023 10:34:48 +0400 Subject: [PATCH 02/13] more work on examples --- Makefile | 17 +++--- bumpversion.py | 43 ++++++++++++--- examples/{dow30_example.py => dow30.py} | 30 ++++++++--- examples/sp500_ndx100.py | 2 +- examples/timing.py | 21 +++++--- examples/universes.py | 72 ++++++++++++++++--------- pyproject.toml | 9 +++- 7 files changed, 136 insertions(+), 58 deletions(-) rename examples/{dow30_example.py => dow30.py} (80%) diff --git a/Makefile b/Makefile index e5ca43b66..03c75a3c6 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ BUILDDIR = build ENVDIR = env BINDIR = $(ENVDIR)/bin EXTRA_SCRIPTS = bumpversion.py -EXAMPLES = examples/*.py +EXAMPLES = examples ifeq ($(OS), Windows_NT) BINDIR=$(ENVDIR)/Scripts @@ -34,10 +34,11 @@ test: ## run tests w/ cov report $(BINDIR)/coverage xml $(BINDIR)/diff-cover coverage.xml --config-file pyproject.toml # disabled for now, we need to change pickle as default on-disk cache - # $(BINDIR)/bandit $(PROJECT)/*.py $(PROJECT)/tests/*.py + # $(BINDIR)/bandit $(PROJECT)/*.py $(TESTS)/*.py lint: ## run linter - $(BINDIR)/pylint $(PROJECT) + $(BINDIR)/pylint $(PROJECT) #$(EXTRA_SCRIPTS) $(EXAMPLES) + $(BINDIR)/diff-quality --violations=pylint --config-file pyproject.toml docs: ## build docs $(BINDIR)/sphinx-build -E docs $(BUILDDIR) @@ -51,10 +52,10 @@ coverage: ## open html cov report fix: ## auto-fix code # selected among many code auto-fixers, tweaked in pyproject.toml - $(BINDIR)/autopep8 -i $(PROJECT)/*.py $(TESTS)/*.py - $(BINDIR)/isort $(PROJECT)/*.py $(TESTS)/*.py + $(BINDIR)/autopep8 -i $(PROJECT)/*.py $(TESTS)/*.py $(EXAMPLES)/*.py $(EXTRA_SCRIPTS) + $(BINDIR)/isort $(PROJECT)/*.py $(TESTS)/*.py $(EXAMPLES)/*.py $(EXTRA_SCRIPTS) # this is the best found for the purpose - $(BINDIR)/docformatter --in-place $(PROJECT)/*.py $(TESTS)/*.py + $(BINDIR)/docformatter --in-place $(PROJECT)/*.py $(TESTS)/*.py $(EXAMPLES)/*.py $(EXTRA_SCRIPTS) release: update lint test ## update version, publish to pypi $(BINDIR)/python bumpversion.py @@ -64,8 +65,8 @@ release: update lint test ## update version, publish to pypi $(BINDIR)/twine upload --skip-existing dist/* examples: ## run examples for docs - for example in hello_world case_shiller dow30_example; \ - do env CVXPORTFOLIO_SAVE_PLOTS=1 $(BINDIR)/python examples/"$$example".py > docs/_static/"$$example"_output.txt; \ + for example in hello_world case_shiller universes dow30; \ + do env CVXPORTFOLIO_SAVE_PLOTS=1 $(BINDIR)/python -m examples."$$example" > docs/_static/"$$example"_output.txt; \ done mv *.png docs/_static/ diff --git a/bumpversion.py b/bumpversion.py index b694f25ce..0f9b3e4ab 100644 --- a/bumpversion.py +++ b/bumpversion.py @@ -1,11 +1,28 @@ -"""Look for __version__ in any __init__.py recursively and upgrade. - -Additionally, look for version string in any setup.py and (Sphinx) conf.py -and do the same. Version is assumed to be in the format X.Y.Z, where X, Y, -and Z are integers. Take argument revision (Z -> Z+1), +# Copyright 2023 Enzo Busseti +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Find __version__ in __init__.py in file tree (BFS) and upgrade. + +Additionally, look for version string in any setup.py, pyproject.toml, +and conf.py and do the same. Version is in the format X.Y.Z, +where X, Y, and Z are integers. Take argument revision (Z -> Z+1), minor (Y -> Y+1, Z -> 0), or major (X -> X+1, Y -> 0, Z -> 0). -Return the updated version string.""" +Add the modifications to git staging, commit with version number and +editable message (opens git configured text editor), tag with version number, +push everything to origin.""" + +from ast import literal_eval import subprocess from pathlib import Path @@ -14,23 +31,33 @@ def findversion(root='.'): """Find version number. Skip [env, venv, .*]. We use the first __init__.py with a __version__ that we find. + + :param root: Root folder of the project. + :type root: Pathlib.Path or str + + :raises ValueError: No version found. + + :returns: Found version string. + :rtype: str """ p = Path(root) for fname in p.iterdir(): if fname.name == '__init__.py': - with open(fname) as f: + with open(fname, encoding="utf-8") as f: lines = f.readlines() for line in lines: if '__version__' in line: - return eval(line.split('=')[1]) + return literal_eval(line.split('=')[1]) if fname.is_dir(): if not (fname.name in ['env', 'venv'] or fname.name[0] == '.'): result = findversion(fname) if result: return result + raise ValueError('Not found any!') + def replaceversion(new_version, version, root='.'): """Replace version number. Skip [env, venv, .*]. diff --git a/examples/dow30_example.py b/examples/dow30.py similarity index 80% rename from examples/dow30_example.py rename to examples/dow30.py index 617f33181..eed4cefbb 100644 --- a/examples/dow30_example.py +++ b/examples/dow30.py @@ -1,7 +1,7 @@ """Example of long-only portfolio among DOW-30 components. Monthly rebalance, backtest spans from the start of data of the -components of the current index (in the 60s) and the other stocks +components of the current index (in the 60s), the other stocks enter the backtest as times goes on. It ends today. This uses an explicit loop to create Multi Period Optimization @@ -17,6 +17,13 @@ :class:`cvxportfolio.Gamma`, as multipliers of the risk and transaction cost terms. We can optimize on those explicitely, by finding the values that maximize some back-test metric (in this case, profit). + +You can run this with this command from the parent directory: + +.. code-block:: bash + + python -m examples.dow30 + """ # Uncomment the logging lines to get online information @@ -32,22 +39,29 @@ import matplotlib.pyplot as plt import numpy as np +from .universes import DOW30 -UNIVERSE = ['MMM', 'AXP', 'AMGN', 'AAPL', 'BA', 'CAT', 'CVX', 'CSCO', 'KO', 'DIS', 'DOW', - 'GS', 'HD', 'HON', 'IBM','INTC', 'JNJ', - 'JPM','MCD', 'MRK', 'MSFT', 'NKE', 'PG', 'CRM', 'TRV', 'VZ', 'V', 'WBA', 'WMT'] - - -sim = cvx.StockMarketSimulator(UNIVERSE, trading_frequency='monthly') +sim = cvx.StockMarketSimulator(DOW30, trading_frequency='monthly') def make_policy(gamma_trade, gamma_risk): + """Build MPO policy given risk and trans. cost multipliers. + + :param gamma_trade: Transaction cost multiplier. + :type gamma_trade: float or int + :param gamma_risk: Risk model multiplier. + :type gamma_risk: float or int + :return: Multi-period optimization policy with given + hyper-parameter values. + :rtype: cvxportfolio.Policy + """ return cvx.MultiPeriodOptimization(cvx.ReturnsForecast() - gamma_risk * cvx.FactorModelCovariance(num_factors=10) - gamma_trade * cvx.StocksTransactionCost(), [cvx.LongOnly(), cvx.LeverageLimit(1)], planning_horizon=6, solver='ECOS') -keys = [(gamma_trade, gamma_risk) for gamma_trade in np.array(range(10))/10 for gamma_risk in [.5, 1, 2, 5, 10]] +keys = [(gamma_trade, gamma_risk) for + gamma_trade in np.array(range(10))/10 for gamma_risk in [.5, 1, 2, 5, 10]] ress = sim.backtest_many([make_policy(*key) for key in keys]) diff --git a/examples/sp500_ndx100.py b/examples/sp500_ndx100.py index 72b89aefd..bd29c0dbb 100644 --- a/examples/sp500_ndx100.py +++ b/examples/sp500_ndx100.py @@ -26,4 +26,4 @@ print(result) # plot value and weights of the portfolio in time -result.plot() +result.plot() diff --git a/examples/timing.py b/examples/timing.py index e64b0e598..e69cdc990 100644 --- a/examples/timing.py +++ b/examples/timing.py @@ -1,6 +1,7 @@ +import time + import cvxportfolio as cvx import matplotlib.pyplot as plt -import time import pandas as pd from .universes import SP500 @@ -64,14 +65,22 @@ # execution and timing, 5 years backtest s = time.time() -result = simulator.backtest(policy, start_time=pd.Timestamp.today() - pd.Timedelta(f'{365*5}d')) -print('BACKTEST TOOK', time.time() - s) -print('SIMULATOR + POLICY TIMES', result.simulator_times.sum() + result.policy_times.sum()) -print('AVERAGE TIME PER ITERATION', result.simulator_times.mean() + result.policy_times.mean()) +result = simulator.backtest( + policy, start_time=pd.Timestamp.today() - pd.Timedelta(f'{365.24*5}d')) + +print('## RESULT') +print(result) + +print('BACKTEST TOOK:', time.time() - s) +print( + 'SIMULATOR + POLICY TIMES:', + result.simulator_times.sum() + result.policy_times.sum()) +print( + 'AVERAGE TIME PER ITERATION:', + result.simulator_times.mean() + result.policy_times.mean()) # plot result.policy_times.plot(label='policy times') result.simulator_times.plot(label='simulator times') plt.legend() plt.show() - diff --git a/examples/universes.py b/examples/universes.py index 391feadcb..88fe10890 100644 --- a/examples/universes.py +++ b/examples/universes.py @@ -16,9 +16,11 @@ If you run it attempts to download updated lists from the relevant Wikipedia pages and it rewrites itself. Be careful when you run it and check that the results make sense. + +We could also save each universe in a ``json`` file. """ -# This was generated on 2023-12-11 09:06:57.144451+00:00 +# This was generated on 2023-12-12 15:18:33.706712+00:00 SP500 = \ ['A', 'AAL', 'AAPL', 'ABBV', 'ABNB', 'ABT', 'ACGL', 'ACN', 'ADBE', 'ADI', @@ -26,8 +28,8 @@ 'ALB', 'ALGN', 'ALK', 'ALL', 'ALLE', 'AMAT', 'AMCR', 'AMD', 'AME', 'AMGN', 'AMP', 'AMT', 'AMZN', 'ANET', 'ANSS', 'AON', 'AOS', 'APA', 'APD', 'APH', 'APTV', 'ARE', 'ATO', 'AVB', 'AVGO', 'AVY', 'AWK', 'AXON', 'AXP', 'AZO', 'BA', - 'BAC', 'BALL', 'BAX', 'BBWI', 'BBY', 'BDX', 'BEN', 'BF.B', 'BG', 'BIIB', - 'BIO', 'BK', 'BKNG', 'BKR', 'BLK', 'BMY', 'BR', 'BRK.B', 'BRO', 'BSX', 'BWA', + 'BAC', 'BALL', 'BAX', 'BBWI', 'BBY', 'BDX', 'BEN', 'BF-B', 'BG', 'BIIB', + 'BIO', 'BK', 'BKNG', 'BKR', 'BLK', 'BMY', 'BR', 'BRK-B', 'BRO', 'BSX', 'BWA', 'BX', 'BXP', 'C', 'CAG', 'CAH', 'CARR', 'CAT', 'CB', 'CBOE', 'CBRE', 'CCI', 'CCL', 'CDAY', 'CDNS', 'CDW', 'CE', 'CEG', 'CF', 'CFG', 'CHD', 'CHRW', 'CHTR', 'CI', 'CINF', 'CL', 'CLX', 'CMA', 'CMCSA', 'CME', 'CMG', 'CMI', 'CMS', 'CNC', @@ -83,14 +85,38 @@ 'TMUS', 'TSLA', 'TTD', 'TXN', 'VRSK', 'VRTX', 'WBA', 'WBD', 'WDAY', 'XEL', 'ZM', 'ZS'] +DOW30 = \ +['AAPL', 'AMGN', 'AXP', 'BA', 'CAT', 'CRM', 'CSCO', 'CVX', 'DIS', 'DOW', 'GS', + 'HD', 'HON', 'IBM', 'INTC', 'JNJ', 'JPM', 'KO', 'MCD', 'MMM', 'MRK', 'MSFT', + 'NKE', 'PG', 'TRV', 'UNH', 'V', 'VZ', 'WBA', 'WMT'] + if __name__ == '__main__': + # import json from pprint import pprint import bs4 as bs import pandas as pd import requests + universes = { + 'sp500': { + 'page':"http://en.wikipedia.org/wiki/List_of_S%26P_500_companies", + 'table_number': 0, + 'column_number': 0, + }, + 'ndx100':{ + 'page':"https://en.wikipedia.org/wiki/Nasdaq-100", + 'table_number': -1, + 'column_number': 1, + }, + 'dow30':{ + 'page':"https://en.wikipedia.org/wiki/Dow_Jones_Industrial_Average", + 'table_number': 0, + 'column_number': 1, + } + } + def get_column_wikipedia_page(page, table_number, column_number): """Get a column as list of strings from a table on wikipedia. @@ -118,23 +144,17 @@ def get_column_wikipedia_page(page, table_number, column_number): column.append(element.strip()) return sorted(column) - def get_sp500_tickers(): - """Get current list of SP500 tickers by parsing wikipedia page. - - :returns: Current SP500 tickers. - :rtype: list - """ - return get_column_wikipedia_page( - "http://en.wikipedia.org/wiki/List_of_S%26P_500_companies", 0, 0) + def adapt_for_yahoo_finance(tickers_list): + """Change tickers to match the spelling of Yahoo Finance. - def get_ndx100_tickers(): - """Get current list of NDX100 tickers by parsing wikipedia page. - - :returns: Current NDX100 tickers. + :param tickers_list: Tickers from Wikipedia. + :type tickers_list: list + + :returns: Adapted tickers. :rtype: list """ - return get_column_wikipedia_page( - "https://en.wikipedia.org/wiki/Nasdaq-100", -1, 1) + + return [el.replace('.', '-') for el in tickers_list] # re-write this file @@ -154,15 +174,17 @@ def get_ndx100_tickers(): # timestamp f.write("\n# This was generated on " + str(pd.Timestamp.utcnow()) + "\n") - # SP500 list - f.write('\nSP500 = \\\n') - pprint( - get_sp500_tickers(), compact=True, width=79, stream=f) + # universes lists + for key, value in universes.items(): + + tickers = adapt_for_yahoo_finance( + get_column_wikipedia_page(**value)) + f.write(f'\n{key.upper()} = \\\n') + pprint(tickers, compact=True, width=79, stream=f) - # NDX100 list - f.write('\nNDX100 = \\\n') - pprint( - get_ndx100_tickers(), compact=True, width=79, stream = f) + # # also save in json + # with open(key + '.json', 'w', encoding='utf-8') as f1: + # json.dump(tickers, f1) # copy everything in the if __name__ == '__main__' clause f.write('\n') diff --git a/pyproject.toml b/pyproject.toml index b3f10382f..2cca3b032 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,12 @@ accept-no-yields-doc = false fail_under = 99 [tool.diff_cover] -# this will be superflous once we push the above to 100 +# this will be superflous once we push coverage to 100 +compare_branch = "origin/master" +fail_under = 99 + +[tool.diff_quality] +# this will be superflous once we push pylint score to 10 compare_branch = "origin/master" fail_under = 99 @@ -65,4 +70,4 @@ select = ["W291","W293","W391","E231","E225","E303"] # tweaked to remove whitespaces and other simple fixes wrap-summaries = 0 wrap-descriptions = 0 -tab-width = 4 \ No newline at end of file +tab-width = 4 From db1e135b0bc98946998b60066d962ec585a3368a Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Wed, 13 Dec 2023 10:35:32 +0400 Subject: [PATCH 03/13] added base class values_in_time --- TODOs.md | 35 +++++++++++++++++++++++ cvxportfolio/estimator.py | 58 ++++++++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 TODOs.md diff --git a/TODOs.md b/TODOs.md new file mode 100644 index 000000000..f3f236022 --- /dev/null +++ b/TODOs.md @@ -0,0 +1,35 @@ +# Features & refactoring + +### `cache.py` + +- probably to be dropped; should use `_loader_*` and `_storer_*` from `data.py` + +### `forecast.py` + +- cache logic needs improvement, not easily exposable to third-parties now with `dataclass.__hash__` + - drop decorator + - drop dataclass + - cache IO logic should be managed by forecaster not by simulator, could be done by `initialize_estimator`; maybe enough to just + define it in the base class of forecasters +- improve names of internal methods, clean them (lots of stuff can be re-used at universe change, ...) +- generalize the mean estimator: + - use same code for `past_returns`, `past_returns**2`, `past_volumes`, ... + - add rolling window option, should be in `pd.Timedelta` + - add exponential moving avg, should be in half-life `pd.Timedelta` +- add same extras to the covariance estimator +- goal: make this module crystal clear; third-party ML models should use it (at least for caching) + +### `estimator.py` + +- `DataEstimator` needs refactoring, too long and complex methods + + + +### Development & testing +- add extra pylint checkers: + - code complexity +- consider removing downloaded data from `test_simulator.py`, so only `test_data.py` requires internet + +## Documentation + +## Examples diff --git a/cvxportfolio/estimator.py b/cvxportfolio/estimator.py index e52491b0a..ac14ebbcf 100644 --- a/cvxportfolio/estimator.py +++ b/cvxportfolio/estimator.py @@ -91,18 +91,56 @@ def current_value(self): """ return self._current_value - def values_in_time_recursive(self, **kwargs): - """Evaluate recursively on sub-estimators. + + # pylint: disable=useless-type-doc,useless-param-doc + def values_in_time( + self, t, current_weights, current_portfolio_value, + past_returns, past_volumes, current_prices, + mpo_step=None, cache=None, **kwargs): + """Evaluate estimator at current time, possibly return current value. + + This method is usually the most important for Estimator classes. + It is called at each point in a back-test with all data of the current + state. Sub-estimators are evaluated first, in a depth-first recursive + tree fashion (defined in :meth:`values_in_time_recursive`). The + signature differs slightly between different estimators, see below. + + :param t: Current timestamp. + :type t: pandas.Timestamp + :param current_weights: Current allocation weights. + :type current_weights: pandas.Series + :param current_portfolio_value: Current total value of the portfolio + in cash units. + :type current_portfolio_value: float + :param past_returns: Past market returns (including cash). + :type past_returns: pandas.DataFrame + :param past_volumes: Past market volumes, or None if not available. + :type past_volumes: pandas.DataFrame or None + :param current_prices: Current (open) prices, or None if not available. + :type current_prices: pandas.Series or None + :param mpo_step: For :class:`cvxportfolio.MultiPeriodOptimization` + which step in future planning this estimator is at: 0 is for + the current step (:class:`cvxportfolio.SinglePeriodOptimization`), + 1 is for day ahead, .... Defaults to ``None`` if unused. + :type mpo_step: int, optional + :param cache: Cache or workspace shared between all elements of an + estimator tree, currently only used by + :class:`cvxportfolio.MultiPeriodOptimization` (and derived + classes). It's useful to avoid re-computing expensive + things like covariance estimates at different MPO steps. + Defaults to ``None`` if unused. + :type cache: dict, optional + :param kwargs: Reserved for future new features. + :type kwargs: dict - This function is called by Simulator classes on Policy classes - returning the current trades list. Policy classes, if they - contain internal estimators, should declare them as attributes - and call this base function (via `super()`) before they do their - internal computation. CvxpyExpression estimators should instead - define this method to update their Cvxpy parameters. + :returns: Current value of the estimator. + :rtype: object or None + """ + # we don't raise NotImplementedError because this is called + # on classes that don't re-define it - Once we finalize the interface all parameters will be listed - here. + def values_in_time_recursive(self, **kwargs): + """Evaluate recursively on sub-estimators. :param kwargs: Various parameters that are passed to all elements contained in a policy object. From 89ddbb3b2a234060878a0dd821019e22ff2f2c12 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Wed, 13 Dec 2023 10:52:12 +0400 Subject: [PATCH 04/13] revert commits that are in other branch --- Makefile | 17 +-- bumpversion.py | 43 +----- examples/__init__.py | 15 -- examples/{dow30.py => dow30_example.py} | 30 +--- examples/sp500_ndx100.py | 92 +++++++++++- examples/timing.py | 82 ++++++++-- examples/universes.py | 191 ------------------------ pyproject.toml | 9 +- 8 files changed, 176 insertions(+), 303 deletions(-) delete mode 100644 examples/__init__.py rename examples/{dow30.py => dow30_example.py} (80%) delete mode 100644 examples/universes.py diff --git a/Makefile b/Makefile index 03c75a3c6..e5ca43b66 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ BUILDDIR = build ENVDIR = env BINDIR = $(ENVDIR)/bin EXTRA_SCRIPTS = bumpversion.py -EXAMPLES = examples +EXAMPLES = examples/*.py ifeq ($(OS), Windows_NT) BINDIR=$(ENVDIR)/Scripts @@ -34,11 +34,10 @@ test: ## run tests w/ cov report $(BINDIR)/coverage xml $(BINDIR)/diff-cover coverage.xml --config-file pyproject.toml # disabled for now, we need to change pickle as default on-disk cache - # $(BINDIR)/bandit $(PROJECT)/*.py $(TESTS)/*.py + # $(BINDIR)/bandit $(PROJECT)/*.py $(PROJECT)/tests/*.py lint: ## run linter - $(BINDIR)/pylint $(PROJECT) #$(EXTRA_SCRIPTS) $(EXAMPLES) - $(BINDIR)/diff-quality --violations=pylint --config-file pyproject.toml + $(BINDIR)/pylint $(PROJECT) docs: ## build docs $(BINDIR)/sphinx-build -E docs $(BUILDDIR) @@ -52,10 +51,10 @@ coverage: ## open html cov report fix: ## auto-fix code # selected among many code auto-fixers, tweaked in pyproject.toml - $(BINDIR)/autopep8 -i $(PROJECT)/*.py $(TESTS)/*.py $(EXAMPLES)/*.py $(EXTRA_SCRIPTS) - $(BINDIR)/isort $(PROJECT)/*.py $(TESTS)/*.py $(EXAMPLES)/*.py $(EXTRA_SCRIPTS) + $(BINDIR)/autopep8 -i $(PROJECT)/*.py $(TESTS)/*.py + $(BINDIR)/isort $(PROJECT)/*.py $(TESTS)/*.py # this is the best found for the purpose - $(BINDIR)/docformatter --in-place $(PROJECT)/*.py $(TESTS)/*.py $(EXAMPLES)/*.py $(EXTRA_SCRIPTS) + $(BINDIR)/docformatter --in-place $(PROJECT)/*.py $(TESTS)/*.py release: update lint test ## update version, publish to pypi $(BINDIR)/python bumpversion.py @@ -65,8 +64,8 @@ release: update lint test ## update version, publish to pypi $(BINDIR)/twine upload --skip-existing dist/* examples: ## run examples for docs - for example in hello_world case_shiller universes dow30; \ - do env CVXPORTFOLIO_SAVE_PLOTS=1 $(BINDIR)/python -m examples."$$example" > docs/_static/"$$example"_output.txt; \ + for example in hello_world case_shiller dow30_example; \ + do env CVXPORTFOLIO_SAVE_PLOTS=1 $(BINDIR)/python examples/"$$example".py > docs/_static/"$$example"_output.txt; \ done mv *.png docs/_static/ diff --git a/bumpversion.py b/bumpversion.py index 0f9b3e4ab..b694f25ce 100644 --- a/bumpversion.py +++ b/bumpversion.py @@ -1,28 +1,11 @@ -# Copyright 2023 Enzo Busseti -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Find __version__ in __init__.py in file tree (BFS) and upgrade. - -Additionally, look for version string in any setup.py, pyproject.toml, -and conf.py and do the same. Version is in the format X.Y.Z, -where X, Y, and Z are integers. Take argument revision (Z -> Z+1), -minor (Y -> Y+1, Z -> 0), or major (X -> X+1, Y -> 0, Z -> 0). +"""Look for __version__ in any __init__.py recursively and upgrade. -Add the modifications to git staging, commit with version number and -editable message (opens git configured text editor), tag with version number, -push everything to origin.""" +Additionally, look for version string in any setup.py and (Sphinx) conf.py +and do the same. Version is assumed to be in the format X.Y.Z, where X, Y, +and Z are integers. Take argument revision (Z -> Z+1), +minor (Y -> Y+1, Z -> 0), or major (X -> X+1, Y -> 0, Z -> 0). +Return the updated version string.""" -from ast import literal_eval import subprocess from pathlib import Path @@ -31,33 +14,23 @@ def findversion(root='.'): """Find version number. Skip [env, venv, .*]. We use the first __init__.py with a __version__ that we find. - - :param root: Root folder of the project. - :type root: Pathlib.Path or str - - :raises ValueError: No version found. - - :returns: Found version string. - :rtype: str """ p = Path(root) for fname in p.iterdir(): if fname.name == '__init__.py': - with open(fname, encoding="utf-8") as f: + with open(fname) as f: lines = f.readlines() for line in lines: if '__version__' in line: - return literal_eval(line.split('=')[1]) + return eval(line.split('=')[1]) if fname.is_dir(): if not (fname.name in ['env', 'venv'] or fname.name[0] == '.'): result = findversion(fname) if result: return result - raise ValueError('Not found any!') - def replaceversion(new_version, version, root='.'): """Replace version number. Skip [env, venv, .*]. diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index 3462e96bb..000000000 --- a/examples/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2023 Enzo Busseti -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""This package contains examples of typical usage of Cvxportfolio. -""" diff --git a/examples/dow30.py b/examples/dow30_example.py similarity index 80% rename from examples/dow30.py rename to examples/dow30_example.py index eed4cefbb..617f33181 100644 --- a/examples/dow30.py +++ b/examples/dow30_example.py @@ -1,7 +1,7 @@ """Example of long-only portfolio among DOW-30 components. Monthly rebalance, backtest spans from the start of data of the -components of the current index (in the 60s), the other stocks +components of the current index (in the 60s) and the other stocks enter the backtest as times goes on. It ends today. This uses an explicit loop to create Multi Period Optimization @@ -17,13 +17,6 @@ :class:`cvxportfolio.Gamma`, as multipliers of the risk and transaction cost terms. We can optimize on those explicitely, by finding the values that maximize some back-test metric (in this case, profit). - -You can run this with this command from the parent directory: - -.. code-block:: bash - - python -m examples.dow30 - """ # Uncomment the logging lines to get online information @@ -39,29 +32,22 @@ import matplotlib.pyplot as plt import numpy as np -from .universes import DOW30 -sim = cvx.StockMarketSimulator(DOW30, trading_frequency='monthly') +UNIVERSE = ['MMM', 'AXP', 'AMGN', 'AAPL', 'BA', 'CAT', 'CVX', 'CSCO', 'KO', 'DIS', 'DOW', + 'GS', 'HD', 'HON', 'IBM','INTC', 'JNJ', + 'JPM','MCD', 'MRK', 'MSFT', 'NKE', 'PG', 'CRM', 'TRV', 'VZ', 'V', 'WBA', 'WMT'] + + +sim = cvx.StockMarketSimulator(UNIVERSE, trading_frequency='monthly') def make_policy(gamma_trade, gamma_risk): - """Build MPO policy given risk and trans. cost multipliers. - - :param gamma_trade: Transaction cost multiplier. - :type gamma_trade: float or int - :param gamma_risk: Risk model multiplier. - :type gamma_risk: float or int - :return: Multi-period optimization policy with given - hyper-parameter values. - :rtype: cvxportfolio.Policy - """ return cvx.MultiPeriodOptimization(cvx.ReturnsForecast() - gamma_risk * cvx.FactorModelCovariance(num_factors=10) - gamma_trade * cvx.StocksTransactionCost(), [cvx.LongOnly(), cvx.LeverageLimit(1)], planning_horizon=6, solver='ECOS') -keys = [(gamma_trade, gamma_risk) for - gamma_trade in np.array(range(10))/10 for gamma_risk in [.5, 1, 2, 5, 10]] +keys = [(gamma_trade, gamma_risk) for gamma_trade in np.array(range(10))/10 for gamma_risk in [.5, 1, 2, 5, 10]] ress = sim.backtest_many([make_policy(*key) for key in keys]) diff --git a/examples/sp500_ndx100.py b/examples/sp500_ndx100.py index bd29c0dbb..9a9b202a2 100644 --- a/examples/sp500_ndx100.py +++ b/examples/sp500_ndx100.py @@ -1,22 +1,98 @@ import cvxportfolio as cvx import matplotlib.pyplot as plt -from .universes import SP500, NDX100 - ## This is an example of a very large backtest, ~600 names and ~6000 days ## with multi period optimization. It shows that all parts of the system scale ## to such usecases. +SP500 = ['MMM', 'AOS', 'ABT', 'ABBV', 'ACN', 'ADM', 'ADBE', 'ADP', + 'AAP', 'AES', 'AFL', 'A', 'APD', 'AKAM', 'ALK', 'ALB', 'ARE', + 'ALGN', 'ALLE', 'LNT', 'ALL', 'GOOGL', 'GOOG', 'MO', 'AMZN', + 'AMCR', 'AMD', 'AEE', 'AAL', 'AEP', 'AXP', 'AIG', 'AMT', 'AWK', + 'AMP', 'AME', 'AMGN', 'APH', 'ADI', 'ANSS', 'AON', 'APA', + 'AAPL', 'AMAT', 'APTV', 'ACGL', 'ANET', 'AJG', 'AIZ', 'T', 'ATO', + 'ADSK', 'AZO', 'AVB', 'AVY', 'AXON', 'BKR', 'BALL', 'BAC', 'BBWI', + 'BAX', 'BDX', 'WRB', 'BRK-B', + 'BBY', 'BIO', 'TECH', 'BIIB', 'BLK', + 'BK', 'BA', 'BKNG', 'BWA', 'BXP', 'BSX', 'BMY', 'AVGO', 'BR', + 'BRO', 'BF-B', + 'BG', 'CHRW', 'CDNS', 'CZR', 'CPT', 'CPB', 'COF', + 'CAH', 'KMX', 'CCL', 'CARR', 'CTLT', 'CAT', 'CBOE', 'CBRE', 'CDW', + 'CE', 'CNC', 'CNP', 'CDAY', 'CF', 'CRL', 'SCHW', 'CHTR', 'CVX', + 'CMG', 'CB', 'CHD', 'CI', 'CINF', 'CTAS', 'CSCO', 'C', 'CFG', + 'CLX', 'CME', 'CMS', 'KO', 'CTSH', 'CL', 'CMCSA', 'CMA', 'CAG', + 'COP', 'ED', 'STZ', 'CEG', 'COO', 'CPRT', 'CTVA', 'CSGP', + 'COST', 'CTRA', 'CCI', 'CSX', 'CMI', 'CVS', 'DHI', 'DHR', 'DRI', + 'DVA', 'DE', 'DAL', 'XRAY', 'DVN', 'DXCM', 'FANG', 'DLR', 'DFS', + 'DISH', 'DIS', 'DG', 'DLTR', 'D', 'DPZ', 'DOV', 'DOW', 'DTE', + 'DUK', 'DD', 'DXC', 'EMN', 'ETN', 'EBAY', 'ECL', 'EIX', 'EW', 'EA', + 'ELV', 'LLY', 'EMR', 'ENPH', 'ETR', 'EOG', 'EPAM', 'EQT', 'EFX', + 'EQIX', 'EQR', 'ESS', 'EL', 'ETSY', 'EVRG', 'ES', 'EXC', + 'EXPE', 'EXPD', 'EXR', 'XOM', 'FFIV', 'FDS', 'FICO', 'FAST', 'FRT', + 'FDX', 'FITB', 'FSLR', 'FE', 'FIS', 'FLT', 'FMC', 'F', + 'FTNT', 'FTV', 'FOXA', 'FOX', 'BEN', 'FCX', 'GRMN', 'IT', 'GEHC', + 'GEN', 'GNRC', 'GD', 'GE', 'GIS', 'GM', 'GPC', 'GILD', 'GL', 'GPN', + 'GS', 'HAL', 'HIG', 'HAS', 'HCA', 'PEAK', 'HSIC', 'HSY', 'HES', + 'HPE', 'HLT', 'HOLX', 'HD', 'HON', 'HRL', 'HST', 'HWM', 'HPQ', + 'HUM', 'HBAN', 'HII', 'IBM', 'IEX', 'IDXX', 'ITW', 'ILMN', 'INCY', + 'IR', 'PODD', 'INTC', 'ICE', 'IFF', 'IP', 'IPG', 'INTU', 'ISRG', + 'IVZ', 'INVH', 'IQV', 'IRM', 'JBHT', 'JKHY', 'J', 'JNJ', 'JCI', + 'JPM', 'JNPR', 'K', 'KDP', 'KEY', 'KEYS', 'KMB', 'KIM', 'KMI', + 'KLAC', 'KHC', 'KR', 'LHX', 'LH', 'LRCX', 'LW', 'LVS', 'LDOS', + 'LEN', 'LNC', 'LIN', 'LYV', 'LKQ', 'LMT', 'L', 'LOW', 'LYB', 'MTB', + 'MRO', 'MPC', 'MKTX', 'MAR', 'MMC', 'MLM', 'MAS', 'MA', 'MTCH', + 'MKC', 'MCD', 'MCK', 'MDT', 'MRK', 'META', 'MET', 'MTD', 'MGM', + 'MCHP', 'MU', 'MSFT', 'MAA', 'MRNA', 'MHK', 'MOH', 'TAP', 'MDLZ', + 'MPWR', 'MNST', 'MCO', 'MS', 'MOS', 'MSI', 'MSCI', 'NDAQ', 'NTAP', + 'NFLX', 'NWL', 'NEM', 'NWSA', 'NWS', 'NEE', 'NKE', 'NI', 'NDSN', + 'NSC', 'NTRS', 'NOC', 'NCLH', 'NRG', 'NUE', 'NVDA', 'NVR', 'NXPI', + 'ORLY', 'OXY', 'ODFL', 'OMC', 'ON', 'OKE', 'ORCL', 'OGN', 'OTIS', + 'PCAR', 'PKG', 'PARA', 'PH', 'PAYX', 'PAYC', 'PYPL', 'PNR', 'PEP', + 'PFE', 'PCG', 'PM', 'PSX', 'PNW', 'PXD', 'PNC', 'POOL', 'PPG', + 'PPL', 'PFG', 'PG', 'PGR', 'PLD', 'PRU', 'PEG', 'PTC', 'PSA', + 'PHM', 'QRVO', 'PWR', 'QCOM', 'DGX', 'RL', 'RJF', 'RTX', 'O', + 'REG', 'REGN', 'RF', 'RSG', 'RMD', 'RVTY', 'RHI', 'ROK', 'ROL', + 'ROP', 'ROST', 'RCL', 'SPGI', 'CRM', 'SBAC', 'SLB', 'STX', 'SEE', + 'SRE', 'NOW', 'SHW', 'SPG', 'SWKS', 'SJM', 'SNA', #'SEDG', + 'SO', + 'LUV', 'SWK', 'SBUX', 'STT', 'STLD', 'STE', 'SYK', 'SYF', 'SNPS', + 'SYY', 'TMUS', 'TROW', 'TTWO', 'TPR', 'TRGP', 'TGT', 'TEL', 'TDY', + 'TFX', 'TER', 'TSLA', 'TXN', 'TXT', 'TMO', 'TJX', 'TSCO', 'TT', + 'TDG', 'TRV', 'TRMB', 'TFC', 'TYL', 'TSN', 'USB', 'UDR', 'ULTA', + 'UNP', 'UAL', 'UPS', 'URI', 'UNH', 'UHS', 'VLO', 'VTR', 'VRSN', + 'VRSK', 'VZ', 'VRTX', 'VFC', 'VTRS', 'VICI', 'V', 'VMC', 'WAB', + 'WBA', 'WMT', 'WBD', 'WM', 'WAT', 'WEC', 'WFC', 'WELL', 'WST', + 'WDC', 'WRK', 'WY', 'WHR', 'WMB', #'WTW', + 'GWW', 'WYNN', 'XEL', + 'XYL', 'YUM', 'ZBRA', 'ZBH', 'ZION', 'ZTS'] + + +NDX100 = ["AMZN", "AAPL", "MSFT", "GOOGL", "TSLA", "GM", + 'NKE', 'MCD', 'GE', 'CVX', + 'XOM', 'MMM', 'UNH', 'HD', 'WMT', 'ORCL', 'INTC', 'JPM', 'BLK', 'BA', 'NVDA', + 'F', 'GS', 'AMD', 'CSCO', 'KO', 'HON', 'DIS', + 'V', 'ADBE', 'AMGN', 'CAT', 'BA', 'HON', 'JNJ', 'AXP', 'PG', 'JPM', + 'IBM', 'MRK', 'MMM', 'VZ', 'WBA', 'INTC', 'PEP', 'AVGO', + 'COST', 'TMUS', 'CMCSA', 'TXN', 'NFLX', 'SBUX', 'GILD', 'ISRG', 'MDLZ', 'BKNG', 'AMAT', + 'ADI', 'ADP', 'VRTX', 'REGN', 'PYPL', 'LRCX', 'PYPL', + 'MU', 'CSX', 'MELI', 'MNST', 'PANW', 'ORLY', 'ASML', 'SNPS', + 'CDNS', 'MAR', 'KLAC', 'FTNT', 'CHTR', 'CHTR', 'MRNA', + 'KHC', 'CTAS', 'AEP', 'DXCM', 'LULU', 'KDP', + 'AZN', 'BIIB', 'ABNB', 'NXPI', 'ADSK', 'EXC', 'MCHP', 'IDXX', 'CPRT', 'PAYX', + 'PCAR', 'XEL', 'PDD', 'WDAY', 'SGEN', 'ROST', 'DLTR', 'RA', #'MRVL', + 'ODFL', 'VRSK', 'ILMN', 'CTSH', 'FAST', 'CSGP', 'WBD', 'GFS', 'CRWD', + 'BKR', 'WBA', 'CEG', 'ANSS', 'DDOG', 'EBAY', 'FANG','ENPH', 'ALGN', 'TEAM', + 'ZS','JD', 'ZM','SIRI','LCID', 'RIVN', 'SGEN', 'MDLZ', 'NFLX', 'GOOGL', 'DXCM'] + + objective = cvx.ReturnsForecast() -.05 * cvx.ReturnsForecastError() \ - - 5 * (cvx.FactorModelCovariance(num_factors=50) - + 0.1 * cvx.RiskForecastError()) \ + - 5 * (cvx.FactorModelCovariance(num_factors=50) + 0.1 * cvx.RiskForecastError()) \ - cvx.StocksTransactionCost(exponent=2) - cvx.StocksHoldingCost() constraints = [cvx.LeverageLimit(3)] -policy = cvx.MultiPeriodOptimization( - objective, constraints, planning_horizon=3, ignore_dpp=True) - +policy = cvx.MultiPeriodOptimization(objective, constraints, planning_horizon=3, ignore_dpp=True) + universe = sorted(set(SP500 + NDX100)) simulator = cvx.StockMarketSimulator(universe) @@ -26,4 +102,4 @@ print(result) # plot value and weights of the portfolio in time -result.plot() +result.plot() diff --git a/examples/timing.py b/examples/timing.py index e69cdc990..c338eb5bb 100644 --- a/examples/timing.py +++ b/examples/timing.py @@ -1,10 +1,68 @@ -import time - import cvxportfolio as cvx import matplotlib.pyplot as plt +import time import pandas as pd -from .universes import SP500 +SP500 = ['MMM', 'AOS', 'ABT', 'ABBV', 'ACN', 'ADM', 'ADBE', 'ADP', + 'AAP', 'AES', 'AFL', 'A', 'APD', 'AKAM', 'ALK', 'ALB', 'ARE', + 'ALGN', 'ALLE', 'LNT', 'ALL', 'GOOGL', 'GOOG', 'MO', 'AMZN', + 'AMCR', 'AMD', 'AEE', 'AAL', 'AEP', 'AXP', 'AIG', 'AMT', 'AWK', + 'AMP', 'AME', 'AMGN', 'APH', 'ADI', 'ANSS', 'AON', 'APA', + 'AAPL', 'AMAT', 'APTV', 'ACGL', 'ANET', 'AJG', 'AIZ', 'T', 'ATO', + 'ADSK', 'AZO', 'AVB', 'AVY', 'AXON', 'BKR', 'BALL', 'BAC', 'BBWI', + 'BAX', 'BDX', 'WRB', 'BRK-B', + 'BBY', 'BIO', 'TECH', 'BIIB', 'BLK', + 'BK', 'BA', 'BKNG', 'BWA', 'BXP', 'BSX', 'BMY', 'AVGO', 'BR', + 'BRO', 'BF-B', + 'BG', 'CHRW', 'CDNS', 'CZR', 'CPT', 'CPB', 'COF', + 'CAH', 'KMX', 'CCL', 'CARR', 'CTLT', 'CAT', 'CBOE', 'CBRE', 'CDW', + 'CE', 'CNC', 'CNP', 'CDAY', 'CF', 'CRL', 'SCHW', 'CHTR', 'CVX', + 'CMG', 'CB', 'CHD', 'CI', 'CINF', 'CTAS', 'CSCO', 'C', 'CFG', + 'CLX', 'CME', 'CMS', 'KO', 'CTSH', 'CL', 'CMCSA', 'CMA', 'CAG', + 'COP', 'ED', 'STZ', 'CEG', 'COO', 'CPRT', 'CTVA', 'CSGP', + 'COST', 'CTRA', 'CCI', 'CSX', 'CMI', 'CVS', 'DHI', 'DHR', 'DRI', + 'DVA', 'DE', 'DAL', 'XRAY', 'DVN', 'DXCM', 'FANG', 'DLR', 'DFS', + 'DISH', 'DIS', 'DG', 'DLTR', 'D', 'DPZ', 'DOV', 'DOW', 'DTE', + 'DUK', 'DD', 'DXC', 'EMN', 'ETN', 'EBAY', 'ECL', 'EIX', 'EW', 'EA', + 'ELV', 'LLY', 'EMR', 'ENPH', 'ETR', 'EOG', 'EPAM', 'EQT', 'EFX', + 'EQIX', 'EQR', 'ESS', 'EL', 'ETSY', 'EVRG', 'ES', 'EXC', + 'EXPE', 'EXPD', 'EXR', 'XOM', 'FFIV', 'FDS', 'FICO', 'FAST', 'FRT', + 'FDX', 'FITB', 'FSLR', 'FE', 'FIS', 'FLT', 'FMC', 'F', + 'FTNT', 'FTV', 'FOXA', 'FOX', 'BEN', 'FCX', 'GRMN', 'IT', 'GEHC', + 'GEN', 'GNRC', 'GD', 'GE', 'GIS', 'GM', 'GPC', 'GILD', 'GL', 'GPN', + 'GS', 'HAL', 'HIG', 'HAS', 'HCA', 'PEAK', 'HSIC', 'HSY', 'HES', + 'HPE', 'HLT', 'HOLX', 'HD', 'HON', 'HRL', 'HST', 'HWM', 'HPQ', + 'HUM', 'HBAN', 'HII', 'IBM', 'IEX', 'IDXX', 'ITW', 'ILMN', 'INCY', + 'IR', 'PODD', 'INTC', 'ICE', 'IFF', 'IP', 'IPG', 'INTU', 'ISRG', + 'IVZ', 'INVH', 'IQV', 'IRM', 'JBHT', 'JKHY', 'J', 'JNJ', 'JCI', + 'JPM', 'JNPR', 'K', 'KDP', 'KEY', 'KEYS', 'KMB', 'KIM', 'KMI', + 'KLAC', 'KHC', 'KR', 'LHX', 'LH', 'LRCX', 'LW', 'LVS', 'LDOS', + 'LEN', 'LNC', 'LIN', 'LYV', 'LKQ', 'LMT', 'L', 'LOW', 'LYB', 'MTB', + 'MRO', 'MPC', 'MKTX', 'MAR', 'MMC', 'MLM', 'MAS', 'MA', 'MTCH', + 'MKC', 'MCD', 'MCK', 'MDT', 'MRK', 'META', 'MET', 'MTD', 'MGM', + 'MCHP', 'MU', 'MSFT', 'MAA', 'MRNA', 'MHK', 'MOH', 'TAP', 'MDLZ', + 'MPWR', 'MNST', 'MCO', 'MS', 'MOS', 'MSI', 'MSCI', 'NDAQ', 'NTAP', + 'NFLX', 'NWL', 'NEM', 'NWSA', 'NWS', 'NEE', 'NKE', 'NI', 'NDSN', + 'NSC', 'NTRS', 'NOC', 'NCLH', 'NRG', 'NUE', 'NVDA', 'NVR', 'NXPI', + 'ORLY', 'OXY', 'ODFL', 'OMC', 'ON', 'OKE', 'ORCL', 'OGN', 'OTIS', + 'PCAR', 'PKG', 'PARA', 'PH', 'PAYX', 'PAYC', 'PYPL', 'PNR', 'PEP', + 'PFE', 'PCG', 'PM', 'PSX', 'PNW', 'PXD', 'PNC', 'POOL', 'PPG', + 'PPL', 'PFG', 'PG', 'PGR', 'PLD', 'PRU', 'PEG', 'PTC', 'PSA', + 'PHM', 'QRVO', 'PWR', 'QCOM', 'DGX', 'RL', 'RJF', 'RTX', 'O', + 'REG', 'REGN', 'RF', 'RSG', 'RMD', 'RVTY', 'RHI', 'ROK', 'ROL', + 'ROP', 'ROST', 'RCL', 'SPGI', 'CRM', 'SBAC', 'SLB', 'STX', 'SEE', + 'SRE', 'NOW', 'SHW', 'SPG', 'SWKS', 'SJM', 'SNA', #'SEDG', + 'SO', + 'LUV', 'SWK', 'SBUX', 'STT', 'STLD', 'STE', 'SYK', 'SYF', 'SNPS', + 'SYY', 'TMUS', 'TROW', 'TTWO', 'TPR', 'TRGP', 'TGT', 'TEL', 'TDY', + 'TFX', 'TER', 'TSLA', 'TXN', 'TXT', 'TMO', 'TJX', 'TSCO', 'TT', + 'TDG', 'TRV', 'TRMB', 'TFC', 'TYL', 'TSN', 'USB', 'UDR', 'ULTA', + 'UNP', 'UAL', 'UPS', 'URI', 'UNH', 'UHS', 'VLO', 'VTR', 'VRSN', + 'VRSK', 'VZ', 'VRTX', 'VFC', 'VTRS', 'VICI', 'V', 'VMC', 'WAB', + 'WBA', 'WMT', 'WBD', 'WM', 'WAT', 'WEC', 'WFC', 'WELL', 'WST', + 'WDC', 'WRK', 'WY', 'WHR', 'WMB', #'WTW', + 'GWW', 'WYNN', 'XEL', + 'XYL', 'YUM', 'ZBRA', 'ZBH', 'ZION', 'ZTS'] # we test the typical time it takes # to solve and simulate an SPO policy, @@ -65,22 +123,14 @@ # execution and timing, 5 years backtest s = time.time() -result = simulator.backtest( - policy, start_time=pd.Timestamp.today() - pd.Timedelta(f'{365.24*5}d')) - -print('## RESULT') -print(result) - -print('BACKTEST TOOK:', time.time() - s) -print( - 'SIMULATOR + POLICY TIMES:', - result.simulator_times.sum() + result.policy_times.sum()) -print( - 'AVERAGE TIME PER ITERATION:', - result.simulator_times.mean() + result.policy_times.mean()) +result = simulator.backtest(policy, start_time=pd.Timestamp.today() - pd.Timedelta(f'{365*5}d')) +print('BACKTEST TOOK', time.time() - s) +print('SIMULATOR + POLICY TIMES', result.simulator_times.sum() + result.policy_times.sum()) +print('AVERAGE TIME PER ITERATION', result.simulator_times.mean() + result.policy_times.mean()) # plot result.policy_times.plot(label='policy times') result.simulator_times.plot(label='simulator times') plt.legend() plt.show() + diff --git a/examples/universes.py b/examples/universes.py deleted file mode 100644 index 88fe10890..000000000 --- a/examples/universes.py +++ /dev/null @@ -1,191 +0,0 @@ -# Copyright 2023 Enzo Busseti -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""This module contains up-to-date universes of stock tickers. - -If you run it attempts to download updated lists from the relevant -Wikipedia pages and it rewrites itself. Be careful when you run it -and check that the results make sense. - -We could also save each universe in a ``json`` file. -""" - -# This was generated on 2023-12-12 15:18:33.706712+00:00 - -SP500 = \ -['A', 'AAL', 'AAPL', 'ABBV', 'ABNB', 'ABT', 'ACGL', 'ACN', 'ADBE', 'ADI', - 'ADM', 'ADP', 'ADSK', 'AEE', 'AEP', 'AES', 'AFL', 'AIG', 'AIZ', 'AJG', 'AKAM', - 'ALB', 'ALGN', 'ALK', 'ALL', 'ALLE', 'AMAT', 'AMCR', 'AMD', 'AME', 'AMGN', - 'AMP', 'AMT', 'AMZN', 'ANET', 'ANSS', 'AON', 'AOS', 'APA', 'APD', 'APH', - 'APTV', 'ARE', 'ATO', 'AVB', 'AVGO', 'AVY', 'AWK', 'AXON', 'AXP', 'AZO', 'BA', - 'BAC', 'BALL', 'BAX', 'BBWI', 'BBY', 'BDX', 'BEN', 'BF-B', 'BG', 'BIIB', - 'BIO', 'BK', 'BKNG', 'BKR', 'BLK', 'BMY', 'BR', 'BRK-B', 'BRO', 'BSX', 'BWA', - 'BX', 'BXP', 'C', 'CAG', 'CAH', 'CARR', 'CAT', 'CB', 'CBOE', 'CBRE', 'CCI', - 'CCL', 'CDAY', 'CDNS', 'CDW', 'CE', 'CEG', 'CF', 'CFG', 'CHD', 'CHRW', 'CHTR', - 'CI', 'CINF', 'CL', 'CLX', 'CMA', 'CMCSA', 'CME', 'CMG', 'CMI', 'CMS', 'CNC', - 'CNP', 'COF', 'COO', 'COP', 'COR', 'COST', 'CPB', 'CPRT', 'CPT', 'CRL', 'CRM', - 'CSCO', 'CSGP', 'CSX', 'CTAS', 'CTLT', 'CTRA', 'CTSH', 'CTVA', 'CVS', 'CVX', - 'CZR', 'D', 'DAL', 'DD', 'DE', 'DFS', 'DG', 'DGX', 'DHI', 'DHR', 'DIS', 'DLR', - 'DLTR', 'DOV', 'DOW', 'DPZ', 'DRI', 'DTE', 'DUK', 'DVA', 'DVN', 'DXCM', 'EA', - 'EBAY', 'ECL', 'ED', 'EFX', 'EG', 'EIX', 'EL', 'ELV', 'EMN', 'EMR', 'ENPH', - 'EOG', 'EPAM', 'EQIX', 'EQR', 'EQT', 'ES', 'ESS', 'ETN', 'ETR', 'ETSY', - 'EVRG', 'EW', 'EXC', 'EXPD', 'EXPE', 'EXR', 'F', 'FANG', 'FAST', 'FCX', 'FDS', - 'FDX', 'FE', 'FFIV', 'FI', 'FICO', 'FIS', 'FITB', 'FLT', 'FMC', 'FOX', 'FOXA', - 'FRT', 'FSLR', 'FTNT', 'FTV', 'GD', 'GE', 'GEHC', 'GEN', 'GILD', 'GIS', 'GL', - 'GLW', 'GM', 'GNRC', 'GOOG', 'GOOGL', 'GPC', 'GPN', 'GRMN', 'GS', 'GWW', - 'HAL', 'HAS', 'HBAN', 'HCA', 'HD', 'HES', 'HIG', 'HII', 'HLT', 'HOLX', 'HON', - 'HPE', 'HPQ', 'HRL', 'HSIC', 'HST', 'HSY', 'HUBB', 'HUM', 'HWM', 'IBM', 'ICE', - 'IDXX', 'IEX', 'IFF', 'ILMN', 'INCY', 'INTC', 'INTU', 'INVH', 'IP', 'IPG', - 'IQV', 'IR', 'IRM', 'ISRG', 'IT', 'ITW', 'IVZ', 'J', 'JBHT', 'JCI', 'JKHY', - 'JNJ', 'JNPR', 'JPM', 'K', 'KDP', 'KEY', 'KEYS', 'KHC', 'KIM', 'KLAC', 'KMB', - 'KMI', 'KMX', 'KO', 'KR', 'KVUE', 'L', 'LDOS', 'LEN', 'LH', 'LHX', 'LIN', - 'LKQ', 'LLY', 'LMT', 'LNT', 'LOW', 'LRCX', 'LULU', 'LUV', 'LVS', 'LW', 'LYB', - 'LYV', 'MA', 'MAA', 'MAR', 'MAS', 'MCD', 'MCHP', 'MCK', 'MCO', 'MDLZ', 'MDT', - 'MET', 'META', 'MGM', 'MHK', 'MKC', 'MKTX', 'MLM', 'MMC', 'MMM', 'MNST', 'MO', - 'MOH', 'MOS', 'MPC', 'MPWR', 'MRK', 'MRNA', 'MRO', 'MS', 'MSCI', 'MSFT', - 'MSI', 'MTB', 'MTCH', 'MTD', 'MU', 'NCLH', 'NDAQ', 'NDSN', 'NEE', 'NEM', - 'NFLX', 'NI', 'NKE', 'NOC', 'NOW', 'NRG', 'NSC', 'NTAP', 'NTRS', 'NUE', - 'NVDA', 'NVR', 'NWS', 'NWSA', 'NXPI', 'O', 'ODFL', 'OKE', 'OMC', 'ON', 'ORCL', - 'ORLY', 'OTIS', 'OXY', 'PANW', 'PARA', 'PAYC', 'PAYX', 'PCAR', 'PCG', 'PEAK', - 'PEG', 'PEP', 'PFE', 'PFG', 'PG', 'PGR', 'PH', 'PHM', 'PKG', 'PLD', 'PM', - 'PNC', 'PNR', 'PNW', 'PODD', 'POOL', 'PPG', 'PPL', 'PRU', 'PSA', 'PSX', 'PTC', - 'PWR', 'PXD', 'PYPL', 'QCOM', 'QRVO', 'RCL', 'REG', 'REGN', 'RF', 'RHI', - 'RJF', 'RL', 'RMD', 'ROK', 'ROL', 'ROP', 'ROST', 'RSG', 'RTX', 'RVTY', 'SBAC', - 'SBUX', 'SCHW', 'SEDG', 'SEE', 'SHW', 'SJM', 'SLB', 'SNA', 'SNPS', 'SO', - 'SPG', 'SPGI', 'SRE', 'STE', 'STLD', 'STT', 'STX', 'STZ', 'SWK', 'SWKS', - 'SYF', 'SYK', 'SYY', 'T', 'TAP', 'TDG', 'TDY', 'TECH', 'TEL', 'TER', 'TFC', - 'TFX', 'TGT', 'TJX', 'TMO', 'TMUS', 'TPR', 'TRGP', 'TRMB', 'TROW', 'TRV', - 'TSCO', 'TSLA', 'TSN', 'TT', 'TTWO', 'TXN', 'TXT', 'TYL', 'UAL', 'UDR', 'UHS', - 'ULTA', 'UNH', 'UNP', 'UPS', 'URI', 'USB', 'V', 'VFC', 'VICI', 'VLO', 'VLTO', - 'VMC', 'VRSK', 'VRSN', 'VRTX', 'VTR', 'VTRS', 'VZ', 'WAB', 'WAT', 'WBA', - 'WBD', 'WDC', 'WEC', 'WELL', 'WFC', 'WHR', 'WM', 'WMB', 'WMT', 'WRB', 'WRK', - 'WST', 'WTW', 'WY', 'WYNN', 'XEL', 'XOM', 'XRAY', 'XYL', 'YUM', 'ZBH', 'ZBRA', - 'ZION', 'ZTS'] - -NDX100 = \ -['AAPL', 'ABNB', 'ADBE', 'ADI', 'ADP', 'ADSK', 'AEP', 'ALGN', 'AMAT', 'AMD', - 'AMGN', 'AMZN', 'ANSS', 'ASML', 'AVGO', 'AZN', 'BIIB', 'BKNG', 'BKR', 'CDNS', - 'CEG', 'CHTR', 'CMCSA', 'COST', 'CPRT', 'CRWD', 'CSCO', 'CSGP', 'CSX', 'CTAS', - 'CTSH', 'DDOG', 'DLTR', 'DXCM', 'EA', 'EBAY', 'ENPH', 'EXC', 'FANG', 'FAST', - 'FTNT', 'GEHC', 'GFS', 'GILD', 'GOOG', 'GOOGL', 'HON', 'IDXX', 'ILMN', 'INTC', - 'INTU', 'ISRG', 'JD', 'KDP', 'KHC', 'KLAC', 'LCID', 'LRCX', 'LULU', 'MAR', - 'MCHP', 'MDLZ', 'MELI', 'META', 'MNST', 'MRNA', 'MRVL', 'MSFT', 'MU', 'NFLX', - 'NVDA', 'NXPI', 'ODFL', 'ON', 'ORLY', 'PANW', 'PAYX', 'PCAR', 'PDD', 'PEP', - 'PYPL', 'QCOM', 'REGN', 'ROST', 'SBUX', 'SGEN', 'SIRI', 'SNPS', 'TEAM', - 'TMUS', 'TSLA', 'TTD', 'TXN', 'VRSK', 'VRTX', 'WBA', 'WBD', 'WDAY', 'XEL', - 'ZM', 'ZS'] - -DOW30 = \ -['AAPL', 'AMGN', 'AXP', 'BA', 'CAT', 'CRM', 'CSCO', 'CVX', 'DIS', 'DOW', 'GS', - 'HD', 'HON', 'IBM', 'INTC', 'JNJ', 'JPM', 'KO', 'MCD', 'MMM', 'MRK', 'MSFT', - 'NKE', 'PG', 'TRV', 'UNH', 'V', 'VZ', 'WBA', 'WMT'] - -if __name__ == '__main__': - - # import json - from pprint import pprint - - import bs4 as bs - import pandas as pd - import requests - - universes = { - 'sp500': { - 'page':"http://en.wikipedia.org/wiki/List_of_S%26P_500_companies", - 'table_number': 0, - 'column_number': 0, - }, - 'ndx100':{ - 'page':"https://en.wikipedia.org/wiki/Nasdaq-100", - 'table_number': -1, - 'column_number': 1, - }, - 'dow30':{ - 'page':"https://en.wikipedia.org/wiki/Dow_Jones_Industrial_Average", - 'table_number': 0, - 'column_number': 1, - } - } - - def get_column_wikipedia_page(page, table_number, column_number): - """Get a column as list of strings from a table on wikipedia. - - This is adapted from: - - https://pythonprogramming.net/sp500-company-price-data-python-programming-for-finance/ - - :param page: Wikipedia URL. - :type page: str - :param table_number: Which table on the page. - :type table_number: int - :param column_number: Which column to extract. - :type column_number: int - - :returns: Sorted strings of the column. - :rtype: list - """ - resp = requests.get(page, timeout=10) - soup = bs.BeautifulSoup(resp.text, 'lxml') - table = soup.find_all( - 'table', {'class': 'wikitable sortable'})[table_number] - column = [] - for row in table.findAll('tr')[1:]: - element = row.findAll('td')[column_number].text - column.append(element.strip()) - return sorted(column) - - def adapt_for_yahoo_finance(tickers_list): - """Change tickers to match the spelling of Yahoo Finance. - - :param tickers_list: Tickers from Wikipedia. - :type tickers_list: list - - :returns: Adapted tickers. - :rtype: list - """ - - return [el.replace('.', '-') for el in tickers_list] - - # re-write this file - - with open(__loader__.path, 'r', encoding='utf-8') as f: - this_file_content = f.readlines() - - code_index = this_file_content.index("if __name__ == '__main__':\n") - - with open(__loader__.path, 'w', encoding='utf-8') as f: - - # header - f.writelines(this_file_content[:13]) - - # docstring - f.write('"""' + __doc__ + '"""\n') - - # timestamp - f.write("\n# This was generated on " + str(pd.Timestamp.utcnow()) + "\n") - - # universes lists - for key, value in universes.items(): - - tickers = adapt_for_yahoo_finance( - get_column_wikipedia_page(**value)) - f.write(f'\n{key.upper()} = \\\n') - pprint(tickers, compact=True, width=79, stream=f) - - # # also save in json - # with open(key + '.json', 'w', encoding='utf-8') as f1: - # json.dump(tickers, f1) - - # copy everything in the if __name__ == '__main__' clause - f.write('\n') - f.writelines(this_file_content[code_index:]) diff --git a/pyproject.toml b/pyproject.toml index 2cca3b032..b3f10382f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,12 +53,7 @@ accept-no-yields-doc = false fail_under = 99 [tool.diff_cover] -# this will be superflous once we push coverage to 100 -compare_branch = "origin/master" -fail_under = 99 - -[tool.diff_quality] -# this will be superflous once we push pylint score to 10 +# this will be superflous once we push the above to 100 compare_branch = "origin/master" fail_under = 99 @@ -70,4 +65,4 @@ select = ["W291","W293","W391","E231","E225","E303"] # tweaked to remove whitespaces and other simple fixes wrap-summaries = 0 wrap-descriptions = 0 -tab-width = 4 +tab-width = 4 \ No newline at end of file From 7403171abf2d73ec8e01ef355e5b61a8c5a8aaed Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Thu, 14 Dec 2023 01:19:48 +0400 Subject: [PATCH 05/13] revert estimator.py for this branch --- cvxportfolio/estimator.py | 58 +++++++-------------------------------- 1 file changed, 10 insertions(+), 48 deletions(-) diff --git a/cvxportfolio/estimator.py b/cvxportfolio/estimator.py index ac14ebbcf..e52491b0a 100644 --- a/cvxportfolio/estimator.py +++ b/cvxportfolio/estimator.py @@ -91,57 +91,19 @@ def current_value(self): """ return self._current_value - - # pylint: disable=useless-type-doc,useless-param-doc - def values_in_time( - self, t, current_weights, current_portfolio_value, - past_returns, past_volumes, current_prices, - mpo_step=None, cache=None, **kwargs): - """Evaluate estimator at current time, possibly return current value. - - This method is usually the most important for Estimator classes. - It is called at each point in a back-test with all data of the current - state. Sub-estimators are evaluated first, in a depth-first recursive - tree fashion (defined in :meth:`values_in_time_recursive`). The - signature differs slightly between different estimators, see below. - - :param t: Current timestamp. - :type t: pandas.Timestamp - :param current_weights: Current allocation weights. - :type current_weights: pandas.Series - :param current_portfolio_value: Current total value of the portfolio - in cash units. - :type current_portfolio_value: float - :param past_returns: Past market returns (including cash). - :type past_returns: pandas.DataFrame - :param past_volumes: Past market volumes, or None if not available. - :type past_volumes: pandas.DataFrame or None - :param current_prices: Current (open) prices, or None if not available. - :type current_prices: pandas.Series or None - :param mpo_step: For :class:`cvxportfolio.MultiPeriodOptimization` - which step in future planning this estimator is at: 0 is for - the current step (:class:`cvxportfolio.SinglePeriodOptimization`), - 1 is for day ahead, .... Defaults to ``None`` if unused. - :type mpo_step: int, optional - :param cache: Cache or workspace shared between all elements of an - estimator tree, currently only used by - :class:`cvxportfolio.MultiPeriodOptimization` (and derived - classes). It's useful to avoid re-computing expensive - things like covariance estimates at different MPO steps. - Defaults to ``None`` if unused. - :type cache: dict, optional - :param kwargs: Reserved for future new features. - :type kwargs: dict - - :returns: Current value of the estimator. - :rtype: object or None - """ - # we don't raise NotImplementedError because this is called - # on classes that don't re-define it - def values_in_time_recursive(self, **kwargs): """Evaluate recursively on sub-estimators. + This function is called by Simulator classes on Policy classes + returning the current trades list. Policy classes, if they + contain internal estimators, should declare them as attributes + and call this base function (via `super()`) before they do their + internal computation. CvxpyExpression estimators should instead + define this method to update their Cvxpy parameters. + + Once we finalize the interface all parameters will be listed + here. + :param kwargs: Various parameters that are passed to all elements contained in a policy object. :type kwargs: dict From 894369c6be11cf32f983c67ec85bc6080e0dac2d Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Thu, 14 Dec 2023 12:30:38 +0400 Subject: [PATCH 06/13] todos roadmap --- TODOs.md | 35 -------------------------- TODOs_ROADMAP.rst | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 35 deletions(-) delete mode 100644 TODOs.md create mode 100644 TODOs_ROADMAP.rst diff --git a/TODOs.md b/TODOs.md deleted file mode 100644 index f3f236022..000000000 --- a/TODOs.md +++ /dev/null @@ -1,35 +0,0 @@ -# Features & refactoring - -### `cache.py` - -- probably to be dropped; should use `_loader_*` and `_storer_*` from `data.py` - -### `forecast.py` - -- cache logic needs improvement, not easily exposable to third-parties now with `dataclass.__hash__` - - drop decorator - - drop dataclass - - cache IO logic should be managed by forecaster not by simulator, could be done by `initialize_estimator`; maybe enough to just - define it in the base class of forecasters -- improve names of internal methods, clean them (lots of stuff can be re-used at universe change, ...) -- generalize the mean estimator: - - use same code for `past_returns`, `past_returns**2`, `past_volumes`, ... - - add rolling window option, should be in `pd.Timedelta` - - add exponential moving avg, should be in half-life `pd.Timedelta` -- add same extras to the covariance estimator -- goal: make this module crystal clear; third-party ML models should use it (at least for caching) - -### `estimator.py` - -- `DataEstimator` needs refactoring, too long and complex methods - - - -### Development & testing -- add extra pylint checkers: - - code complexity -- consider removing downloaded data from `test_simulator.py`, so only `test_data.py` requires internet - -## Documentation - -## Examples diff --git a/TODOs_ROADMAP.rst b/TODOs_ROADMAP.rst new file mode 100644 index 000000000..dafce531a --- /dev/null +++ b/TODOs_ROADMAP.rst @@ -0,0 +1,64 @@ +TODOs and ROADMAP +================= + +Cvxportfolio follows the `semantic versioning `_ +specification for changes in its public API. The public API is defined +as: + +- public methods, *i.e.*, without a leading underscore ``_`` +- methods and objects clearly documented as such and/or used in the examples. + +*Internal* methods that are used by +Cvxportfolio objects to communicate with each other (or other tasks) and don't +have a leading underscore, are considered public +if they are exposed through the HTML documentation +and are used in the examples. + +In this document we list the planned +changes, in particular the ones that are relevant for semantic versioning, and +their planned release. + +``cvxportfolio.cache`` +---------------------- +- [ ] Not part of public API; to be redesigned and probably dropped. Should use + `_loader_*` and `_storer_*` from `data.py`. Target ``1.1.0``. + +``cvxportfolio.forecast`` +------------------------- + +- cache logic needs improvement, not easily exposable to third-parties now with ``dataclass.__hash__`` + - drop decorator + - drop dataclass + - cache IO logic should be managed by forecaster not by simulator, could be done by ``initialize_estimator``; maybe enough to just + define it in the base class of forecasters +- improve names of internal methods, clean them (lots of stuff can be re-used at universe change, ...) +- generalize the mean estimator: + - use same code for ``past_returns``, ``past_returns**2``, ``past_volumes``, ... + - add rolling window option, should be in ``pd.Timedelta`` + - add exponential moving avg, should be in half-life ``pd.Timedelta`` +- add same extras to the covariance estimator +- goal: make this module crystal clear; third-party ML models should use it (at least for caching) + +``cvxportfolio.estimator`` +-------------------------- + +- [ ] ``DataEstimator`` needs refactoring, too long and complex methods. Target + ``1.0.4``. +- [ ] ``Estimator`` could get a ``finalize_estimator`` method used for storing + data, like risk models on disk. + +Development & testing +--------------------- +- Add extra pylint checkers. + - Code complexity. Target ``1.0.4``. +- Consider removing downloaded data from ``test_simulator.py``, + so only ``test_data.py`` requires internet. + +Documentation +------------- + +Examples +-------- +- [ ] Restore examples from paper. Target ``1.0.4``, PR #118. +- [ ] Expose more (all?) examples through HTML docs. Target ``1.0.4``, PR #118. +- [ ] Consider making examples a package that can be pip installed. From a303eeadac41a5f27b95e23daf54267305bb42f0 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Thu, 14 Dec 2023 12:51:56 +0400 Subject: [PATCH 07/13] todos roadmap --- TODOs_ROADMAP.rst | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/TODOs_ROADMAP.rst b/TODOs_ROADMAP.rst index dafce531a..02c0d045d 100644 --- a/TODOs_ROADMAP.rst +++ b/TODOs_ROADMAP.rst @@ -20,8 +20,9 @@ their planned release. ``cvxportfolio.cache`` ---------------------- + - [ ] Not part of public API; to be redesigned and probably dropped. Should use - `_loader_*` and `_storer_*` from `data.py`. Target ``1.1.0``. + ``_loader_*`` and ``_storer_*`` from ``cvxportfolio.data``. Target ``1.1.0``. ``cvxportfolio.forecast`` ------------------------- @@ -30,7 +31,7 @@ their planned release. - drop decorator - drop dataclass - cache IO logic should be managed by forecaster not by simulator, could be done by ``initialize_estimator``; maybe enough to just - define it in the base class of forecasters + define it in the base class of forecasters - improve names of internal methods, clean them (lots of stuff can be re-used at universe change, ...) - generalize the mean estimator: - use same code for ``past_returns``, ``past_returns**2``, ``past_volumes``, ... @@ -43,22 +44,40 @@ their planned release. -------------------------- - [ ] ``DataEstimator`` needs refactoring, too long and complex methods. Target - ``1.0.4``. -- [ ] ``Estimator`` could get a ``finalize_estimator`` method used for storing - data, like risk models on disk. + ``1.0.4``. +- ``Estimator`` could define base logic for on-disk caching. By itself it + wouldn't do anything, actual functionality implemented by forecasters' base + class. + - [ ] ``initialize_estimator`` could get optional market data partial + signature for caching. Default None, no incompatible change. + - [ ] Could get a ``finalize_estimator`` method used for storing + data, like risk models on disk, doesn't need arguments; it can use the + partial signature got above. No incompatible change. + +``cvxportfolio.simulator`` +-------------------------- + +``cvxportfolio.risks`` +---------------------- + +``cvxportfolio.policies`` +------------------------- Development & testing --------------------- + - Add extra pylint checkers. + - Code complexity. Target ``1.0.4``. - Consider removing downloaded data from ``test_simulator.py``, - so only ``test_data.py`` requires internet. + so only ``test_data.py`` requires internet. Documentation ------------- Examples -------- + - [ ] Restore examples from paper. Target ``1.0.4``, PR #118. - [ ] Expose more (all?) examples through HTML docs. Target ``1.0.4``, PR #118. - [ ] Consider making examples a package that can be pip installed. From 73bc4381492f7e80a598351b046a8cc1e2cacfc6 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Thu, 14 Dec 2023 13:38:04 +0400 Subject: [PATCH 08/13] todos roadmap --- .gitignore | 3 +++ TODOs_ROADMAP.rst | 49 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 6e63296c2..8d1ae4d89 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,9 @@ ENV/ # Spyder project settings .spyderproject +# Codium +.vscode/* + # Rope project settings .ropeproject diff --git a/TODOs_ROADMAP.rst b/TODOs_ROADMAP.rst index 02c0d045d..f6c30362a 100644 --- a/TODOs_ROADMAP.rst +++ b/TODOs_ROADMAP.rst @@ -10,9 +10,8 @@ as: *Internal* methods that are used by Cvxportfolio objects to communicate with each other (or other tasks) and don't -have a leading underscore, are considered public -if they are exposed through the HTML documentation -and are used in the examples. +have a leading underscore, are considered public if they are exposed through +the HTML documentation and/or are used in the examples. In this document we list the planned changes, in particular the ones that are relevant for semantic versioning, and @@ -28,12 +27,14 @@ their planned release. ------------------------- - cache logic needs improvement, not easily exposable to third-parties now with ``dataclass.__hash__`` + - drop decorator - drop dataclass - cache IO logic should be managed by forecaster not by simulator, could be done by ``initialize_estimator``; maybe enough to just - define it in the base class of forecasters + define it in the base class of forecasters - improve names of internal methods, clean them (lots of stuff can be re-used at universe change, ...) - generalize the mean estimator: + - use same code for ``past_returns``, ``past_returns**2``, ``past_volumes``, ... - add rolling window option, should be in ``pd.Timedelta`` - add exponential moving avg, should be in half-life ``pd.Timedelta`` @@ -47,12 +48,16 @@ their planned release. ``1.0.4``. - ``Estimator`` could define base logic for on-disk caching. By itself it wouldn't do anything, actual functionality implemented by forecasters' base - class. + class. + - [ ] ``initialize_estimator`` could get optional market data partial - signature for caching. Default None, no incompatible change. + signature for caching. Default None, no incompatible change. - [ ] Could get a ``finalize_estimator`` method used for storing - data, like risk models on disk, doesn't need arguments; it can use the - partial signature got above. No incompatible change. + data, like risk models on disk, doesn't need arguments; it can use the + partial signature got above. No incompatible change. + +``cvxportfolio.data`` +-------------------------- ``cvxportfolio.simulator`` -------------------------- @@ -63,18 +68,40 @@ their planned release. ``cvxportfolio.policies`` ------------------------- +Optimization policies +~~~~~~~~~~~~~~~~~~~~~ + +- [ ] Improve behavior for infeasibility/unboundedness/solver error. Target + ``1.1.0``. + +``cvxportfolio.constraints`` +---------------------------- + +- [ ] Add missing constraints from the paper. Target ``1.1.0``. +- [ ] Make ``MarketNeutral`` accept arbitrary benchmark (policy object). + +``cvxportfolio.result`` +----------------------- + +- [ ] Make ``BackTestResult`` interface methods with ``MarketSimulator`` + public. + + Development & testing --------------------- -- Add extra pylint checkers. +- [ ] Add extra pylint checkers. - - Code complexity. Target ``1.0.4``. -- Consider removing downloaded data from ``test_simulator.py``, + - [ ] Code complexity. Target ``1.0.4``. +- [ ] Consider removing downloaded data from ``test_simulator.py``, so only ``test_data.py`` requires internet. Documentation ------------- +- Manual +- Quickstart + Examples -------- From b406a927dbdf9c5e0af349ad93662eea6c40912c Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Fri, 15 Dec 2023 12:27:44 +0400 Subject: [PATCH 09/13] readme --- README.rst | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 9e86fd83f..144027cbe 100644 --- a/README.rst +++ b/README.rst @@ -15,25 +15,59 @@ The documentation of the library is at Installation ------------ -You can install our latest release with +Cvxportolio is written in Python and can easily installed in any Python +environment by simple: -.. code:: bash +.. code:: console pip install -U cvxportfolio You can see how this works on our `Installation and Hello -World `__ youtube video. +World `__ youtube video. +Anaconda installs +`are also supported `_. +Its main dependencies are `Cvxpy `_ for interfacing +with numerical solvers and `Pandas `_ +for interfacing with databases. We don't require any specific version of our +dependencies and test against all recent ones (up to a few years ago). -Testing locally ---------------- -After installing you can run our unit test suite in you local -environment by +Versioning and releases +----------------------- -.. code:: bash +Cvxportfolio follows the `semantic versioning ` +specification. No breaking change in its public API will be introduced +until the next major version (``2.0.0``), which won't happen for some time. +New features in the public API are introduced with minor versions +(``1.1.0``, ``1.2.0``, ...), and only bug fixes at each revision. + +The history of our releases (source distributions and wheels) is visible on our +`PyPI page `_. + +Releases are also tagged in our git repository and include a short summary +of changes in +`their commit messages `_. + +We maintain a document listing the planned changes and target releases +`here `_. + + +Testing +------- + +After installing you can run our unit test suite in you local environment by + +.. code:: console python -m cvxportfolio.tests +We test against recent python versions (3.9, 3.10, 3.11) and recent versions +of the main dependencies (from pandas 1.4, cvxpy 1.1, ..., up to the current +versions) on all major operating systems. So, Cvxportfolio doesn't require +any specific version of any dependency, and should work in any pre-existing +environment. You can see the automated testing code +`here `_. + Simple Example -------------- From 2ef5e3ab99e7d78a6a2faa02c702afd013252e0d Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Fri, 15 Dec 2023 12:47:23 +0400 Subject: [PATCH 10/13] readme --- README.rst | 74 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/README.rst b/README.rst index 144027cbe..5b27ffb6b 100644 --- a/README.rst +++ b/README.rst @@ -31,29 +31,8 @@ with numerical solvers and `Pandas `_ for interfacing with databases. We don't require any specific version of our dependencies and test against all recent ones (up to a few years ago). - -Versioning and releases ------------------------ - -Cvxportfolio follows the `semantic versioning ` -specification. No breaking change in its public API will be introduced -until the next major version (``2.0.0``), which won't happen for some time. -New features in the public API are introduced with minor versions -(``1.1.0``, ``1.2.0``, ...), and only bug fixes at each revision. - -The history of our releases (source distributions and wheels) is visible on our -`PyPI page `_. - -Releases are also tagged in our git repository and include a short summary -of changes in -`their commit messages `_. - -We maintain a document listing the planned changes and target releases -`here `_. - - -Testing -------- +Test +---- After installing you can run our unit test suite in you local environment by @@ -63,11 +42,10 @@ After installing you can run our unit test suite in you local environment by We test against recent python versions (3.9, 3.10, 3.11) and recent versions of the main dependencies (from pandas 1.4, cvxpy 1.1, ..., up to the current -versions) on all major operating systems. So, Cvxportfolio doesn't require -any specific version of any dependency, and should work in any pre-existing -environment. You can see the automated testing code +versions) on all major operating systems. You can see the automated testing code `here `_. + Simple Example -------------- @@ -127,6 +105,40 @@ how a simple sweep over hyper-parameters, taking advantage of our sophisticated parallel backtest machinery, quickly provides results on the best strategy to apply to any given selection of assets. + +Versions and releases +--------------------- + +Cvxportfolio follows the `semantic versioning `_ +specification. No breaking change in its public API will be introduced +until the next major version (``2.0.0``), which won't happen for some time. +New features in the public API are introduced with minor versions +(``1.1.0``, ``1.2.0``, ...), and only bug fixes at each revision. + +The history of our releases (source distributions and wheels) is visible on our +`PyPI page `_. + +Releases are also tagged in our git repository and include a short summary +of changes in +`their commit messages `_. + +We maintain a document listing the planned changes and target releases +`here `_. + + +Contributions +------------- + +We welcome contributors and you don't need to sign a CLA. If you don't have +a Github account you may also send a +`git patch via email `_ to the project +maintainer. + +Bug fixes, improvements in the documentations and examples, +new constraints, new cost objects, ..., are good contributions and can be done +even if you're not familiar with the low-level details on the library. + + Development ----------- @@ -135,7 +147,7 @@ repository (or, `fork on Github `__ and then clone your fork) -.. code:: bash +.. code:: console git clone https://github.com/cvxgrp/cvxportfolio.git cd cvxportfolio @@ -145,7 +157,7 @@ Then, you should have a look at our and possibly change the ``PYTHON`` variable to match your system’s python interpreter. Once you have done that, -.. code:: bash +.. code:: console make env make test @@ -157,7 +169,7 @@ test suite. You activate the shell environment with one of scripts in ``env/bin`` (or ``env\Scripts`` on Windows), for example if you use bash on POSIX -.. code:: bash +.. code:: console source env/bin/activate @@ -171,13 +183,14 @@ Windows) like we do in the Makefile. Additionally, to match our CI/CD pipeline, you may set the following `git hooks `__ -.. code:: bash +.. code:: console echo "make lint" > .git/hooks/pre-commit chmod +x .git/hooks/pre-commit echo "make test" > .git/hooks/pre-push chmod +x .git/hooks/pre-push + Examples from the paper ----------------------- @@ -187,6 +200,7 @@ the paper. As you may see from those ipython notebooks a lot of the logic that was implemented there, outside of Cvxportfolio proper, is being included and made automatic in newer versions of Cvxportfolio. + Citing ------------ From d1034db0d1f8e9eab6df23a55829c277454a6815 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Fri, 15 Dec 2023 13:24:43 +0400 Subject: [PATCH 11/13] including readme in docs --- README.rst | 72 +++++++++++++++++++++--------------- docs/contributing.rst | 15 ++++++++ docs/index.rst | 85 +++---------------------------------------- 3 files changed, 64 insertions(+), 108 deletions(-) create mode 100644 docs/contributing.rst diff --git a/README.rst b/README.rst index 5b27ffb6b..ceb6c7d52 100644 --- a/README.rst +++ b/README.rst @@ -12,6 +12,9 @@ paper `__. The documentation of the library is at `www.cvxportfolio.com `__. + +.. Installation + Installation ------------ @@ -26,11 +29,15 @@ You can see how this works on our `Installation and Hello World `__ youtube video. Anaconda installs `are also supported `_. -Its main dependencies are `Cvxpy `_ for interfacing -with numerical solvers and `Pandas `_ + +Cvxportfolio's main dependencies are `Cvxpy `_ for +interfacing with numerical solvers and `Pandas `_ for interfacing with databases. We don't require any specific version of our dependencies and test against all recent ones (up to a few years ago). + +.. Test + Test ---- @@ -42,9 +49,11 @@ After installing you can run our unit test suite in you local environment by We test against recent python versions (3.9, 3.10, 3.11) and recent versions of the main dependencies (from pandas 1.4, cvxpy 1.1, ..., up to the current -versions) on all major operating systems. You can see the automated testing code -`here `_. +versions) on all major operating systems. You can see the `automated testing code +`_. + +.. Simple Example Simple Example -------------- @@ -106,25 +115,16 @@ sophisticated parallel backtest machinery, quickly provides results on the best strategy to apply to any given selection of assets. -Versions and releases ---------------------- - -Cvxportfolio follows the `semantic versioning `_ -specification. No breaking change in its public API will be introduced -until the next major version (``2.0.0``), which won't happen for some time. -New features in the public API are introduced with minor versions -(``1.1.0``, ``1.2.0``, ...), and only bug fixes at each revision. - -The history of our releases (source distributions and wheels) is visible on our -`PyPI page `_. - -Releases are also tagged in our git repository and include a short summary -of changes in -`their commit messages `_. +Examples from the paper +----------------------- -We maintain a document listing the planned changes and target releases -`here `_. +In branch `0.0.X `__ +you can find the original material used to generate plots and results in +the paper. As you may see from those ipython notebooks a lot of the +logic that was implemented there, outside of Cvxportfolio proper, is +being included and made automatic in newer versions of Cvxportfolio. +.. Contributions Contributions ------------- @@ -191,15 +191,29 @@ Additionally, to match our CI/CD pipeline, you may set the following chmod +x .git/hooks/pre-push -Examples from the paper ------------------------ +.. Versions + +Versions and releases +--------------------- + +Cvxportfolio follows the `semantic versioning `_ +specification. No breaking change in its public API will be introduced +until the next major version (``2.0.0``), which won't happen for some time. +New features in the public API are introduced with minor versions +(``1.1.0``, ``1.2.0``, ...), and only bug fixes at each revision. + +The history of our releases (source distributions and wheels) is visible on our +`PyPI page `_. + +Releases are also tagged in our git repository and include a short summary +of changes in +`their commit messages `_. + +We maintain a `document listing the planned changes and target releases +`_. -In branch `0.0.X `__ -you can find the original material used to generate plots and results in -the paper. As you may see from those ipython notebooks a lot of the -logic that was implemented there, outside of Cvxportfolio proper, is -being included and made automatic in newer versions of Cvxportfolio. +.. Citing Citing ------------ @@ -248,7 +262,7 @@ The latter is also the first chapter of this PhD thesis: Licensing --------- -Cvxportfolio is licensed under the `Apache 2.0 `_ permissive +Cvxportfolio is licensed under the `Apache 2.0 `_ permissive open source license. .. |CVXportfolio on PyPI| image:: https://img.shields.io/pypi/v/cvxportfolio.svg diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 000000000..67e59e66c --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,15 @@ +Contributing to Cvxportfolio +============================ + +Cvxportfolio is a collaborative project. Its development is hosted +on Github at `this repository `_. + +You can open bug reports using the +`issue tracking `_ +system there. + +The following is copied from the README file on the repository. + +.. include:: ../README.rst + :start-after: .. Contributions + :end-before: .. Versions \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index a01df50f7..2de020c7b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,22 +11,11 @@ naming conventions, and assumptions, are described in the accompanying `paper`_. This was written as a collaborative work by Stanford University researchers and BlackRock Inc. investment professionals. - -Installation ------------- - -Cvxportolio is written in Python and can be easily installed in any environment by simple: - -.. code-block:: console - - pip install -U cvxportfolio - -We show how this is done on our `Installation and Hello World `_ youtube video. -Its main dependencies are `Cvxpy `_ for interfacing -with numerical solvers and `Pandas `_ -for interfacing with databases. +.. include:: ../README.rst + :start-after: .. Installation + :end-before: .. Simple Example Hello World Example ------------------- @@ -67,72 +56,9 @@ estimates of covariance matrices. We present the results of each back-test with a clear interface, :class:`cvxportfolio.BacktestResult`, which defines various metrics of backtest performance and the logic to both print and plot them. - - -Testing locally ---------------- -We ship our unit test suite with the software library, so after installing you can test -in your local environment with: - -.. code-block:: console - - python -m cvxportfolio.tests - -We test against recent python versions (3.9, 3.10, 3.11) and recent versions of the main -dependencies (from pandas 1.4, cvxpy 1.1, ..., up to the current versions) on all major -operating systems. So, Cvxportfolio doesn't require any specific version of -any dependency, and should work in any pre-existing environment. -Licensing ---------- - -Cvxportfolio is licensed under the `Apache 2.0 `_ permissive -open source license. - - -Citing ------------- - -If you use Cvxportfolio in work that leads to publication, you can cite the following: - -.. code-block:: bibtex - - @misc{busseti2017cvx, - author = "Busseti, Enzo and Diamond, Steven and Boyd, Stephen", - title = "Cvxportfolio", - month = "January", - year = "2017", - note = "Portfolio Optimization and Back--{T}esting", - howpublished = {\url{https://github.com/cvxgrp/cvxportfolio}}, - } - - @article{boyd2017multi, - author = "Boyd, Stephen and Busseti, Enzo and Diamond, Steven and Kahn, Ron and Nystrup, Peter and Speth, Jan", - journal = "Foundations and Trends in Optimization", - title = "Multi--{P}eriod Trading via Convex Optimization", - month = "August", - year = "2017", - number = "1", - pages = "1--76", - volume = "3", - url = {\url{https://stanford.edu/~boyd/papers/pdf/cvx_portfolio.pdf}}, - } - - -The latter is also the first chapter of this PhD thesis: - -.. code-block:: bibtex - - @phdthesis{busseti2018portfolio, - author = "Busseti, Enzo", - title = "Portfolio Management and Optimal Execution via Convex Optimization", - school = "Stanford University", - address = "Stanford, California, USA", - month = "May", - year = "2018", - url = {\url{https://stacks.stanford.edu/file/druid:wm743bj5020/thesis-augmented.pdf}}, - } - +.. include:: ../README.rst + :start-after: .. Versions Table of Contents ----------------- @@ -151,6 +77,7 @@ Table of Contents data internals examples + contributing .. _paper: https://stanford.edu/~boyd/papers/pdf/cvx_portfolio.pdf .. _FRED: https://fred.stlouisfed.org/ From 4edebec665eb639e4981f8c07b44081b89e729c5 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Fri, 15 Dec 2023 13:51:40 +0400 Subject: [PATCH 12/13] readme --- README.rst | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index ceb6c7d52..8678f6203 100644 --- a/README.rst +++ b/README.rst @@ -55,7 +55,7 @@ versions) on all major operating systems. You can see the `automated testing cod .. Simple Example -Simple Example +Simple example -------------- In the following example market data is downloaded by a public source @@ -94,8 +94,23 @@ includes holding and transaction costs, using the models described in the paper, and default parameters that are typical for the US stock market. -Some Other Examples -------------------- +Other examples +-------------- + +`Many examples +`_ +are shown in the documentation website, along with +their output and comments. + +`Even more example scripts +`_ +are available in the code repository. + +`The original examples from the paper +`_ +are visible in a dedicated branch, +and are being translated to run with the stable versions (``1.0.*``) of the +library. We show in the example on `user-provided forecasters `__ @@ -115,15 +130,6 @@ sophisticated parallel backtest machinery, quickly provides results on the best strategy to apply to any given selection of assets. -Examples from the paper ------------------------ - -In branch `0.0.X `__ -you can find the original material used to generate plots and results in -the paper. As you may see from those ipython notebooks a lot of the -logic that was implemented there, outside of Cvxportfolio proper, is -being included and made automatic in newer versions of Cvxportfolio. - .. Contributions Contributions @@ -191,6 +197,21 @@ Additionally, to match our CI/CD pipeline, you may set the following chmod +x .git/hooks/pre-push +Code style and quality +---------------------- + +We follow the `PEP8 `_ specification for +code style. This is enforced by the `Pylint +`_ automated linter, with options +in the `Pyproject +`_ +configuration file. +Pylint is also used to enforce code quality standards, along with some of its +optional plugins. +Docstrings are written in the `Sphinx style +`_, are also checked by +Pylint, and are used to generate the documentation. + .. Versions Versions and releases From 66e7df2b664ae06a9006a5bfebb5eaee6d61e968 Mon Sep 17 00:00:00 2001 From: Enzo Busseti Date: Fri, 15 Dec 2023 13:56:13 +0400 Subject: [PATCH 13/13] readme --- README.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 8678f6203..543efd6f6 100644 --- a/README.rst +++ b/README.rst @@ -137,13 +137,16 @@ Contributions We welcome contributors and you don't need to sign a CLA. If you don't have a Github account you may also send a -`git patch via email `_ to the project -maintainer. +`git patch via email `_ to the +`project maintainer `_. Bug fixes, improvements in the documentations and examples, new constraints, new cost objects, ..., are good contributions and can be done even if you're not familiar with the low-level details on the library. - +For more advanced contributions we recommend reading the +`TODOs and roadmap +`_ +file. Development ----------- @@ -200,8 +203,8 @@ Additionally, to match our CI/CD pipeline, you may set the following Code style and quality ---------------------- -We follow the `PEP8 `_ specification for -code style. This is enforced by the `Pylint +Cvxportfolio follows the `PEP8 `_ +specification for code style. This is enforced by the `Pylint `_ automated linter, with options in the `Pyproject `_