有感兴趣量化技术,特别是大数据、人工智能在量化中的应用的童鞋们可以关注我的公众号,脚本代码如果排版不清楚请看公众号:

馨视野(datahomex):

通过这个系列前面2篇文章的介绍,大家对于程序化交易肯定有了基本的认识,不过那仅仅是程序化交易,还不是量化交易。

今天,数据君给大家介绍一款基于python的开源的量化框架backtrader,开启我们正式的量化交易的学习之路。

一、认识backtrader

1.1 介绍

backtrader 官网 :

https://www.backtrader.com/

看下官网对backtrader的介绍:

A feature-rich Python framework for backtesting and trading

backtrader allows you to focus on writing reusable trading strategies, indicators and analyzers instead of having to spend time building infrastructure.

backtrader是一个基于python的,用于回测和交易的开源框架,它可以使使用者专注于交易策略、指标的开发,而不用关心系统的基础架构。

简而言之,backtrader帮我们搭好了量化交易的整体框架,预留了丰富的接口供开发者使用,可以支持量化交易的所有环节,包括数据加载、策略开发、策略回溯、实时交易。

 

1.2 组件

backtrader 核心组件:

1、回测系统(Cerebro):

  • 需要设置系统性的参数,如初始资金、佣金、交易头寸大小等;

  • 需要注入自定义开发的交易策略和测试数据集;

2、交易策率(Strategy):

量化交易开发里最核心的部分,需要开发买入卖出信号、设计交易策略;

3、数据加载(Data Feed):测试数据集加载到回测系统中的组件;

上述三个是backtrader在量化开发中的核心模块,其他还有一些辅助性组件,如可视化组件(Plotting)、内置指标组件(Indicators)、分析组件(Analyzers)、实盘交易组件(LiveTrading)等。

这些组件的功能和使用说明官网文档介绍非常详细,当然后面系列文章里我们也都会一一介绍。

 

二、官网 Hello Algotrading!

我们先用官网给出的一个入门例子,来领略下backtrade框架的魅力,执行下面脚本,最后可以看一下交易策略在测试数据集上的表现。

2.1 程序解读

from datetime import datetime
import backtrader as bt

# Create a subclass of Strategy to define the indicators and logic

# 第一步、开发自定义的策略,需要继承backtrader的Strategy类
class SmaCross(bt.Strategy):
    # list of parameters which are configurable for the strategy
    params = dict(
        pfast=10,  # period for the fast moving average
        pslow=30   # period for the slow moving average
    )
    # 通用日志打印函数,可以打印下单、交易记录,非必须,可选
    def log(self, txt, dt=None):
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))
        
	# 初始化函数,初始化属性、指标的计算,整个回测系统运行期间只执行一次
    def __init__(self):
        sma1 = bt.ind.SMA(period=self.p.pfast)  # fast moving average
        sma2 = bt.ind.SMA(period=self.p.pslow)  # slow moving average
        self.crossover = bt.ind.CrossOver(sma1, sma2)  # crossover signal
	
	# 订单状态消息通知函数
	def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            return
        # 检查订单是否完成
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))
            else:
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
	
	# 每个交易日都会依次循环调用
    def next(self):
        if not self.position:  # not in the market
            if self.crossover > 0:  # if fast crosses slow to the upside
                self.buy()  # enter long

        elif self.crossover < 0:  # in the market & cross to the downside
            self.close()  # close long position
            
# Create a data feed
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath,'603186.csv')
data = bt.feeds.GenericCSVData(
        dataname=datapath,
        fromdate=datetime.datetime(2010, 1, 1),
        todate=datetime.datetime(2020, 4, 12),
        dtformat='%Y%m%d',
        datetime=2,
        open=3,
        high=4,
        low=5,
        close=6,
        volume=10,
        reverse=True
    )
    
