diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a085b4a..4be1753 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - run: pip install -U .[test] - if: matrix.test-type == 'lint' - run: flake8 + run: ruff backtesting - if: matrix.test-type == 'lint' run: mypy backtesting - if: matrix.test-type == 'lint' diff --git a/backtesting/__init__.py b/backtesting/__init__.py index e663c6e..78c5918 100644 --- a/backtesting/__init__.py +++ b/backtesting/__init__.py @@ -53,10 +53,10 @@ itself find their way back to the community. # API Reference Documentation """ try: - from ._version import version as __version__ # noqa: F401 + from ._version import version as __version__ except ImportError: __version__ = '?.?.?' # Package not installed -from .backtesting import Backtest, Strategy # noqa: F401 from . import lib # noqa: F401 from ._plotting import set_bokeh_output # noqa: F401 +from .backtesting import Backtest, Strategy # noqa: F401 diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 5b89fc5..6befd32 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -257,7 +257,7 @@ this.labels = this.labels || formatter.doFormat(ticks return this.labels[index] || ""; ''') - NBSP = '\N{NBSP}' * 4 + NBSP = '\N{NBSP}' * 4 # noqa: E999 ohlc_extreme_values = df[['High', 'Low']].copy(deep=False) ohlc_tooltips = [ ('x, y', NBSP.join(('$index', diff --git a/backtesting/_stats.py b/backtesting/_stats.py index df72e1f..086d4e3 100644 --- a/backtesting/_stats.py +++ b/backtesting/_stats.py @@ -1,4 +1,4 @@ -from typing import List, TYPE_CHECKING, Union +from typing import TYPE_CHECKING, List, Union import numpy as np import pandas as pd diff --git a/backtesting/_util.py b/backtesting/_util.py index 38078be..70386b2 100644 --- a/backtesting/_util.py +++ b/backtesting/_util.py @@ -1,6 +1,6 @@ import warnings -from typing import Dict, List, Optional, Sequence, Union, cast from numbers import Number +from typing import Dict, List, Optional, Sequence, Union, cast import numpy as np import pandas as pd diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 296dfa4..45e0e83 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -9,11 +9,11 @@ import multiprocessing as mp import os import sys import warnings -from abc import abstractmethod, ABCMeta +from abc import ABCMeta, abstractmethod from concurrent.futures import ProcessPoolExecutor, as_completed from copy import copy from functools import lru_cache, partial -from itertools import repeat, product, chain, compress +from itertools import chain, compress, product, repeat from math import copysign from numbers import Number from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union @@ -29,7 +29,7 @@ except ImportError: def _tqdm(seq, **_): return seq -from ._plotting import plot +from ._plotting import plot # noqa: I001 from ._stats import compute_stats from ._util import _as_str, _Indicator, _Data, try_ @@ -75,7 +75,7 @@ class Strategy(metaclass=ABCMeta): setattr(self, k, v) return params - def I(self, # noqa: E741, E743 + def I(self, # noqa: E743 func: Callable, *args, name=None, plot=True, overlay=None, color=None, scatter=False, **kwargs) -> np.ndarray: @@ -126,7 +126,7 @@ class Strategy(metaclass=ABCMeta): try: value = func(*args, **kwargs) except Exception as e: - raise RuntimeError(f'Indicator "{name}" errored with exception: {e}') + raise RuntimeError(f'Indicator "{name}" error') from e if isinstance(value, pd.DataFrame): value = value.values.T @@ -190,7 +190,7 @@ class Strategy(metaclass=ABCMeta): super().next() """ - class __FULL_EQUITY(float): + class __FULL_EQUITY(float): # noqa: N801 def __repr__(self): return '.9999' _FULL_EQUITY = __FULL_EQUITY(1 - sys.float_info.epsilon) @@ -664,7 +664,7 @@ class Trade: if order: order.cancel() if price: - kwargs = dict(stop=price) if type == 'sl' else dict(limit=price) + kwargs = {'stop': price} if type == 'sl' else {'limit': price} order = self.__broker.new_order(-self.size, trade=self, **kwargs) setattr(self, attr, order) @@ -1409,13 +1409,13 @@ class Backtest: Tuple[pd.Series, pd.Series, dict]]: try: from skopt import forest_minimize - from skopt.space import Integer, Real, Categorical - from skopt.utils import use_named_args from skopt.callbacks import DeltaXStopper from skopt.learning import ExtraTreesRegressor + from skopt.space import Categorical, Integer, Real + from skopt.utils import use_named_args except ImportError: raise ImportError("Need package 'scikit-optimize' for method='skopt'. " - "pip install scikit-optimize") + "pip install scikit-optimize") from None nonlocal max_tries max_tries = (200 if max_tries is None else diff --git a/backtesting/lib.py b/backtesting/lib.py index f364ef5..14c01ef 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -12,18 +12,18 @@ Please raise ideas for additions to this collection on the [issue tracker]. """ from collections import OrderedDict +from inspect import currentframe from itertools import compress from numbers import Number -from inspect import currentframe -from typing import Sequence, Optional, Union, Callable +from typing import Callable, Optional, Sequence, Union import numpy as np import pandas as pd -from .backtesting import Strategy from ._plotting import plot_heatmaps as _plot_heatmaps from ._stats import compute_stats as _compute_stats from ._util import _Array, _as_str +from .backtesting import Strategy __pdoc__ = {} @@ -461,8 +461,8 @@ class TrailingStrategy(Strategy): Set the lookback period for computing ATR. The default value of 100 ensures a _stable_ ATR. """ - h, l, c_prev = self.data.High, self.data.Low, pd.Series(self.data.Close).shift(1) - tr = np.max([h - l, (c_prev - h).abs(), (c_prev - l).abs()], axis=0) + hi, lo, c_prev = self.data.High, self.data.Low, pd.Series(self.data.Close).shift(1) + tr = np.max([hi - lo, (c_prev - hi).abs(), (c_prev - lo).abs()], axis=0) atr = pd.Series(tr).rolling(periods).mean().bfill().values self.__atr = atr diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index 27fd989..8a267c4 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -18,21 +18,21 @@ from pandas.testing import assert_frame_equal from backtesting import Backtest, Strategy from backtesting._stats import compute_drawdown_duration_peaks +from backtesting._util import _Array, _as_str, _Indicator, try_ from backtesting.lib import ( OHLCV_AGG, + SignalStrategy, + TrailingStrategy, barssince, compute_stats, cross, crossover, - quantile, - SignalStrategy, - TrailingStrategy, - resample_apply, plot_heatmaps, + quantile, random_ohlc_data, + resample_apply, ) -from backtesting.test import GOOG, EURUSD, SMA -from backtesting._util import _Indicator, _as_str, _Array, try_ +from backtesting.test import EURUSD, GOOG, SMA SHORT_DATA = GOOG.iloc[:20] # Short data for fast tests with no indicator lag @@ -146,7 +146,7 @@ class TestBacktest(TestCase): assert float(self.data.Close) == self.data.Close[-1] - def next(self, FIVE_DAYS=pd.Timedelta('3 days')): + def next(self, _FEW_DAYS=pd.Timedelta('3 days')): # noqa: N803 assert self.equity >= 0 assert isinstance(self.sma, _Indicator) @@ -193,7 +193,7 @@ class TestBacktest(TestCase): assert self.position.size < 0 trade = self.trades[0] - if self.data.index[-1] - self.data.index[trade.entry_bar] > FIVE_DAYS: + if self.data.index[-1] - self.data.index[trade.entry_bar] > _FEW_DAYS: assert not trade.is_long assert trade.is_short assert trade.size < 0 @@ -290,7 +290,7 @@ class TestBacktest(TestCase): except TypeError: return a == b - diff = {key: print(key) or value + diff = {key: print(key) or value # noqa: T201 for key, value in stats.filter(regex='^[^_]').items() if not almost_equal(value, expected[key])} self.assertDictEqual(diff, {}) @@ -510,7 +510,7 @@ class TestStrategy(TestCase): class TestOptimize(TestCase): def test_optimize(self): bt = Backtest(GOOG.iloc[:100], SmaCross) - OPT_PARAMS = dict(fast=range(2, 5, 2), slow=[2, 5, 7, 9]) + OPT_PARAMS = {'fast': range(2, 5, 2), 'slow': [2, 5, 7, 9]} self.assertRaises(ValueError, bt.optimize) self.assertRaises(ValueError, bt.optimize, maximize='missing key', **OPT_PARAMS) @@ -556,7 +556,7 @@ class TestOptimize(TestCase): def test_max_tries(self): bt = Backtest(GOOG.iloc[:100], SmaCross) - OPT_PARAMS = dict(fast=range(2, 10, 2), slow=[2, 5, 7, 9]) + OPT_PARAMS = {'fast': range(2, 10, 2), 'slow': [2, 5, 7, 9]} for method, max_tries, random_state in (('grid', 5, 0), ('grid', .3, 0), ('skopt', 7, 0), @@ -589,7 +589,7 @@ class TestOptimize(TestCase): def test_multiprocessing_windows_spawn(self): df = GOOG.iloc[:100] - kw = dict(fast=[10]) + kw = {'fast': [10]} stats1 = Backtest(df, SmaCross).optimize(**kw) with patch('multiprocessing.get_start_method', lambda **_: 'spawn'): @@ -633,7 +633,7 @@ class TestPlot(TestCase): bt = Backtest(GOOG.iloc[:100], SmaCross) bt.run() with _tempfile() as f: - for p in dict(plot_volume=False, + for p in dict(plot_volume=False, # noqa: C408 plot_equity=False, plot_return=True, plot_pl=False, @@ -722,8 +722,8 @@ class TestPlot(TestCase): self.assertEqual(stats['Equity Final [$]'], 0) self.assertEqual(len(trades), 2) assert trades[['EntryTime', 'ExitTime']].equals( - pd.DataFrame(dict(EntryTime=pd.to_datetime(['2006-11-01', '2008-11-14']), - ExitTime=pd.to_datetime(['2007-10-31', '2009-09-21'])))) + pd.DataFrame({'EntryTime': pd.to_datetime(['2006-11-01', '2008-11-14']), + 'ExitTime': pd.to_datetime(['2007-10-31', '2009-09-21'])})) assert trades['PnL'].round().equals(pd.Series([23469., -34420.])) with _tempfile() as f: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4866195 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,37 @@ +[tool.ruff] +exclude = [ + '.git', + '.eggs', + '__pycache__', + 'doc/examples', +] +ignore = [ + 'U006', + 'U007', + 'U009', + 'N802', + 'N806', + 'C901', + 'B008', + 'B011', +] +line-length = 100 +select = [ + 'I', + 'E', + 'F', + 'W', + 'U', + 'N', + 'C', + 'B', + 'T', + 'M', + 'YTT', +] + +[tool.ruff.pep8-naming] +ignore-names = [ + 'l', + 'h', +] \ No newline at end of file diff --git a/setup.py b/setup.py index 056993c..1b347f6 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ if __name__ == '__main__': 'scikit-optimize', ], 'dev': [ - 'flake8', + 'ruff', 'coverage', 'mypy', ],