Skip to content

Commit 81f522d

Browse files
authored
Merge pull request polakowo#824 from ChadThackray/feature/migrate-to-pyproject-toml
Migrate to pyproject.toml, pandas 2.0 and new python version compatibility
2 parents f0cae11 + bfb3096 commit 81f522d

14 files changed

Lines changed: 272 additions & 129 deletions

File tree

.github/workflows/tests.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
test:
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, windows-latest, macos-latest]
16+
python-version: ["3.10", "3.11", "3.12", "3.13"]
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Install uv
22+
uses: astral-sh/setup-uv@v5
23+
24+
- name: Set up Python ${{ matrix.python-version }}
25+
run: uv python install ${{ matrix.python-version }}
26+
27+
- name: Install dependencies
28+
run: uv sync --extra cov
29+
30+
- name: Run tests
31+
run: uv run pytest tests/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ venvs
1212
.DS_Store
1313
docs/api-markdown
1414
build
15+
uv.lock

MANIFEST.in

Lines changed: 0 additions & 1 deletion
This file was deleted.

pyproject.toml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
[build-system]
2+
requires = ["setuptools>=61.0"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "vectorbt"
7+
dynamic = ["version"]
8+
description = "Python library for backtesting and analyzing trading strategies at scale"
9+
readme = "README.md"
10+
license-files = ["LICENSE.md"]
11+
authors = [
12+
{name = "Oleg Polakow", email = "olegpolakow@vectorbt.pro"}
13+
]
14+
requires-python = ">=3.10"
15+
classifiers = [
16+
"Development Status :: 5 - Production/Stable",
17+
"Intended Audience :: Developers",
18+
"Intended Audience :: Financial and Insurance Industry",
19+
"Programming Language :: Python :: 3.10",
20+
"Programming Language :: Python :: 3.11",
21+
"Programming Language :: Python :: 3.12",
22+
"Programming Language :: Python :: 3.13",
23+
"Operating System :: OS Independent",
24+
"Intended Audience :: Science/Research",
25+
"Topic :: Software Development",
26+
"Topic :: Office/Business :: Financial",
27+
"Topic :: Scientific/Engineering :: Information Analysis",
28+
]
29+
dependencies = [
30+
"numpy>=1.23",
31+
"pandas>=2.0,<3.0",
32+
"scipy",
33+
"matplotlib",
34+
"plotly>=4.12.0",
35+
"ipywidgets>=7.0.0",
36+
"anywidget",
37+
"numba>=0.60",
38+
"dill",
39+
"tqdm",
40+
"dateparser",
41+
"imageio",
42+
"scikit-learn",
43+
"schedule",
44+
"requests",
45+
"pytz",
46+
"mypy_extensions",
47+
]
48+
49+
[project.optional-dependencies]
50+
full = [
51+
"yfinance>=0.2.22",
52+
"python-binance",
53+
"ccxt>=4.0.14",
54+
"alpaca-py",
55+
"ray>=1.4.1",
56+
"ta",
57+
"pandas-ta-classic",
58+
"TA-Lib",
59+
"python-telegram-bot>=13.4,<20.0",
60+
"quantstats>=0.0.37",
61+
]
62+
full-no-talib = [
63+
"yfinance>=0.2.22",
64+
"python-binance",
65+
"ccxt>=4.0.14",
66+
"alpaca-py",
67+
"ray>=1.4.1",
68+
"ta",
69+
"pandas-ta-classic",
70+
"python-telegram-bot>=13.4,<20.0",
71+
"quantstats>=0.0.37",
72+
]
73+
cov = [
74+
"pytest",
75+
"pytest-cov",
76+
"pytest-xdist",
77+
"codecov",
78+
]
79+
80+
[project.urls]
81+
Homepage = "https://github.com/polakowo/vectorbt"
82+
83+
[tool.setuptools.dynamic]
84+
version = {attr = "vectorbt._version.__version__"}
85+
86+
[tool.setuptools.packages.find]
87+
include = ["vectorbt*"]
88+
89+
[tool.setuptools.package-data]
90+
vectorbt = ["templates/*.json"]

setup.py

Lines changed: 2 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,3 @@
1-
from setuptools import setup, find_packages
1+
from setuptools import setup
22

3-
with open('README.md', 'r', encoding="utf-8", errors='ignore') as fh:
4-
long_description = fh.read()
5-
6-
version = {}
7-
with open("vectorbt/_version.py", encoding="utf-8") as fp:
8-
exec(fp.read(), version)
9-
10-
setup(
11-
name='vectorbt',
12-
version=version['__version__'],
13-
description='Python library for backtesting and analyzing trading strategies at scale',
14-
author='Oleg Polakow',
15-
author_email='olegpolakow@vectorbt.pro',
16-
long_description=long_description,
17-
long_description_content_type='text/markdown',
18-
url='https://github.com/polakowo/vectorbt',
19-
packages=find_packages(),
20-
package_data={
21-
'vectorbt': ['templates/*.json']
22-
},
23-
install_requires=[
24-
'numpy>=1.16.5',
25-
'pandas',
26-
'scipy',
27-
'matplotlib',
28-
'plotly>=4.12.0',
29-
'ipywidgets>=7.0.0',
30-
'anywidget',
31-
"numba>=0.53.1, <0.57.0; python_version<'3.10'",
32-
"numba>=0.56.0, <0.57.0; python_version>='3.10' and python_version<'3.11'",
33-
"numba>=0.57.0; python_version>='3.11'",
34-
'dill',
35-
'tqdm',
36-
'dateparser',
37-
'imageio',
38-
'scikit-learn',
39-
'schedule',
40-
'requests',
41-
'pytz',
42-
'typing_extensions; python_version < "3.8"',
43-
'mypy_extensions'
44-
],
45-
extras_require={
46-
'full': [
47-
'yfinance>=0.2.22',
48-
'python-binance',
49-
'ccxt>=4.0.14',
50-
'alpaca-py',
51-
'ray>=1.4.1',
52-
'ta',
53-
'pandas-ta-classic',
54-
'TA-Lib',
55-
'python-telegram-bot>=13.4,<20.0', # LGPLv3
56-
'quantstats>=0.0.37'
57-
],
58-
'full-no-talib': [
59-
'yfinance>=0.2.22',
60-
'python-binance',
61-
'ccxt>=4.0.14',
62-
'alpaca-py',
63-
'ray>=1.4.1',
64-
'ta',
65-
'pandas-ta-classic',
66-
'python-telegram-bot>=13.4,<20.0', # LGPLv3
67-
'quantstats>=0.0.37'
68-
],
69-
'cov': [
70-
'pytest',
71-
'pytest-cov',
72-
'codecov'
73-
]
74-
},
75-
python_requires='>=3.6',
76-
license='Apache 2.0 with Commons Clause',
77-
classifiers=[
78-
'Development Status :: 5 - Production/Stable',
79-
'Intended Audience :: Developers',
80-
'Intended Audience :: Financial and Insurance Industry',
81-
'License :: Free for non-commercial use',
82-
'Programming Language :: Python :: 3.6',
83-
'Programming Language :: Python :: 3.7',
84-
'Programming Language :: Python :: 3.8',
85-
'Programming Language :: Python :: 3.9',
86-
'Programming Language :: Python :: 3.10',
87-
'Programming Language :: Python :: 3.11',
88-
'Programming Language :: Python :: 3.12',
89-
'Programming Language :: Python :: 3.13',
90-
'Operating System :: OS Independent',
91-
'Intended Audience :: Science/Research',
92-
'Topic :: Software Development',
93-
'Topic :: Office/Business :: Financial',
94-
'Topic :: Scientific/Engineering :: Information Analysis'
95-
],
96-
)
3+
setup()

tests/test_generic.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212

1313
seed = 42
1414

15+
16+
def pandas_applymap(df: pd.DataFrame, func):
17+
"""Pandas' native element-wise map, compatible with pandas 2.0+."""
18+
if hasattr(pd.DataFrame, 'map'):
19+
return df.map(func)
20+
return df.applymap(func)
21+
1522
day_dt = np.timedelta64(86400000000000)
1623

1724
df = pd.DataFrame({
@@ -588,7 +595,7 @@ def mult_nb(i, col, x):
588595
)
589596
pd.testing.assert_frame_equal(
590597
df.vbt.applymap(mult_nb),
591-
df.applymap(lambda x: x * 2)
598+
pandas_applymap(df, lambda x: x * 2)
592599
)
593600

594601
def test_filter(self):
@@ -602,7 +609,7 @@ def greater_nb(i, col, x):
602609
)
603610
pd.testing.assert_frame_equal(
604611
df.vbt.filter(greater_nb),
605-
df.applymap(lambda x: x if x > 2 else np.nan)
612+
pandas_applymap(df, lambda x: x if x > 2 else np.nan)
606613
)
607614

608615
def test_apply_and_reduce(self):

tests/test_indicators.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from collections import namedtuple
23
from datetime import datetime
34
from itertools import product
@@ -2114,18 +2115,26 @@ def test_dir(self):
21142115
)
21152116
ind = F.from_apply_func(lambda ts, in_out, p1, p2: (ts + in_out, ts + in_out)).run(ts, 100, 200)
21162117
test_attr_list = dir(ind)
2117-
assert test_attr_list == [
2118+
expected_attrs = [
21182119
'__annotations__',
21192120
'__class__',
21202121
'__delattr__',
21212122
'__dict__',
21222123
'__dir__',
21232124
'__doc__',
21242125
'__eq__',
2126+
]
2127+
if sys.version_info >= (3, 13):
2128+
expected_attrs.append('__firstlineno__')
2129+
expected_attrs.extend([
21252130
'__format__',
21262131
'__ge__',
21272132
'__getattribute__',
21282133
'__getitem__',
2134+
])
2135+
if sys.version_info >= (3, 11):
2136+
expected_attrs.append('__getstate__')
2137+
expected_attrs.extend([
21292138
'__gt__',
21302139
'__hash__',
21312140
'__init__',
@@ -2140,6 +2149,10 @@ def test_dir(self):
21402149
'__repr__',
21412150
'__setattr__',
21422151
'__sizeof__',
2152+
])
2153+
if sys.version_info >= (3, 13):
2154+
expected_attrs.append('__static_attributes__')
2155+
expected_attrs.extend([
21432156
'__str__',
21442157
'__subclasshook__',
21452158
'__weakref__',
@@ -2245,7 +2258,8 @@ def test_dir(self):
22452258
'wrapper',
22462259
'writeable_attrs',
22472260
'xs'
2248-
]
2261+
])
2262+
assert test_attr_list == expected_attrs
22492263

