Python量化交易实战-36模拟sma交易和回测

auto-trade xuhss 1149℃ 0评论

pyalgotrade模拟交易和回测

可以看到上节课的代码,我们计算了最基础的simpleMovingAverage均价的指标,最终获取了平安银行收盘价的数据,和SMA均价的数据,
也就是说,我们学会了如何获取数据定义数据,以及计算一个基础的技术指标,然后将数据打印出来。

今天我们学习当我们计算出指标之后,怎么利用指标进行交易,比如说得到了买入卖出信号之后 如何对这些数据进行回测,我们回到官网,
我们继续延续上节课的示例代码的后面。

一、官方示例代码详细讲解

它这个下面还给了一个例子,多计算了一个叫做rsi的指标(相对强弱指标)。

from pyalgotrade import strategy
from pyalgotrade.barfeed import quandlfeed
from pyalgotrade.technical import ma
from pyalgotrade.technical import rsi

def safe_round(value, digits):
    if value is not None:
        value = round(value, digits)
    return value

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        super(MyStrategy, self).__init__(feed)
        self.__rsi = rsi.RSI(feed[instrument].getCloseDataSeries(), 14)
        self.__sma = ma.SMA(self.__rsi, 15)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%s %s %s" % (
            bar.getClose(), safe_round(self.__rsi[-1], 2), safe_round(self.__sma[-1], 2)
        ))

# 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's bars.
myStrategy = MyStrategy(feed, "orcl")
myStrategy.run()

你可以了解这个指标最基础的计算方式,它的计算规则就是将涨的天数的均价除以下跌的天数的均价,得到的结果越大,达标涨势越好。

大家感兴趣的话可以自己验证,这里的使用方法和上节课的ma的计算方法一直。后面是它算出来的结果:

20210701184658 - Python量化交易实战-36模拟sma交易和回测

接著往下看交易和回测的部分,也就是trading的部分,我们简单来看一下它的翻译:

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

  • 如果调整后的收盘价高于 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)

这里还是通过MyStrategy定义交易规则:

def __init__(self, feed, instrument, smaPeriod):
        super(MyStrategy, self).__init__(feed, 1000)

他的构造函数多了一个参数:smaPeriod,代表的是 多少天均价的周期值

接着调用父类的构造函数,传递feed数据集1000,这里的1000代表cash_or_brk,你可以理解为投入的资金。它默认值是一百万,这里传递1000

        self.__position = None
        self.__instrument = instrument

当前的仓位__position设置为空,

        # We'll use adjusted close values instead of regular close values.
        self.setUseAdjustedValues(True)

接着,使用调整后的收盘价替代原来的收盘价。

        self.__sma = ma.SMA(feed[instrument].getPriceDataSeries(), smaPeriod)

这里依然计算了sma的值,调用的是ma.SMA方法,feed[instrument]表示对应股票代码的数据集,getPriceDataSeries表示获取价格数据,第二个参数是smaPeriod,表示 多少天均价的周期值

再往下看发现多了几个函数:这些函数用来控制买入和卖出。

  1. onEnterOk买入,
  2. onEnterCanceled买入取消,
  3. onExitOk卖出,
  4. onExitCanceled卖出取消,
    def onEnterOk(self, position):
        execInfo = position.getEntryOrder().getExecutionInfo()
        self.info("BUY at $%.2f" % (execInfo.getPrice()))

获取到一个执行的信息,execInfo。通过仓位position获取到买入的订单,再获取到执行的信息。然后将买入委托的价格打印出来。

也就是说,这个函数的作用是:当我产生买入信号的之后,有没有买入委托,如果执行买入,就打印信息。同样对于onExitOk函数:

    def onExitOk(self, position):
        execInfo = position.getExitOrder().getExecutionInfo()
        self.info("SELL at $%.2f" % (execInfo.getPrice()))
        self.__position = None

这个函数会寻找有没有卖出委托,如果有,就打印出来。如果执行成功,__position=None就是卖空了。

    def onEnterCanceled(self, position):
        self.__position = None

当买入取消了,也会__position=None

    def onExitCanceled(self, position):
        # If the exit was canceled, re-submit it.
        self.__position.exitMarket()

当卖出取消,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()

