Skip to content

Commit 583259f

Browse files
authored
Merge branch 'PyPortfolio:main' into No_yfinance
2 parents 65794a3 + 2d1b58a commit 583259f

File tree

7 files changed

+267
-64
lines changed

7 files changed

+267
-64
lines changed

.github/actions/setup-project/action.yml

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# Action: Setup Project (composite action)
22
#
3-
# Purpose: Bootstrap a Python project in GitHub Actions by:
4-
# - Installing Task, uv, and uvx into a local ./bin directory
5-
# - Detecting presence of pyproject.toml and exposing it as an output
6-
# - Creating a virtual environment with uv and syncing dependencies
3+
# Purpose: Bootstrap a Python project within GitHub Actions by:
4+
# - Installing uv and uvx into a local ./bin directory and adding it to PATH
5+
# - Detecting the presence of pyproject.toml and exposing that as an output
6+
# - Creating a virtual environment with uv and (optionally) syncing dependencies
77
#
88
# Inputs:
9-
# - python-version: Python version used for the virtual environment (default: 3.12)
9+
# - python-version: Python version for the uv-managed virtual environment (default: 3.12)
1010
#
1111
# Outputs:
1212
# - pyproject_exists: "true" if pyproject.toml exists, otherwise "false"
1313
#
1414
# Notes:
1515
# - Safe to run in repositories without pyproject.toml; dependency sync will be skipped.
16-
# - Used by workflows such as CI, Book, Marimo, and Release.
16+
# - Purely a CI helper — it does not modify repository files.
1717

1818
name: 'Setup Project'
1919
description: 'Setup the project'
@@ -32,7 +32,7 @@ outputs:
3232
runs:
3333
using: 'composite'
3434
steps:
35-
- name: Set up task, uv, uvx and the venv
35+
- name: Set up uv, uvx and the venv
3636
shell: bash
3737
run: |
3838
mkdir -p bin
@@ -41,17 +41,9 @@ runs:
4141
echo "Adding ./bin to PATH"
4242
echo "$(pwd)/bin" >> $GITHUB_PATH
4343
44-
# Install Task
45-
curl -fsSL https://taskfile.dev/install.sh | sh -s -- -d -b ./bin
46-
4744
# Install uv and uvx
4845
curl -fsSL https://astral.sh/uv/install.sh | UV_INSTALL_DIR="./bin" sh
4946
50-
- name: Check version for task
51-
shell: bash
52-
run: |
53-
task --version
54-
5547
- name: Check version for uv
5648
shell: bash
5749
run: |

