|  | 
| 8 | 8 | 
 | 
| 9 | 9 | from __future__ import annotations | 
| 10 | 10 | 
 | 
| 11 |  | -import multiprocessing as mp | 
| 12 | 11 | import sys | 
| 13 | 12 | import warnings | 
| 14 | 13 | from abc import ABCMeta, abstractmethod | 
|  | 
| 24 | 23 | from numpy.random import default_rng | 
| 25 | 24 | 
 | 
| 26 | 25 | from ._plotting import plot  # noqa: I001 | 
| 27 |  | -from ._stats import compute_stats | 
|  | 26 | +from ._stats import compute_stats, dummy_stats | 
| 28 | 27 | from ._util import ( | 
| 29 | 28 |     SharedMemoryManager, _as_str, _Indicator, _Data, _batch, _indicator_warmup_nbars, | 
| 30 | 29 |     _strategy_indicators, patch, try_, _tqdm, | 
| @@ -211,7 +210,7 @@ def next(self): | 
| 211 | 210 |         """ | 
| 212 | 211 | 
 | 
| 213 | 212 |     class __FULL_EQUITY(float):  # noqa: N801 | 
| 214 |  | -        def __repr__(self): return '.9999' | 
|  | 213 | +        def __repr__(self): return '.9999'  # noqa: E704 | 
| 215 | 214 |     _FULL_EQUITY = __FULL_EQUITY(1 - sys.float_info.epsilon) | 
| 216 | 215 | 
 | 
| 217 | 216 |     def buy(self, *, | 
| @@ -449,7 +448,7 @@ def __repr__(self): | 
| 449 | 448 |                                                  ('tp', self.__tp_price), | 
| 450 | 449 |                                                  ('contingent', self.is_contingent), | 
| 451 | 450 |                                                  ('tag', self.__tag), | 
| 452 |  | -                                             ) if value is not None)) | 
|  | 451 | +                                             ) if value is not None))  # noqa: E126 | 
| 453 | 452 | 
 | 
| 454 | 453 |     def cancel(self): | 
| 455 | 454 |         """Cancel the order.""" | 
| @@ -578,7 +577,7 @@ def __init__(self, broker: '_Broker', size: int, entry_price: float, entry_bar, | 
| 578 | 577 |     def __repr__(self): | 
| 579 | 578 |         return f'<Trade size={self.__size} time={self.__entry_bar}-{self.__exit_bar or ""} ' \ | 
| 580 | 579 |                f'price={self.__entry_price}-{self.__exit_price or ""} pl={self.pl:.0f}' \ | 
| 581 |  | -               f'{" tag="+str(self.__tag) if self.__tag is not None else ""}>' | 
|  | 580 | +               f'{" tag=" + str(self.__tag) if self.__tag is not None else ""}>' | 
| 582 | 581 | 
 | 
| 583 | 582 |     def _replace(self, **kwargs): | 
| 584 | 583 |         for k, v in kwargs.items(): | 
| @@ -1309,7 +1308,8 @@ def run(self, **kwargs) -> pd.Series: | 
| 1309 | 1308 |         # np.nan >= 3 is not invalid; it's False. | 
| 1310 | 1309 |         with np.errstate(invalid='ignore'): | 
| 1311 | 1310 | 
 | 
| 1312 |  | -            for i in _tqdm(range(start, len(self._data)), desc=self.run.__qualname__): | 
|  | 1311 | +            for i in _tqdm(range(start, len(self._data)), desc=self.run.__qualname__, | 
|  | 1312 | +                           unit='bar', mininterval=2, miniters=100): | 
| 1313 | 1313 |                 # Prepare data and indicators for `next` call | 
| 1314 | 1314 |                 data._set_length(i + 1) | 
| 1315 | 1315 |                 for attr, indicator in indicator_attrs: | 
| @@ -1425,9 +1425,7 @@ def optimize(self, *, | 
| 1425 | 1425 |         maximize_key = None | 
| 1426 | 1426 |         if isinstance(maximize, str): | 
| 1427 | 1427 |             maximize_key = str(maximize) | 
| 1428 |  | -            stats_keys = compute_stats( | 
| 1429 |  | -                [], np.r_[[np.nan]], pd.DataFrame({col: [np.nan] for col in ('Close',)}), None, 0).index | 
| 1430 |  | -            if maximize not in stats_keys: | 
|  | 1428 | +            if maximize not in dummy_stats().index: | 
| 1431 | 1429 |                 raise ValueError('`maximize`, if str, must match a key in pd.Series ' | 
| 1432 | 1430 |                                  'result of backtest.run()') | 
| 1433 | 1431 | 
 | 
| @@ -1503,9 +1501,9 @@ def _optimize_grid() -> Union[pd.Series, Tuple[pd.Series, pd.Series]]: | 
| 1503 | 1501 |                                     [p.values() for p in param_combos], | 
| 1504 | 1502 |                                     names=next(iter(param_combos)).keys())) | 
| 1505 | 1503 | 
 | 
| 1506 |  | -            with mp.Pool() as pool, \ | 
|  | 1504 | +            from . import Pool | 
|  | 1505 | +            with Pool() as pool, \ | 
| 1507 | 1506 |                     SharedMemoryManager() as smm: | 
| 1508 |  | - | 
| 1509 | 1507 |                 with patch(self, '_data', None): | 
| 1510 | 1508 |                     bt = copy(self)  # bt._data will be reassigned in _mp_task worker | 
| 1511 | 1509 |                 results = _tqdm( | 
| @@ -1567,7 +1565,8 @@ def memoized_run(tup): | 
| 1567 | 1565 |                 stats = self.run(**dict(tup)) | 
| 1568 | 1566 |                 return -maximize(stats) | 
| 1569 | 1567 | 
 | 
| 1570 |  | -            progress = iter(_tqdm(repeat(None), total=max_tries, leave=False, desc='Backtest.optimize')) | 
|  | 1568 | +            progress = iter(_tqdm(repeat(None), total=max_tries, leave=False, | 
|  | 1569 | +                                  desc=self.optimize.__qualname__, mininterval=2)) | 
| 1571 | 1570 |             _names = tuple(kwargs.keys()) | 
| 1572 | 1571 | 
 | 
| 1573 | 1572 |             def objective_function(x): | 
|  | 
0 commit comments