22502264
def test_get_talib_indicators(self):
22512265
if talib_available:

tests/test_utils.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1744,8 +1744,27 @@ def test_is_deep_equal(self):
17441744
assert checks.is_deep_equal(0, 0)
17451745
assert not checks.is_deep_equal(0, False)
17461746
assert not checks.is_deep_equal(0, 1)
1747-
assert checks.is_deep_equal(lambda x: x, lambda x: x)
1747+
# Function comparison - same instance
1748+
f = lambda x: x
1749+
assert checks.is_deep_equal(f, f)
1750+
# Function comparison - separately defined but semantically identical
1751+
f1 = lambda x: x
1752+
f2 = lambda x: x
1753+
assert checks.is_deep_equal(f1, f2)
1754+
# Function comparison - different behavior
17481755
assert not checks.is_deep_equal(lambda x: x, lambda x: 2 * x)
1756+
# Function in nested structure - shared reference
1757+
shared_func = lambda x: x
1758+
assert checks.is_deep_equal({'f': shared_func}, {'f': shared_func})
1759+
# Function with closure - same captured value
1760+
y = 10
1761+
f3 = lambda x: x + y
1762+
f4 = lambda x: x + y
1763+
assert checks.is_deep_equal(f3, f4)
1764+
# Function with different default values
1765+
f5 = lambda x, d=1: x + d
1766+
f6 = lambda x, d=2: x + d
1767+
assert not checks.is_deep_equal(f5, f6)
17491768

17501769
def test_is_instance_of(self):
17511770
class _A:

vectorbt/base/array_wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ def _wrap(arr):
569569
if checks.is_series(out):
570570
out = out.map(lambda x: self.index[x] if x != -1 else np.nan)
571571
else:
572-
out = out.applymap(lambda x: self.index[x] if x != -1 else np.nan)
572+
out = out.map(lambda x: self.index[x] if x != -1 else np.nan)
573573
if to_timedelta:
574574
# Convert to timedelta
575575
out = self.to_timedelta(out, silence_warnings=silence_warnings)
@@ -657,7 +657,7 @@ def _wrap_reduced(arr):
657657
if checks.is_series(out):
658658
out = out.map(lambda x: self.index[x] if x != -1 else np.nan)
659659
elif checks.is_frame(out):
660-
out = out.applymap(lambda x: self.index[x] if x != -1 else np.nan)
660+
out = out.map(lambda x: self.index[x] if x != -1 else np.nan)
661661
else:
662662
out = self.index[out] if out != -1 else np.nan
663663
if to_timedelta:

0 commit comments

Comments
 (0)