# 实例化回测系统,Cerebro翻译为大脑,回测系统是整个backtrader控制中枢
cerebro = bt.Cerebro()  # create a "Cerebro" engine instance
# 初始资金 10000
cerebro.broker.setcash(10000)
# 佣金
cerebro.broker.setcommission(commission=0.002)
cerebro.adddata(data)  # Add the data feed
cerebro.addstrategy(SmaCross)  # Add the trading strategy
cerebro.run()  # run it all
cerebro.plot()  # and plot it with a single command

应用backtrader 框架开发步骤:

1、第一步开发一个自己的交易策略类

class SmaCross(bt.Strategy)

自定义交易策略类需要继承bt.Strategy类,bt.Strategy类提供了很多方法供我们重写,介绍几个重要的函数:

init()函数:初始化函数,回测过程只执行一次,对于策略需要用到的一次性的静态指标,建议都在这个函数里面实现,如上面例子里长短期的移动平均指标;

next()函数:最核心的函数,每个交易日都会调用一次,需要根据当日的交易信号实现买卖下单逻辑,如上面例子当短期移动平均线上穿长期移动平均线,买入做多;否则卖出平仓。

代码中交易函数说明:

self.buy(): 买入做多

self.sell(): 卖出做空

另外常用交易函数:

self.close():清仓

self.cancel():取消订单

notify_order函数:订单消息通知函数,打印每次交易下单的信息,包括交易价格、交易金额、佣金等。

2、第二步 准备回测数据集

modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath,'orcl.txt')
data = bt.feeds.YahooFinanceCSVData(dataname= datapath,
                                 fromdate=datetime(2000, 1, 1),
                                 todate=datetime(2000, 12, 31))

有了策略,我们需要回测数据集用来检验我们策略的有效性,backtrader框架对回测数据集有严格的格式要求,

默认需要包含:

datetime(交易日期),open(开盘价),high(最高价),low(最低价),close(收盘价),volume(成交量)

backtrader里datafeeds数据模块支持多种类型的数据形式,包括第三方api实时数据(Yahoo、VisualChart、Sierra Chart、Interactive Brokers 盈透、OANDA、Quandl),本地txt、csv文件,pandas的dataframe,InfluxDB、MT4CSV 等,我们最常用的可能就是第三方api,本地文件或者dataframe。

如果学习过dbms(关系型数据库),大家可以把datafeeds理解为一张张二维表,如果没有数据库基础,可以想象成一张张excel表,每一行表示某个交易日该股票的交易信息,每一列在backtrader里可以认为是一条line。

 

backtrader 中的datafeeds很多概念还是比较难理解的,后续我们再详细讲解。

3、创建回测系统实例(直白点也可以说创建一个大脑)

cerebro = bt.Cerebro()  # create a "Cerebro" engine instance

回测实例创建后,将交易策略、回测数据注入到回测实例,也可以设置初始资金、佣金等信息,最后执行回测实例就会打印整个交易策略在测试数据集上的交易详情。

# 初始资金 10000
cerebro.broker.setcash(10000)
# 佣金
cerebro.broker.setcommission(commission=0.002)
# 回测数据集
cerebro.adddata(data)  # Add the data feed
# 交易策率
cerebro.addstrategy(SmaCross)  # Add the trading strategy
2019-06-12, BUY EXECUTED, Price: 29.61, Cost: 2961.00, Comm 0.00
2019-10-10, SELL EXECUTED, Price: 39.79, Cost: 2961.00, Comm 0.00
2019-10-28, BUY EXECUTED, Price: 46.99, Cost: 4699.00, Comm 0.00
2019-11-20, SELL EXECUTED, Price: 43.00, Cost: 4699.00, Comm 0.00
2019-12-20, BUY EXECUTED, Price: 40.86, Cost: 4086.00, Comm 0.00
2019-12-24, SELL EXECUTED, Price: 38.64, Cost: 4086.00, Comm 0.00
2020-01-10, BUY EXECUTED, Price: 43.69, Cost: 4369.00, Comm 0.00
2020-03-23, SELL EXECUTED, Price: 49.00, Cost: 4369.00, Comm 0.00

