backtrader学习

结构:
创建示例: bt.Cerebro()
喂数据:cerebro.adddata(data)
加策略:cerebro.addstrategy(SmaCross)
运行:cerebro.run()
输出:cerebro.plot()

1. feeds

数据源以feeds形式加到bt里,支持读取本地文件、数据库导入、雅虎文件导入、df导入。
格式:bt.feeds.[PandasData] ()

# 读取和导入 CSV 文件
data = bt.feeds.GenericCSVData(dataname='filename.csv', ...)
# dataframe
data = bt.feeds.PandasData(dataname='filename.csv', ...)
# YahooFinanceData
data = bt.feeds.YahooFinanceData(dataname='MSFT',...)

Backtrader 中的数据表格默认情况下包含 7 条 line,这 7 条 line 的位置也是固定的,
依次为 ('close', 'low', 'high', 'open', 'volume', 'openinterest', 'datetime')

data = bt.feeds.PandasData(dataname=df, datetime=None,
                           open=0, high=1, low=2, close=3, volume=4, openinterest=-1)  

# 或者
data = bt.feeds.YahooFinanceData(dataname='MSFT',
                                 fromdate=datetime(2011, 1, 1),
                                 todate=datetime(2012, 12, 31))

# 以 GenericCSVData 为例进行参数说明(其他导入函数参数类似)
data = bt.feeds.GenericCSVData(
    dataname='daily_price.csv', # 数据源,CSV文件名 或 Dataframe对象
    fromdate=st_date, # 读取的起始时间
    todate=ed_date, # 读取的结束时间
    nullvalue=0.0, # 缺失值填充
    dtformat=('%Y-%m-%d'), # 日期解析的格式
    # 下面是数据表格默认包含的 7 个指标,取值对应指标在 daily_price.csv 中的列索引位置
    datetime=0, # 告诉 GenericCSVData, datetime 在 daily_price.csv 文件的第1列
    high=3, 
    low=4,
    open=2,
    close=5,
    volume=6,
    openinterest=-1) # 如果取值为 -1 , 告诉 GenericCSVData 该指标不存在

目前一个完整的喂数据如下:

import backtrader as bt

cerebro = bt.Cerebro()
stock_hfq_df = pd.read_excel("./data/sh600000.xlsx", index_col='date', parse_dates=True)
data = bt.feeds.PandasData(dataname=stock_hfq_df, fromdate=start_date, todate=end_date)  # 加载数据
cerebro.adddata(data)  # 将数据传入回测系统

导入的数据表格不一定非要含有上述7个指标,没有的指标标记为-1,也可能不止上述几个指标,比如增加pe,pb等。可重新自定义读取函数

class Addmoredata(PandasData):
    lines = ('news','heat','turnover_rate_f','circ_mv','total_mv',)
    params = (('news',7),('heat',8),('turnover_rate_f',9),('circ_mv',10),('total_mv',11),)

data = Addmoredata(dataname=df)
cerebro.adddata(data)  # 将数据传入回测系统

多股票加入:


datafeed1 = bt.feeds.PandasData(dataname=data1)
cerebro.adddata(datafeed1, name='600466.SH')

datafeed2 = bt.feeds.PandasData(dataname=data2)
cerebro.adddata(datafeed2, name='600466.SH')

2.加策略

class 策略名(bt.Stragety):
    pass

cerebro.addstrategy(策略名)

2.1 日志

    def log(self, txt, dt=None):
        ''' 日志函数 '''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))
    def next(self):
        # 记录数据序列的收盘价
        self.log('收盘价, %.2f' % (self.dataclose[0]))

log()表示调用提供的日期和txt变量

这里多个股票怎么表示,怎么选取,还没找到答案,先待定。

2.2 init初始函数

重要!
主要为对变量的定义,包括收盘价,自设指标,自设信号等

