PyAlgoTrade 0.20 中文文档(一)(2)

简介: PyAlgoTrade 0.20 中文文档(一)

PyAlgoTrade 0.20 中文文档(一)(1)https://developer.aliyun.com/article/1524140

交易

让我们用一个简单的策略继续,这次模拟实际交易。这个想法非常简单:

  • 如果调整后的收盘价高于 SMA(15),我们就进入多头头寸(我们放置一个市价买单)。

  • 如果已经存在多头头寸,并且调整后的收盘价低于 SMA(15),我们就退出多头头寸(我们放置一个卖市价单)。
from __future__ import print_function
from pyalgotrade import strategy
from pyalgotrade.barfeed import quandlfeed
from pyalgotrade.technical import ma
class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, smaPeriod):
        super(MyStrategy, self).__init__(feed, 1000)
        self.__position = None
        self.__instrument = instrument
        # We'll use adjusted close values instead of regular close values.
        self.setUseAdjustedValues(True)
        self.__sma = ma.SMA(feed[instrument].getPriceDataSeries(), smaPeriod)
    def onEnterOk(self, position):
        execInfo = position.getEntryOrder().getExecutionInfo()
        self.info("BUY at $%.2f" % (execInfo.getPrice()))
    def onEnterCanceled(self, position):
        self.__position = None
    def onExitOk(self, position):
        execInfo = position.getExitOrder().getExecutionInfo()
        self.info("SELL at $%.2f" % (execInfo.getPrice()))
        self.__position = None
    def onExitCanceled(self, position):
        # If the exit was canceled, re-submit it.
        self.__position.exitMarket()
    def onBars(self, bars):
        # Wait for enough bars to be available to calculate a SMA.
        if self.__sma[-1] is None:
            return
        bar = bars[self.__instrument]
        # If a position was not opened, check if we should enter a long position.
        if self.__position is None:
            if bar.getPrice() > self.__sma[-1]:
                # Enter a buy market order for 10 shares. The order is good till canceled.
                self.__position = self.enterLong(self.__instrument, 10, True)
        # Check if we have to exit the position.
        elif bar.getPrice() < self.__sma[-1] and not self.__position.exitActive():
            self.__position.exitMarket()
def run_strategy(smaPeriod):
    # Load the bar feed from the CSV file
    feed = quandlfeed.Feed()
    feed.addBarsFromCSV("orcl", "WIKI-ORCL-2000-quandl.csv")
    # Evaluate the strategy with the feed.
    myStrategy = MyStrategy(feed, "orcl", smaPeriod)
    myStrategy.run()
    print("Final portfolio value: $%.2f" % myStrategy.getBroker().getEquity())
run_strategy(15) 

如果你运行脚本,你应该会看到类似于这样的东西:

