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的计算方法一直。后面是它算出来的结果:
接著往下看交易和回测的部分,也就是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
,表示 多少天均价的周期值。
再往下看发现多了几个函数:这些函数用来控制买入和卖出。
- onEnterOk买入,
- onEnterCanceled买入取消,
- onExitOk卖出,
- 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手起卖。
运行以上代码:
这时因为我们的行情数据里面没有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)
运行:
到这里我们就完成了简单的回测,知道是否赚钱。接下来就可以尝试多周期的回测,比如说设定不同的smaPeriod
。这里大体的逻辑是一样的,只是指标不一样。
这里给出的最后结果是总持仓的金额,我们之前主要是给出交易的信号。也就是买入卖出的信号。所以这里会忽略很多地方。比如说交易费率,每一次交易价格的细节都会被忽略。
当然更多的细节会提升你了解这个开源项目的门槛。所以最开始做量化的时候,可以先进行量化分析,然后配合手工。这样是最快的方式。
三、多参数回测
再来到官网示例的页面:
调用这个循环,注释多余的输出代码:可以看到时间越长,收益越低。11天的均价收益最高
转载请注明:xuhss » Python量化交易实战-36模拟sma交易和回测