Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Python Tests

on:
push

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install pytest
pip install -r requirements.txt
pip install -e .


- name: Test with pytest
run: |
python -m pip install pytest-html pytest-cov
pytest tests/ --html=test-report-${{ matrix.os }}-${{ matrix.python-version }}.html --self-contained-html

- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report-${{ matrix.os }}-${{ matrix.python-version }}
path: test-report-${{ matrix.os }}-${{ matrix.python-version }}.html
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ dist
quantstats.egg-info
QuantStats.egg-info
Icon
/tests
.vscode
Icon
QuantStats_Lumi.egg-info/*
Expand Down
30 changes: 26 additions & 4 deletions quantstats_lumi/_plotting/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,34 @@ def plot_timeseries(

_plt.yscale("symlog" if log_scale else "linear")


# Set y-axis limits to avoid blank space at the bottom and top
min_val = returns.min()
max_val = returns.max()
# At this stage returns are a frame!
min_val = -1
max_val = +1

if isinstance(returns, _pd.Series):
# todo: this case can not be used. returns is always a frame
min_val = returns.min()
max_val = returns.max()

if isinstance(returns, _pd.DataFrame):
min_val = returns.min().min()
max_val = returns.max().max()

if benchmark is not None:
min_val = min(min_val, benchmark.min())
max_val = max(max_val, benchmark.max())
bench_min = min_val
bench_max = max_val

if isinstance(benchmark, _pd.Series):
bench_min = benchmark.min()
bench_max = benchmark.max()
if isinstance(benchmark, _pd.DataFrame):
bench_min = benchmark.min().min()
bench_max = benchmark.max().max()

min_val = min(min_val, bench_min)
max_val = max(max_val, bench_max)

# Handle cases where min_val or max_val might be NaN or Inf
if not _np.isfinite(min_val) or not _np.isfinite(max_val) or min_val == max_val:
Expand Down
2 changes: 1 addition & 1 deletion quantstats_lumi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def multi_shift(df, shift=3):
dfs = [df.shift(i) for i in _np.arange(shift)]
for ix, dfi in enumerate(dfs[1:]):
dfs[ix + 1].columns = [str(col) for col in dfi.columns + str(ix + 1)]
return _pd.concat(dfs, 1, sort=True)
return _pd.concat(dfs, axis=1, sort=True)


def to_returns(prices, rf=0.0):
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

42 changes: 42 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""global fixtures."""

from __future__ import annotations

from pathlib import Path

import pandas as pd
import pytest


@pytest.fixture(scope="session", name="resource_dir")
def resource_fixture():
"""Resource fixture."""
return Path(__file__).parent / "resources"


@pytest.fixture
def returns(resource_dir) -> pd.DataFrame:
"""Fixture that returns a Series with Meta returns.

Args:
resource_dir: The resource_dir fixture containing the path to test resources.

Returns:
pd.Series: A Series containing Meta returns.

"""
return pd.read_csv(resource_dir / "meta.csv", index_col="Date", parse_dates=["Date"]).squeeze()


@pytest.fixture
def benchmark(resource_dir) -> pd.DataFrame:
"""Fixture that returns a Series with benchmark returns.

Args:
resource_dir: The resource_dir fixture containing the path to test resources.

Returns:
pd.Series: A Series containing SPY benchmark returns.

"""
return pd.read_csv(resource_dir / "benchmark.csv", index_col="Date", parse_dates=["Date"]).squeeze()
Loading