params = (
        ('maperiod', 15),
    )

    def __init__(self):
        # 定义收盘价
        self.dataclose = self.datas[0].close
 
        # 定义状态,价格,佣金
        self.order = None
        self.buyprice = None
        self.buycomm = None
 
        # 增加移动平均指标
        self.sma = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=self.params.maperiod)

         # 增加划线的指标
        bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
        # 增加划线指标2
        rsi = bt.indicators.RSI(self.datas[0])
        bt.indicators.SmoothedMovingAverage(rsi, period=10)

         # 自设信号:生成是个line
        # data0 是日线数据
        sma0 = btind.SMA(self.data0, period=15)  # 15 天的平均
        # data1 是周线数据
        sma1 = btind.SMA(self.data1, period=5)  # 5 周的平均
        self.buysig = sma0 > sma1()

        #自设信号:用bt.And来表示同时取信号
        sma = btind.SimpleMovinAverage(self.data, period=20)
        close_over_sma = self.data.close > sma
        sma_dist_to_high = self.data.high - sma
        sma_dist_small = sma_dist_to_high < 3.5
        # 注意and在python中不能重写,所以只能通过函数实现。
        sell_sig = bt.And(close_over_sma, sma_dist_small)

    def __init__(self):
        sma1 = btind.SMA(self.data.close, period=15)
#如果sma1大于close,那么返回数据源的low line,否则返回high line。特别注意的是在这里并没有返回实际有效的值,只是返回lines对象,类似我们之前使用SimpleMovingAverage
        high_or_low = bt.If(sma1 > self.data.close, self.data.low, self.data.high)
        sma2 = btind.SMA(high_or_low, period=15)

多股的自设指标

MIN_PERIOD = 20

    # 可配置策略参数
    params = dict(
        period = MIN_PERIOD,    # 均线周期
        stake = 100,            # 单笔交易股票数目
    )
    def __init__(self):
        self.inds = dict()
        for i, d in enumerate(self.datas):
            self.inds[d] = bt.ind.SMA(d.close, period=self.p.period)


2.3 notify

主要为记录订单状态和交易状态

def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return
 
        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        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
                self.buycomm = order.executed.comm
            else:  # Sell
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
 
            self.bar_executed = len(self)
 
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')
 
        # Write down: no pending order
        self.order = None
 
    def notify_trade(self, trade):
        if not trade.isclosed:
            return
 
        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                 (trade.pnl, trade.pnlcomm))


2.4 next

重要!!
进入买入卖出操作,此部分内置函数较多
还可以增加pre_next


    def next(self):
 
        # 在这里,操作符用于判决。
        if self.sma > 30.0:
            print('sma is greater than 30.0')
        if self.sma > self.data.close:
            print('sma is above the close price')
        if self.sell_sig:  # if sell_sig == True: 这种写法也可以
            print('sell sig is True')
        else:
            print('sell sig is False')

2.4.1 单只股票的买卖操作

def next(self):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        # 检查是否有未完成的交易单,有则不挂新单。
        if self.order:
            return

        # 检查是否在场中(有头寸),有则不买入
        if not self.position:

            # 买入逻辑
            if self.dataclose[0] < self.dataclose[-1]:
                    # current close less than previous close

                    if self.dataclose[-1] < self.dataclose[-2]:
                        # previous close less than the previous close

                        # BUY, BUY, BUY!!! (with default parameters)
                        self.log('BUY CREATE, %.2f' % self.dataclose[0])

                        # 跟踪当前交易单,避免重复下单。
                        self.order = self.buy()

        else:

            # 有头寸,考虑卖出
            if len(self) >= (self.bar_executed + 5):
                # 当前据买入日期过去5bar,执行卖出
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # 跟踪卖单
                self.order = self.sell()

plus版本

