【PA交易】BackTrader(一): 如何使用实时tick数据和蜡烛图

背景和需求

整合Tick数据是PA交易的回测与实盘基本需求。多数交易回测框架往往缺乏对大规模Tick数据直接而全面的支持。Tick数据因其体量庞大(例如,某棕榈油主力合约四年间的数据达8GB)为结合价格趋势与PA分析带来挑战,凸显了实时动态数据源的重要性。实时数据不仅能应对数据规模问题,还能减少因数据时序差异引发的回测或实盘错误,确保分析准确性。

这里提及的8GB棕榈油合约Tick数据已转存MySQL数据库格式,并在文末以笔者整理后的SQL导出文件形式提供下载,数据仅供研究与测试,勿用作商业用途。

概述

在探索Backtrader时,我注意到将其与Tick数据集成至回测流程颇为简便,故撰写系列笔记以记录并分享这一过程。本笔记基于当前版本Backtrader编写。未来或许有更优框架或Backtrader自身改进Tick数据处理方式,目前而言,本系列文章提供的Tick数据与K线结合的免费解决方案,是非常易于上手的免费选择之一。

本系列《BackTrader如何使用实时tick数据和蜡烛图》预计将分成上中下三篇:

  • 上篇:本文,较水,对背景和DataFeed做出介绍,同时介绍tick数据如何读取到回测策略流程中。完整读完后可以做到将tick数据加入到BackTrader策略中。
  • 中篇:待完成,主要介绍如何将tick结合KBar同时读取到回测策略流程中。
  • 下篇:待完成,主要介绍跨周期回测,可看做Multiple Timeframes结合tick的实操记录。

《BackTrader如何使用实时tick数据和蜡烛图》系列小文,属于PA交易和量化交易文集。PA交易和量化交易文集将会主要专注于低频Python实现。对于一些高频部分目前暂不涉及。鉴于高频本身对于网络和硬件要求较高,多数朋友(包括笔者)可能都没有交易所托管或者购买高性能计算的计划,并且目前笔者也没有转向高频C++的计划和需求。

PS:题外话:笔者十多年艹C艹经验都懒得用C艹。

笔者交易市场主要为国内商品期货市场,个人平时主要做农产品基本面分析,后续计划考虑进行对包括油厂、美农、拉尼娜等各类数据进行一些因子挖掘工作。个人精力有限,有相关爱好的朋友有缘可以寻找合作机会。

BackTrader

之所以选择BackTrade主要原因有以下几点:

  • 相比于pyAlgoTrade:目前Gabriel Becedillas大神已经全面转到 basana 开发,不再更新pyAlgoTrade。巴萨那主要专注于加密币,担心代码中一些跟币圈相关的词汇会迷惑。
  • 相比于vnpy:VeighNa封装过多,缺乏灵活性。使用其集成平台那就会失去灵活性,图省事的话不如使用其他一些集成平台了。另外,记得林园大佬说过一句话,一家公司发生减持,管他什么原因呢,不买就是了。同理,vnpy.cn还是vnpy.com分不清就都不用了吧。

本文适合已经至少完成了BackTrade的Quickstart Guide - Backtrader有需求的朋友阅读,如果还对BackTrader一窍不通,可以花上15分钟简单阅读以下官方的文档,并实操一下,简单单步调试一下源码。

DataFeed

BackTrader的数据源主要由Data Feeds - Backtrader模块进行处理, 其中大量使用了元编程技巧。底层实现中元类(metaclasses)。DataFeed底层主要核心数据通过LineBuffer进行封装。LineBuffer数据由LineRoot持有,自LineRoot以下的继承关系如下图所示:

其中代码逻辑比较简单的是PandasData,我们可以把它看做一个自定义DataFeed的示例,根据这个类的实现自己定制数据源。

Lines

DataFeed中数据是通过LineBuffer和策略模块进行共享,在上层OHLC中定义了7个标准的数据LineBuffer:

class OHLC(DataSeries):
    lines = ('close', 'low', 'high', 'open', 'volume', 'openinterest',)


class OHLCDateTime(OHLC):
    lines = (('datetime'),)

因为类hierarchy的限制,我们拓展DataFeed需要从AbstractDataBase或者DataBase层面进行集成,否则将会面临大量底层逻辑代码的重复实现。如上类图所见,包括PandasData在内的绝大多数BackTrader内置数据源类型都是继承于DataBase。

下面我们首先基于class PandasData(feed.DataBase)实现一个基础的tick数据加入到策略运行过程的示例,之后使用动态tick数据。这个实现实现非常简单,并不需要太多高深的编程技能。

