欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

pyalgotrade量化交易回测

程序员文章站 2022-07-13 15:43:17
...

转载请注明出处:https://blog.csdn.net/xuezoutianya/article/details/104059649

pyalgotrade官网文档http://gbeced.github.io/pyalgotrade/docs/v0.20/html/

官方给的教程很容易上手,初学者可以参考官方文档中的示例策略,唯一的问题是例程中的下载方法不适用于下载国内股票数据。

本博文介绍如何下载国内股票数据,以及如何定制化你自己的交易策略并显示回测结果。

如何下载国内股票数据

在网上查了不少博文,找到两种方式,但第一种方式我自己用会报错,这里还是列出来

  • 使用pyalgotrade_tushare模块

安装方法

pip install pyalgotrade_tushare

使用代码

# 导入pyalgotrade_tushare模块
from pyalgotrade_tushare import tools

'''
其他代码段
'''

# 股票代码,str类型,如"399300"(沪深300)
instruments = ["399300"]
# 第二个参数是起始日期,第三个参数是结束日期,第四个参数是下载目录,如"."(当前目录)
feeds = tools.build_feed(instruments, 2018, 2019, ".")
  • 自己封装函数或者模块

MyDownload.py

原代码出处https://blog.csdn.net/lawme/article/details/51495349,我添加了临时文件删除,以及格式修正

import tushare as ts
import pandas as pd
import os
 
def download_csv(code, start_date, end_date, filepath):
    data = ts.get_hist_data(code, start=start_date, end=end_date)

    # 数据存盘
    data.to_csv('temp.csv')

    # 读出数据,DataFrame格式
    df = pd.read_csv('temp.csv')

    # 从df中选取数据段,改变段名;新段'Adj Close'使用原有段'close'的数据  
    df2 = pd.DataFrame({'Date' : df['date'], 'Open' : df['open'],
                        'High' : df['high'],'Close' : df['close'],
                        'Low' : df['low'],'Volume' : df['volume'],
                        'Adj Close':df['close']})

    # 按照Yahoo格式的要求,调整df2各段的顺序
    dt = df2.pop('Date')
    df2.insert(0,'Date',dt)
    o = df2.pop('Open')
    df2.insert(1,'Open',o)
    h = df2.pop('High')
    df2.insert(2,'High',h)
    l = df2.pop('Low')
    df2.insert(3,'Low',l)
    c = df2.pop('Close')
    df2.insert(4,'Close',c)
    v = df2.pop('Volume')
    df2.insert(5,'Volume',v)

    # 新格式数据存盘,不保存索引编号  
    df2.to_csv(filepath, index=False)
    os.remove("temp.csv")

在同目录主代码中使用上述模块

from pyalgotrade.barfeed import quandlfeed
# 导入自定义模块
import MyDownload

'''
其他代码段
'''

instrument = "399300"
# 下载股票数据
MyDownload.download_csv(instrument, "2017-01-01", "2020-01-01", instrument+".csv")

# 从CSV文件加载bar feed
feed = quandlfeed.Feed()
feed.addBarsFromCSV(instrument, instrument+".csv")

注:由于pyalgotrade与pandas存在兼容性问题,使用第二种方法会出现警告,但不影响使用

如何定制化交易策略

交易策略的定制是为了解决一下问题:

在什么时候买?买多少?

在什么时候卖?卖多少?

以下以日线交易数据为例进行说明

onBar函数在遍历到每天的交易数据都会被调用一次,所以上述问题的具体解决是在这个函数中实现。但是除了定投以外,股票的买卖需要择时,需要参考股票的指标数据,pyalgotrade提供了很多股票分析常用的指标计算函数,具体请参考pyalgotrade.technical库。

我将默认金额即$1000000分成10层仓位,并按照MACD的金叉买入3层,死叉卖出3层制定的交易策略代码如下(其中还计算了很多其他指标但尚未用于交易策略的制定),可以通过修改加入新的股票指标,股票指标计算函数的参数,买卖判断条件和仓位买卖层数自定义交易策略。

