我正在对定义为类的交易策略进行回测。我正在尝试选择要在模型中输入的最佳参数组合,因此我在给定的时间段上运行多个回测,尝试不同的组合。这个想法是能够选择第一代种群来输入遗传算法。似乎是多处理的完美工作!
所以我尝试了很多东西,看看什么效果更快。我打开了10个Spyder控制台(是的,我试过了(,并为每个控制台运行了一个参数组合(所有控制台同时运行(。
用于每个Spyder控制台的示例代码:
class MyStrategy(day,parameters):
# my strategy that runs on a single day
backtesting=[]
for day in days:
backtesting_day=MyStrategy(day,single_parameter_combi)
backtesting.append(backtesting_day)
然后,我尝试了使用池的多处理方式。
多处理中使用的示例代码:
class MyStrategy(day,parameters):
# my strategy that runs on a single day
def single_run_backtesting(single_parameter_combi):
backtesting=[]
for day in days:
backtesting_day=MyStrategy(day,single_parameter_combi)
backtesting.append(backtesting_day)
return backtesting
def backtest_many(list_of parameter_combinations):
p=multiprocessing.pool()
result=p.map(single_run_backtesting,list_of parameter_combinations)
p.close()
p.join()
return result
if __name__ == '__main__':
parameter_combis=[...] # a list of parameter combinations, 10 different ones in this case
result = backtest_many(parameter_combis)
我还尝试了以下方法:打开 5 个 Spyder 控制台并在 for 循环中运行该类的 2 个实例,如下所示,以及一个包含 10 个类实例的 Spyder 控制台。
class MyStrategy(day,parameters):
# my strategy that runs on a single day
parameter_combis=[...] # a list of parameter combinations
backtest_dict={k: [] for k in range(len(parameter_combis)} # make a dictionary of empty lists
for day in days:
for j,single_parameter_combi in enumerate(parameter_combis):
backtesting_day=MyStrategy(day,single_parameter_combi)
backtest_dict[j].append(backtesting_day)
令我非常惊讶的是,多处理大约需要 25 分钟才能完成一天,大约在同一时间,单个 Spyder 控制台在 for 循环中有 10 个类实例,神奇的是,当我同时运行 10 个 Spyder 控制台时,它只需要 15 分钟。如何处理这些信息?这对我来说真的没有意义。我在 Windows 12 上运行一台 10-cpu 机器。
考虑到我计划使用 96 核机器在 AWS 上运行东西,在遗传算法中交叉了大约 100 个参数组合,该算法应该运行大约 20-30 代(完整的回测是 2 个工作月 = 44 天(。
我的问题是:我错过了什么???最重要的是,这只是规模的差异吗? 我知道,例如,如果您定义一个简单的平方函数并串行运行 100 次,则多处理实际上比 for 循环慢。你开始看到大约10000倍的优势,例如:https://github.com/vprusso/youtube_tutorials/blob/master/multiprocessing_and_threading/multiprocessing/multiprocessing_pool.py
当我使用多处理进行多达 100 种组合时,我会看到性能差异吗,如果是这种情况,有什么办法可以在 advnace 中知道吗?我是否正确编写了代码?其他想法?您认为如果我在"上面"一步中使用多处理,在多天内在单个参数组合中使用多处理会显着加快速度吗?
要扩展我的评论"尝试p.imap_unordered()
.":
p.map()
可确保按参数列表中相同的顺序获取结果。为了实现这一目标,一些工人必须闲置一段时间。 对于您的用例 - 本质上是参数组合的网格搜索 - 您真的不需要将它们按相同的顺序排列,您只想最终获得最佳选择。(此外,引用文档,"它可能会导致非常长的可迭代对象使用高内存。考虑使用 imap(( 或 imap_unordered( 和显式块大小选项来提高效率。
相比之下,p.imap_unordered()
并不真正关心 - 它只是将事情排队,工人在释放时处理它们。
还值得尝试使用chunksize
参数 - 引用imap()
文档,"对于很长的迭代对象,使用较大的块大小值可以使作业完成速度比使用默认值 1 快得多。(因为您花在排队和同步上的时间更少(。
最后,对于您的特定用例,您可能需要考虑让主进程使用生成器函数生成无限数量的参数组合,并在找到足够好的解决方案或经过足够的时间后中断循环。
下面是一个简单的函数和一个人为的问题(找到两个随机数 0..1 以最大化它们的总和(。请记住也从worker函数返回原始参数集,否则您将无法访问它!:)
import random
import multiprocessing
import time
def find_best(*, param_iterable, worker_func, metric_func, max_time, chunksize=10):
best_result = None
best_metric = None
start_time = time.time()
n_results = 0
with multiprocessing.Pool() as p:
for result in p.imap_unordered(worker_func, param_iterable, chunksize=chunksize):
n_results += 1
elapsed_time = time.time() - start_time
metric = metric_func(result)
if best_metric is None or metric > best_metric:
print(f'{elapsed_time}: Found new best solution, metric {metric}')
best_metric = metric
best_result = result
if elapsed_time >= max_time:
print(f'{elapsed_time}: Max time reached.')
break
final_time = time.time() - start_time
print(f'Searched {n_results} results in {final_time} s.')
return best_result
# ------------
def generate_parameter():
return {'a': random.random(), 'b': random.random()}
def generate_parameters():
while True:
yield generate_parameter()
def my_worker(parameters):
return {
'parameters': parameters, # remember to return this too!
'value': parameters['a'] + parameters['b'], # our maximizable metric
}
def my_metric(result):
return result['value']
def main():
result = find_best(
param_iterable=generate_parameters(),
worker_func=my_worker,
metric_func=my_metric,
max_time=5,
)
print(f'Best result: {result}')
if __name__ == '__main__':
main()
运行示例:
~/Desktop $ python3 so59357979.py
0.022627830505371094: Found new best solution, metric 0.5126700311039976
0.022940874099731445: Found new best solution, metric 0.9464256914062249
0.022969961166381836: Found new best solution, metric 1.2946600313637404
0.02298712730407715: Found new best solution, metric 1.6255217652861256
0.023016929626464844: Found new best solution, metric 1.7041449687571075
0.02303481101989746: Found new best solution, metric 1.8898109980050104
0.030200958251953125: Found new best solution, metric 1.9031436071918972
0.030324935913085938: Found new best solution, metric 1.9321951916206537
0.03880715370178223: Found new best solution, metric 1.9410837287942249
0.03970479965209961: Found new best solution, metric 1.9649277383314245
0.07829880714416504: Found new best solution, metric 1.9926667738329622
0.6105098724365234: Found new best solution, metric 1.997217792614364
5.000051021575928: Max time reached.
Searched 621931 results in 5.07216 s.
Best result: {'parameters': {'a': 0.997483, 'b': 0.999734}, 'value': 1.997217}
(顺便说一下,这在chunksize=1
时几乎慢了 6 倍。