2000-01-26 00:00:00 strategy [INFO] BUY at $25.84
2000-01-28 00:00:00 strategy [INFO] SELL at $23.45
2000-02-03 00:00:00 strategy [INFO] BUY at $25.22
2000-02-22 00:00:00 strategy [INFO] SELL at $26.92
2000-02-23 00:00:00 strategy [INFO] BUY at $27.41
2000-03-31 00:00:00 strategy [INFO] SELL at $36.51
2000-04-07 00:00:00 strategy [INFO] BUY at $38.11
2000-04-12 00:00:00 strategy [INFO] SELL at $35.49
2000-04-19 00:00:00 strategy [INFO] BUY at $35.80
2000-04-20 00:00:00 strategy [INFO] SELL at $33.61
2000-04-28 00:00:00 strategy [INFO] BUY at $35.74
2000-05-05 00:00:00 strategy [INFO] SELL at $33.70
2000-05-08 00:00:00 strategy [INFO] BUY at $34.29
2000-05-09 00:00:00 strategy [INFO] SELL at $33.55
2000-05-16 00:00:00 strategy [INFO] BUY at $35.35
2000-05-19 00:00:00 strategy [INFO] SELL at $32.78
2000-05-31 00:00:00 strategy [INFO] BUY at $33.35
2000-06-23 00:00:00 strategy [INFO] SELL at $36.80
2000-06-27 00:00:00 strategy [INFO] BUY at $37.51
2000-06-28 00:00:00 strategy [INFO] SELL at $37.37
2000-06-29 00:00:00 strategy [INFO] BUY at $37.37
2000-06-30 00:00:00 strategy [INFO] SELL at $36.60
2000-07-03 00:00:00 strategy [INFO] BUY at $36.94
2000-07-05 00:00:00 strategy [INFO] SELL at $34.97
2000-07-21 00:00:00 strategy [INFO] BUY at $35.26
2000-07-24 00:00:00 strategy [INFO] SELL at $35.12
2000-07-26 00:00:00 strategy [INFO] BUY at $34.06
2000-07-28 00:00:00 strategy [INFO] SELL at $34.21
2000-08-01 00:00:00 strategy [INFO] BUY at $34.24
2000-08-02 00:00:00 strategy [INFO] SELL at $33.24
2000-08-04 00:00:00 strategy [INFO] BUY at $35.66
2000-09-11 00:00:00 strategy [INFO] SELL at $39.19
2000-09-29 00:00:00 strategy [INFO] BUY at $37.05
2000-10-02 00:00:00 strategy [INFO] SELL at $36.31
2000-10-20 00:00:00 strategy [INFO] BUY at $32.90
2000-10-31 00:00:00 strategy [INFO] SELL at $29.72
2000-11-20 00:00:00 strategy [INFO] BUY at $22.14
2000-11-21 00:00:00 strategy [INFO] SELL at $22.59
2000-12-01 00:00:00 strategy [INFO] BUY at $24.02
2000-12-15 00:00:00 strategy [INFO] SELL at $26.81
2000-12-18 00:00:00 strategy [INFO] BUY at $27.32
2000-12-21 00:00:00 strategy [INFO] SELL at $25.33
2000-12-22 00:00:00 strategy [INFO] BUY at $27.67
Final portfolio value: $974.87

但是,如果我们使用 30 作为 SMA 周期,而不是 15?那会产生更好的结果还是更差的结果?我们当然可以做这样的事情:

for i in range(10, 30):
    run_strategy(i) 

我们会发现,使用 SMA(20)可以获得更好的结果:

Final portfolio value: $1071.03

如果我们只需要尝试有限的参数值集合,那么这是可以的。但是如果我们必须测试一个具有多个参数的策略,那么串行方法肯定不会随着策略变得更加复杂而扩展。

优化

满足优化器组件。这个想法非常简单:

  • 有一个负责的服务器:
  • 提供运行策略的条形图。

  • 提供运行策略的参数。

  • 记录每个工作人员的策略结果。

  • 有多个负责的工作人员:
  • 使用服务器提供的条形图和参数运行策略。

为了说明这一点,我们将使用一个称为RSI2的策略,它需要以下参数:

  • 用于趋势识别的 SMA 周期。我们将其称为 entrySMA,范围在 150 到 250 之间。

  • 退出点使用较小的 SMA 周期。我们将其称为 exitSMA,范围在 5 到 15 之间。

  • 用于进入短头寸/多头头寸的 RSI 周期。我们将其称为 rsiPeriod,范围在 2 到 10 之间。

  • 用于多头头寸进入的 RSI 超卖阈值。我们将其称为 overSoldThreshold,范围在 5 到 25 之间。

  • 用于短头寸进入的 RSI 超买阈值。我们将其称为 overBoughtThreshold,范围在 75 到 95 之间。

如果我的数学没错的话,这些是 4409559 个不同的组合。

对于一个参数集测试该策略大约需要 0.16 秒。如果我串行执行所有组合,那么评估它们并找到最佳参数组合将需要大约 8.5 天的时间。那是一个很长的时间,但如果我可以让十台 8 核计算机来做这个工作,那么总时间将缩短到约 2.5 小时。

长话短说,我们需要并行化

让我们从下载“IBM”的 3 年每日柱开始:

python -m "pyalgotrade.tools.quandl" --source-code="WIKI" --table-code="IBM" --from-year=2009 --to-year=2011 --storage=. --force-download --frequency=daily

将此代码保存为 rsi2.py:

