@@ -824,23 +824,35 @@ def _batch(seq):
824
824
for i in range (0 , len (seq ), n ):
825
825
yield seq [i :i + n ]
826
826
827
- # If multiprocessing start method is 'fork' (i.e. on POSIX), use
828
- # a pool of processes to compute results in parallel.
829
- # Otherwise (i.e. on Windos), sequential computation will be "faster".
830
- if mp .get_start_method (allow_none = False ) == 'fork' :
831
- with ProcessPoolExecutor () as executor :
832
- futures = [executor .submit (self ._mp_task , params )
833
- for params in _batch (param_combos )]
834
- for future in _tqdm (as_completed (futures ), total = len (futures )):
835
- for params , stats in future .result ():
836
- heatmap [tuple (params .values ())] = maximize (stats )
837
- else :
838
- if os .name == 'posix' :
839
- warnings .warn ("For multiprocessing support in `Backtest.optimize()` "
840
- "set multiprocessing start method to 'fork'." )
841
- for params in _tqdm (param_combos ):
842
- for _ , stats in self ._mp_task ([params ]):
843
- heatmap [tuple (params .values ())] = maximize (stats )
827
+ # Save necessary objects into "global" state; pass into concurrent executor
828
+ # (and thus pickle) nothing but two numbers; receive nothing but numbers.
829
+ # With start method "fork", children processes will inherit parent address space
830
+ # in a copy-on-write manner, achieving better performance/RAM benefit.
831
+ backtest_uuid = np .random .random ()
832
+ param_batches = list (_batch (param_combos ))
833
+ Backtest ._mp_backtests [backtest_uuid ] = (self , param_batches , maximize )
834
+ try :
835
+ # If multiprocessing start method is 'fork' (i.e. on POSIX), use
836
+ # a pool of processes to compute results in parallel.
837
+ # Otherwise (i.e. on Windos), sequential computation will be "faster".
838
+ if mp .get_start_method (allow_none = False ) == 'fork' :
839
+ with ProcessPoolExecutor () as executor :
840
+ futures = [executor .submit (Backtest ._mp_task , backtest_uuid , i )
841
+ for i in range (len (param_batches ))]
842
+ for future in _tqdm (as_completed (futures ), total = len (futures )):
843
+ batch_index , values = future .result ()
844
+ for value , params in zip (values , param_batches [batch_index ]):
845
+ heatmap [tuple (params .values ())] = value
846
+ else :
847
+ if os .name == 'posix' :
848
+ warnings .warn ("For multiprocessing support in `Backtest.optimize()` "
849
+ "set multiprocessing start method to 'fork'." )
850
+ for batch_index in _tqdm (range (len (param_batches ))):
851
+ _ , values = Backtest ._mp_task (backtest_uuid , batch_index )
852
+ for value , params in zip (values , param_batches [batch_index ]):
853
+ heatmap [tuple (params .values ())] = value
854
+ finally :
855
+ del Backtest ._mp_backtests [backtest_uuid ]
844
856
845
857
best_params = heatmap .idxmax ()
846
858
@@ -856,10 +868,14 @@ def _batch(seq):
856
868
return self ._results , heatmap
857
869
return self ._results
858
870
859
- def _mp_task (self , param_combos ):
860
- return [(params , stats ) for params , stats in ((params , self .run (** params ))
861
- for params in param_combos )
862
- if stats ['# Trades' ]]
871
+ @staticmethod
872
+ def _mp_task (backtest_uuid , batch_index ):
873
+ bt , param_batches , maximize_func = Backtest ._mp_backtests [backtest_uuid ]
874
+ return batch_index , [maximize_func (stats ) if stats ['# Trades' ] else np .nan
875
+ for stats in (bt .run (** params )
876
+ for params in param_batches [batch_index ])]
877
+
878
+ _mp_backtests = {}
863
879
864
880
@staticmethod
865
881
def _compute_drawdown_duration_peaks (dd : pd .Series ):
0 commit comments