测试CSV

 为简化测试开发流程, 便于说明理解,使用我上传的CSV文件DCE.m2501.tick.202402,大小2M,比较适合框架开发过程的测试:https://download.csdn.net/download/u012677852/89460596?spm=1001.2014.3001.5503icon-default.png?t=N7T8https://download.csdn.net/download/u012677852/89460596?spm=1001.2014.3001.5503

这个文件是笔者采集的实盘豆粕合约数据,因为不是主力合约,数据量较小适合测试开发使用。

对于如何验证网络实盘tick数据,可以参考我的文章:【PA交易】前端根据内盘商品期货Tick数据合并日线Bar-CSDN博客

下文及后文如有需要,一律直接使用该文件名,不再特殊说明。

静态读入tick数据

如前所述,为了能够支持策略运行过程中使用tick数据源,首先定义一个数据类继承自bt.feed.DataBase。因为我们此步骤仅仅使用静态数据进行调通,所以类定义如下:

class MyDataFeedStatic(bt.feed.DataBase):

因为我们是tick数据,其中的数据字段是类继承结构上层OHLC中没有定义的Line,所以我们需要使用一个tuple声明他们:


lines = (('price', 'vol', 'amount', 'ccl', 'bid', 'bidVol', 'ask', 'askVol'))

之后为了简便起见,直接将测试CSV读取的DF保存在类中,并且指定一个_idx表示当前读取到的DF的行索引, 如下所示:


class MyDataFeedStatic(bt.feed.DataBase):
    lines = (('price', 'vol', 'amount', 'ccl', 'bid', 'bidVol', 'ask', 'askVol'))

    def __init__(self, df):
        super(MyDataFeedStatic, self).__init__()
        self.df = df
        self._idx = 0

之后需要重载_load方法, 该方法由AbstractDataBase的load方法调用, 具体相关调用逻辑可以参考BackTrader源码。我们这里简单的逐行使用读取DF数据的方式实现这个方法:

def _load(self):
	if self._idx >= len(self.df):
		# exhausted all rows
		return False
	
	for datafield in self.getlinealiases():
		if datafield == 'tickdt':
			continue
		if datafield in TICK_DATA_COLUMNS:
			line = getattr(self.lines, datafield)
			line[0] = self.df[datafield].iloc[self._idx]
			# print(f'load {datafield} success')
	
	# -------------------------------------------
	# 添加日期时间
	tstamp = self.df['tickdt'].iloc[self._idx]
	self.lines.datetime[0] = date2num(tstamp)
	
	self._idx += 1
	return True


注意测试文件中的datatime使用的是tickdt作为列名,在BackTrader底层中会依赖datatime字段,这里因为数据中本来没有dataname字段,所以必须填充这个Line。

此时,一个简单的基于tick的数据源已经实现了,我们来使用它。首先装载过程,只需要预先读取一个DF,并传入即可:

df = pd.read_csv('./datas/DCE.m2501.tick.202402.csv')
df['tickdt'] = pd.to_datetime(df['tickdt'])
data_feed = sfeed.MyDataFeedStatic(df)
cerebro.adddata(data_feed)

之后在策略构造函数中,为了后续使用方便,可以给每条管线起一个别名:

class TestStrategy(bt.Strategy):
    def __init__(self):
        # Ticks 字段
        self.tickdt = self.datas[0].datetime
        self.price = self.datas[0].price
        self.vol = self.datas[0].close
        self.amount = self.datas[0].amount
        self.ccl = self.datas[0].ccl
        self.bid = self.datas[0].bid
        self.bidVol = self.datas[0].bidVol
        self.ask = self.datas[0].ask
        self.askVol = self.datas[0].askVol
        self.local_tz = get_localzone()

之后在next方法中就可以访问到具体每个Tick的数据了:


def log(self, txt, dt=None):
	''' 本地化时间输出 '''
	utc_datetime = self.datetime if dt is None else dt
	date = utc_datetime.date(0, tz=self.local_tz)
	time = utc_datetime.time(0, tz=self.local_tz)
	print('%s %s: %s' % (date, time, txt))