2.2 结果解读

 

这个简单的交易策略竟然表现还不错总体盈利,太出乎意料,不过这不是我们关心的重点,我们重点是介绍一下怎么看这个图:

1、最上面部分是账号目前现金和资产情况,红线表示现金价值、蓝线表示总资产,可以看到总会总现金是10928

2、下面K线红线是sma10,蓝线是sma30,红线是当日收盘价

3、红色倒三角表示卖出做空,绿色三角表示买入做多

4、最下面部分则是买卖信号线,值大于0的时候表示短期均线上传长期均线买入;小于0的时候则卖出。

 

三、开发一个自定义策略

3.1 实现上期入门策略

上期数据君开发了一个简单的入门策略,我们用backtrader框架来检验一下。

买入信号:sma5上穿sma10,当时我们获取的是5分钟级别的K线数据;

卖出信号,满足下面任意一个条件:

1) sma5下穿sma10 ;

2)当前收盘价格>1.01 * 买入价格 并且 当前(sma5-sma10)< 上期(sma5-sma10) * 0.95;

3)当前收盘价格 < 0.97 * 买入价格;

# 实现上期入门策略
class SmaCross(bt.Strategy):
    # list of parameters which are configurable for the strategy
    params = dict(
        pfast=10,  # period for the fast moving average
        pslow=30   # period for the slow moving average
    )
​
    # 通用日志打印函数,可以打印下单、交易记录,非必须,可选
    def log(self, txt, dt=None):
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))
​
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            return
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
                self.buyprice = order.executed.price # 记录成本价
            else:
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
        return super().notify_order(order)
​
    def __init__(self):
        self.dataclose = self.datas[0].close
        self.sma1 = bt.ind.SMA(period=self.p.pfast)  # fast moving average
        self.sma2 = bt.ind.SMA(period=self.p.pslow)  # slow moving average 
        self.dif = self.sma1 - self.sma2
        self.crossover = bt.ind.CrossOver(self.sma1, self.sma2)  # crossover signal
        self.buyprice = None # 买入成本价格
​
    def next(self):
        if not self.position:  # not in the market
            if self.crossover > 0:  # if fast crosses slow to the upside
                self.buy()  # enter long
        else:
            condition = (self.dataclose[0] - self.buyprice) / self.dataclose[0]
            if self.crossover < 0 or condition < 0.97 or (condition>1.01 and self.dif[0] < self.dif[-1] * 0.95) :  
                # in the market & cross to the downside
                self.close(volume=False)  # close long position

 

3.2 加载自定义数据集

在【第一篇来啦童鞋】里,数据君通过调用/market/history/kline接口,获取了5分钟级别的K线数据,并做了本地持久化保存,这次就利用这部分数据,来回测上面简单的策略,具体代码如下:

# 加载数据到模型中
from sqlalchemy import create_engine
import pandas as pd
import time
engine = create_engine('mysql://root:123456@127.0.0.1/stock?charset=utf8mb4') 
conn = engine.connect()
sql=("select * from huobi_daily")
df = pd.read_sql(sql,engine)
df['date'] = df['id'].apply(lambda x:time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(x)))
df['date'] = pd.to_datetime(df['date'])
df.index = df.date
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data) 

 

3.3 运行结果

 

总结:

今天,我们完成了一个完整的量化交易程序的开发,从回测情况看,我们的上期的策略在数币短线交易上看效果很一般,当然这个也是情理之中,毕竟策略太简单了,后面我们重点会放在自定义策略的开发上,可能会结合统计模型、机器学习、深度学习等复杂技术,不过整个框架和流程都是一样的。

另外,今天介绍的backtrader框架在后面还会一直沿用,其中更多的功能和细节到时也会做说明,希望大家可以和数据君在量化的路上越走越远。

Logo

专业量化交易与投资者大本营

更多推荐