.github/workflows/release.yml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
name: PyPI Release
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
check_tag:
9+
name: Check tag
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v5
14+
15+
- uses: actions/setup-python@v6
16+
with:
17+
python-version: '3.11'
18+
19+
- shell: bash
20+
run: |
21+
TAG="${{ github.event.release.tag_name }}"
22+
GH_TAG_NAME="${TAG#v}"
23+
PY_VERSION=$(python - <<'PY'
24+
import pathlib, tomllib
25+
data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
26+
print(data.get("project").get("version"))
27+
PY
28+
)
29+
if [ "${GH_TAG_NAME}" != "${PY_VERSION}" ]; then
30+
echo "::error::Tag (${GH_TAG_NAME}) does not match pyproject.toml version (${PY_VERSION})."
31+
exit 2
32+
fi
33+
34+
build_wheels:
35+
name: Build wheels
36+
runs-on: ubuntu-latest
37+
38+
steps:
39+
- uses: actions/checkout@v5
40+
41+
- uses: actions/setup-python@v6
42+
with:
43+
python-version: '3.11'
44+
45+
- name: Build wheel
46+
run: |
47+
python -m pip install build
48+
python -m build --wheel --sdist --outdir wheelhouse
49+
50+
- name: Store wheels
51+
uses: actions/upload-artifact@v4
52+
with:
53+
name: wheels
54+
path: wheelhouse/*
55+
56+
pytest-nosoftdeps:
57+
name: no-softdeps
58+
runs-on: ${{ matrix.os }}
59+
needs: [build_wheels]
60+
strategy:
61+
fail-fast: false
62+
matrix:
63+
os: [ubuntu-latest, macos-latest, windows-latest]
64+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
65+
66+
steps:
67+
- uses: actions/checkout@v5
68+
69+
- name: Set up Python ${{ matrix.python-version }}
70+
uses: actions/setup-python@v6
71+
with:
72+
python-version: ${{ matrix.python-version }}
73+
74+
- name: Setup macOS
75+
if: runner.os == 'macOS'
76+
run: |
77+
brew install libomp # https://github.com/pytorch/pytorch/issues/20030
78+
79+
- name: Get full Python version
80+
id: full-python-version
81+
shell: bash
82+
run: echo version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") >> $GITHUB_OUTPUT
83+
84+
- name: Install dependencies
85+
shell: bash
86+
run: |
87+
pip install ".[dev]"
88+
89+
- name: Show dependencies
90+
run: python -m pip list
91+
92+
- name: Run pytest
93+
shell: bash
94+
run: python -m pytest tests
95+
96+
upload_wheels:
97+
name: Upload wheels to PyPI
98+
runs-on: ubuntu-latest
99+
needs: [pytest-nosoftdeps]
100+
101+
permissions:
102+
id-token: write
103+
104+
steps:
105+
- uses: actions/download-artifact@v5
106+
with:
107+
name: wheels
108+
path: wheelhouse
109+
110+
- name: Publish package to PyPI
111+
uses: pypa/gh-action-pypi-publish@release/v1
112+
with:
113+
packages-dir: wheelhouse/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ dist
4848

4949
artifacts
5050

51+
bin

Makefile

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
## Makefile — PyPortfolioOpt developer conveniences
2+
#
3+
# This Makefile exposes common local development tasks and a friendly
4+
# `make help` index.
5+
# Conventions used by the help generator:
6+
# - Lines with `##` after a target are turned into help text.
7+
# - Lines starting with `##@` create section headers in the help output.
8+
# This file does not affect the library itself; it only streamlines dev workflows.
9+
10+
# Colors for pretty output in help messages
11+
BLUE := \033[36m
12+
BOLD := \033[1m
13+
GREEN := \033[32m
14+
RED := \033[31m
15+
RESET := \033[0m
16+
17+
# Default goal when running `make` with no target
18+
.DEFAULT_GOAL := help
19+
20+
# Declare phony targets (they don't produce files)
21+
.PHONY: install install-uv test fmt
22+
23+
UV_INSTALL_DIR := ./bin
24+
25+
##@ Bootstrap
26+
install-uv: ## ensure uv (and uvx) are installed locally
27+
@mkdir -p ${UV_INSTALL_DIR}
28+
@if [ -x "${UV_INSTALL_DIR}/uv" ] && [ -x "${UV_INSTALL_DIR}/uvx" ]; then \
29+
:; \
30+
else \
31+
printf "${BLUE}Installing uv${RESET}\n"; \
32+
curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=${UV_INSTALL_DIR} sh 2>/dev/null || { printf "${RED}[ERROR] Failed to install uv ${RESET}\n"; exit 1; }; \
33+
fi
34+
35+
install: install-uv ## install
36+
@printf "${BLUE}[INFO] Creating virtual environment...${RESET}\n"
37+
# Create the virtual environment
38+
@./bin/uv venv --python 3.12 || { printf "${RED}[ERROR] Failed to create virtual environment${RESET}\n"; exit 1; }
39+
@printf "${BLUE}[INFO] Installing dependencies${RESET}\n"
40+
@./bin/uv sync --all-extras --frozen || { printf "${RED}[ERROR] Failed to install dependencies${RESET}\n"; exit 1; }
41+
42+
43+
##@ Development and Testing
44+
test: install ## run all tests
45+
@printf "${BLUE}[INFO] Running tests...${RESET}\n"
46+
@./bin/uv pip install pytest pytest-cov pytest-html
47+
@mkdir -p _tests/html-coverage _tests/html-report
48+
@./bin/uv run pytest tests --cov=pypfopt --cov-report=term --cov-report=html:_tests/html-coverage --html=_tests/html-report/report.html
49+
50+
fmt: install-uv ## check the pre-commit hooks and the linting
51+
@./bin/uvx pre-commit run --all-files
52+
@./bin/uvx deptry .
53+
54+
##@ Meta
55+
help: ## Display this help message
56+
+@printf "$(BOLD)Usage:$(RESET)\n"
57+
+@printf " make $(BLUE)<target>$(RESET)\n\n"
58+
+@printf "$(BOLD)Targets:$(RESET)\n"
59+
+@awk 'BEGIN {FS = ":.*##"; printf ""} /^[a-zA-Z_-]+:.*?##/ { printf " $(BLUE)%-15s$(RESET) %s\n", $$1, $$2 } /^##@/ { printf "\n$(BOLD)%s$(RESET)\n", substr($$0, 5) }' $(MAKEFILE_LIST)

README.md

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,28 @@
1-
<p align="center">
2-
<img width=60% src="https://github.com/PyPortfolio/PyPortfolioOpt/blob/main/media/logo_v1.png?raw=true">
3-
</p>
1+
## Welcome to PyPortfolioOpt
42

5-
<!-- buttons -->
6-
<p align="center">
7-
<a href="https://www.python.org">
8-
<img src="https://img.shields.io/pypi/pyversions/PyPortfolioOpt.svg"
9-
alt="python"></a> &nbsp;
10-
<a href="https://www.python.org">
11-
<img src="https://img.shields.io/badge/Platforms-linux--64,win--64,osx--64-orange.svg?style=flat-square"
12-
alt="platforms"></a> &nbsp;
13-
<a href="https://pypi.org/project/PyPortfolioOpt/">
14-
<img src="https://img.shields.io/badge/pypi-v1.5.6-brightgreen.svg"
15-
alt="pypi"></a> &nbsp;
16-
<a href="https://opensource.org/licenses/MIT">
17-
<img src="https://img.shields.io/badge/license-MIT-brightgreen.svg"
18-
alt="MIT license"></a> &nbsp;
19-
<a href="https://github.com/pyportfolio/pyportfolioopt/actions">
20-
<img src="https://github.com/pyportfolio/pyportfolioopt/actions/workflows/main.yml/badge.svg?branch=main"
21-
alt="build"></a> &nbsp;
22-
<a href="https://pepy.tech/project/pyportfolioopt">
23-
<img src="https://pepy.tech/badge/pyportfolioopt"
24-
alt="downloads"></a> &nbsp;
25-
<a href="https://mybinder.org/v2/gh/pyportfolio/pyportfolioopt/main/?filepath=cookbook">
26-
<img src="https://mybinder.org/badge_logo.svg"
27-
alt="binder"></a> &nbsp;
28-
</p>
3+
<a href="https://pyportfolioopt.readthedocs.io/en/latest/"><img src="https://github.com/PyPortfolio/PyPortfolioOpt/blob/main/media/logo_v1.png?raw=true" width="275" align="right" /></a>
294

30-
<!-- content -->
5+
PyPortfolioOpt is a library implementing portfolio optimization methods, including
6+
classical mean-variance optimization, Black-Litterman allocation, or shrinkage and Hierarchical Risk Parity.
7+
PyPortfolioOpt is inspired by scikit-learn; it is **extensive** yet easily **extensible**, for casual investors, or professionals looking for an easy prototyping tool. Whether you are a fundamentals-oriented investor who has identified a
8+
handful of undervalued picks, or an algorithmic trader who has a basket of
9+
strategies, PyPortfolioOpt can help you combine your alpha sources in a risk-efficient way.
3110

32-
PyPortfolioOpt is a library that implements portfolio optimization methods, including
33-
classical mean-variance optimization techniques and Black-Litterman allocation, as well as more
34-
recent developments in the field like shrinkage and Hierarchical Risk Parity.
3511

36-
It is **extensive** yet easily **extensible**, and can be useful for either a casual investors, or a professional looking for an easy prototyping tool. Whether you are a fundamentals-oriented investor who has identified a
37-
handful of undervalued picks, or an algorithmic trader who has a basket of
38-
strategies, PyPortfolioOpt can help you combine your alpha sources
39-
in a risk-efficient way.
12+
<!-- buttons -->
13+
14+
| | **[Documentation](https://pyportfolioopt.readthedocs.io/en/latest/)** · **[Tutorials](https://github.com/pyportfolio/pyportfolioopt/tree/main/cookbook)** · **[Release Notes](https://github.com/PyPortfolio/PyPortfolioOpt/releases)** |
15+
|---|---|
16+
| **Open&#160;Source** | [![MIT](https://img.shields.io/github/license/pyportfolio/pyportfolioopt)](https://github.com/pyportfolio/pyportfolioopt/blob/master/LICENSE) [![GC.OS Sponsored](https://img.shields.io/badge/GC.OS-Sponsored%20Project-orange.svg?style=flat&colorA=0eac92&colorB=2077b4)](https://gc-os-ai.github.io/) | |
17+
| **Tutorials** | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/pyportfolio/pyportfolioopt/main/?filepath=cookbook) |
18+
| **Community** | [![!discord](https://img.shields.io/static/v1?logo=discord&label=discord&message=chat&color=lightgreen)](https://discord.gg/7uKdHfdcJG) [![!slack](https://img.shields.io/static/v1?logo=linkedin&label=LinkedIn&message=news&color=lightblue)](https://www.linkedin.com/company/pyportfolioopt/) |
19+
| **CI/CD** | [![github-actions](https://img.shields.io/github/actions/workflow/status/pyportfolio/pyportfolioopt/main.yml?logo=github)](https://github.com/pyportfolio/pyportfolioopt/actions/workflows/release.yml) [![readthedocs](https://img.shields.io/readthedocs/pyportfolioopt?logo=readthedocs)](https://pyportfolioopt.readthedocs.io/en/latest/?badge=latest) |
20+
| **Code** | [![!pypi](https://img.shields.io/pypi/v/pyportfolioopt?color=orange)](https://pypi.org/project/pyportfolioopt/) [![!python-versions](https://img.shields.io/pypi/pyversions/pyportfolioopt)](https://www.python.org/) [![!black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) |
21+
| **Downloads** | ![PyPI - Downloads](https://img.shields.io/pypi/dw/pyportfolioopt) ![PyPI - Downloads](https://img.shields.io/pypi/dm/pyportfolioopt) [![Downloads](https://static.pepy.tech/personalized-badge/pyportfolioopt?period=total&units=international_system&left_color=grey&right_color=blue&left_text=cumulative%20(pypi))](https://pepy.tech/project/pyportfolioopt) |
22+
| **Citation** | [JOSS article](https://joss.theoj.org/papers/10.21105/joss.03066) |
4023

41-
**PyPortfolioOpt has been [published](https://joss.theoj.org/papers/10.21105/joss.03066) in the Journal of Open Source Software 🎉**
4224

43-
PyPortfolioOpt is now being maintained by [Tuan Tran](https://github.com/88d52bdba0366127fffca9dfa93895).
25+
<!-- content -->
4426

4527
Head over to the **[documentation on ReadTheDocs](https://pyportfolioopt.readthedocs.io/en/latest/)** to get an in-depth look at the project, or check out the [cookbook](https://github.com/pyportfolio/pyportfolioopt/tree/main/cookbook) to see some examples showing the full process from downloading data to building a portfolio.
4628

@@ -271,9 +253,6 @@ The covariance matrix encodes not just the volatility of an asset, but also how
271253
ef = EfficientFrontier(mu, S, weight_bounds=(-1, 1))
272254
```
273255

274-
```result
275-
```
276-
277256
- Market neutrality: for the `efficient_risk` and `efficient_return` methods, PyPortfolioOpt provides an option to form a market-neutral portfolio (i.e weights sum to zero). This is not possible for the max Sharpe portfolio and the min volatility portfolio because in those cases because they are not invariant with respect to leverage. Market neutrality requires negative weights:
278257

279258
```python
@@ -311,9 +290,6 @@ SBUX: 0.0330
311290
ef = EfficientFrontier(mu, S, weight_bounds=(0, 0.1))
312291
```
313292

314-
```result
315-
```
316-
317293
One issue with mean-variance optimization is that it leads to many zero-weights. While these are
318294
"optimal" in-sample, there is a large body of research showing that this characteristic leads
319295
mean-variance portfolios to underperform out-of-sample. To that end, I have introduced an
@@ -352,7 +328,7 @@ SBUX: 0.0695
352328

353329
### Black-Litterman allocation
354330

355-
As of v0.5.0, we now support Black-Litterman asset allocation, which allows you to combine
331+
Pyportfolioopt supports Black-Litterman asset allocation, which allows you to combine
356332
a prior estimate of returns (e.g the market-implied returns) with your own views to form a
357333
posterior estimate. This results in much better estimates of expected returns than just using
358334
the mean historical return. Check out the [docs](https://pyportfolioopt.readthedocs.io/en/latest/BlackLitterman.html) for a discussion of the theory, as well as advice
@@ -439,9 +415,6 @@ PyPortfolioOpt provides a test dataset of daily returns for 20 tickers:
439415
['GOOG', 'AAPL', 'FB', 'BABA', 'AMZN', 'GE', 'AMD', 'WMT', 'BAC', 'GM', 'T', 'UAA', 'SHLD', 'XOM', 'RRC', 'BBY', 'MA', 'PFE', 'JPM', 'SBUX']
440416
```
441417

442-
```result
443-
```
444-
445418
These tickers have been informally selected to meet several criteria:
446419

447420
- reasonably liquid
@@ -483,7 +456,7 @@ BibTex::
483456

484457
Contributions are _most welcome_. Have a look at the [Contribution Guide](https://github.com/PyPortfolio/PyPortfolioOpt/blob/main/CONTRIBUTING.md) for more.
485458

486-
I'd like to thank all of the people who have contributed to PyPortfolioOpt since its release in 2018.
459+
We'd like to thank all of the people who have contributed to PyPortfolioOpt since its release in 2018.
487460
Special shout-outs to:
488461

489462
- Tuan Tran

0 commit comments

Comments
 (0)