Skip to content

Commit edf0107

Browse files
committed
BUG: Fix optimization hanging on MS Windows
Fixes #1256
1 parent 84e685a commit edf0107

File tree

5 files changed

+27
-7
lines changed

5 files changed

+27
-7
lines changed

backtesting/__init__.py

+20
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,23 @@
6868
from . import lib # noqa: F401
6969
from ._plotting import set_bokeh_output # noqa: F401
7070
from .backtesting import Backtest, Strategy # noqa: F401
71+
72+
73+
# Add overridable backtesting.Pool used for parallel optimization
74+
def Pool(processes=None, initializer=None, initargs=()):
75+
import multiprocessing as mp
76+
if mp.get_start_method() == 'spawn':
77+
import warnings
78+
warnings.warn(
79+
"If you want to use multi-process optimization with "
80+
"`multiprocessing.get_start_method() == 'spawn'` (e.g. on Windows),"
81+
"set `backtesting.Pool = multiprocessing.Pool` (or of the desired context) "
82+
"and hide `bt.optimize()` call behind a `if __name__ == '__main__'` guard. "
83+
"Currently using thread-based paralellism, "
84+
"which might be slightly slower for non-numpy / non-GIL-releasing code. "
85+
"See https://github.com/kernc/backtesting.py/issues/1256",
86+
category=RuntimeWarning, stacklevel=3)
87+
from multiprocessing.dummy import Pool
88+
return Pool(processes, initializer, initargs)
89+
else:
90+
return mp.Pool(processes, initializer, initargs)

backtesting/backtesting.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
from __future__ import annotations
1010

11-
import multiprocessing as mp
1211
import sys
1312
import warnings
1413
from abc import ABCMeta, abstractmethod
@@ -1503,9 +1502,9 @@ def _optimize_grid() -> Union[pd.Series, Tuple[pd.Series, pd.Series]]:
15031502
[p.values() for p in param_combos],
15041503
names=next(iter(param_combos)).keys()))
15051504

1506-
with mp.Pool() as pool, \
1507-
SharedMemoryManager() as smm:
1508-
1505+
from . import Pool
1506+
with SharedMemoryManager() as smm, \
1507+
Pool(processes=1) as pool:
15091508
with patch(self, '_data', None):
15101509
bt = copy(self) # bt._data will be reassigned in _mp_task worker
15111510
results = _tqdm(

backtesting/lib.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
from __future__ import annotations
1515

16-
import multiprocessing as mp
1716
import warnings
1817
from collections import OrderedDict
1918
from inspect import currentframe
@@ -569,7 +568,8 @@ def run(self, **kwargs):
569568
Wraps `backtesting.backtesting.Backtest.run`. Returns `pd.DataFrame` with
570569
currency indexes in columns.
571570
"""
572-
with mp.Pool() as pool, \
571+
from . import Pool
572+
with Pool() as pool, \
573573
SharedMemoryManager() as smm:
574574
shm = [smm.df2shm(df) for df in self._dfs]
575575
results = _tqdm(

backtesting/test/_test.py

-1
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,6 @@ def test_indicators_picklable(self):
10011001
class TestDocs(TestCase):
10021002
DOCS_DIR = os.path.join(os.path.dirname(__file__), '..', '..', 'doc')
10031003

1004-
@unittest.skipIf('win' in sys.platform, "Locks up with `ModuleNotFoundError: No module named '<run_path>'`")
10051004
@unittest.skipUnless(os.path.isdir(DOCS_DIR), "docs dir doesn't exist")
10061005
def test_examples(self):
10071006
examples = glob(os.path.join(self.DOCS_DIR, 'examples', '*.py'))

doc/examples/Quick Start User Guide.py renamed to doc/examples/QuickStartUserGuide.py

+2
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ def next(self):
184184
# +
185185
# %%time
186186

187+
# if __name__ == '__main__':
188+
187189
stats = bt.optimize(n1=range(5, 30, 5),
188190
n2=range(10, 70, 5),
189191
maximize='Equity Final [$]',

0 commit comments

Comments
 (0)