from pyalgotrade import strategy
from pyalgotrade.technical import ma
from pyalgotrade.technical import rsi
from pyalgotrade.technical import cross
class RSI2(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold):
        super(RSI2, self).__init__(feed)
        self.__instrument = instrument
        # We'll use adjusted close values, if available, instead of regular close values.
        if feed.barsHaveAdjClose():
            self.setUseAdjustedValues(True)
        self.__priceDS = feed[instrument].getPriceDataSeries()
        self.__entrySMA = ma.SMA(self.__priceDS, entrySMA)
        self.__exitSMA = ma.SMA(self.__priceDS, exitSMA)
        self.__rsi = rsi.RSI(self.__priceDS, rsiPeriod)
        self.__overBoughtThreshold = overBoughtThreshold
        self.__overSoldThreshold = overSoldThreshold
        self.__longPos = None
        self.__shortPos = None
    def getEntrySMA(self):
        return self.__entrySMA
    def getExitSMA(self):
        return self.__exitSMA
    def getRSI(self):
        return self.__rsi
    def onEnterCanceled(self, position):
        if self.__longPos == position:
            self.__longPos = None
        elif self.__shortPos == position:
            self.__shortPos = None
        else:
            assert(False)
    def onExitOk(self, position):
        if self.__longPos == position:
            self.__longPos = None
        elif self.__shortPos == position:
            self.__shortPos = None
        else:
            assert(False)
    def onExitCanceled(self, position):
        # If the exit was canceled, re-submit it.
        position.exitMarket()
    def onBars(self, bars):
        # Wait for enough bars to be available to calculate SMA and RSI.
        if self.__exitSMA[-1] is None or self.__entrySMA[-1] is None or self.__rsi[-1] is None:
            return
        bar = bars[self.__instrument]
        if self.__longPos is not None:
            if self.exitLongSignal():
                self.__longPos.exitMarket()
        elif self.__shortPos is not None:
            if self.exitShortSignal():
                self.__shortPos.exitMarket()
        else:
            if self.enterLongSignal(bar):
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                self.__longPos = self.enterLong(self.__instrument, shares, True)
            elif self.enterShortSignal(bar):
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                self.__shortPos = self.enterShort(self.__instrument, shares, True)
    def enterLongSignal(self, bar):
        return bar.getPrice() > self.__entrySMA[-1] and self.__rsi[-1] <= self.__overSoldThreshold
    def exitLongSignal(self):
        return cross.cross_above(self.__priceDS, self.__exitSMA) and not self.__longPos.exitActive()
    def enterShortSignal(self, bar):
        return bar.getPrice() < self.__entrySMA[-1] and self.__rsi[-1] >= self.__overBoughtThreshold
    def exitShortSignal(self):
        return cross.cross_below(self.__priceDS, self.__exitSMA) and not self.__shortPos.exitActive() 

这是服务器脚本:

import itertools
from pyalgotrade.optimizer import server
from pyalgotrade.barfeed import quandlfeed
def parameters_generator():
    instrument = ["ibm"]
    entrySMA = range(150, 251)
    exitSMA = range(5, 16)
    rsiPeriod = range(2, 11)
    overBoughtThreshold = range(75, 96)
    overSoldThreshold = range(5, 26)
    return itertools.product(instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold)
# The if __name__ == '__main__' part is necessary if running on Windows.
if __name__ == '__main__':
    # Load the bar feed from the CSV files.
    feed = quandlfeed.Feed()
    feed.addBarsFromCSV("ibm", "WIKI-IBM-2009-quandl.csv")
    feed.addBarsFromCSV("ibm", "WIKI-IBM-2010-quandl.csv")
    feed.addBarsFromCSV("ibm", "WIKI-IBM-2011-quandl.csv")
    # Run the server.
    server.serve(feed, parameters_generator(), "localhost", 5000) 

服务器代码正在做 3 件事:

  1. 声明一个生成器函数,产生策略的不同参数组合。

  2. 使用我们下载的 CSV 文件加载源数据。

  3. 运行服务器,它将在端口 5000 上等待传入连接。

这是工作脚本,使用 pyalgotrade.optimizer.worker 模块并行运行由服务器提供数据的策略:

from pyalgotrade.optimizer import worker
import rsi2
# The if __name__ == '__main__' part is necessary if running on Windows.
if __name__ == '__main__':
    worker.run(rsi2.RSI2, "localhost", 5000, workerName="localworker") 

当你运行服务器和客户端时,你会在服务器控制台上看到类似这样的东西:

2017-07-21 22:56:51,944 pyalgotrade.optimizer.server [INFO] Starting server
2017-07-21 22:56:51,944 pyalgotrade.optimizer.xmlrpcserver [INFO] Loading bars
2017-07-21 22:56:52,609 pyalgotrade.optimizer.xmlrpcserver [INFO] Started serving
2017-07-21 22:58:50,073 pyalgotrade.optimizer.xmlrpcserver [INFO] Best result so far 1261295.07089 with parameters ('ibm', 150, 5, 2, 83, 24)
.
.

以及在工作节点控制台上看到类似这样的东西:

2017-07-21 22:56:57,884 localworker [INFO] Started running
2017-07-21 22:56:57,884 localworker [INFO] Started running
2017-07-21 22:56:58,439 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 84, 15)
2017-07-21 22:56:58,498 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 94, 5)
2017-07-21 22:56:58,918 localworker [INFO] Result 1137855.88871
2017-07-21 22:56:58,918 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 84, 14)
2017-07-21 22:56:58,996 localworker [INFO] Result 1027761.85581
2017-07-21 22:56:58,997 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 93, 25)
2017-07-21 22:56:59,427 localworker [INFO] Result 1092194.67448
2017-07-21 22:57:00,016 localworker [INFO] Result 1260766.64479
.
.

请注意,你应该运行 只有一个服务器和一个或多个工作节点

如果你只想在自己的桌面上并行运行策略,你可以像这样利用 pyalgotrade.optimizer.local 模块:

import itertools
from pyalgotrade.optimizer import local
from pyalgotrade.barfeed import quandlfeed
import rsi2
def parameters_generator():
    instrument = ["ibm"]
    entrySMA = range(150, 251)
    exitSMA = range(5, 16)
    rsiPeriod = range(2, 11)
    overBoughtThreshold = range(75, 96)
    overSoldThreshold = range(5, 26)
    return itertools.product(instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold)
# The if __name__ == '__main__' part is necessary if running on Windows.
if __name__ == '__main__':
    # Load the bar feed from the CSV files.
    feed = quandlfeed.Feed()
    feed.addBarsFromCSV("ibm", "WIKI-IBM-2009-quandl.csv")
    feed.addBarsFromCSV("ibm", "WIKI-IBM-2010-quandl.csv")
    feed.addBarsFromCSV("ibm", "WIKI-IBM-2011-quandl.csv")
    local.run(rsi2.RSI2, feed, parameters_generator()) 

该代码正在做 3 件事:

  1. 声明一个生成器函数,产生不同的参数组合。

  2. 使用我们下载的 CSV 文件加载源数据。

  3. 使用 pyalgotrade.optimizer.local 模块并行运行策略,找到最佳结果。

当你运行此代码时,你应该看到类似这样的东西:

2017-07-21 22:59:26,921 pyalgotrade.optimizer.local [INFO] Starting server
2017-07-21 22:59:26,922 pyalgotrade.optimizer.xmlrpcserver [INFO] Loading bars
2017-07-21 22:59:26,922 pyalgotrade.optimizer.local [INFO] Starting workers
2017-07-21 22:59:27,642 pyalgotrade.optimizer.xmlrpcserver [INFO] Started serving
2017-07-21 23:01:14,306 pyalgotrade.optimizer.xmlrpcserver [INFO] Best result so far 1261295.07089 with parameters ('ibm', 150, 5, 2, 83, 24)
.
.

绘图

PyAlgoTrade 可以很容易地绘制策略执行。

将此保存为 sma_crossover.py:

from pyalgotrade import strategy
from pyalgotrade.technical import ma
from pyalgotrade.technical import cross
class SMACrossOver(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, smaPeriod):
        super(SMACrossOver, self).__init__(feed)
        self.__instrument = instrument
        self.__position = None
        # We'll use adjusted close values instead of regular close values.
        self.setUseAdjustedValues(True)
        self.__prices = feed[instrument].getPriceDataSeries()
        self.__sma = ma.SMA(self.__prices, smaPeriod)
    def getSMA(self):
        return self.__sma
    def onEnterCanceled(self, position):
        self.__position = None
    def onExitOk(self, position):
        self.__position = None
    def onExitCanceled(self, position):
        # If the exit was canceled, re-submit it.
        self.__position.exitMarket()
    def onBars(self, bars):
        # If a position was not opened, check if we should enter a long position.
        if self.__position is None:
            if cross.cross_above(self.__prices, self.__sma) > 0:
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                # Enter a buy market order. The order is good till canceled.
                self.__position = self.enterLong(self.__instrument, shares, True)
        # Check if we have to exit the position.
        elif not self.__position.exitActive() and cross.cross_below(self.__prices, self.__sma) > 0:
            self.__position.exitMarket() 