def next(self):
        if self.order:
            return
            # 入场:价格突破上轨线且空仓时
        if self.buy_signal > 0 and self.buy_count == 0:
            atr = self.ATR[0]

            self.buy_size = self.broker.getvalue() * 0.01 / self.ATR
            self.buy_size = int(self.buy_size / 100) * 100
            self.sizer.p.stake = self.buy_size  # 指定买卖多少股。
            print('ATR', atr, 'buy_size', self.sizer.p.stake, 'close', self.data.close[0], '市值',
                  self.broker.getvalue() * 0.01)
            self.buy_count = 1
            # print('空仓时买入', self.buy_size)
            self.order = self.buy()
            # 加仓:价格上涨了买入价的0.5的ATR且加仓次数少于3次(含)
        elif self.data.close > self.buyprice + 0.5 * self.ATR[0] and 0 < self.buy_count <= 4:
            # print('加仓买入')
            self.buy_size = self.broker.getvalue() * 0.01 / self.ATR
            self.buy_size = int(self.buy_size / 100) * 100
            self.sizer.p.stake = self.buy_size
            self.order = self.buy()
            self.buy_count += 1
            # 离场:价格跌破下轨线且持仓时
        elif self.sell_signal < 0 and self.buy_count > 0:
            # print('平仓信号卖出')
            self.order = self.sell()
            self.buy_count = 0
            # 止损:价格跌破买入价的2个ATR且持仓时
        elif self.data.close < (self.buyprice - 2 * self.ATR[0]) and self.buy_count > 0:
            # print('止损信号卖出')
            self.order = self.sell()
            self.buy_count = 0

常见的函数

# 当前nav
self.broker.getvalue()

#下单
self.order_target_percent(secu_data, target_pct, name=secu)
self.order_target_value(secu_data, target_val, name=secu)
self.buy(secu_data, order_amount, name=secu) # code, num, name
self.sell(secu_data, order_amount, name=secu)

# 清仓
self.order = self.close(secu_data)

# 仓位
self.getposition().size

# 日期
self.datetime.date()

2.4.2 多只股票的买卖操作

image.png
# 获取名字
self.getdatabyname('300015')._name
# 对应收盘
self.getdatabyname('300015').close[0]

self.getdatanames()按顺序返回所有股票的名称list
self.getdatabyname(secu_name):返回该股票的data
def next(self):
        if self.order:  # 检查是否有指令等待执行
            return
        # 如果是最后一天,不进行买卖
        if pd.Timestamp(self.datas[0].datetime.date(0)) == end_dates[self.datas[0]._name]:
            return
        # 是否持仓
        if len(self.buy_list) < 2:  # 没有持仓
            # 没有购买的票
            for secu in set(self.getdatanames()) - set(self.buy_list):
                data = self.getdatabyname(secu)
                # 如果突破 20 日均线买买买,不要在最后一根bar的前一天买
                if data.close > self.sma[secu] and pd.Timestamp(data.datetime.date(1)) < end_dates[secu]:
                    # 买买买
                    order_value = self.broker.getvalue() * 0.48
                    order_amount = self.downcast(order_value / data.close[0], 100)
                    self.order = self.buy(data, size=order_amount, name=secu)
                    self.log(f"买{secu}, price:{data.close[0]:.2f}, amout: {order_amount}")
                    self.buy_list.append(secu)
        elif self.position:
            now_lst = []
            for secu in self.buy_list:
                data = self.getdatabyname(secu)
                # 执行卖出条件判断:收盘价格跌破20日均线,或者股票最后一根bar 的前一天之剔除日
                if data.close[0] < self.sma[secu] or pd.Timestamp(data.datetime.date(1)) >= end_dates[secu]:
                    # 卖卖卖
                    self.order = self.order_target_percent(data, 0, name=secu)
                    self.log(f"卖{secu}, price:{data.close[0]:.2f}, pct: 0")
                    continue
                now_lst.append(secu)
            self.buy_list = now_lst

多只股票有定时和不定时处理,定时如每月1号按照股息率排序买入,不定时处理每日判断进行调仓。

定时调仓,需要增加定时器

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容