from pyalgotrade import strategy, broker, plotter
from pyalgotrade.barfeed import quandlfeed
from pyalgotrade.stratanalyzer import returns, sharpe
from pyalgotrade.technical import ma, macd, rsi, stoch, bollinger
from pyalgotrade import broker as basebroker

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, bBandsPeriod):
        super(MyStrategy, self).__init__(feed)
        self.__instrument = instrument
        # 使用调整后的数据
        if feed.barsHaveAdjClose():
            self.setUseAdjustedValues(True)
        # 持有仓位
        self.__holdPosition = 0.0
        # 空闲仓位
        self.__emptyPosition = 10.0
        # 单元持仓金额
        self.__unit = self.getBroker().getCash(False) / 10
        # 统计收盘价
        self.__priceDS = feed[instrument].getPriceDataSeries()
        # 计算macd指标
        self.__macd = macd.MACD(self.__priceDS, 12, 26, 9)
        # 计算KD指标
        self.__stoch = stoch.StochasticOscillator(feed[instrument], 9, 3)
        # 计算rsi指标
        self.__rsi7 = rsi.RSI(self.__priceDS, 7)
        self.__rsi14 = rsi.RSI(self.__priceDS, 14)
        # 计算布林线
        self.__bbands = bollinger.BollingerBands(self.__priceDS, bBandsPeriod, 2)

    def getPriceDS(self):
        return self.__priceDS
        
    def getMACD(self):
        return self.__macd

    def getStoch(self):
        return self.__stoch
        
    def getRSI7(self):
        return self.__rsi7
        
    def getRSI14(self):
        return self.__rsi14

    def getBollingerBands(self):
        return self.__bbands
    
    def onOrderUpdated(self, order):
        if order.isBuy():
            orderType = "Buy"
        else:
            orderType = "Sell"
        self.info("%s order %d updated - Status: %s" % (
            orderType, order.getId(), basebroker.Order.State.toString(order.getState())
        ))

    def onBars(self, bars):
        lower = self.__bbands.getLowerBand()[-1]
        middle = self.__bbands.getMiddleBand()[-1]
        upper = self.__bbands.getUpperBand()[-1]
        
        if lower is None:
            return

        bar = bars[self.__instrument]
        # 持有股票份额
        shares = self.getBroker().getShares(self.__instrument)
        # 最新股价
        price = bar.getPrice()
        
        # 买入策略
        if self.__macd.getHistogram()[-1] is None or self.__macd.getHistogram()[-2] is None:
            return
        # 金叉形成
        if self.__macd.getHistogram()[-2] < 0 and self.__macd.getHistogram()[-1] > 0:
            PositionToBuy = 0
            if self.__emptyPosition >= 3:
                PositionToBuy = 3
            else:
                PositionToBuy = self.__emptyPosition
            sharesToBuy = int(PositionToBuy * self.__unit / price)
            if(self.marketOrder(self.__instrument, sharesToBuy)):
                self.__holdPosition += PositionToBuy
                self.__emptyPosition -= PositionToBuy
                self.info("Placing buy market order for %s shares" % sharesToBuy)
        # 死叉形成
        elif self.__macd.getHistogram()[-2] > 0 and self.__macd.getHistogram()[-1] < 0:
            PositionToSell = 0
            if self.__holdPosition >= 3:
                PositionToSell = -3
            else:
                PositionToSell = self.__holdPosition
            sharesToSell = int(PositionToSell * self.__unit / price)
            if(self.marketOrder(self.__instrument, sharesToSell)):
                self.__holdPosition += PositionToSell
                self.__emptyPosition -= PositionToSell
                self.info("Placing sell market order for %s shares" % sharesToSell)

如何显示回测结果

图表显示

输出股票日线的折线图

# 创建MyStrategy实例
myStrategy = MyStrategy(feed, instrument, bBandsPeriod)

plt = plotter.StrategyPlotter(myStrategy, True, True, True)
plt.plot()

在股票日线上添加指标曲线

# 图表中添加BOLL
plt.getInstrumentSubplot(instrument).addDataSeries("upper", myStrategy.getBollingerBands().getUpperBand())
plt.getInstrumentSubplot(instrument).addDataSeries("middle", myStrategy.getBollingerBands().getMiddleBand())
plt.getInstrumentSubplot(instrument).addDataSeries("lower", myStrategy.getBollingerBands().getLowerBand())

为MACD,STOCH,RSI各新建一个图表

# 图表添加MACD
plt.getOrCreateSubplot("macd").addDataSeries("DIF", myStrategy.getMACD())
plt.getOrCreateSubplot("macd").addDataSeries("DEA", myStrategy.getMACD().getSignal())
plt.getOrCreateSubplot("macd").addDataSeries("MACD", myStrategy.getMACD().getHistogram())

# 图表添加KD
plt.getOrCreateSubplot("stoch").addDataSeries("K", myStrategy.getStoch())
plt.getOrCreateSubplot("stoch").addDataSeries("D", myStrategy.getStoch().getD())

# 图表添加RSI
plt.getOrCreateSubplot("rsi").addDataSeries("RSI7", myStrategy.getRSI7())
plt.getOrCreateSubplot("rsi").addDataSeries("RSI14", myStrategy.getRSI14())
plt.getOrCreateSubplot("rsi").addLine("Overbought", 70)
plt.getOrCreateSubplot("rsi").addLine("Oversold", 30)

文本输出

# 添加回测分析
returnsAnalyzer = returns.Returns()
myStrategy.attachAnalyzer(returnsAnalyzer)

# 添加夏普比率分析
sharpeRatioAnalyzer = sharpe.SharpeRatio()
myStrategy.attachAnalyzer(sharpeRatioAnalyzer)

# 运行策略
myStrategy.run()
    
# 输出投资组合的最终资产总值
print("最终资产总值: $%.2f" % myStrategy.getBroker().getEquity())
# 输出总收益
print("总收益: %.2f %%" % (returnsAnalyzer.getCumulativeReturns()[-1] * 100))
# 输出夏普比率
print("夏普比率: %.2f" % sharpeRatioAnalyzer.getSharpeRatio(0))

运行结果示例

pyalgotrade量化交易回测

命令行输出结果如下:

最终资产总值: $955164.54
总收益: -4.48 %
夏普比率: -0.12

注:这个是完整代码的运行结果,从回测结果来看,我目前的交易策略并不给力,哈哈。

完整代码见https://github.com/beyondyouth/pyal