PyAlgoTrade 0.20 中文文档(一)(3)https://developer.aliyun.com/article/1524142

相关文章
|
4月前
|
数据采集 编解码 自动驾驶
世界模型 LingBot-World,正式开源!
蚂蚁灵波团队开源世界模型LingBot-World,专为交互式仿真设计。其核心LingBot-World-Base具备高保真、强动态、长时序一致性(支持近10分钟稳定生成)和实时交互能力(≈16FPS,延迟<1秒),依托可扩展数据引擎,从游戏环境学习物理与因果规律,打造具身智能、自动驾驶等领域的“数字演练场”。
1277 1
|
4月前
|
Linux 虚拟化 iOS开发
UTM 4.7.5 发布 - 在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS
UTM 4.7.5 发布 - 在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS
685 0
UTM 4.7.5 发布 - 在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS
|
数据采集 关系型数据库 MySQL
python-协程(async、await关键字与asyncio)
python-协程(async、await关键字与asyncio)
2110 0
|
3月前
|
数据采集 人工智能 自然语言处理
别再给AI塞提示词了:Skill正在重塑Agent的能力边界
OpenClaw 的 Skill 体系代表 Agent 工程化新范式:不堆提示词,而是将 AI 能力拆解为可描述、可按需加载、可复用的单元。通过渐进式披露与三层加载机制,提升工具调用准确率与系统稳定性,让经验沉淀为可继承、可协作的工程资产。
|
人工智能 网络协议 Linux
MCP 协议: Streamable HTTP 是最佳选择
随着AI应用变得越来越复杂并被广泛部署,原有的通信机制面临着一系列挑战。近期MCP仓库的PR #206引入了一个全新的Streamable HTTP传输层替代原有的HTTP+SSE传输层。本文将详细分析该协议的技术细节和实际优势。
7149 102
|
4月前
|
人工智能 运维 API
火爆全网的Skill自己怎么做?老金来教你!(含避坑指南)
本文深度解析Anthropic官方Skills开发指南(anthropics/skills),揭秘“渐进式展示”三层架构:100词元数据决定触发、5000词主体承载核心逻辑、资源按需加载。老金亲测踩坑,提炼6步实操流程与避坑公式,助你零基础打造高效、可维护的专业Skill。(239字)
|
运维 程序员 开发者
Docker凉了,国内镜像站全军覆没!
近期在使用Docker部署时,因网络问题导致镜像拉取失败。尽管尝试了阿里云、清华、中科大等国内镜像站均无效,最终找到仍可用的镜像源并分享解决方案。文中提供可正常访问的镜像地址及配置方法,帮助开发者快速恢复开发环境,解决燃眉之急。
1213 0
Docker凉了,国内镜像站全军覆没!
|
10月前
|
人工智能 运维 安全
MCP协议深度解析:客户端-服务器架构的技术创新
作为一名长期关注AI技术发展的博主摘星,我深刻感受到了MCP(Model Context Protocol)协议在AI生态系统中的革命性意义。MCP协议作为Anthropic公司推出的开放标准,正在重新定义AI应用与外部系统的交互方式,其基于JSON-RPC 2.0的通信机制为构建可扩展、安全的AI应用提供了坚实的技术基础。在深入研究MCP协议规范的过程中,我发现这一协议不仅解决了传统AI应用在资源访问、工具调用和上下文管理方面的痛点,更通过其独特的三大核心概念——资源(Resources)、工具(Tools)、提示词(Prompts)——构建了一个完整的AI应用生态系统。MCP协议的客户端-
695 0
MCP协议深度解析:客户端-服务器架构的技术创新
|
算法 Oracle 关系型数据库
PyAlgoTrade 0.20 中文文档(一)(1)
PyAlgoTrade 0.20 中文文档(一)
345 0
|
存储 缓存 JavaScript
npm link 与 pnpm link 的用法以及不同之处
npm link 与 pnpm link 的用法以及不同之处
1857 0