利用所有可用的内核是我为反向交易者考虑的事情,但从未完成。支持自然运算,删除数组符号,包含新指针和 bla, bla, bla。
实际上,我不是优化的忠实拥护者,因此也不是为优化使用所有内核的忠实拥护者。恕我直言,一个好主意值得一百万次优化。
笔记
最初的多核支持已经存在,并且适用于众所周知的测试用例集。鉴于pickle表现出的行为,预计其他一些调整可以确保在进行多核优化时所有指针和函数可以在进程之间来回传递。
笔记
在 1.0.10.88 版本中已经推送了一些针对多核的额外更正,以使更多“不可挑选”的东西变得可挑选。到目前为止,指针测试没有显示任何问题。
但是 BigMikeTrading 论坛中的某个人询问了该平台与其他平台相比必须提供的功能,我提到了一些功能,例如PyAlgoTrade已经具备(甚至是多机)
完成它所需的小而正确的推动就在那里。根据过去的经验,并且因为互联网上充满了我已经知道的参考数据:即使是最简单的多线程(无论 GIL 律师怎么说)在 Python 中都是不行的,无论版本如何。多线程在 Python 中是假的,因为您有多个线程但没有并行运行代码。使用 IO 绑定线程创建抽象和单独的代码路径运行可能很好,但它确实是一个杀手。
然后只剩下一个选择:模块multiprocessing或类似的。
展望光明的未来,我决定选择现代版本: concurrent.futures (后来证明是一个错误的选择)即使这意味着为 Python 2.6/2.7 支持添加外部依赖项。
历史:
Python 的一些动态特性不能很好地在进程之间来回发送数据
当酸洗(串行化)诸如未在模块级别定义的类,lambda,对实例方法的引用和没有唯一名称的动态类(即使这些类本身是唯一的)时,所涉及的模块(
pickle)会阻塞
我把这些东西分散在代码中。然后我从 pathos multiprocess https://pypi.python.org/pypi/multiprocess找到了dill和兄弟姐妹。显然他们会解决串行化问题,但会增加更多的外部依赖……不,不。
回到绘图板上,看看是否可以将不可拾取的项目设为可拾取,即使pickle模块产生了错误,这会让一些老 GCC 开发人员非常高兴。
它完成了……还是没有?
将不可拾取的物品重做为可拾取的东西
使用 Python 2.7.9 运行测试并轻而易举地运行……使用我机器的 8 个内核流畅而令人耳目一新
使用 Python 3.4.3 运行测试,8 核开始运行,但经过一些优化后,每个后续策略的运行时间会越来越长……直到无法忍受。
显然,将结果(一个完整的运行策略)提取到主进程中遇到了与内存分配相关的一些限制(而且我的机器有足够的可用 RAM……对于几个小时的并行优化来说绰绰有余)
一些额外的阅读让我考虑简化我的场景:
使用
concurrent.futures似乎是未来的证明但是标准的
multiprocessing模块已经具备了backtrader需求
闻起来一直在使用矫枉过正,一些行很快被重新设计,并且:
测试在 Python 2.7 上运行良好(甚至比以前更快)
测试运行速度与 Python 3.4 一样快
是时候进行清理,运行完整的测试并运行推送和发布 1.0.9.88。没有新指针……只是简单的旧多核优化
阅读完所有内容……是时候编写一个关于如何控制优化以使用多个内核的令人耳目一新的脚本了
- 好消息……无需做任何事情……无需用户干预即可完成
当用户希望优化strategy时, Strategy子类被添加到Cerebro实例中,如下所示:
cerebro.optstrategy(StrategyClass, *args, **kwargs)
与将策略传递给Cerebro的常规方式相反:
cerebro.addstrategy(StrategyClass, *args, **kwargs)
这一直如此,并没有改变。背景是:
-
Cerebro需要了解是否要优化策略以正确处理策略的参数,这些参数可能已经是常规策略的可iterables对象
现在……通过optstrategy传递给cerebro的optstrategy获得了使用机器所有可用内核的额外好处。
当然,如果最终用户希望对使用过的内核进行细粒度控制……这是可能的。创建Cerebro的标准方法:
大脑= bt。 Cerebro () # runonce 是True , preload 是True并且 “new” maxcpus 是 None
maxcpus (此版本的新参数)是控制键:
maxcpus = None -> 使用所有可用的 CPU
maxcpus = 1 -> 不要运行多核
maxcpues = 2 … -> 使用指定的内核数
这是一种选择退出策略,因为多核已经加入。
在具有 16 GB RAM、运行 Windows 8.1 和 Python 64bit 2.7.9 的 4 核(每个核 2x 线程 - 总共 8 个逻辑处理器)机器上进行比较
使用 1 个内核运行:326 秒
8核运行:127秒
不同的测试运行表明,该比率平均约为 2.75:1。
不幸的是,进程的创建/销毁和对象的来回酸洗具有潜在的好处,但加速仍然很重要。
该图显示了正在使用的 8 个内核。
代码如下。只需将maxcpus参数更改为1即可将测试限制为 1 个内核。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import time
from six.moves import xrange
import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds
class OptimizeStrategy(bt.Strategy):
params = (('smaperiod', 15),
('macdperiod1', 12),
('macdperiod2', 26),
('macdperiod3', 9),
)
def __init__(self):
# Add indicators to add load
btind.SMA(period=self.p.smaperiod)
btind.MACD(period_me1=self.p.macdperiod1,
period_me2=self.p.macdperiod2,
period_signal=self.p.macdperiod3)
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro(maxcpus=None)
# Add a strategy
cerebro.optstrategy(
OptimizeStrategy,
smaperiod=xrange(5, 40),
macdperiod1=xrange(12, 20),
macdperiod2=xrange(26, 30),
macdperiod3=xrange(9, 15),
)
# Create a Data Feed
datapath = ('../datas/2006-day-001.txt')
data = bt.feeds.BacktraderCSVData(dataname=datapath)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# clock the start of the process
tstart = time.clock()
# Run over everything
stratruns = cerebro.run()
# clock the end of the process
tend = time.clock()
print('==================================================')
for stratrun in stratruns:
print('**************************************************')
for strat in stratrun:
print('--------------------------------------------------')
print(strat.p._getkwargs())
print('==================================================')
# print out the result
print('Time used:', str(tend - tstart))