{"id":20305934,"url":"https://github.com/vivekpa/optimalportfolio","last_synced_at":"2025-04-05T14:05:56.252Z","repository":{"id":39667257,"uuid":"157863773","full_name":"VivekPa/OptimalPortfolio","owner":"VivekPa","description":"An open source library for portfolio optimisation","archived":false,"fork":false,"pushed_at":"2024-02-27T21:38:36.000Z","size":7663,"stargazers_count":360,"open_issues_count":4,"forks_count":92,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-03-29T13:09:01.266Z","etag":null,"topics":["algorithmic-trading","portfolio-management","portfolio-optimization","quantitative-finance","risk-management","risk-modelling"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VivekPa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-16T12:20:25.000Z","updated_at":"2025-03-17T22:11:19.000Z","dependencies_parsed_at":"2024-11-14T17:10:53.207Z","dependency_job_id":"a13737ea-1407-4736-a0c1-387cc4a888cc","html_url":"https://github.com/VivekPa/OptimalPortfolio","commit_stats":{"total_commits":86,"total_committers":3,"mean_commits":"28.666666666666668","dds":0.03488372093023251,"last_synced_commit":"cb27cbc6f0832bfc531c085454afe1ca457ea95e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VivekPa%2FOptimalPortfolio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VivekPa%2FOptimalPortfolio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VivekPa%2FOptimalPortfolio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VivekPa%2FOptimalPortfolio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VivekPa","download_url":"https://codeload.github.com/VivekPa/OptimalPortfolio/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247345852,"owners_count":20924102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithmic-trading","portfolio-management","portfolio-optimization","quantitative-finance","risk-management","risk-modelling"],"created_at":"2024-11-14T17:10:48.070Z","updated_at":"2025-04-05T14:05:56.227Z","avatar_url":"https://github.com/VivekPa.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Optimal Portfolio\n\n\u003cp align=\"left\"\u003e\n    \u003ca href=\"https://www.python.org/\"\u003e\n        \u003cimg src=\"https://ForTheBadge.com/images/badges/made-with-python.svg\"\n            alt=\"python\"\u003e\u003c/a\u003e \u0026nbsp;\n    \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square\"\n            alt=\"MIT license\"\u003e\u003c/a\u003e \u0026nbsp;\n\u003c/p\u003e\n\n**OptimalPortfolio** is an open source library for portfolio optimisation. This library implements classical portfolio optimisation techniques for equities, but is also extendable for non-equity products given the right adjustments in invariants. Furthermore, certain modern advances in portfolio optimisation, such as Hierarchical Risk Parity is also implemented. \n\nRegardless of whether you are a fundamental investor, or an algorithmic trader, this library can aid you in allocating your capital in the most risk efficient way, allowing to optimise your utility. *For more details on the project design and similar content, please check out [Engineer Quant](https://medium.com/engineer-quant)*\n\n*Disclaimer: This is not trading or investment advice. Trading involves significant risk and do so at your risk.*\n\n\n## Contents\n- [Contents](#contents)\n- [Overview](#overview)\n- [Full Sequence](#full-sequence)\n- [Functionality](#functionality)\n  - [Expected Returns](#expected-returns)\n  - [Risk Models](#risk-models)\n  - [Objective Functions](#objective-functions)\n  - [Constraints](#constraints)\n- [Market Invariants](#market-invariants)\n- [Moment Estimation](#moment-estimation)\n    - [Nonparametric Estimators](#nonparametric-estimators)\n    - [Maximum Likelihood Estimators](#maximum-likelihood-estimators)\n    - [Shrinkage Estimators](#shrinkage-estimators)\n- [Optimal Allocations](#optimal-allocations)\n    - [Higher Moment Optimisation](#higher-moment-optimisation)\n    - [Compared to Sharpe Ratio](#compared-to-sharpe-ratio)\n- [Roadmap](#roadmap)\n\n## Overview\nThis library aims to make optimising portfolios accessible to every trader and investor. To install this library, download it and run\n```bash\nbash install.sh\n```\n\n## Full Sequence\nThe pipeline for using this library is delibrately modular, so as to allow users to incorporate their own proprietary stacks and code into the optimisation. The full sequence from data to weights is in the ``examples/full_sequence.ipynb`` notebook, but for clarity, it is\n\n- Acquire stock (or other asset class) data\n- Calculate market invariants (for stocks is returns)\n- Calculate moments of invariants (for simple mean-variance optimisation it is mean and covariance)\n- Optimise weights according to a utility function, subject to constraints, with moments of invariants as inputs\n\n```python\nimport pandas as pd\nimport numpy as np\n\nimport PortOpt.invariants as invs\nimport PortOpt.moment_est as moments\nfrom PortOpt.opt_allocations import Optimiser\nimport PortOpt.utility_functions as utils\n\ntickers = ['AAPL', 'MSFT', 'CVX', 'GE', 'GOOGL']\nstock_data = utils.get_data(tickers, '1d', '2015-01-01', '2021-01-01')\n\n# Compute market invariants\n\nstock_returns = invs.stock_invariants(stock_data)\n\nriskmodel = moments.RiskModel(tickers)\nstock_cov = riskmodel.avg_hist_cov(stock_data)\n\n# Optimise weights according to mean-variance\n\noptimiser = Optimiser(tickers, exp_returns, stock_cov)\nweights = optimiser.mean_variance(threshold=0.1, type='variance')\nprint(weights)\nweight_tearsheet(weights)\n```\n\nThis should have the following output:\n```txt\nAAPL     0.182566\nMSFT     0.143655\nCVX      0.190582\nGE       0.117543\nGOOGL    0.365654\n\nAnnual Return: 15.026\nAnnual Volatility: 23.203\nAnnual Sharpe: 0.561\n```\n\n## Functionality\n\nListed below is the current overall functionality of the library, split into the various parts of the pipeline.\n\n### Expected Returns\n- Nonparametric\n  - Mean Historical Returns\n  - Mean Exponentially Weighted Returns\n  - Capital Asset Pricing Model (CAPM)\n- Shrinkage\n  - James-Stein Mean Shrinkage\n\n### Risk Models\n- Nonparametric\n  - Historical Covariance\n  - Exponentially Weighted Covariance\n- Covariance Shrinkage\n    - Identity Shrinkage\n    - Scaled Variance Shrinkage\n    - Ledoit-Wolf Single Index\n    - Ledoit-Wolf Constant Correlation\n\n### Objective Functions\n- Mean-Variance Optimisation\n  - Maximise Returns with a risk limit\n  - Minimise risk with a returns limit\n- Minimum Volatility\n- Maximum Sharpe Ratio\n\n### Constraints\n- Long Short Neutral\n- Maximum Position on an asset\n\n### Adding Custom Constraints/Objectives\nSince constraints are very case specific, I have added a functionality to be able to add your own constraints to the optimisation problem. We add a long/short neutral constraint using the custom method to demonstrate\n\n```python\noptimiser = Optimiser(tickers, exp_returns, stock_cov)\n\n# Add LS neutral constraint\noptimiser.add_constraints([lambda x: cp.sum(x) == 0])\n\n# Optimise for mean-variance\nweights = optimiser.mean_variance(threshold=0.1, type='variance')\nprint(weights)\nweight_tearsheet(weights)\n```\n\n## Market Invariants\nThe first step to optimising any portfolio is calculating market invariants. Market invariants are defined as aspects of market prices that have some determinable statistical behaviour over time. For stock prices, the compounded returns are the market invariants. So when we calculate these invariants, we can statistically model them and gain useful insight into their behaviour. So far, calculating market invariants of stock prices. The same can be implemented for options and bonds, but data acquisition is an issue.\n\n## Moment Estimation\nOnce the market invariants have been calculated, it is time to model the statistical properties of the invariants. This is an actively researched and studied field and due to the nature of the complexity involved in modelling the statistical properties of large market data, there are several limitations in estimating the moments of the distributions.\n\n### Nonparametric Estimators\nThe simplest method of estimating the mean and covariance of invariants are the sample mean and covariance. However, this can be extended by introducing weightage for the timestamps, i.e giving more weight to recent data than older data. One interesting approach I have taken is introducing exponentially weighted mean and covariance, which I read about [here](https://reasonabledeviations.science/2018/08/15/exponential-covariance/).\n\n\u003c!-- ### Maximum Likelihood Estimators\nMaximum likelihood estimators (MLE) are intended to maximise the probability that the data points occur within a prescribed distribution. The procedure hence involves choosing a distribution or a class of distributions and then fitting the data to the distribution such that the log probability of the data points are maximised by the parameters of the distributions. This will in turn give us the optimal estimators of the distribution for market invariants. MLE has been implemented for the following distributions:\n\n- Multivariate Normal\n- Multivariate Student t\n\nThe MLE estimate for Student-t distribution is computed using Expectation Maximisation (EM)\nalgorithm.  --\u003e\n\n### Shrinkage Estimators\nNonparametric estimators only converge to the population estimate as the number of independent, identically distribution (IID) data points tends to infinity. However, as anyone who has experimented with financial data will be aware, market data is everchaning, and in some cases the volume of data is minimal. In these circumstances, nonparametric estiamtors do not work as well as intended, and this will cause issues later on in the optimiser. \n\nOne method to circumvent this is to impose some known structure in the market data. This is the essence of shrinkage estimation. We _shrink_ the sample moment towards a structure that we know _a-priori_. \n\nAn example of shrinkage will be Ledoit-Wolf Single-Index Model Covariance Shrinkage. This approach assumes that stock returns are significantly influenced by market returns. So we model the stock returns as a linear regression of market returns:\n\n$$\n\\hat{r}_{i,t} = \\alpha_{i} + \\beta_{i} \\hat{r}_{m, t} + \\epsilon_{i,t}\n$$\n\nWe calculate \\(\\beta_i\\) and \\(\\alpha_i\\) from regression, and we assume the error \\(\\epsilon\\) is independent and normally distributed, i.e., \\(\\text{Cov}(\\epsilon_i, \\epsilon_j) = 0\\), \\(\\text{Cov}(\\hat{r}_m, \\epsilon_i) = 0\\), and \\(\\mathbb{E}[\\epsilon] = 0\\), \\(\\text{Var}(\\epsilon) = \\sigma_i^2\\). Given this, we let the shrinkage matrix be:\n\n$$\nF = \\beta \\beta^T \\hat{\\sigma}_m^2 + \\Sigma_{\\epsilon}\n$$\n\nwhere \\(\\Sigma_{\\epsilon}\\) is the diagonal matrix of error variances. We can now calculate the shrunk covariance matrix as:\n\n$$\n\\Sigma = \\alpha F + (1 - \\alpha) S\n$$\n\nWe can go a step further by choosing \\(\\alpha\\) optimally, but for the sake of brevity, I will leave that to the interested reader to find. The source papers are in the references directory for those interested.\n\n## Optimal Allocations\nClassical asset allocation is the efficient frontier allocation. This is also known as the mean-variance optimisation as it takes into account the estimators of the mean and variance. The procedure of optimisation involves choosing an utility function and optimising it for portfolio weights. So far, I have implmented Mean-Variance, Minimum Volatility and Maximum Sharpe Optimisation. \n\n### Mean-Variance\nMean-variance optimisation can take on two forms: maximising returns with a variance limit, or minimising variance with a return limit. Both forms call the same ``mean_variance()`` method in the ``Optimiser`` class.\n\n### Minimum Volatility\nMinimum volatility optimisation minimises volatility whilst constraining the weights to fit a certain portfolio profile (Long only, Long/Short)\n\n### Maximum Sharpe \nMaximum Sharpe optimisation requires a little more nuance as maximising sharpe ratio is not a convex optimisation problem. Hence, we have to do a variable transformation, with a few assumptions, to convert the problem into a convex optimisation one. \n\n\u003c!-- ### Higher Moment Optimisation\nThe core principle of optimisation with higher moments is identical to any other optimisation: given some utility function and constraints, find the weights of each of the portfolio entries such that the utility function is maximised. The only difference is that the utility function in this case would contain as arguments, higher moments. Furthermore, by adding coefficients to each moment, we are able to take into account investor risk aversion and preferences.\nThis version of the package includes higher moment optimization based on higher co-moments, which makes much more statistical sense than the column-wise higher order moments in the original package.  --\u003e\n\n\u003c!-- ### Compared to Sharpe Ratio\nWhen doing backtests, higher moment optimisation works better than using Sharpe ratio to optimise allocations. --\u003e\n\n## Roadmap\nI have the following planned out and am working on implementing them:\n\n\u003c!-- - Market Invariants\n  - Calculating invariants for bonds and derivatives --\u003e\n\n- Nonparametric Estimators\n  - Exponentially weighted skew and kurtosis\n\u003c!-- - Maximum Likelihood Estimators\n  - Student-t Distribution\n  - Stable Distributions --\u003e\n- Shrinkage Estimators\n  - Shrinkage for higher moments\n  - Optimal shrinkage coefficient for custom shrinkage matrices\n\n- Optimisations\n  - Extending Hierarchical Risk Parity\n  - Higher Moment Optimisation\n  - Backtesting optimisation\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvivekpa%2Foptimalportfolio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvivekpa%2Foptimalportfolio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvivekpa%2Foptimalportfolio/lists"}