def next(self):
    self.log('Tick price, %.2f' % self.price[0], self

输出:

2024-02-01 17:00:00.059001: Tick price, 3127.00
2024-02-01 17:00:00.558999: Tick price, 3126.00
2024-02-01 17:00:01.058996: Tick price, 3126.00
2024-02-01 17:00:01.557997: Tick price, 3126.00
.....

实时运行

当我们运行策略时,会发现策略的next方法并不是紧跟着数据源的_load方法运行。这是因为当前的策略运行不是实时模式。还需要在数据源类MyDataFeedStatic中Override一个方法:

class MyDataFeedStatic(...):
    
    def islive(self):
        return True

这样策略将以实时模式运行,_load()函数中打印日志可以看到每次load运行之后才会运行策略的next函数。

题外话

这里的DF读写仅仅是一个模拟数据逐条实时传入的方式。考虑到数据源的复杂性,作为分享,这是我所使用的基础架构:

架构中抽象出一个数据模块,这个模块负责全部的数据处理,无论数据来自于CSV、数据库还是CTP。而MyDataFeed实际充当了一个数据模块和策略运行之间的Bridge角色(adapter)。MyDataFeed会在_load函数中向数据模块索要数据,如果数据没有就绪,则会阻塞操作(可以在当前示例代码中添加sleep(0.5)模拟CTP)。这样无论回测还是实盘,在数据管理都实现了一致性,即:一个策略的书写可以兼容不同场景,当我们回测夏普优秀,SimNow模拟回报丰厚的时候,我们可以通过切换数据模块参数的方式将策略快速无缝切换到实盘。

继续阅读

下篇文章将会首先将本文中的简单读取DF改为一个模拟上述架构中的简单的数据读取器。之后实现Tick和K Bar同时传递到策略中。

更多测试数据:

如果需要更多测试数据,可以下载这里的tic数据文件,已经整理成MySQL表,这里是导出的SQL,后续会更新更多其他测试数据:

内盘期货棕榈, 主力1、5、9月合约2020年到2024年TICK数据

链接:https://pan.baidu.com/s/1UTt9Ei0dSaQ971Iq-YPIGA?pwd=j7hq 
提取码:j7hq 

(仅限测试开发学习使用, 请勿商用)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/739881.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言基础——函数(2)

ʕ • ᴥ • ʔ づ♡ど 🎉 欢迎点赞支持🎉 文章目录 前言 一、return语句 二、数组做函数参数 三、嵌套调用和链式访问 3.1 嵌套调用 3.2 链式访问 四、函数声明和定义 4.1 单个文件 4.2 多个文件 总结 前言 大家好啊,继我们上一…

深入测评:ONLYOFFICE 8.1 桌面编辑器究竟有多强大?

ONLYOFFICE 8.1桌面编辑器 文章目录 ONLYOFFICE 8.1桌面编辑器一、ONLYOFFICE的简介二、ONLYOFFICE 8.1新功能和改进2.1 轻松编辑器 PDF 文件2.2 用幻灯片版式快速修改幻灯片2.3 无缝切换文档编辑、审阅和查看模式2.4 改进从右至左语言的支持 & 新的本地化选项2.5 隐藏“连…

【AI大模型】GPTS 与 Assistants API

前言 2023 年 11 月 6 日,OpenAI DevDay 发表了一系列新能力,其中包括:GPT Store 和 Assistants API。 GPTs 和 Assistants API 本质是降低开发门槛 可操控性和易用性之间的权衡与折中: 更多技术路线选择:原生 API、…

洗地机选购指南,什么品牌最值得购买?2024四大口碑品牌推荐

随着炎炎夏日的到来,家里的地板清洁会成为人们“沉重”的负担,而拥有一台能够高效又轻松完成地板深度清洁的洗地机是一件非常幸福的事儿。但是,面对市场上琳琅满目的洗地机品牌和型号,如何找到一款综合性能都不错的洗地机成为了许…

mac怎么压缩pdf文件,苹果电脑怎么压缩pdf文件大小

在当今数字化时代,PDF文件已成为广泛使用的文档格式之一。然而,PDF 文件可能会因其包含的图像、图形和其他元素而导致文件较大,这可能会影响文件的传输、存储和共享。因此,对 PDF 文件进行压缩以减小其文件大小是很有必要的。今天…

【pytorch04】创建Tensor

numpy中的数据创建tensor 数据已经在numpy中了,将numpy中的数据转到tensor中来,因为我们将使用tensor在GPU上进行加速运算 从NUMPY导入的FLOAT其实是DOUBLE类型 list中的数据创建tensor FloatTensor()和大写的Tensor()接收的是shape(即数据的…

解析JavaScript中逻辑运算符和||的返回值机制

本文主要内容:了解逻辑运算符 &&(逻辑与)和 ||(逻辑或)的返回值。 在JavaScript中,逻辑运算符 &&(逻辑与)和 ||(逻辑或)的返回值可能并不总…

区块链学习03-空投篇

Hybrid 是与 以太坊 兼容的第 2 层区块链,集成了混合专家 (MoE) 框架,支持以即插即用的方式轻松创建和货币化 AI 代理。该平台旨在提高区块链应用程序中数据的完整性和可用性,支持跨各个行业开发和部署 AI 驱动的解决方案。 Hybrid 正在为其…

php上传zip压缩包到服务器并解压,解析压缩包内excel表格数据导入到数据库

需求: 1.需要管理后台将excel表格中的每条单词数据导入到数据库中. 2.每条单词数据对应的图片和音频文件需要上传到服务器中. 为了让客户上传数据方便,考虑了一下决定通过后台上传压缩包的方式实现 测试压缩包: 压缩包的目录结构 管理后台导入教材 public function upload…

kylin v10 离线安装chrome centos离线安装chrome linux离线安装谷歌浏览器

1. 先用自己联网的计算机,下载离线安装包,浏览器输入链接下载安装包: https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm 1.2. 信创环境不用执行下面,因为没网 1.3. 若为阿里云服务器,或服…

【速速收藏】适用于Linux系统的五个优秀PDF编辑器

PDF (Portable Document Format) 是便携文档格式的缩写,这是一种用于电子共享文档的标准格式,广泛应用于各种文档类型的存储和分发。然而,有时我们可能需要对PDF文档进行更改和编辑。本文将介绍五款在Linux平台上广受欢迎的PDF编辑器。 ​​…

七种不同类型测宽仪技术参数 看看哪种能用于您的产线?

在线测宽仪种类众多,原理不同,产品不同,型号不同,其技术参数也各不相同。不同的测量范围与测量精度,适用于不同规格的板材,看看您的板材能适用于哪种范围。 1、单测头平行光测宽仪 点光源发射的光经过发射…

期末成绩怎么快速发给家长

Hey各位老师们,今天来聊一个超级实用的话题:如何快速高效的向家长们传达学生的期末成绩。你可能会想,这不是很简单吗?直接班级群发个消息不就得了?但别忘了,保护学生隐私和自尊心也是很重要的哦&#xff01…

【方案】基于5G智慧工业园区解决方案(PPT原件)

5G智慧工业园区整体解决方案旨在通过集成5G通信技术、物联网、大数据和云计算等先进技术,实现园区的智能化、高效化和绿色化。 该方案首先构建高速、稳定的5G网络,确保园区内设备、人员与物流的实时连接和高效沟通。其次,通过工业物联网技术&…

RFID技术在人工晶体清洗台上的应用案例分析

RFID技术在人工晶体清洗台上的应用案例分析 应用背景 在医疗领域中人工晶体清洗台发挥着极为重要的作用,随着市场需求的持续增长、技术的不断创新、定制化趋势的加强以及环保要求的提高,人工晶体清洗台不免暴露出一下应用痛点需要解决。 痛点&#xff…

「动态规划」如何求最长摆动子序列的长度?

376. 摆动序列https://leetcode.cn/problems/wiggle-subsequence/description/ 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也…

720漫游工具又双叒叕上新了一批新功能

一、720漫游全景图片上传支持「自定义水印」 全景图片素材上传支持自定义水印设置,通过自定义水印,可以在全景图片上打上自定义的水印图片保护用户版权利益,同时强化自身品牌露出。具体操作如下: 打开「创建720漫游作品页」-选择…

MIL图像处理那些事:定义感兴趣区域ROI的两种方法(示例项目C#源码)

文章目录 效果展示第一种方法:通过鼠标框选GetROIForm构造函数如何缩放--MdispZoom的使用Ctr+滚轮缩放放大两倍:如何平移--MdispPan的使用双击返回ROI第二种方法:直接编辑ROI框显示ROI示例项目C#源码(百度网盘)本示例提供两种方法定义感兴趣区域ROI 效果展示 第一种方法:通过鼠…

从广州到上海|荣载光的智慧 与SSHT共同探索智能照明更多想象空间

随着生活水平的提高,大众对高品质生活的追求脚步逐步加快,人们对智能照明的需求日益多样化,不再仅仅满足于传统的照明功能,而是转向智能照明系统,提出更高的需求。 展望未来,中国智能照明市场预计将迎来全…

定时触发-uniapp + uniCloud 订阅消息实战教程(三)

上一节已经对云函数有了一定的了解,但是,为了发送订阅消息,只会云函数还是差了那么一点意思,所以接下来的这一节,将带领大家熟悉一下定时触发。 熟悉定时触发 如果云函数需要定时/定期执行,即定时触发&…