onBars除了是提供行情数据的方法.在交易过程中进行条件判断的部分(计算买入和卖出信号)也是出现在这个函数中。、

if self.__sma[-1] is None:
    return

首先它会判断数据是否足够,如果数据不足,直接return。因为如果设置为15天,没有到15天就是计算不出均价的。

    bar = bars[self.__instrument]

初始数据集设置

    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()

1.如果当前股票没有买入:

如果当前价格大于均价,就买入。self.enterLong进行买入操作,它的第二个参数10代表:10股。表示买入的股数。如果是A股,一次至少买入100股。第三个参数为True,表示买入操作直到被取消前,都是可以执行的。

2.如果当前股票有买入:

就需要判断有没有卖出的条件了,如果当前价格小于均价,并且__position.exitActive当前不是正在撤单的情况下,就会执行卖出交易的逻辑。

所以如果要定义一个自己的策略,需要去实例化父类的这5个方法。接着就是主题的调用实现的部分:

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())

它将之前的获取数据:

# 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()
# 打印总持仓:(cash + shares * price) (持有现金 + 股数*价格)
#getBroker和交易里面订单相关的信息,包括持股,现金都是从broker来的
print("Final portfolio value: $%.2f" % myStrategy.getBroker().getEquity())

这些操作封装起来,最后直接通过调用这个方法,传递smaPeriod多少天均价的周期值,进行主体脚本的执行。

二、实战

我们基于它的这个例子来改写我们自己的脚本。

#使用pyalgotrade进行数据回测
import pyalgotrade
from pyalgotrade import strategy
from pyalgotrade.barfeed import quandlfeed
from pyalgotrade_tushare import tools, barfeed
from pyalgotrade.technical import ma

def safe_round(value, digits):
    if value is not None:
        value = round(value, digits)
    return value

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, smaPeriod):
        super(MyStrategy, self).__init__(feed, 10000)
        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, 100, 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
    instruments = ["000001"]
    feeds = tools.build_feed(instruments, 2016, 2018, "histdata")
    print(feeds)

    # Evaluate the strategy with the feed's bars.
    myStrategy = MyStrategy(feeds, instruments[0], smaPeriod)
    myStrategy.run()
    # 打印总持仓:(cash + shares * price) (持有现金 + 股数*价格)
    print("Final portfolio value: $%.2f" % myStrategy.getBroker().getEquity())

run_strategy(15)

这里我将super(MyStrategy, self).__init__(feed, 10000)初始的现金设置为10000,如果最后大于这个值,表示赚钱了!

self.__position = self.enterLong(self.__instrument, 100, True)第二个参数传递100,因为A股是1手起卖。

运行以上代码:

20210703062007 - Python量化交易实战-36模拟sma交易和回测

这时因为我们的行情数据里面没有AdjClose:

    def setUseAdjustedValues(self, useAdjusted):
        if useAdjusted and not self.barsHaveAdjClose():
            raise Exception("The barfeed doesn't support adjusted close values")

去除掉这段代码即可

使用原来的默认的收盘价。

    def __init__(self, feed, instrument, smaPeriod):
        super(MyStrategy, self).__init__(feed, 10000)
        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)

运行:

20210703062638 - Python量化交易实战-36模拟sma交易和回测

到这里我们就完成了简单的回测,知道是否赚钱。接下来就可以尝试多周期的回测,比如说设定不同的smaPeriod。这里大体的逻辑是一样的,只是指标不一样。

这里给出的最后结果是总持仓的金额,我们之前主要是给出交易的信号。也就是买入卖出的信号。所以这里会忽略很多地方。比如说交易费率,每一次交易价格的细节都会被忽略。

当然更多的细节会提升你了解这个开源项目的门槛。所以最开始做量化的时候,可以先进行量化分析,然后配合手工。这样是最快的方式。

三、多参数回测

再来到官网示例的页面:

20210703063722 - Python量化交易实战-36模拟sma交易和回测

调用这个循环,注释多余的输出代码:可以看到时间越长,收益越低。11天的均价收益最高

20210703064038 - Python量化交易实战-36模拟sma交易和回测

转载请注明:xuhss » Python量化交易实战-36模拟sma交易和回测

喜欢 (6)

您必须 登录 才能发表评论!