玩了 量化交易 也差不多有 2 年多了,总算有个大概的认识了。
天朝内的投资市场A股(包括期货),简单点的量化工具如 迅投QMT、PTrade 和 掘金量化 之类的,还是比较容易上手的。有点 Python 基础不是什么难事儿。打比方的话就如一辆 奥托,也不是不能开,就那么回事儿。主要是在支持 Level 2 逐笔行情方面不给力。必竟是在公开的互联网上,在用户的电脑上运行。所以硬件性能、安全性、都不能有太多要求。散户玩玩还是可以的。
功夫量化 算是可以在 服务器 端运行的工具了,可以在券商托管的机房里,接收 Level 2 逐笔行情,真正毫秒级,(纳秒那真是吹牛逼的)。目前个人也接触了几个版本了,中泰的 SmartX 内置的功夫(有加工和修改)、华鑫证券的纯原生功夫(已升级到2.4版)、银河证券的功夫(略有加工和修改2.4版)。可以比喻成 奥迪了,做A股性能足足的了。特此整理下笔记,以备日后运用得更加顺畅。
2022/9
# 一、 安装,配置和添加策略
本章指导用户从 0 开始,在 Windows 系统中安装 功夫量化 ,并配置好 模拟(SIM)帐户和行情源,最后添加一个 测试程序,在日志中打印输出 “Hellow World”。
# 1.1、 安装
纯正的 功夫量化,当然来自其官网:
www.kungfu-trader.com
但是,强烈建议还是找券商的客户经理要一个 安装包 比较合适。因为各券商 与 功夫量化 合作,都会把自家的柜台接口事先写好,用起来方便。同时还根据自家的情况做过一些小小的修改。如果用网上公开的版本,很可能与自己的券商接口接不上,或者有这样那样的一些小Bug,自己去一一排查吧,费时费力。(你要是就喜欢解难题,那祝开心就好。)
最容易上手的 功夫量化 ,就直接去官网下载一个 Windows 版,在自己的电脑上就可以跑起来了。

就在首页底部即可看到下载的按钮。

如介绍那样,功夫量化 内置了 sim模拟柜台,开箱即用、简单方便。当然只是用来模拟和学习。要真正用到实盘上,还得花钱,或咨询你的客户经理。
本篇的内容是从 功夫量化 Windows 版开始上手的,对初学者来说是最容易学习的方式,图形化鼠标操作应该十分熟悉。如果你就是要玩硬核的,就要挑战高难度,不然不开心!可以尝试用 Linux 版本。(厉害了!老铁)
点击 Windows 下载后,会得到一个安装文件,如:
Kungfu-2.3.9-win-app-07141510.exe
安装过程十分简单,就是一路 “下一步Next”就好,没有任何难度。
WARNING
可能会出现的问题是,你的 Windows 系统中缺失 C++ 相关的组件。 请下载 Visual C++ Redistributable for Visual Studio 2019 安装一下就好。或者把补丁打齐、把 微软运行库 安装一下即可解决。
安装成功之后,在 Windows 桌面上就会出现一个 功夫 的图标,如下图所示:
用鼠标左键双击这个图标,即可开始运行 功夫量化。
看,安装过程,就是如此简单,你已经全部完成了。你应该看到如下的主界面了
# 1.2、 配置

如果你的电脑网络正常,请关注一下界面的左下角,有一个 “主进程状态”,

绿色表示一切正常。鼠标左键点击一下,可以看到 主控进程、数据进程和通信进程,都是在 运行中 的状态,这就很 OK 了。
细心如你,一定发现了,在 “主进程状态” 旁边,还有一个 “帐户进程状态”,是灰色的,这肯定表示 没有正常工作 啊。
是的,因为刚刚安装好,还没有添加 帐户 和 行情源 啊。
帐户 ———— 就是用来 模拟交易,买卖股票的罗。(你要直接上实盘?那你厉害!666666666)
行情源 ———— 当然就是用来接收报价数据的啊。
如果都没有的话,那玩啥?所以,功夫量化 已经准备好了开箱即用的 sim(模拟)帐号和行情源。如果你是用券商提供的安装包安装的,那其中应该已经内置了模拟帐户和行情源,都来自于你的券商。比如,中泰的柜台叫 XTP、华鑫的柜台叫 tora、银河证券的柜台叫 ItpGalaxy,行情源叫 HuarGalaxy......诸如此类(名称不整洋气点,没有排面!),各家都有起名大师。作为初学者,我们就都用 sim(模拟)吧。
# 1.2.1、 添加柜台帐户(简称TD)
请点击主界面 帐户列表 区域的 “添加” 按钮

在弹出的小窗口中,点击选择 SIM(模拟),然后点击确定。

在接下来的窗口中,输入帐号,任意几个数字都可以。然后在下面的 match_mode(撮合模式)中,选择 fill(全部成交)

顺便解释一下撮合模式中的其它几个选项,分别代表:
| 选项 | 撮合模式 |
|---|---|
| reject | 拒绝成交 |
| pend | 挂单 |
| cancel | 撤单 |
| partialfillandcancel | 部分成交后撤单 |
| partialfill | 部分成交 |
| fill | 全部成交 |
基本上在实际交易的过程中,这些情况都会遇到。作为初学者,我们就先选择 fill(全部成交)吧,意即,这个模拟帐户中,只要下了委托单,就肯定会成交。不会出现撤单啊、拒单啊、部分成交啊之类的情况。以后,根据需要、再选择其它的撮合模式分别做测试吧。
最后,请点击确定。你会看到主界面上的 帐户列表 区域,出现了刚刚添加的模拟帐户。恭喜,这就表示添加柜台帐户,成功!
这时,请点击 “进程” 下面的 开关,成为绿色,此时 状态 显示为 “就绪”

你可以通过滑动下面的滑块,看到右侧更多的信息。在帐户这一行的末尾,有 3 个按钮,分别是 “打开日志文件”、“帐户设置”和“删除帐户”,其功能就是字面意思,应该不难理解吧。

添加帐户的操作,就到这里了。
# 1.2.2、 添加行情源(简称MD)
点击 主界面 左上区域 中的 “行情源” 标签,即可切换到 行情源 面板。类拟 帐户列表 中一样,也有个 “添加” 按钮

在弹出的小窗口中,点击选择 SIM(模拟),然后点击确定。

完事了,就这么简单。
这时可以点击 “进程” 下面的 开关,使之成为绿色,此时 状态 显示为 “就绪”,就一切 OK 了。

现在,再看一眼主界面右下角吧,是不是 帐户进程状态 和 主进程状态 一样,都是绿色的了。这就说明 功夫量化 已经完全可用了。

# 1.3、添加策略
用 功夫量化 当然是用来跑策略啊、实现自动交易啊。
先准备一个简单的策略程序吧,就用来测试一下。具体代码如下:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source = "sim" # TD 和 MD 的源,都是 sim
account_str = "123456" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
context.log.info('Hellow World ')
# TEST01.py 的内容结束
2
3
4
5
6
7
8
9
10
11
12
就保存为 TEST01.py 文件吧,呆会儿就能用上。
WARNING
注意 :
策略应和功夫安装目录在一个盘符下面,策略文件路径最好不要有空格。
在运行策略之前一定要看下启动的账户柜台进程(td)和行情源柜台进程(md)是否与策略中填写的柜台ID一致
1、用鼠标左键点击 主界面 最左侧的 “策略” 按钮,以切换到 策略面板。在 策略面板 的 策略区域 右上角有个 “添加” 按钮

2、 点击了 “添加” 按钮后,会弹出 “添加策略” 窗口,在其中为策略先起个名字,然后点击 “入口文件” 后面的按钮,选择刚刚准备好的 TEST01.py ,最后点击 “确定” 。
3、成功添加了策略之后,即可在 策略区域 看到刚刚添加的 策略。其所在的一行包括、状态,启动运行的滑动开关,以及最靠右的四个按钮,分别是 查看日志、设置策略、编辑修改 和删除策略

4、运行策略,查看日志

看到日志中显示了 “Hellow World ”,程序猿们,是不是就秒懂了?
# 1.4、 小结
从一开始,在 Windows 上安装 功夫量化。然后配置好 模拟的柜台帐号与行情源。最后添加一个 测试程序 TEST01.py, 看到日志窗口中输出了经典的 “Hellow World”。应该没有什么难度吧。
重要提示:实际工作中,从安装包开始,还是直接找 券商 索取比较好,因为功夫量化是和券商一起工作的。换句话说,券商不支持,单玩 功夫量化 也玩不起来啊。(自己在家模拟玩吗?这么有兴致?)券商的安装包中,通常已经内置了柜台帐号和行情源,配置起来也十分方便,不然你自己猜啊?就算你猜中,接通了,技术上你是牛逼了,但不合法啊!懂?!所以,重点还是在如何写策略程序,用代码实现具体的交易过程,这才是正解。
如果你启动 功夫量化 之后,卡在了 “系统提示” ,可运行一次 清理journal,再重启即可。

# 二、 功夫量化 Python API 入门
主要介绍 功夫量化 中 Python 的版本以及预先安装好的第三方库。同时初步讨论一个最重要的 对象 context 及其简单的方法(函数)和属性(变量)。然后展示了官网上的 Demo 例程,有编程基础的朋友,阅读了本章之后,基本就可以上手了。
所以谓 API ( 应用程序编程接口),简称 接口。在本文档中,说人话就是:把做股票的一些基本操作, 用 Python 这种计算机语言 描述一遍,让电脑可以执行。已经有人(或公司)写好了现成的,可以拿来就用的指令、方法(函数),就是 API,把这些汇集成册,就是 API手册。
举个例子:我要下个委托单,买100股 600002这个股票,10.55 的时候买!
我们查一下功夫量化的 API 手册,哦,原来这样写:
# 买入沪市品种 600002 股票 100股,要价为 10.55
context.insert_order(
"600002", # 品种的代码
Exchange.SSE, # 交易所名称
"sim", # 柜台的名称
"123456", # 帐号
10.55, # 价格
100, # 数量
PriceType.Limit, # 价格类型
Side.Buy, # 买卖方向
Offset.Open # 开平方向
)
2
3
4
5
6
7
8
9
10
11
12
13
这就是学习 API 的作用。你通过学习,把基本操作 与 Python 语言的 API 联系起来,就可以根据策略要求,选择条件组合,打造自己的自动交易程序了。或者为他人代写程序、开辟你的另一条赚钱之路。
功夫量化 中已经内置了 Python ,版本是 3.7.5 -> 3.7.9 ,功夫量化 2.4 版之后,把 Python 升级到了 3.8 以上了。同时也带上了一些第三方的 库:
url = "http://mirrors.aliyun.com/pypi/simple/"
verify_ssl = false
name = "pypi"
[packages]
conan = "==1.22"
pywin32 = {version = "==227",sys_platform = "== 'win32'"}
pyinstaller = "==3.6"
click = "==7.1.1"
tabulate = "==0.8.6"
prompt_toolkit = "==1.0.14"
PyInquirer = "==1.0.3"
psutil = "==5.7.0"
chinesecalendar = "==1.4.0"
zhdate = "==0.1"
schema = "==0.7.1"
rx = "==3.0.1"
numpy = "==1.16.4"
pandas = "==0.24.2"
statsmodels = "==0.10.1"
sortedcontainers = "==2.1.0"
recordclass = "==0.12.0.1"
dotted_dict = "==1.1.2"
plotly = "==4.0.0"
tushare = "==1.2.39"
[dev-packages]
clang-format = "==9.0.0"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
你可以理解为 功夫量化 也是一个第三方的 库。开始写代码的时候,引用这个 库,就可以方便的调用其中已经写好的各种 方法(函数) 和 属性(变量),来构造你自己的交易策略。省得你还得从头写起,这就是常说的不要自己重复造轮子。
# 2.1、 初识 context 对象(Object)
最重要的 对象(Object),没有之一 !!
既然把 功夫量化 看作是一个 库 来导入,当然就为用起来方便啊,里面有现成的、已经写好的 对象、方法(函数),可以复制粘帖嘛。
首先必须要介绍的,最最重要的一个 对象 context 。字面意思就是 承上启下关联上下文 的 对象,在整个程序中随处可见,就是传说中的全局范围可见,它就是策略程序的本体。同时,还可以根据需要随时附加上自定义的属性和方法。在其它的量化工具中,也常常可见到类似的 对象,甚至名称也类似 context 。如果把策略程序比喻成盖一座房子、那么 context 可以比喻为 水泥或钉子,哪哪儿都用得上它,哪哪儿都需要它。
说这么多,证明一下。来罗——
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.log.info('程序开始 ')
context.log.info('这个策略程序的名称是:{} '.format(context.name))
"""
程序开始
这个策略程序的名称是:test
"""
2
3
4
5
6
7
8
9
10
11
12
13
看到了吧,context 有个属性(变量)叫 name ,就是当前运行的这个策略程序的名称。所以说,它就是策略程序的本体。
在前面的 Hellow 例子中,其实我们已经用到了
context.log.info()
这个 log 其实就是附加在 context 上的一个属性。众所周知,对象 的属性和方法(函数)是可以随时自定义的。你也可以随时写个变量 abc ,然后附加到 context 上,成为它的属性 context.abc ,然后在程序的任何地方,只要有 context ,当然也就有你刚刚自定义的 context.abc 了。(这不就是 全局变量?)甚至,你还可以自定义一个 函数 f_xyz(),然后把这个函数附加到 context 上,这样它就成为了一个方法,context.f_xyz()。同样的,只要有 context 的地方,就可以随时调用 context.f_xyz() 方法,(这个 函数 就成了一个全局函数)这就是 面向对象 的编程特性。
好了,先打住。知道你们一个个都要原地起飞了,摩拳擦掌都要开始自己动手写自定义方法和属性了,一个个的癞蛤蟆找青娃 ———— 玩得花。敢不敢先看看 context 已经自带了哪些方法和属性?如果实在不好用,再想着怎么自己去造轮子?用现成的不香吗?
请看下面的示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.log.info('程序开始 ')
context.log.info('Hellow World ')
context.log.info('context是什么? {}'.format(context))
context.log.info('context的内容: {}'.format(dir(context)))
2
3
4
5
6
7
8
9
10
11
12
运行之后会看到如下结果:
程序开始
Hellow World
context是什么? <click.core.Context object at 0x000001BBFA96FEC8>
context的内容: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_close_callbacks', '_depth', '_meta', 'abort', 'add_account', 'add_time_interval', 'add_timer', 'allow_extra_args', 'allow_interspersed_args', 'archive_dir', 'args', 'auto_envvar_prefix', 'book', 'books', 'buy', 'call_on_close', 'cancel_order', 'category', 'close', 'color', 'command', 'command_path', 'config_location', 'console_location', 'constants', 'dataset_dir', 'default_map', 'ensure_object', 'exit', 'fail', 'find_object', 'find_root', 'forward', 'get_account_book', 'get_account_cash_limit', 'get_help', 'get_usage', 'group', 'help_option_names', 'hold_book', 'hold_positions', 'home', 'ignore_unknown_options', 'inbox_dir', 'index_location', 'info_name', 'insert_order', 'invoke', 'invoked_subcommand', 'is_book_held', 'is_positions_mirrored', 'location', 'log', 'log_level', 'logger', 'lookup_default', 'loop', 'low_latency', 'make_formatter', 'make_order', 'max_content_width', 'meta', 'mode', 'name', 'now', 'obj', 'params', 'parent', 'path', 'protected_args', 'replay', 'req_deregister', 'req_history_order', 'req_history_trade', 'resilient_parsing', 'runtime_dir', 'runtime_locator', 'scope', 'sell', 'show_default', 'strategy', 'strftime', 'strptime', 'subscribe', 'subscribe_all', 'terminal_width', 'token_normalize_func', 'trading_day', 'utils', 'wc_context']
2
3
4
5
6
7
前面的 Hellow World 就不说了。
<click.core.Context object at 0x000001BBFA96FEC8>
表示 context 是一个 对象,0x00开头的那一串字符是 16位进制的内存地址,程序猿们看到了都默默点头。
然后是一个列表,其中每一项都是 context 的一个属性或方法。(有没有吓跑一大群人,哈哈。)
别怕,我们先挑两个简单的瞧瞧:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.log.info('程序开始 ')
context.log.info('context.name 表示当前策略名称: {}'.format((context.name)))
context.log.info('context.now() 返回当前时间戳: {}'.format((context.now())))
"""
运行结果:
程序开始
context.name 表示 当前策略名称: TEST01
context.now() 返回当前时间戳: 1636327025027141500
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这么简单的例子就不用多解释了吧?关于 时间戳 其实就是一个 单位为 纳秒 的整数,是从 1970-01-01 00:00:00.000 开始计算的。功夫量化 中用到了 20 位。其实 13 位左右,一般到 秒 或 毫秒 就差不多了。当然现代计算机的算力强劲,纳秒 多牛逼,多么高大上!
以上都是幼儿园级别的,敢不敢来 2 个小学一年级以上的?
安排
# 2.1.1、 添加帐户 context.add_account()
任何一个自动交易程序,最关键的第一件事儿,当然是要告诉电脑,你的帐户是什么啊。帐户、柜台都没有,或者写错了,那岂不是天大的笑话。
这个方法(函数)就是告诉你的电脑,你要用的帐户是什么。
语法: context.add_account(source_id, account_id)
参数:source_id, account_id
source_id 是一个字符串,代表 柜台的名称,如 'sim' ;
account_id 也是一个字符串,一般是一串数字,如 '123456'
返回值: 无
# 2.1.2、 订阅品种 context.subscribe()
最关键的第二件事儿,就是要告诉电脑,你准备交易什么品种。你要紧盯全网行情?只要上市的品种,你都要 买买买 ?大哥,豪气!求带我飞!
TIP
本文档中只讨论A股的交易品种。当然 功夫量化 也是支持期货市场的。
语法: context.subscribe(source_id, instruments, exchange_id)
参数:source_id, instruments, exchange_id
source_id 同上,是一个字符串,代表 柜台的名称,如 'sim' ;
instruments 是一个列表(List),一对方括号包含1个或多个字符串,每一个字符串代表一个交易品种,如 ['600001','600002']
exchange_id 是一个字符串,代表 市场的名称。众所周知,A股市场目前有三家了,深市、沪市和北交所。因此,对应的品种需要填上对应的市场ID, Exchange.SSE代表上海沪市、 Exchange.SZE代表深圳市场、后来 Exchange.BSE代表北京证券交易所,简称北交所。
返回值: 无
这就是任何一个自动交易程序,最关键的两件事儿:
一,添加帐号、
二,订阅品种。
具体操作就是对应了 context 的两个方法(函数)。因此,在每一个程序开头的位置,必然会看到类似如下的代码:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456") # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅上海沪市的2个品种
2
3
4
5
6
7
8
其中第5行 def pre_start(context): 是程序启动时调用的第一个方法(函数),即初始化方法(函数),我们接下来马上就要讨论到。添加帐号 和 订阅品种 这两个关键动作,就写在这个方法(函数)中,这是必不可缺的。
# 2.1.3、 订阅全部品种 context.subscribe_all()
看过上一节的方法(函数)之后,是不是还觉得有点麻烦。事前还得准备好一个列表,如:['600001','600002',......]。这要是订阅成百上千个,不得累死?来个简单粗暴点的!安排:
语法: context.subscribe_all(source_id)
参数:source_id
source_id 就是代表柜台名称的字符串,如 "tora" ;
WARNING
注意 : sim 模拟柜台不支持全市场订阅
没了,够简单吧。
返回值: 无
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("tora", "3055845") # 添加自己的帐号
context.subscribe_all("tora") # 订阅全部可交易的品种
2
3
4
5
6
7
8
如此一来,就等于订阅了全市场的全部品种,大约 6000+ 多种 。当然,这也需要足够大的内存和 CPU 的能力来支撑。如果电脑硬件配置不足,很可能就卡死了。
# 2.1.4、 输出日志消息 context.log.info()
这个方法(函数),我们已经认识了,用过了。它就是用来输出文字,进行人机对话的。调试程序啊、查看一下某个变量的值啊、检查一下获取的值是不是符合预期啊,用它来代替 print() 就对了。它还可以输出几个级别的信息,如: context.log.warning、context.log.error 、context.log.debug 分别对应 警告消息、错误消息和调试消息。想要输出的文本,就是它的形式参数,类型当然是一个 字符串(string),带上 format() 方法可以让输出的文本更容易阅读。前面的 "Hellow World" 的例子,都是用这个方法(函数)来实现的。
身为初学者,在开发编程、调试的过程中,我们会经常用到这个方法(函数)。
# 2.1.5、 转换时间格式
context.strftime()/context.strptime()
有 python 语言基础的朋友已经秒懂了。没错,它们就是用来转换时间格式的啊,一个用来把 毫秒时间戳 转换为 文本类型(格式),方便阅读;另一个就是把 文本类型(格式) 的时间,转换为 datetime.datetime 的 毫秒时间戳,注意 文本字符串必须是 "%Y-%m-%d %H:%M:%S." 的格式,注意最后有一个英文句点(.)不要漏掉了 。
请看下面的示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context): # pre_start() 方法(函数), 传入参数 context
context.log.info(" 这个策略的名称是: {} ".format(context.name))
context.log.info(" 当前时间是 {} 纳秒(自 1970-01-01 08:00:00 以来)".format(context.now()))
context.log.info(" 当前时间转换为 文本类型(格式)为: {} ".format(context.strftime(context.now())))
context.log.info(" 2020-02-02 09:30:01. 转换为时间戳: {} ".format(context.strptime("2020-02-02 09:30:01.")))
"""
运行的结果如下:
这个策略的名称是: test
今天的日期是: 2020-07-19 00:00:00
当前时间是 1558231726820023165 纳秒(自 1970-01-01 08:00:00 以来)
当前时间转换为 文本类型(格式)为: 2020-07-19 9:55:26.820023165
2020-02-02 09:30:01. 转换为时间戳: 1580607001000000000
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.1.6、 延时执行/定时重复执行
context.add_timer()/context.add_time_interval()
这 2 个方法(函数)是十分有用的。顾名思义,
context.add_timer() 表达,延迟 xxx 秒(指定一段时间)之后,调用执行一个函数/指令,只执行一次;
context.add_time_interval() 表达,每 xxx 秒(指定时间间隔)执行一个函数/指令,重复执行,一直执行,只到程序终止;
放在一起,就是为了避免搞混淆了。(不要问为什么,问就是:我没有。)
参数:2 个方法(函数)的参数都相同。
nano 是一个整数,表示纳秒单位的时间。一个是表达 延迟多长时间之后,执行一次;另一个是表示重复执行的间隔时间;
callback 是想要执行的函数或指令,通常用 简单的匿名函数即可;
上示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context): # pre_start() 方法(函数), 传入参数 context
context.add_timer(1*1000000000,lambda ctx, event: context.log.info("延迟 1 秒,打印输出 1 次。 "))
context.add_time_interval(3*1000000000,lambda ctx, event: context.log.info("每 3 秒,打印输出一次,一直重复下去。 "))
"""
运行的结果如下:
延迟 1 秒,打印输出 1 次。
每 3 秒,打印输出一次,一直重复下去。
每 3 秒,打印输出一次,一直重复下去。
每 3 秒,打印输出一次,一直重复下去。
每 3 秒,打印输出一次,一直重复下去。
每 3 秒,打印输出一次,一直重复下去。
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.1.7、 官网 Demo
关于 context 的重要性,可见其非同一般吧,功夫量化 的基本方法(函数),用到的对象(Object),都是绑定在这个 context 上面的,如前所述,它就是一个 承上启下,联接上下文的,十分重要的对象(Object)。后面的章节还会更详细的讨论它自带的、可以让我们方便使用的、直接可以复制粘帖的方法(函数)和属性(变量)。
所以一开始,功夫量化 就给了一个 Demo 的示例,有基础的同学参考这个 Demo 中的代码示例,直接引用(就是 复制粘帖 呗),多方便。想起网上一个段子,网友评论疼讯,一天天的就知道 复制粘帖,没想到大企鹅回嘴呛声了。“大把的人还不知道复制哪儿,粘帖哪儿呢?” ———— 没毛病!
所以,学习 Python API 的目的,就是要知道“复制什么?粘帖到那儿”。 ———— 一点毛病没有!
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
# 期货
# SOURCE = "ctp"
# ACCOUNT = "089270"
# tickers = ["rb2001","rb2003"]
# VOLUME = 2
# EXCHANGE = Exchange.SHFE
#股票
SOURCE = "xtp"
ACCOUNT = "15040910"
tickers = ["600000","600001"]
VOLUME = 200
EXCHANGE = Exchange.SSE
# 启动前回调,添加交易账户,订阅行情,策略初始化计算等
def pre_start(context):
context.add_account(SOURCE, ACCOUNT, 100000.0)
context.subscribe(SOURCE, tickers, EXCHANGE)
# context.subscribe("bar", tickers, EXCHANGE)
# context.subscribe_all(SOURCE)
context.ordered = False
# 启动准备工作完成后回调,策略只能在本函数回调以后才能进行获取持仓和报单
def post_start(context):
context.log.warning("post_start")
log_book(context,None)
# context.req_history_order(ACCOUNT)
# context.req_history_trade(ACCOUNT)
# 收到快照行情时回调,行情信息通过quote对象获取
def on_quote(context, quote):
# context.log.info("[on_quote] {}".format(quote))
if quote.instrument_id in tickers:
# 如果没有报单则报单
if not context.ordered:
order_id = context.insert_order(quote.instrument_id, EXCHANGE, ACCOUNT, quote.last_price, VOLUME, PriceType.Limit, Side.Buy, Offset.Open)
# order_id = context.insert_order(quote.instrument_id, EXCHANGE, ACCOUNT, quote.last_price, VOLUME, PriceType.Any, Side.Buy, Offset.Open)#上期所不支持市价单
if order_id > 0:
context.ordered = True
context.log.info("[order] (rid){} (ticker){}".format(order_id, quote.instrument_id))
# 通过添加时间回调,在三秒以后撤单
context.add_timer(context.now() + 3 * 1000000000, lambda ctx, event: cancel_order(ctx, order_id))
# 收到k线行情时回调,行情信息通过bar对象获取
def on_bar(context, bar):
context.log.warning("[on_bar] {}".format(bar))
# 收到订单状态回报时回调
def on_order(context, order):
context.log.info("[on_order] {}".format(order))
# 收到成交信息回报时回调
def on_trade(context, trade):
context.log.info("[on_trade] {}".format(trade))
# 策略释放资源前回调,仍然可以获取持仓和报单
def pre_stop(context):
context.log.info("[befor strategy stop]")
# 策略释放资源后回调
def post_stop(context):
context.log.info("[befor process stop]")
## 自定义函数
# 自定义持仓和资金日志输出函数
def log_book(context, event):
context.log.warning("[avail]{}".format(context.book.asset.avail))
context.log.warning("[acc_avail]{}".format(context.get_account_book(SOURCE, ACCOUNT).asset.avail))
book = context.book
asset = book.asset
context.log.warning(
"[strategy capital] (avail){} (margin){} (market_value){} (initial_equity){} (dynamic_equity){} (static_equity){} (realized_pnl){} (unrealized_pnl){}".format(
asset.avail, asset.margin, asset.market_value, asset.initial_equity,
asset.dynamic_equity, asset.static_equity, asset.realized_pnl, asset.unrealized_pnl))
book = context.get_account_book(SOURCE, ACCOUNT)
asset = book.asset
context.log.warning(
"[account capital] (avail){} (margin){} (market_value){} (initial_equity){} (dynamic_equity){} (static_equity){} (realized_pnl){} (unrealized_pnl){}".format(
asset.avail, asset.margin, asset.market_value, asset.initial_equity,
asset.dynamic_equity, asset.static_equity, asset.realized_pnl, asset.unrealized_pnl))
context.logger.warning("acc_pos")
for key in book.long_positions:
log_stock_pos(context, book.long_positions[key])
# log_future_pos(context, pos)
context.logger.warning("str_pos")
for key in context.book.long_positions:
log_stock_pos(context, context.book.long_positions[key])
# log_future_pos(context, pos)
# 自定义期货持仓数据日志输出函数
def log_future_pos(context, pos):
context.log.info(
"(instrument_id){} (instrument_type){} (exchange_id){}(direction){} (volume){} (yesterday_volume){} (last_price){} (settlement_price){} (pre_settlement_price){} (avg_open_price){} (position_cost_price){} (margin){} (position_pnl){} (realized_pnl){} (unrealized_pnl){} ".format(
pos.instrument_id, pos.instrument_type, pos.exchange_id,
pos.direction, pos.volume, pos.yesterday_volume, pos.last_price, pos.settlement_price, pos.pre_settlement_price,
pos.avg_open_price, pos.position_cost_price, pos.margin, pos.position_pnl, pos.realized_pnl, pos.unrealized_pnl))
# 自定义股票持仓数据日志输出函数
def log_stock_pos(context, pos):
context.log.info(
"(instrument_id){} (instrument_type){} (exchange_id){} (direction){} (volume){} (yesterday_volume){} (last_price){} (pre_close_price){} (close_price){} (avg_open_price){} (position_cost_price){} (realized_pnl){} (unrealized_pnl){} ".format(
pos.instrument_id, pos.instrument_type, pos.exchange_id, pos.direction,
pos.volume, pos.yesterday_volume, pos.last_price, pos.pre_close_price, pos.close_price, pos.avg_open_price,
pos.position_cost_price, pos.realized_pnl, pos.unrealized_pnl))
# 自定义撤单回调函数
def cancel_order(context, order_id):
action_id = context.cancel_order(order_id)
if action_id > 0:
context.log.info("[cancel order] (action_id){} (rid){} ".format(action_id, order_id))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
看到 Demo 这么多代码。有编程基础的朋友都笑了,这太 easy 了,简单小儿科;不太熟悉电脑的朋友都哭了,啊————这么难,逃了逃了。
所以既然都看到了这儿了,多少也了解到:本文档就是为了让更多的不太熟悉电脑的朋友们也能够上手 功夫量化 而撰写的,并且力求比官方文档更易理解、更易参考。(必须吐槽官方文档,浓浓的知识诅咒的味道,一般二般的计算机专业的大学生也得费点劲儿才能理解透彻,何况普通大众、芸芸股民乎————)
其实先从 Demo 例程的开头几行看起,就算没有计算机基础的朋友,也能看个八九不离十。
# -*- coding: UTF-8 -*- 国际惯例,表示下面这些代码的国际字符集是 UTF-8
import kungfu.yijinjing.time as kft # 导入 易筋经 里的 时间模块,重命名为 kft
from kungfu.wingchun.constants import * # 导入 咏春 模块
#股票
SOURCE = "sim" # 股票帐户的柜台,本地模拟就用 sim
ACCOUNT = "123456" # 股票帐号,如前面的例子可以用 12345
tickers = ["600000","600001"] # 想要关注的股票代码
VOLUME = 200 # 自定义一个参数,代表 交易量
EXCHANGE = Exchange.SSE # 自定义一个变量,代表 上海沪市的 代码 Exchange.SSE
2
3
4
5
6
7
8
9
10
瞧,就这么简单。本文档的所有内容只讨论有关股票的部分,因此 期货部分 省略。再往下就是一些 功夫量化 中已经写好的 方法(函数) 和 属性(变量)了,只要参考 API 手册,根据自己的策略,复制粘帖、修修改改、删删减减,一步一步的实现自己的交易策略,就是这样一个过程。
# context 小结:
好了,这一小节就到这里了,再说多了,怕把所有人都吓跑了。除了拿出来一个 Demo 吓唬吓唬了各位,其它的全是大白话,应该没有什么难以理解的地方吧。
简单一句话就是介绍了 功夫量化 中用到的 Python 版本,以及已经安装好的 第三方 库。最最重要的一个对象就是 context ,没有之一。它是全局可见的,也就是随时随地都可以直接调用,程序猿们都秒懂了。不太熟悉计算机的朋友只要没被劝退,看懂 Demo 例程中的前 10 行,应该没有任何压力。
# Exchange 交易所 对象(Object)
本章中出现的一个简单的对象(Object) 就是用来表达 交易所 的对象。甚至可以看成是一个简单的枚举型变量 或 一个包含交易所ID的列表,具体内容如下表所示:
| 属性 | 值 | 说明 |
|---|---|---|
| Exchange.BSE | "BSE" | 北京证券交易所(北交所) |
| Exchange.SSE | "SSE" | 上海证券交易所(上交所) |
| Exchange.SZE | "SZE" | 深圳证券交易所(深交所) |
| Exchange.SHFE | "SHFE" | 上海期货交易所 |
| Exchange.DCE | "DCE" | 大连商品交易所 |
| Exchange.CZCE | "CZCE" | 郑州商品交易所 |
| Exchange.CFFEX | "CFFEX" | 中国金融期货交易所 |
| Exchange.INE | "INE" | 上海国际能源交易中心 |
交易股票主要还是 上交所 和 深交所。北交所 才开没多久嘛。
接下来的内容,就是介绍 功夫量化 中的各种 方法(函数) 和 属性(变量),认识它们所表达的意思以及具体的执行步骤,这就是 API 指南的意义。可以把它们想象成建筑材料一样,搬来放在合适的地方,打造自己心中模型的样子。说白了就是 复制粘帖 到 正确的位置。开发量化交易程序就是如此 ———— 没啥神秘的。是否能进入量化交易的大门,只取决于你是否继续阅读此文档了。
# 2.2、 基本方法(函数)
功夫量化 官方已写好了一些基本的方法(函数),以供直接、方便的调用。说人话就是给我们复制粘帖的原材料。
先吐槽两句:本人也勉强算是老程序猿了,一直学得就是 函数、参数、返回值啥啥的。然后时代变革,都要面向对象。非要把 函数 叫 方法、非要把 变量 叫 属性......得得得,你们说中杯就中杯、说大杯就大杯、说超大杯就超大杯,好吧。我服了。
功夫量化 是基于 Python 语言的,当然也是 面向对象 的。(所以只能向强大的势力低头)
下表列出了 功夫量化 中的基本方法(函数),先瞅一眼混个脸熟吧。
| 编号 | 方法(函数)名称 | 功能简述 | 备注 |
|---|---|---|---|
| 01 | pre_start() | 策略启动前的初始化,只执行一次 | 必不可缺 |
| 02 | post_start() | 策略登录帐户,成功建立连接后,只执行一次 | 通常在此获取帐户的信息 |
| 03 | pre_stop() | 策略停止前的准备工作,只执行一次 | 可以省略,但养成好习惯是个不错的主意 |
| 04 | post_stop() | 策略断开帐户时的收尾工作,只执行一次 | 可以省略,检查下门关好没有 |
| 05 | on_quote() | 接收 3 秒一跳快照行情的方法(函数) | 绝对重点,最常用的方法(函数) |
| 06 | on_transaction() | 接收 逐笔行情的方法(函数) | 这已经是高频了,大佬。 |
| 07 | on_entrust() | 接收 逐笔委托 行情的方法(函数) | 玩得这么高端吗! |
| 08 | on_bar() | 接收 K 线行情的方法(函数) | 量化传统的 K 线图表交易策略不可少 |
| 09 | on_order() | 接收 委托单 消息的方法(函数) | 每下一单用这个核对,十分必要 |
| 10 | on_trade() | 接收 成交单 消息的方法(函数) | 每成交一单用这个核对,是必需的 |
可以说每一个交易策略程序,都或多或少包含几个上述这些方法(函数),它们就象基础的材料木头和石块、尤其是 pre_start() 啊、on_quote() 啊,肯定是要用上的,同时还有 context 对象 。回忆一下,连最简单的 “Hello World” 程序都不例外呢。
接下来,我们就逐一来认识这些 方法(函数)吧。
# 2.2.1、 初始化 pre_start():
功能: 策略程序一开始启动时,就调用执行此方法(函数),仅执行一次。通常用来做一些准备工作,如:添加帐号、订阅品种、读取文件等。
语法: def pre_start(context):
参数:context
看到 context 了吧,都说了它是随处可见的,哪哪儿都有它。必须的!
返回值: 无
示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
2
3
4
5
6
7
前面的例子中,其实已经见过面了。通常在这个 初始化方法(函数)中,会做两件事,一是添加自己的股票帐号、另一件事就是 订阅自己想要交易的 股票品种。如果还有什么其它的准备工作,比如声明几个变量做为参数啊、读取文件加载数据啊,也可以在这个方法(函数)中执行。
# 2.2.2、 连接检查 post_start():
功能: 当运行策略程序的电脑 成功连接 券商交易柜台服务器的主机后,会自动调用执行此方法(函数),仅执行一次。通常用来检验一下准备工作是否有遗漏,或者查看一下帐户中的 持仓信息、可用资金等。
语法: def post_start(context):
参数:context
只要是 功夫量化 的程序, context 这个 对象 作为参数是必须的!哪那儿都有它!
返回值: 无
示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def post_start(context):
context.log.info('连接柜台主机 sim 已成功 帐号 12345 已登录成功。')
context.log.info('检验工作完成')
2
3
4
5
6
7
8
9
10
11
在实际运用中,post_start() 方法(函数)中,更多的是执行一些检验帐户的操作,因为既然执行到了这个方法(函数),那一定是帐户和柜台主机已经联通了,帐户中有没有持仓单啊、有多少可用资金啊、帐户的名称啊、诸如这些信息就可以获取到了,在此可以打印输出到日志,核对一下。
# 2.2.3、 快照行情 on_quote(): (3秒一跳)
功能:这个方法(函数)可是重点!敲黑板!每收到一个订阅品种的报价、就会调用执行一次此方法(函数)。
语法: def on_quote(context, quote, location):
参数:context, quote, location
返回值: 无
在 形式参数 列表中:
context 就不再重复了,反正哪哪儿都有它;
location 是 功夫量化 2.4 版之后新增的一个参数,包含当前运行程序的电脑的一些信息,如:帐号、柜台名称等。
quote 就是重点中的重点了。用脚也想到了, quote 肯定是一个 对象(Object) 了,它其中包含了什么呢?当然是报价数据啊,请看下例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_quote(context, quote, location):
context.log.info("quote 是什么类型? {}".format(type(quote))) # 输出 quote 的类型
context.log.info("quote 中有什么? {}".format(dir(quote))) # 输出 quote 中的内容
"""
运行结果:
quote 是什么? <class 'pykungfu.longfist.types.Quote'>
quote 中有什么? ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__has_data__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__parse__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__tag__', '__uid__', 'ask_price', 'ask_volume', 'bid_price', 'bid_volume', 'close_price', 'data_time', 'exchange_id', 'high_price', 'instrument_id', 'instrument_type', 'iopv', 'last_price', 'low_price', 'lower_limit_price', 'open_interest', 'open_price', 'pre_close_price', 'pre_open_interest', 'pre_settlement_price', 'settlement_price', 'source_id', 'trading_day', 'trading_phase_code', 'turnover', 'upper_limit_price', 'volume']
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
又是这么密密麻麻一大堆、而且还在不停的滚动,恐惧症都犯了。因为 on_quote() 这个方法(函数)是每接收到一次报价就执行一次的,大约是 3 秒一次。因此这些信息也就会不停的滚动了。看到这么多的信息也不用怕,其实它们全是属性(变量),其中保存的就是报价数据了,看了下面这个例子,就一目了然了:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_quote(context, quote):
context.log.info("收到的交易品种的报价数据如下:\n")
context.log.info("\n 申卖价[卖一...卖五] ask_price:{} \
\n 申卖量 ask_volume:{} \
\n 申买价[买一...买五] bid_price:{} \
\n 申买量 bid_volume: {} \
\n 收盘价 close_price: {} \
\n 报价时间[纳秒] data_time: {} \
\n 交易所 exchange_id : {} \
\n 最高价 high_price: {} \
\n 交易品种 instrument_id: {} \
\n 交易品种类型 instrument_type: {} \
\n iopv: {} \
\n 最新价 last_price: {} \
\n 最低价 low_price: {} \
\n 跌停价 lower_limit_price: {} \
\n 持仓量(期货用)open_interest: {} \
\n 今开盘价 open_price:{} \
\n 昨收盘价 pre_close_price:{} \
\n 昨持仓量(期货用)pre_open_interest:{} \
\n 昨结价(期货用)pre_settlement_price:{} \
\n 结算价(期货用)settlement_price:{} \
\n 柜台 source_id:{} \
\n 交易日 trading_day:{} \
\n 换手 turnover:{} \
\n 涨停价 upper_limit_price:{} \
\n 成交量 volume:{} \
".format(
quote.ask_price,
quote.ask_volume,
quote.bid_price,
quote.bid_volume,
quote.close_price,
context.strftime(quote.data_time), # 把纳秒时间戳转换为文本格式。
quote.exchange_id,
quote.high_price,
quote.instrument_id,
quote.instrument_type,
quote.iopv,
quote.last_price,
quote.low_price,
quote.lower_limit_price,
quote.open_interest,
quote.open_price,
quote.pre_close_price,
quote.pre_open_interest,
quote.pre_settlement_price,
quote.settlement_price,
quote.source_id,
quote.trading_day,
quote.turnover,
quote.upper_limit_price,
quote.volume
)
)
"""
运行结果:
收到的交易品种的报价数据如下:
申卖价[卖一...卖五] ask_price:[193.04, 193.05, 193.06, 193.3, 193.34, 193.38, 193.39, 193.42, 193.43, 194.14]
申卖量 ask_volume:[100, 100, 400, 100, 100, 100, 900, 1000, 900, 800]
申买价[买一...买五] bid_price:[192.96, 192.95, 192.94, 192.93, 192.92, 192.91, 192.9, 192.89, 192.88, 192.87]
申买量 bid_volume: [1200, 2800, 800, 1900, 1900, 1000, 1500, 500, 300, 700]
收盘价 close_price: 0.0
报价时间[纳秒] data_time: 2020-08-11 11:09:30.614508500
交易所 exchange_id : SSE
最高价 high_price: 0.0
交易品种 instrument_id: 600001
交易品种类型 instrument_type: InstrumentType.Stock
iopv: 0.0
最新价 last_price: 193.0
最低价 low_price: 0.0
跌停价 lower_limit_price: 0.0
持仓量(期货用)open_interest: 0.0
今开盘价 open_price:0.0
昨收盘价 pre_close_price:0.0
昨持仓量(期货用)pre_open_interest:0.0
昨结价(期货用)pre_settlement_price:0.0
结算价(期货用)settlement_price:0.0
柜台 source_id:
交易日 trading_day:
换手 turnover:0.0
涨停价 upper_limit_price:0.0
成交量 volume:0
......
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
这下应该很清楚了吧。如果这都觉得麻烦,那么在 功夫量化 的自己的 app 中,也可以更简单点,如以下代码:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_quote(context, quote):
context.log.info("收到的交易品种的报价数据如下:\n")
context.log.info(quote) # 直接输出 quote 这个对象
"""
运行结果如下:
收到的交易品种的报价数据如下:
{"ask_price":[202.9,203.54,203.55,203.71,203.77,204.43,204.52,204.56,205.13,205.16],"ask_volume":[100,600,100,300,100,900,200,100,100,100],"bid_price":[202.88,202.82,202.81,202.8,202.79,202.78,202.77,202.76,202.75,202.74],"bid_volume":[69400,1100,900,500,1000,1400,100,100,600,1900],"close_price":0.0,"data_time":1666515225010159000,"exchange_id":"SSE","high_price":0.0,"instrument_id":"600001","instrument_type":1,"iopv":0.0,"last_price":202.89,"low_price":0.0,"lower_limit_price":0.0,"open_interest":0.0,"open_price":0.0,"pre_close_price":0.0,"pre_open_interest":0.0,"pre_settlement_price":0.0,"settlement_price":0.0,"source_id":"","trading_day":"","trading_phase_code":"","turnover":0.0,"upper_limit_price":0.0,"volume":0}
{"ask_price":[202.9,203.54,203.55,203.71,203.77,204.43,204.52,204.56,205.13,205.16],"ask_volume":[100,600,100,300,100,900,200,100,100,100],"bid_price":[202.88,202.82,202.81,202.8,202.79,202.78,202.77,202.76,202.75,202.74],"bid_volume":[69400,1100,900,500,1000,1400,100,100,600,1900],"close_price":0.0,"data_time":1666515225010159000,"exchange_id":"SSE","high_price":0.0,"instrument_id":"600001","instrument_type":1,"iopv":0.0,"last_price":202.89,"low_price":0.0,"lower_limit_price":0.0,"open_interest":0.0,"open_price":0.0,"pre_close_price":0.0,"pre_open_interest":0.0,"pre_settlement_price":0.0,"settlement_price":0.0,"source_id":"","trading_day":"","trading_phase_code":"","turnover":0.0,"upper_limit_price":0.0,"volume":0}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
拉拉杂杂那么多,但实际运用中,也就主要关心其中的几个,比如 申买价/量 ask_price/volume、申卖价/量 bid_price/volume、报价时间 data_time、交易品种instrument_id、最新价 last_price、昨收盘价 pre_close_price、换手 turnover、涨停价 upper_limit_price、成交量 volume......差不多了。(难不成你玩个股票还能用上 三角函数,还解个微积分?你装得累不累?)
你盯盘时,是不是也就只会盯着价格?但电脑程序老厉害了,盯着这么多数据呢,而且程序可以同时盯住多个品种的报价,是不是比你的肉眼精准多了?这就是量化交易程序的优势。
# 基本方法 小结
这么快就小结了?才刚刚看明白一点呢。本文档是面向初学者、新手的入门指南嘛,搞太多了、太复杂了,把人都吓跑了就不妙了。
本节内容确实简单:就介绍了 功夫量化 中,程序必然会用到的 3 个基本的方法(函数),其中:
pre_start() 就是你点击 运行 那个按钮后,电脑第一个执行的 方法(函数),并且只执行一次。
post_start() 是建立起网络联接之后,执行一次的方法(函数),通常用来校验一下自己的帐号、资金,核对一下。
on_quote() 是最重要的了,通俗的说就是 “盯盘”。电脑可厉害了,只要是用 context.subscribe() 订阅过的品种,它都能盯住,而且 3 秒 一跳,比你的肉眼厉害多了,就问你服不服?怕不怕!盯住的数据,它还都能记住,保存在 quote 这个 对象(Object) 中,以便后一步进行计算和比较。
# quote 快照行情 对象(Object)
quote 中包含的是 3秒一跳的快照行情数据,具体内容可参考下表。
| 序号 | 属性名称 | 表达意义 | 备注 |
|---|---|---|---|
| 01 | ask_price | 申卖价[卖一...卖五] | |
| 02 | ask_volume | 申卖量 | |
| 03 | bid_price | 申买价[买一...买五] | |
| 04 | bid_volume | 申买量 | |
| 05 | close_price | 收盘价 | |
| 06 | data_time | 报价时间[纳秒] | |
| 07 | exchange_id | 交易所 | |
| 08 | high_price | 最高价 | |
| 09 | instrument_id | 交易品种 | |
| 10 | instrument_type | 交易品种类型 | |
| 11 | iopv | ||
| 12 | last_price | 最新价 | |
| 13 | low_price | 最低价 | |
| 14 | lower_limit_price | 跌停价 | |
| 15 | open_interest | 持仓量 | 期货用 |
| 16 | open_price | 今开盘价 | |
| 17 | pre_close_price | 昨收盘价 | |
| 18 | pre_open_interest | 昨持仓量 | 期货用 |
| 19 | pre_settlement_price | 昨结价 | 期货用 |
| 20 | settlement_price | 结算价 | 期货用 |
| 21 | source_id | 柜台 | |
| 22 | trading_day | 交易日 | |
| 23 | turnover | 换手 | |
| 24 | upper_limit_price | 涨停价 | |
| 25 | volume | 成交量 |
WARNING
quote 对象 中所包含的数据,因版本不同,或 券商不同,内容也有所差异。具体信息以券商提供的手册为准。
接下来,当然就是要 下单交易 了。因此下一章,我们开始讨论 功夫量化 中的用来下单交易的方法(函数),让电脑为我们自动执行交易,只要指令下得对,那效率比你用手拍精准多了!不服来战!
# 三、 交易方法(函数)
终于来到最激动人心的操作步骤了,本章要讨论的内容可是和真金白银直接相关,都是钱、都是钱啊!(所以强烈建议用模拟帐户测试。)前面我们已经知道了如何让电脑程序代替我们的肉眼盯盘,那现在,看准了就该让电脑程序代替我们手拍下单了。你平时做交易难道不是这样的吗?眼盯手拍,完事儿之后,拍自己大腿。现在有电脑帮你盯盘、帮你下单。(完事了你可以有理由砸电脑了,都是电脑的锅!)
在没有用量化自动交易程序之前,其实你也是在用某一款券商给你的软件、用鼠标点击界面上的“买入”或“卖出”按钮,来进行操作,这个动作通常被称为 报单。程序无非是把这个 报单 按钮,用指令的形式替换一下罢了。当你 报单 时,就是下了一个 委托单,电脑程序称之为 Order,当价格合适时,即撮合成功时,这个 委托单 就会变成 成交单,电脑程序称之为 Trade,这就表示你已经成功完成了一次交易。如果这是一个 “买入”动作,那么你自己的帐户里,就会出现一笔对应的 持仓单,电脑称之为 Position,对应的买入品种的持有数量就会 增加;反之,如果你刚刚完成的是一个 “卖出”动作,并且顺利成交了,那么你的帐户里,这个品种对应的 持仓单,可卖数量就会减少。低买高卖,你就赚了。高卖低出,那就是 割肉!瞧,交易股票,就是这样一个过程,一点也不难。
那么,首先要介绍的,当然是 下单 这个动作对应的 方法(函数),也就是刚刚说的,对应 “买卖”按钮的指令。
# 3.1 报单 context.insert_order()
功能:这个方法(函数)实现 买入/卖出 的 报单 操作。这可是和真金白银直接相关的指令,一定要谨慎在意。
语法:
context.insert_order(
instrument_id, # 想交易的品种代码 如 '600002'
exchange_id, # 交易所名称 如 沪市 Exchange.SSE
source_id, # 券商的柜台名称 模拟用 'sim'
account_id, # 帐号 如 '123456'
limit_price, # 报单的价格 如 22.41
volume, # 报单的手数,具体品种数量 如 200
priceType, # 报价类型 对象,建议用 PriceType.Limit
side, # 买卖方向,Side.Buy 或 Side.Sell
offset, # 开平方向,Offset.Open 或 Offset.Close
hedgeFlag, # 投机类型,可省略不写
is_swap # 是否交换仓位,可省略不写,默认为 False
)
2
3
4
5
6
7
8
9
10
11
12
13
参数:
| 编号 | 参数名称 | 类型 | 说明 | 备注 |
|---|---|---|---|---|
| 01 | instrument_id | 字符串 str | 交易品种的代码 | 如: '600478' |
| 02 | exchange_id | 对象 Exchange | 交易所的代码 | 如:沪市 Exchange.SSE |
| 03 | source_id | 字符串 str | 柜台的名称 | 如:'sim' |
| 04 | account_id | 字符串 str | 帐号 | 如:'123456' |
| 05 | limit_price | 浮点数 float | 想要成交的价格 | 如:21.84 |
| 06 | volume | 整数 int | 想要成交的数量 | 如:100 表示交易一手股票 |
| 07 | priceType | 对象 PriceType | 报单类型 | 如:PriceType.Limit |
| 08 | side | 对象 Side | 买卖方向 | 如:Side.Buy 表示买入 |
| 09 | offset | 对象 Offset | 开平方向 | 如:Offset.Close 表示平仓 持有单 |
| 10 | hedgeFlag | 对象 HedgeFlag | 投机套保标识 | 可省略不写此参数 |
| 11 | is_swap | 对象 is_swap | 是否互换单 | 可省略不写此参数,默认为 否 False |
好家伙,这么多参数,头昏。
不昏不昏,前面几个简单参数,就不啰嗦了。如果连 帐号、交易所、价格、数量都还搞不清楚,那还是劝退吧。投资交易市场不适合你。
从 07 参数开始,咋还是个 对象(Object) 呢?哎,这就是 面向对象 编程的特色。一个 对象(Object) 可以作为参数 传递给一个 方法(函数),这样做的好处就是把这个 对象(Object) 包含的全部内容也一并 传递 到了 函数 内部,不仅属性(变量),还包含 对象 自带的方法(函数)。(感觉是不是套娃一样?扯远了扯远了,头更昏了。)说简单点,参数 priceType 就是几个可选值,交易股票时,建议就用 PriceType.Limit ,复制粘帖就好了。
同理,
08 参数 side ,就记住: 买 就是 Side.Buy ; 卖 就是 Side.Sell ,齐活儿。
09 参数 offset ,就记住: 想要新买入一个品种就是 Offset.Open ;想平仓卖出一个持有的品种,就是 Offset.Close,复制粘帖 ;
10 参数,可省略,就是可以不写。所以,我们暂时不讨论;
11 参数,是 2.4 版新增的一个参数,一般也用不上。所以,我们也不讨论;
(哈哈,划水成功)
返回值:order_id 一个长整数,代表 委托单 的编号。
终于看到一个有 返回值 的函数了,不容易啊。人和人说话,有来言有去语啊。这才是正常人的交流啊。人机对话,也要有发起、有回报啊。当年计算机老师都说了,一个没有返回消息的程序,就是一个粗鲁的程序,没有礼貌。所以在函数式编程的时候,每一个函数都是有返回值的,就算真的啥也没有,好歹返回一个 0 啊,讲礼貌很难吗?好吧,吐槽暂停、说正事儿。
这个 返回值 order_id 就是一串数字编号,方便之后用这个编号就可以查阅到这个 委托单,事后检查是很重要的。接下来我们马上就会用上的。
说这么多,不如上示例代码:
# 买入沪市品种 600000 股票 200股,要价为 12.0
context.insert_order(
"600000", # 品种的代码
Exchange.SSE, # 交易所名称
"sim", # 柜台的名称
"123456", # 帐号
12.0, # 价格
200, # 数量
PriceType.Limit, # 价格类型
Side.Buy, # 买卖方向
Offset.Open # 开平方向
)
# 卖出持有的沪市品种 600000 股票 200股,要价为 18.0
context.insert_order(
"600000", # 品种的代码
Exchange.SSE, # 交易所名称
"sim", # 柜台的名称
"123456", # 帐号
18.0, # 价格
200, # 数量
PriceType.Limit, # 价格类型
Side.Sell, # 买卖方向
Offset.Close # 开平方向
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
好家伙,12 块买入,18 块卖出,交易 200 股,赚了 1200 大洋啊,发财发财!(醒醒、做梦还早,想屁吃呢?)
以上,就是实现 买/卖 的报单 方法(函数)了,显而易见,它其实也是一个附加在 context 对象上的 方法(函数),因此只要有 context 的地方,就可以随时调用这个方法(函数),来随时进行报单的操作。
那么问题来了,我如何知道报单是否成功了呢?
Good Question,真是个好问题。
当我们用 context.insert_order() 报单后,就会用到另一个基本方法(函数)来接收回报消息,接下来让我们详细讨论一下吧。
# 3.2 报单回报 on_order():
报单,就是你用鼠标左键点击 “买”/“卖” 按钮时的动作,一旦你点击完成,就会生成一个 委托单。换作电脑程序,就是执行了一次上一节刚刚讨论过的报单方法(函数)context.insert_order(),之后、你的电脑会向交易所的服务器发送一个 报单 请求。你可以想象是你的电脑朝着交易所的方向,喊了一嗓子。
那么问题来了,你的电脑真的喊了这一嗓子吗?有没有偷懒,还是中间卡壳了,甚至喊错了?那岂不是坏菜了?所以,这时候就要用到一个方法(函数)和一个 对象 来检查一下:
语法:
def on_order(context, order, location):
参数:context, order, location
返回值: 无
看上去这么眼熟呢,和前面的 on_quote() 不能说一模一样,就是简单换了个 order嘛。没错,说明好记性啊。
形式参数列表中:
context 是肯定有的,就不啰嗦了;
location 是 功夫量化 2.4 版之后新增的一个参数,前面也见过面了,复制粘帖就完了。
关键点就是这个 order 了,没错,这也是一个对象。其中包含的内容如下所示:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_order(context, order , location):
context.log.info('order对象: {}'.format(order))
"""
运行结果:
order对象: {"account_id":"","client_id":"","commission":0.0,"error_id":0,"error_msg":"","exchange_id":"SSE","frozen_price":15.47,"hedge_flag":0,"insert_time":1666776871211807000,"instrument_id":"600002","instrument_type":0,"limit_price":15.47,"offset":0,"order_id":5911543042808599379,"parent_id":1666776871211,"price_type":0,"side":0,"source_id":"","status":2,"tax":0.0,"time_condition":0,"trading_day":"20221026","update_time":1666776871211807000,"volume":500,"volume_condition":0,"volume_left":500,"volume_traded":0}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如上例的代码中,第 9 行的 on_order() 方法(函数),是在 报单 时自动触发的。那在模拟(sim)环境中如何执行呢?简单,请跟随下面的步骤操作:
1、让示例代码运行起来;什么?你忘了?那请自行复习一下第一章的内容吧。

2、点击界面右上角的 “下单” 按钮。
3、在弹出的 下单面板中,填好想要交易的品种以及价格、数量等数据后,点击 下单。

在 策略日志 区域就可以看到打印输出的信息了。
好吧,这还看不明白了?非得捋一捋?安排,请参考下例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_order(context, order, location ):
context.log.info('order对象中包含的数据如下: \n')
context.log.info("\n 委托单ID order_id : {} \
\n 报单时间 insert_time: {} \
\n 更新时间 update_time:{} \
\n 交易日 trading_day : {} \
\n 报单品种 instrument_id: {} \
\n 交易品种类型 instrument_type: {} \
\n 交易所 exchange_id: {} \
\n 报单价格 limit_price: {} \
\n 冻结价格 frozen_price: {} \
\n 报单数量 volume:{} \
\n 剩余数量 volume_left:{} \
\n 税费 tax:{} \
\n 手续费 commission: {} \
\n 订单状态 status: {} \
\n 错误ID error_id : {} \
\n 错误消息 error_msg : {} \
\n 买卖方向 side: {} \
\n 开平方向 offset: {} \
\n 投机套保标识 hedge_flag: {} \
\n 价格类型 price_type:{} \
\n 成交量类型 volume_condition:{} \
\n 成交时间类型 time_condition:{} \
".format(
order.order_id,
context.strftime(order.insert_time),
context.strftime(order.update_time),
order.trading_day,
order.instrument_id,
order.instrument_type,
order.exchange_id,
order.limit_price,
order.frozen_price,
order.volume,
order.volume_left,
order.tax,
order.commission,
order.status,
order.error_id,
order.error_msg,
order.side,
order.offset,
order.hedge_flag,
order.price_type,
order.volume_condition,
order.time_condition,
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
成功运行之后,在策略日志会看到如下消息:
order对象中包含的数据如下:
委托单ID order_id : 5911543042808599385
报单时间 insert_time: 2020-10-16 13:46:41.248089300
更新时间 update_time:2020-10-16 13:46:41.248089300
交易日 trading_day : 20201026
报单品种 instrument_id: 600000
交易品种类型 instrument_type: InstrumentType.Unknown
交易所 exchange_id: SSE
报单价格 limit_price: 14.87
冻结价格 frozen_price: 14.87
报单数量 volume:300
剩余数量 volume_left:300
税费 tax:0.0
手续费 commission: 0.0
订单状态 status: OrderStatus.Pending
错误ID error_id : 0
错误消息 error_msg :
买卖方向 side: Side.Buy
开平方向 offset: Offset.Open
投机套保标识 hedge_flag: HedgeFlag.Speculation
价格类型 price_type:PriceType.Limit
成交量类型 volume_condition:VolumeCondition.Any
成交时间类型 time_condition:TimeCondition.IOC
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这够清楚了吧。其实在实际运用中,也就主要关心其中的几个,核对 报单品种 instrument_id、报单价格 limit_price、报单数量 volume 是不是与你预期的相符。刚刚提过的 委托单ID order_id,就是那一大串数字,也是很重要的一个信息。报单时间 insert_time也是,注意核对。
最重要的莫过于 订单状态 了,这可是检查 报单 是否成功的重要标志。所谓状态嘛,当然是有几种情况了。
| 整数值 | 属性 | 说明 |
|---|---|---|
| 0 | Unknown | 未知 |
| 1 | Submitted | 已报单 |
| 2 | Pending | 已报单,等待成交中 |
| 3 | Cancelled | 已撤单 |
| 4 | Error | 发生错误 |
| 5 | Filled | 已成交 |
| 6 | PartialFilledNotActive | 部分成交,剩余已撤单(部成部撤) |
| 7 | PartialFilledActive | 部分成交,剩余未撤单 |
请参考以下示例巩固你的理解吧。
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_order(context, order, location ):
context.log.info("报单回报 order 对象 {}".format(order))
if order.status == OrderStatus.Submitted:
context.log.warning("报单已提交!\n报单编号:{} \n报单时间为: {} \n报单品种为: {} \n报单价格为: {} \n价格类型为: {} \n报单数量为: {} \n买卖方向为: {} \n开平方向为: {} "
.format(order.order_id,context.strftime(order.insert_time),order.instrument_id,order.limit_price,order.price_type,order.volume,order.side,order.offset))
elif order.status == OrderStatus.Pending:
context.log.warning("报单已提交,等待成交中!\n报单编号:{} \n报单时间为: {} \n报单品种为: {} \n报单价格为: {} \n价格类型为: {} \n报单数量为: {} \n买卖方向为: {} \n开平方向为: {} "
.format(order.order_id,context.strftime(order.insert_time),order.instrument_id,order.limit_price,order.price_type,order.volume,order.side,order.offset))
elif order.status == OrderStatus.Filled:
context.log.warning("报单已成交!\n报单编号:{} \n报单时间为: {} \n报单品种为: {} \n报单价格为: {} \n价格类型为: {} \n报单数量为: {} \n买卖方向为: {} \n开平方向为: {} "
.format(order.order_id,context.strftime(order.insert_time),order.instrument_id,order.limit_price,order.price_type,order.volume,order.side,order.offset))
elif order.status == OrderStatus.PartialFilledNotActive:
context.log.warning("报单部成部撤!\n报单编号:{} \n报单时间为: {} \n报单品种为: {} \n报单价格为: {} \n价格类型为: {} \n报单数量为: {} \n剩余数量为: {}\n买卖方向为: {} \n开平方向为: {} "
.format(order.order_id,context.strftime(order.insert_time),order.instrument_id,order.limit_price,order.price_type,order.volume,order.volume_left,order.side,order.offset))
if order.status == OrderStatus.Error:
context.log.warning("报单发生错误!!\n错误ID :{} \n 错误消息:{} \n报单时间为: {} \n报单品种为: {} \n报单价格为: {} \n价格类型为: {} \n报单数量为: {} \n剩余数量为: {}\n买卖方向为: {} \n开平方向为: {} "
.format(order.error_id,order.error_msg,context.strftime(order.insert_time),order.instrument_id,order.limit_price,order.price_type,order.volume,order.volume_left,order.side,order.offset))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
当运行时执行了 on_order() 方法(函数),你就会在策略日志中看到类似信息:
报单已提交,等待成交中!
报单编号:5911543043329843913
报单时间为: 2022-10-26 20:48:43.704049000
报单品种为: 600001
报单价格为: 54.44
价格类型为: PriceType.Limit
报单数量为: 100
买卖方向为: Side.Buy
开平方向为: Offset.Open
2
3
4
5
6
7
8
9
这将十分方便你复盘时,检查核验报单的情况。
既然都报单了,接下来当然是静待成交了。如果你报单的价格、数量,正好市场上有对手盘与你交易,那么这一单就会顺利撮合成交了。
现在用脚都想到了,成交了电脑必须给我一个回报啊,这么没有礼貌的吗?
Good idea,真是个好想法。所以该请下一个 方法(函数)登场了。
# 3.3 成交回报 on_trade():
当你完成 报单 后,上一节的 order 会告诉你,“我已经确实报单了啊,没错”。接下来自然就是等待 交易服务器 回应你的电脑喊的这一嗓子了。这个过程就是 成交回报。根据市场上的具体情况 以及 你要求的价格、数量,这一单是否能顺利成交?还是成交一部分?还是全都成交不了?这必须要第一时间回报啊,不然血压都蹭蹭的往上窜。
幸运的是,这个过程是全自动的。你的报单只要有成交的情况,就会自动执行一个方法(函数),它就是 on_trade(),我们来仔细看看吧:
语法:
def on_trade(context, trade, location):
参数:context, trade, location
返回值: 无
这简直就是三胞胎中的老三嘛 ,看上去和前面的 on_quote() 、on_order() 有啥区别?不就是又换了个 对象 trade嘛。
形式参数列表中:
context 就是原样 copy;
location 是 功夫量化 2.4 版之后新增的一个参数,也是原样复制粘帖。
无非这回是 trade ,不用说也知道,肯定也是一个对象罗。如法炮制,先看看其中包含的内容:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_trade(context, trade , location):
context.log.info('trade对象: {}'.format(trade))
"""
运行结果:
trade对象: {"account_id":"123456","client_id":"","close_today_volume":0,"commission":0.0,"exchange_id":"SSE","hedge_flag":0,"instrument_id":"600000","instrument_type":0,"offset":0,"order_id":5911543042258576638,"parent_order_id":0,"price":50.41,"side":1,"source_id":"sim","tax":0.0,"trade_id":5911543041550000966,"trade_time":1666871286176515400,"trading_day":"","volume":300}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
同样的,这个 on_trade() 方法(函数),是在 成交 后自动触发的。在模拟(sim)环境中想要执行一次,请参考上例中的操作,启动运行后,手动下单即可,并无差别。
知道你们非得捋一捋才能看明白?安排:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_trade(context, trade, location):
context.log.info('trade对象中包含的数据如下: \n')
context.log.info("\n 帐号 account_id : {} \
\n 客户名称 client_id: {} \
\n 平今仓数量 close_today_volume:{} \
\n 手续费 commission : {} \
\n 交易所名称 exchange_id: {} \
\n 投机套保标识 hedge_flag: {} \
\n 交易品种 instrument_id: {} \
\n 交易品种类型 instrument_type: {} \
\n 开平方向 offset: {} \
\n 委托单号 order_id:{} \
\n 关联委托单号 parent_order_id:{} \
\n 成交价格 price: {} \
\n 买卖方向 side: {} \
\n 柜台名称 source_id : {} \
\n 税费 tax:{} \
\n 成交单号 trade_id : {} \
\n 成交时间 trade_time:{} \
\n 交易日 trading_day:{} \
\n 成交量 volume:{} \
".format(
trade.account_id,
trade.client_id,
trade.close_today_volume,
trade.commission,
trade.exchange_id,
trade.hedge_flag,
trade.instrument_id,
trade.instrument_type,
trade.offset,
trade.order_id,
trade.parent_order_id,
trade.price,
trade.side,
trade.source_id,
trade.tax,
trade.trade_id,
context.strftime(trade.trade_time),
trade.trading_day,
trade.volume,
)
)
"""
运行结果
帐号 account_id : 123456
客户名称 client_id:
平今仓数量 close_today_volume:0
手续费 commission : 0.0
交易所名称 exchange_id: SSE
投机套保标识 hedge_flag: HedgeFlag.Speculation
交易品种 instrument_id: 600001
交易品种类型 instrument_type: InstrumentType.Stock
开平方向 offset: Offset.CloseToday
委托单号 order_id:5911543043034753411
关联委托单号 parent_order_id:0
成交价格 price: 64.54
买卖方向 side: Side.Sell
柜台名称 source_id : sim
税费 tax:0.0
成交单号 trade_id : 5911543040797941231
成交时间 trade_time:2022-10-28 14:52:11.947687500
交易日 trading_day:
成交量 volume:200
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
这下就十分清楚了吧。实际运用中,主要关心的就那么几个属性,比如 交易的品种 instrument_id、成交的价格 price、成交量 volume 、成交的时间 trade_time,以及 成交单号 trade_id 和 委托单号 order_id,这些都是用来事后检查核对的依据。
哎,买定离手,错爱不究。我们已经讨论过 报单 ,以及 委托单 和 成交单 了。这时候,突然,哎哎,不行,我后悔了。我要撤回,不玩了不玩了,把钱还我!
这...这这......如果已经成交了,也就是说 on_trade() 已经执行了,说明 交易所 已经撮合了这笔交易,数量已经全部成交了,那就没有机会 撤回 了。真的就是 爱错了也要爱下去。(割肉呗,还有啥说的?)如果还在 委托单 这一步。还记得 委托单 的状态吗?(这么快忘了,那还是快回头复习一下吧。)如果 委托单 的 状态 还不是 已成交 Filled,那我们就可以用下面这个方法(函数)来完成 撤单 动作了。
# 3.4 撤单 context.cancel_order()
撤单很简单,只需要提供 委托单 的单号,同时 委托单 还没有成交,即 order.status != Filled ,这时就还有机会 撤回 这个 委托单。具体的方法(函数)如下:
语法:
context.cancel_order(order_id)
参数:order_id
返回值: action_id
参数只需要一个,就是 报单 时自动生成的 委托单号,现在知道这个属性(变量)order.order_id 的用处了吧。
这个函数也是很有礼貌的,有一个返回值,表达 撤回 动作的编号,也是用来事后检查的。
撤单 这个动作和 报单 一样,也可以想象为你的电脑朝着交易所的方向,喊了一嗓子。只不过这次喊的内容是:把编号 xxxx 的单子给我撤回来,我改主意了!同理,为了检查一下这一嗓子喊没喊到位,on_order() 也会执行一次,对应这个 撤单 的动作,也会有一次记录。请看示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
import time
def pre_start(context):
context.add_account("sim", "123456", 100000.0) # 添加自己的帐号
context.subscribe("sim",['600001','600002'], Exchange.SSE) # 订阅品种
def on_quote(context, quote, location):
context.log.info("收到的交易品种 {} 的报价,管它三七二十一,报单再说!"
.format(quote.instrument_id))
order_id = context.insert_order(quote.instrument_id,
Exchange.SSE,
"sim", # 功夫量化 2.4 版要求填写 柜台
"123456",
quote.last_price,
100,
PriceType.Limit,
Side.Buy,
Offset.Open
)
time.sleep(1) # 等一秒
context.log.info("过了1秒,我改变主意了,我要撤单!")
action_id = context.cancel_order(order_id)
context.log.info("撤单 动作生成的单号是:{}!".format(action_id ))
def on_order(context, order, location ):
if order.status == OrderStatus.Submitted:
context.log.warning("报单已提交!\n报单编号:{} \n报单时间为: {} \n报单品种为: {} \n报单价格为: {} \n价格类型为: {} \n报单数量为: {} \n买卖方向为: {} \n开平方向为: {} "
.format(order.order_id,context.strftime(order.insert_time),order.instrument_id,order.limit_price,order.price_type,order.volume,order.side,order.offset))
elif order.status == OrderStatus.Pending:
context.log.warning("报单已提交,等待成交中!\n报单编号:{} \n报单时间为: {} \n报单品种为: {} \n报单价格为: {} \n价格类型为: {} \n报单数量为: {} \n买卖方向为: {} \n开平方向为: {} "
.format(order.order_id,context.strftime(order.insert_time),order.instrument_id,order.limit_price,order.price_type,order.volume,order.side,order.offset))
if order.status == OrderStatus.Cancelled:
context.log.warning("报单已撤回!\n撤单编号:{} \n撤单时间为: {} \n撤单品种为: {} \n撤单数量为: {} \n剩余数量为:{} \n买卖方向为: {} \n开平方向为: {} "
.format(order.order_id,context.strftime(order.insert_time),order.instrument_id,order.volume,order.volume_left,order.side,order.offset))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
如果你要运行上例的代码,建议将模拟帐户 'sim' 的选项,设置为 "Pend" ,即 报单 之后,委托单的状态是 等待中,这样即可看到 撤单 动作。可参考 第一章 1.2 配置 添加柜台帐户中的操作。

运行结果如下:
收到的交易品种 600002 的报价,管它三七二十一,报单再说!
报单已提交,等待成交中!
报单编号:5911543041790302880
报单时间为: 2020-10-09 10:33:19.034556900
报单品种为: 600000
报单价格为: 12.0
价格类型为: PriceType.Limit
报单数量为: 200
买卖方向为: Side.Buy
开平方向为: Offset.Open
过了1秒,我改变主意了,我要撤单!
撤单 动作生成的单号是:5911543043161467152!
报单已撤回!
撤单编号:5911543041790302880
撤单时间为: 2020-10-09 10:33:20.034556900
撤单品种为: 600000
撤单数量为: 200
剩余数量为:200
买卖方向为: Side.Buy
开平方向为: Offset.Open
......
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
通过以上例子,你应该对 报单、撤单 以及方法(函数) on_order() 有了更清晰的理解吧。特别是 order 对象的 属性(变量) order.status 的几种状态所表达的意义,更加清楚了吧。
# 3.5、 小结
终于到了小结了。这一章的内容够份量了吧。基本把交易过程中最关键的操作都说明白了。(就这?)不然呢?你以为量化交易多么高大上、多么牛逼、多么复杂?还真没有,基本的动作可不就这些吗?你还能玩个花出来?交易之前翻个筋斗?和你用肉眼手拍一样一样的。无非是用计算机语言中的方法(函数)、或者 称之为 指令、代替了你的手去点“买/卖”按钮而已,没别的。
一次完整的交易过程,不过如此。报单 --> 生成 委托单 --> 等待成交 或者 改主意了,撤回。这些动作、你之前是用手拍、现在用电脑程序呗。执行起来又快又准,精准高效。(最关键的,错了你可以砸电脑!)只要 报单,就会自动执行一次 on_order(),就是为了方便检查这个操作执行的情况,撤单也是一样。交易所成交了,也会自动执行一次 on_trade() ,也是为了方便事后检查核对。
本章中用到的 对象(Object)整理如下:
# order 报单 对象(Object)
order 中包含的是 报单 的信息,就是由你的电脑发起,向交易所喊的那一嗓子,其中包含的具体信息可参考下表。
| 序号 | 属性名称 | 表达意义 | 备注 |
|---|---|---|---|
| 01 | order_id | 委托单ID | 一长串数字 |
| 02 | insert_time | 报单时间 | 纳秒时间戳 |
| 03 | update_time | 更新时间 | |
| 04 | trading_day | 交易日 | 如:20201026 |
| 05 | instrument_id | 报单品种 | |
| 06 | instrument_type | 交易品种类型 | |
| 07 | exchange_id | 交易所 | 如:沪市 Exchange.SSE |
| 08 | limit_price | 报单价格 | |
| 09 | frozen_price | 冻结价格 | |
| 10 | volume | 报单数量 | |
| 11 | volume_left | 剩余数量 | |
| 12 | tax | 税费 | |
| 13 | commission | 手续费 | |
| 14 | status | 订单状态 | |
| 15 | error_id | 错误ID | |
| 16 | error_msg | 错误消息 | |
| 17 | side | 买卖方向 | |
| 18 | offset | 开平方向 | |
| 19 | hedge_flag | 投机套保标识 | |
| 20 | price_type | 价格类型 | |
| 21 | volume_condition | 成交量类型 | |
| 22 | time_condition | 成交时间类型 |
在 报单 的过程中,有几个参数本身就是来自功夫量化的 对象(Object),如:
# PriceType 价格类型 对象(Object)
在 报单 方法(函数)context.insert_order() 中,用作形式参数传递。在 报单回报 的对象 order 中,作为属性值回传回来。
| 整数值 | 属性 | 说明 |
|---|---|---|
| 0 | Limit | 限价 |
| 1 | Any | 市价 |
| 2 | FakBest5 | 上海深圳最优五档即时成交剩余撤销,不需要报价 |
| 3 | ForwardBest | 仅深圳本⽅最优价格申报, 不需要报价 |
| 4 | ReverseBest | 上海最优五档即时成交,剩余部分转为限价,深圳对⼿⽅最优价格申报,不需要报价 |
| 5 | Fak | 股票(仅深圳)即时成交,剩余部分撤销,不需要报价;期货即时成交剩余撤销,需要报价 |
| 6 | Fok | 股票(仅深圳)市价全额成交或者撤销,不需要报价;期货全部或撤销,需要报价 |
| 7 | unkonwn | 上交所的标识,意味着“增加委托”. |
# Side 买卖方向 对象(Object)
买卖方向如字面意思,就是表达 买入/卖出。但 功夫量化 同时也是支持期货市场的,所以为了表达实际运用中的更多情况,这个 对象(Object) 的属性还挺多的。
| 序号 | 属性 | 说明 |
|---|---|---|
| 01 | Buy | 买 |
| 02 | Sell | 卖 |
| 03 | Lock | 锁仓 |
| 04 | Unlock | 解锁 |
| 05 | Exec | ⾏权 |
| 06 | Drop | 放弃⾏权 |
| 07 | Purchase | 申购 |
| 08 | Redemption | 赎回 |
| 09 | Split | 拆分 |
| 10 | Merge | 合并 |
| 11 | MarginTrade | 融资买⼊ |
| 12 | ShortSell | 融券卖出 |
| 13 | RepayMargin | 卖券还款 |
| 14 | RepayStock | 买券还券 |
| 15 | CashRepayMargin | 现⾦还款 |
| 16 | StockRepayStock | 现券还券 |
| 17 | SurplusStockTransfer | 余券划转 |
| 18 | GuaranteeStockTransferIn | 担保品转⼊ |
| 19 | GuaranteeStockTransferOut | 担保品转出 |
对于股票交易来说,就用到 买、卖 ,即示例中的 Side.Buy 或 Side.Sell。
# Offset 开平方向 对象(Object)
对应 买/卖 的动作,就有 开新仓和平仓已有持单的动作。这个 对象(Object) 就是用来表达 开仓/平仓 的。
| 序号 | 属性 | 说明 |
|---|---|---|
| 01 | Open | 开新单 |
| 02 | Close | 平已持有单 |
| 03 | CloseToday | 平今天买入的仓位 |
| 04 | CloseYesterday | 平昨 |
对应 买入,就是 Offset.Open 罗,对应 卖出,就是 Offset.Close 罗。这应该没有任何难理解的吧?猩猩都会。
# HedgeFlag 投机套保标识 对象(Object)
这个对象(Object),在 C++ 语言中才用到,Python 程序中是不用的。做股票交易也用不上。它也只有一个属性值 Speculation,因此瞅一眼,略过。
# IsSwap 是否为互换单 对象(Object)
这个对象(Object),做股票交易也是用不上的。它其实就是 2 个布尔值 ,非 真(True)即(假),也瞅一眼就好。
# OrderStatus 委托单状态 对象(Object)
| 序号 | 属性 | 说明 |
|---|---|---|
| 01 | Unknown | 未知 |
| 02 | Submitted | 已提交 |
| 03 | Pending | 等待 |
| 04 | Cancelled | 已撤单 |
| 05 | Error | 错误 |
| 06 | Filled | 已成交 |
| 07 | PartialFilledNotActive | 部分成交不在队列中(部成部撤) |
| 08 | PartialFilledActive | 部分成交还在队列中 |
# Direction 多空 对象(Object)
用来表达 持仓单 ⽅向的对象(Object),也只有 2 种可能,不是多(Long),就是 空(Short)。股票交易用不上。
# trade 成交回报 对象(Object)
| 序号 | 属性名称 | 表达意义 | 备注 |
|---|---|---|---|
| 01 | account_id | 帐号 | |
| 02 | client_id | 客户名称 | |
| 03 | close_today_volume | 平今仓数量 | |
| 04 | commission | 手续费 | |
| 05 | exchange_id | 交易所 | |
| 06 | hedge_flag | 投机套保标识 | |
| 07 | instrument_id | 交易品种 | |
| 08 | instrument_type | 交易品种类型 | |
| 09 | offset | 开平方向 | |
| 10 | order_id | 委托单号 | |
| 11 | parent_order_id | 关联委托单号 | |
| 12 | price | 成交价格 | |
| 13 | side | 买卖方向 | |
| 14 | source_id | 柜台名称 | |
| 15 | tax | 税费 | |
| 16 | trade_id | 成交单号 | |
| 17 | trade_time | 成交时间 | |
| 18 | trading_day | 成交时间交易日 | |
| 19 | volume | 成交量 |
好家伙,这也不少啊,脑袋都炸了。简单的、一撇而过就好了,猩猩都懂的,也不费什么脑子。重点就是order、PriceType、trade,好好看看前面的例子,加深理解。
# 四、逐笔行情
接下来我们要玩点高级点的了。
逐笔行情,通常资深股民都有所了解。就是常说的 Level 2 级行情、其中包含有 10-15 档报价、逐笔交易、逐笔委托......内容还挺多的。加上坊间传来传去,各种造词大神还真的挺有创意,就是让你头昏眼花、听不懂、但感觉很厉害就对了。
功夫量化中用到的逐笔行情,根据政策要求是不允许出现在券商机房之外的场景中的。意即,你能在手机上、或你自己家里电脑上(互联网)看到的所谓 逐笔行情,都是 逗你玩。
WARNING
想要用到 逐笔行情,只能通过券商申请,在券商提供的机房中使用。所以有在公开互联网上所谓的逐笔行情,或多或少都是骗局!
# 4.1 逐笔行情 on_transaction():
没错,就是这个 transcaction 。一眼看上去,这不就是前面那 3 胞胎的堂兄弟吗?没错,果然好记性!可以把这个方法,看作是 on_quote() 的加强版,专门用来接收 逐笔行情 的方法(函数)。对应的就是市场中的 已成交(trade)和 已撤单 (cancel)的订单数据。
语法:
def on_transaction(context, transaction, location):
参数:context, transaction, location
返回值: 无
不能说是完全复制,也基本是完全复制。就是把 quote 换成了 transaction 嘛。
形式参数列表中:
context 是必须有的;
location 是 功夫量化 2.4 版之后新增的一个参数。
关键点就是这个 transaction 了,当然也是一个对象。其中包含的内容如下所示:
def on_transaction(context, transaction , location):
context.log.info("transaction对象: {}".format(transaction))
"""
运行结果:
"transaction对象:
{"ask_no":4494061,"bid_no":0,"biz_index":0,"bs_flag":2,"data_time":1667181875434000000,"exchange_id":"SSE","exec_type":1,"instrument_id":"110077","instrument_type":3,
"main_seq":801,"price":124.288,"seq":5724859,"trading_day":"","volume":10}
"""
2
3
4
5
6
7
8
9
TIP
功夫量化的 "sim" 是不提供 逐笔行情 的。因此,在本地模拟环境中,是无法接收到 transaction 的。并且,因券商不同,沪市和深市 的 逐笔行情 还略有差别,实际运用中,注意认真甄别。
按照惯例还是捋捋吧:
def on_transaction(context, transaction,location ): # 接收已订阅品种的报价数据
context.log.info("收到的 逐笔行情 数据如下:\n")
context.log.info("\n 卖⽅订单号 ask_no:{} \
\n 买⽅订单号 bid_no:{} \
\n 业务序号 biz_index:{} \
\n 内外盘标识 bs_flag: {} \
\n ⽣成时间 data_time: {} \
\n 交易所 exchange_id : {} \
\n 成交标识 exec_type: {} \
\n 交易品种 instrument_id: {} \
\n 交易品种类型 instrument_type: {} \
\n 主序号 main_seq:{} \
\n 价格 price: {} \
\n ⼦序号 seq: {} \
\n 交易日 trading_day:{} \
\n 成交量 volume:{} \
".format(
transaction.ask_no,
transaction.bid_no,
transaction.biz_index,
transaction.bs_flag,
context.strftime(transaction.data_time), # 转换文本格式。
transaction.exchange_id,
transaction.exec_type,
transaction.instrument_id,
transaction.instrument_type,
transaction.main_seq,
transaction.price,
transaction.seq,
transaction.trading_day,
transaction.volume
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
如果你开通了 Level 2 逐笔行情 的权限,成功运行上例后,会看到如下结果:
收到的 逐笔行情 数据如下:
卖⽅订单号 ask_no:7332611
买⽅订单号 bid_no:7202545
业务序号 biz_index:0
内外盘标识 bs_flag: BsFlag.Sell
⽣成时间 data_time: 2020-11-02 14:18:22.880000000
交易所 exchange_id : SSE
成交标识 exec_type: ExecType.Trade
交易品种 instrument_id: 110077
交易品种类型 instrument_type: InstrumentType.Bond
主序号 main_seq:801
价格 price: 137.75
⼦序号 seq: 9658848
交易日 trading_day:20221103
成交量 volume:4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
大部分的属性(变量)所表达的意思一目了然,不需要过多解释吧。
接下来讨论几个值得特别关注的:
bs_flag ———— 这个属性(变量)实际是一个 对象,只有 3 个具体的值:分别是:Unknown 未知、Buy 买 和 Sell 卖;意即这次的 逐笔行情,是一个 买单、卖单或是未知的状态;
exec_type ———— 这是十分重要的一个属性,也是一个 对象,也只有 3 个具体的值:Unknown 未知、
Cancel 撤单 和 Trade 成交;用脚也想到了,只有 Trade 的报价才是真正换手成功的价格,而 撤单 和 未知,就说明价格是虚报的,或无效的。
data_time ———— 这个价格产生的时间当然也是重要的考量之一,这是交易所的时间,精准到毫秒(甚至纳秒),用来核对逐笔行情的报价时,是重要的依据。
# 4.2 逐笔委托 on_entrust():
上一节讨论是的大堂哥,这一节该介绍二堂哥了。这个方法(函数)就是专门用来接收 逐笔委托 的。对应的就是市场中的 已委托的 订单数据,相信你还记得,在 报单 之后就会生成一个 委托单,只要没有 成交 之前,委托单是可以撤消的。记住这一点。
语法:
def on_entrust(context, entrust, location):
参数:context, entrust, location
返回值: 无
是不是就是二堂哥,也就是把 transaction 换成了 entrust 嘛。
形式参数列表中:
context 是必须有的;
location 是 功夫量化 2.4 版之后新增的一个参数。
关键点就是这个 entrust 了,当然也是一个对象。其中包含的内容如下所示:
def on_entrust(context, entrust,location ):
context.log.info("收到的 逐笔委托 数据如下:{}".format(entrust))
"""
运行结果:
收到的 逐笔委托 数据如下:{"biz_index":0,"data_time":1667355317080000000,"exchange_id":"SSE","instrument_id":"110077","instrument_type":3,"main_seq":801,"price":140.06,"price_type":0,"seq":3967742,"side":0,"trading_day":"20221103","volume":6}
"""
2
3
4
5
6
7
8
按照惯例还是捋捋吧:
def on_entrust(context, entrust,location ): # 接收已订阅品种的报价数据
context.log.info("收到的 逐笔委托 数据如下:")
context.log.info("\n 业务序号 biz_index:{} \
\n ⽣成时间 data_time: {} \
\n 交易所 exchange_id : {} \
\n 交易品种 instrument_id: {} \
\n 交易品种类型 instrument_type: {} \
\n 主序号 main_seq:{} \
\n 价格 price: {} \
\n ⼦序号 seq: {} \
\n 委托⽅向(买卖) side: {} \
\n 交易日 trading_day:{} \
\n 成交量 volume:{} \
".format(
entrust.biz_index,
context.strftime(entrust.data_time), # 转换文本格式。
entrust.exchange_id,
entrust.instrument_id,
entrust.instrument_type,
entrust.main_seq,
entrust.price,
entrust.seq,
entrust.side,
entrust.trading_day,
entrust.volume
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
同样的,不要只盯着 时间、价格、委托量、这些基本的考量。时间 和 委托方向,也是十分重要的。灵活运用在你自己的交易策略中吧。
# 4.3、 小结
又这么快就小结了?对啊,关于 逐笔交易 也就这两个方法(函数)了。on_transaction 用来接收逐笔行情的 交易单和撤单,on_entrust()用来接收 逐笔委托 的数据,你还要啥?这已经是高频交易的门坎了。
再次提醒,Level 2 逐笔行情 ,程序能用上的那种,目前(2022年)必然是在券商的机房里才有,不会放在公网(互联网)上。在公网上的,多少带点 逗你玩 的意思,注意提防。
接收到的 逐笔行情 和 逐笔委托,重点关注的属性(变量)、无非也就是 时间、品种、价格、委托/成交的买卖方向、成交/撤单。诸如这些。(券商不同、以及 沪市深市不同,这些属性会有些许差别,请留意券商文档中的说明部分。)让它们成为你的交易策略中的条件因子吧。运用熟练了,你的交易必然就会快人一步。
# transaction 逐笔行情 对象(Object)
| 序号 | 属性名称 | 表达意义 | 备注 |
|---|---|---|---|
| 01 | ask_no | 卖⽅订单号 | |
| 02 | bid_no | 买⽅订单号 | |
| 03 | biz_index | 业务序号 | |
| 04 | bs_flag | 内外盘标识 | 市场、券商不同而不同 |
| 05 | data_time | ⽣成时间 | 交易所时间 |
| 06 | exchange_id | 交易所 | |
| 07 | exec_type | 成交标识 | 对象(Object) |
| 08 | instrument_id | 交易品种 | |
| 09 | instrument_type | 交易品种类型 | |
| 10 | main_seq | 主序号 | |
| 11 | price | 成交价格 | |
| 12 | seq | ⼦序号 | |
| 13 | trading_day | 交易日 | |
| 14 | volume | 成交量 |
# BsFlag 买卖标识 对象(Object)
虽然是一个 对象,但其实只有 3 个属性(变量)。
| 整数值 | 属性 | 说明 |
|---|---|---|
| 0 | BsFlag.Unknown | 未知 |
| 1 | BsFlag.Buy | 买 |
| 2 | BsFlag.Sell | 卖 |
# ExecType 交易执行类型 对象(Object)
也只有 3 个属性(变量)
| 整数值 | 属性 | 说明 |
|---|---|---|
| 0 | ExecType.Unknown | 未知 |
| 1 | ExecType.Cancel | 撤消单 |
| 2 | ExecType.Trade | 成交单 |
不言而喻,BsFlag 和 ExecType 这 2 个对象是相当重要的参数吧。
# entrust 逐笔委托 对象(Object)
| 序号 | 属性名称 | 表达意义 | 备注 |
|---|---|---|---|
| 01 | biz_index | 业务序号 | |
| 02 | data_time | ⽣成时间 | |
| 03 | exchange_id | 交易所 | |
| 04 | instrument_id | 交易品种 | |
| 05 | instrument_type | 交易品种类型 | |
| 06 | main_seq | 主序号 | |
| 07 | price | 价格 | |
| 08 | seq | ⼦序号 | |
| 09 | side | 委托⽅向(买卖) | |
| 10 | trading_day | 交易日 | |
| 11 | volume | 成交量 |
# 五、帐户信息
俗话说“会买的是徒弟,会卖的才是师傅”。低买高卖谁都懂啊,但实际操作中呢?都追到最高点,割肉在最低点......命苦啊!
所以,时刻关注自己的帐户的状况,乃是重中之重。本章就要详细讨论有关获取帐户数据的方法(函数)和对象。

任何一个股票交易帐户中,都有一个 查询 功能,可以在其中看到在关帐户的一些信息,比如,帐户上有多少钱啊、持有多少股票啊、下了几个委托单啊、成交了几单啊......诸如此类。这些信息就是 订单薄 BOOK,随时随地关注自己的帐户状态,才是步入高手的境界。知已知彼才能百战不怠嘛 。
# 5.1、策略程序的订单薄 context.book 对象
都学习到这里的朋友,应该用脚都想到了:为了描述刚刚提到的 订单薄 BOOK,必须要用一个 对象(Object)啊。很明显,已经准备好了,就是 context.book ,BOOK 本身就是一个 对象(Object),同时又作为一个 属性(变量),绑定在 context 身上。这样的好处很明显,就是随处都可以访问了,方便我们的使用。
TIP
正因为是绑定在 context 上,因此它只包含了当前 策略程序 的订单信息。而不是帐户中全部的订单信息。请注意同时运行多个策略时的区别。
首先当然我们要先看看其中包括什么了,请参考下例:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source = "sim"
account_str = "123456"
def pre_start(context):
context.add_account(source, account_str) # 添加自己的帐号
def post_start(context ):
context.log.info('context.book 中包括的内容如下:')
context.log.info("\n 描述普通帐户信息的 对象 asset:{} \
\n 描述两融帐户信息的 对象 asset_margin :{} \
\n 描述手续费的 对象 commissions:{} \
\n 获取多头仓位的方法 get_long_position:{} \
\n 获取仓位的方法 get_position:{} \
\n 判断是否有持仓的方法 get_position_for:{} \
\n 获取空头仓位的方法 get_short_position:{} \
\n 判断是否有多头仓位的方法 has_long_position:{} \
\n 判断是否有仓位的方法 has_position:{} \
\n 判断是否有持仓的方法 has_position_for:{} \
\n 判断是有空头仓位的方法 has_short_position:{} \
\n 描述持仓品种的对象 instruments:{} \
\n 对应多头仓位的对象 long_positions:{} \
\n 委托单输入对象 order_inputs:{} \
\n 委托单对象 orders:{} \
\n 对应空头仓位的对象 short_positions:{} \
\n 成交单对象trades:{} \
\n 更新的方法 update:{} \
".format(context.book.asset,
context.book.asset_margin,
context.book.commissions,
context.book.get_long_position,
context.book.get_position,
context.book.get_position_for,
context.book.get_short_position,
context.book.has_long_position,
context.book.has_position,
context.book.has_position_for,
context.book.has_short_position,
context.book.instruments,
context.book.long_positions,
context.book.order_inputs,
context.book.orders,
context.book.short_positions,
context.book.trades,
context.book.update,
)
)
""" 运行结果如下:
context.book 中包括的内容如下:
普通帐户信息 asset:{"accumulated_fee":0.0,"avail":0.0,"close_pnl":0.0,"dynamic_equity":0.0,"frozen_cash":0.0,"frozen_fee":0.0,"frozen_margin":0.0,"holder_uid":1666259557,"initial_equity":0.0,"intraday_fee":0.0,"ledger_category":1,"margin":0.0,"market_value":0.0,"position_pnl":0.0,"realized_pnl":0.0,"static_equity":0.0,"trading_day":"20221114","unrealized_pnl":0.0,"update_time":1668421192424703500}
两融帐户信息 asset_margin :{"avail_margin":0.0,"cash_debt":0.0,"cash_margin":0.0,"collateral_ratio":0.0,"commission_ratio":0.0,"credit":0.0,"holder_uid":1666259557,"ledger_category":1,"margin":0.0,"margin_interest":0.0,"margin_market_value":0.0,"settlement":0.0,"short_cash":0.0,"short_margin":0.0,"short_market_value":0.0,"total_asset":0.0,"trading_day":"20221114","update_time":0}
描述手续费的对象 commissions:[pykungfu.wingchun.CommissionMap object at 0x0000028F0FBECC70]
获取多头仓位的方法 get_long_position:[bound method PyCapsule.get_long_position of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
获取仓位的方法 get_position:[bound method PyCapsule.get_position of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
判断是否有持仓的方法 get_position_for:[bound method PyCapsule.get_position_for of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
获取空头仓位的方法 get_short_position:[bound method PyCapsule.get_short_position of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
判断是否有多头仓位的方法 has_long_position:[bound method PyCapsule.has_long_position of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
判断是否有仓位的方法 has_position:[bound method PyCapsule.has_position of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
判断是否有持仓的方法 has_position_for:[bound method PyCapsule.has_position_for of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
判断是有空头仓位的方法 has_short_position:[bound method PyCapsule.has_short_position of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
描述持仓品种的对象 instruments:[pykungfu.wingchun.InstrumentMap object at 0x0000028F0AAD9F70]
对应多头仓位的对象 long_positions:[pykungfu.wingchun.PositionMap object at 0x0000028F0FBECFB0]
委托单对象order_inputs:[pykungfu.wingchun.OrderInputMap object at 0x0000028F0FBF1C70]
更新的方法 update:[bound method PyCapsule.update of [pykungfu.wingchun.Book object at 0x0000028F0FBECBF0]]
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
可见这个 context.book 不简单吧,就算如上例那样输出到日志,也把人看得晕晕乎乎。为了看得更清晰一点,还是捋成表格吧,对象在上,方法(函数)在下:
| 序号 | 名称 | 类型 | 功能 | 备注 |
|---|---|---|---|---|
| 01 | asset | 对象(Object) | 描述普通帐户的信息 | |
| 02 | asset_margin | 对象(Object) | 描述两融帐户的信息 | |
| 03 | commissions | 对象(Object) | 描述手续费 | |
| 04 | instruments | 对象(Object) | 描述品种代码 | |
| 05 | long_positions | 对象(Object) | 对应多头仓位 | |
| 06 | short_positions | 对象(Object) | 对应空头仓位 | |
| 07 | order_inputs | 对象(Object) | 描述委托单输入 | |
| 08 | orders | 对象(Object) | 描述委托单 | |
| 09 | trades | 对象(Object) | 描述成交单 | |
| 方法(函数) | ||||
| 10 | has_position() | 方法(函数) | 判断是否有仓位的方法 | |
| 11 | has_position_for() | 方法(函数) | 判断是否有持仓的方法 | |
| 12 | has_long_position() | 方法(函数) | 判断是否有多头仓位的方法 | |
| 13 | has_short_position() | 方法(函数) | 判断是有空头仓位的方法 | |
| 14 | get_position() | 方法(函数) | 获取仓位的方法 | |
| 15 | get_position_for() | 方法(函数) | 判断是否有持仓的方法 | |
| 16 | get_long_position() | 方法(函数) | 获取多头仓位的方法 | |
| 17 | get_short_position() | 方法(函数) | 获取空头仓位的方法 | |
| 18 | update() | 方法(函数) | 更新的方法 |
哎哎,先别跑嘛。本文档只涉及普通帐户,并且只讨论与股票相关的内容。因此,什么两融啊、空头啊,都删了删了。这下轻松多了吧?所以本文档仅讨论 context.book 中的以下这些内容:
- asset 对象(Object)
- long_positions 对象(Object)
- order_inputs 对象(Object)
- orders 对象(Object)
- trades 对象(Object)
- has_position() 方法(函数)
- has_position_for() 方法(函数)
- has_long_position() 方法(函数)
- get_position() 方法(函数)
- get_position_for() 方法(函数)
- get_long_position() 方法(函数)
- update() 方法(函数)
# 5.2、 帐户的订单薄 context.get_account_book()
有朋友就提问了:你刚刚说的那个 BOOK ,特别强调是 当前 策略程序 的 订单薄。那我肯定要时刻关注我的帐户里的所有资金啊!
认真、仔细、严谨哈,值得表扬。因为本章第一节就提到了, context.book 中只包含了当前策略程序的订单信息,所以有些属性(变量)的值就是 0,如果想要获取帐户中全部的订单薄信息,就要用到这个方法(函数)了。特别是你想用一个程序控制多个帐户的时候,就十分有用了。(玩得真花,先玩好一个策略、一个帐户,可以有?这还没开始,就想飞,还想多重影分身,一起飞?)
请参考下例:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source = "sim"
account_A = "123456"
account_B = "654321"
def pre_start(context):
context.log.info('程序开始 ')
context.add_account(source, account_A )
context.add_account(source, account_B )
def post_start(context ):
A_Book = context.get_account_book(source, account_A)
B_Book = context.get_account_book(source, account_B)
context.log.info('A_Book 中包括的内容如下:')
context.log.info("\n 累计手续费 A_Book.asset.accumulated_fee: {} \
\n 可用资金 A_Book.asset.avail: {} \
\n 平仓盈亏(期货) A_Book.asset.close_pnl: {} \
\n 动态权益 A_Book.asset.dynamic_equity: {} \
\n 冻结资金 A_Book.asset.frozen_cash: {} \
\n 冻结手续费(期货) A_Book.asset.frozen_fee: {} \
\n 冻结保证金(期货) A_Book.asset.frozen_margin: {} \
\n 持有人ID A_Book.asset.holder_uid: {} \
\n 期初权益 A_Book.asset.initial_equity: {} \
\n 当日手续费 A_Book.asset.intraday_fee: {} \
\n 账户类别 A_Book.asset.ledger_category: {} \
\n 保证金(期货) A_Book.asset.margin: {} \
\n 市值(股票) A_Book.asset.market_value: {} \
\n 持仓盈亏(期货) A_Book.asset.position_pnl: {} \
\n 累计收益 A_Book.asset.realized_pnl: {} \
\n 静态权益 A_Book.asset.static_equity: {} \
\n 交易日 A_Book.asset.trading_day: {} \
\n 未实现盈亏 A_Book.asset.unrealized_pnl: {} \
\n 更新时间(功夫时间) A_Book.asset.update_time: {} \
".format(A_Book.asset.accumulated_fee,
A_Book.asset.avail,
A_Book.asset.close_pnl,
A_Book.asset.dynamic_equity,
A_Book.asset.frozen_cash,
A_Book.asset.frozen_fee,
A_Book.asset.frozen_margin,
A_Book.asset.holder_uid,
A_Book.asset.initial_equity,
A_Book.asset.intraday_fee,
A_Book.asset.ledger_category,
A_Book.asset.margin,
A_Book.asset.market_value,
A_Book.asset.position_pnl,
A_Book.asset.realized_pnl,
A_Book.asset.static_equity,
A_Book.asset.trading_day,
A_Book.asset.unrealized_pnl,
A_Book.asset.update_time
)
)
context.log.info('=============================================')
context.log.info('B_Book 中包括的内容如下:')
context.log.info("\n 累计手续费 B_Book.asset.accumulated_fee: {} \
\n 可用资金 B_Book.asset.avail: {} \
\n 平仓盈亏(期货) B_Book.asset.close_pnl: {} \
\n 动态权益 B_Book.asset.dynamic_equity: {} \
\n 冻结资金 B_Book.asset.frozen_cash: {} \
\n 冻结手续费(期货) B_Book.asset.frozen_fee: {} \
\n 冻结保证金(期货) B_Book.asset.frozen_margin: {} \
\n 持有人ID B_Book.asset.holder_uid: {} \
\n 期初权益 B_Book.asset.initial_equity: {} \
\n 当日手续费 B_Book.asset.intraday_fee: {} \
\n 账户类别 B_Book.asset.ledger_category: {} \
\n 保证金(期货) B_Book.asset.margin: {} \
\n 市值(股票) B_Book.asset.market_value: {} \
\n 持仓盈亏(期货) B_Book.asset.position_pnl: {} \
\n 累计收益 B_Book.asset.realized_pnl: {} \
\n 静态权益 B_Book.asset.static_equity: {} \
\n 交易日 B_Book.asset.trading_day: {} \
\n 未实现盈亏 B_Book.asset.unrealized_pnl: {} \
\n 更新时间(功夫时间) B_Book.asset.update_time: {} \
".format(B_Book.asset.accumulated_fee,
B_Book.asset.avail,
B_Book.asset.close_pnl,
B_Book.asset.dynamic_equity,
B_Book.asset.frozen_cash,
B_Book.asset.frozen_fee,
B_Book.asset.frozen_margin,
B_Book.asset.holder_uid,
B_Book.asset.initial_equity,
B_Book.asset.intraday_fee,
B_Book.asset.ledger_category,
B_Book.asset.margin,
B_Book.asset.market_value,
B_Book.asset.position_pnl,
B_Book.asset.realized_pnl,
B_Book.asset.static_equity,
B_Book.asset.trading_day,
B_Book.asset.unrealized_pnl,
B_Book.asset.update_time
)
)
""" 运行结果
A_Book 中包括的内容如下:
累计手续费 A_Book.asset.accumulated_fee: 0.0
可用资金 A_Book.asset.avail: 953247.987
平仓盈亏(期货) A_Book.asset.close_pnl: 0.0
动态权益 A_Book.asset.dynamic_equity: 986335.167
冻结资金 A_Book.asset.frozen_cash: 0.0
冻结手续费(期货) A_Book.asset.frozen_fee: 0.0
冻结保证金(期货) A_Book.asset.frozen_margin: 0.0
持有人ID A_Book.asset.holder_uid: 679239496
期初权益 A_Book.asset.initial_equity: 0.0
当日手续费 A_Book.asset.intraday_fee: 0.0
账户类别 A_Book.asset.ledger_category: LedgerCategory.Account
保证金(期货) A_Book.asset.margin: 0.0
市值(股票) A_Book.asset.market_value: 33087.18
持仓盈亏(期货) A_Book.asset.position_pnl: 0.0
累计收益 A_Book.asset.realized_pnl: 0.0
静态权益 A_Book.asset.static_equity: 0.0
交易日 A_Book.asset.trading_day: 20300424
未实现盈亏 A_Book.asset.unrealized_pnl: 1934.8300000000013
更新时间(功夫时间) A_Book.asset.update_time: 1668603493982232838
=============================================
B_Book 中包括的内容如下:
累计手续费 B_Book.asset.accumulated_fee: 0.0
可用资金 B_Book.asset.avail: 953247.987
平仓盈亏(期货) B_Book.asset.close_pnl: 0.0
动态权益 B_Book.asset.dynamic_equity: 986335.167
冻结资金 B_Book.asset.frozen_cash: 0.0
冻结手续费(期货) B_Book.asset.frozen_fee: 0.0
冻结保证金(期货) B_Book.asset.frozen_margin: 0.0
持有人ID B_Book.asset.holder_uid: 679239496
期初权益 B_Book.asset.initial_equity: 0.0
当日手续费 B_Book.asset.intraday_fee: 0.0
账户类别 B_Book.asset.ledger_category: LedgerCategory.Account
保证金(期货) B_Book.asset.margin: 0.0
市值(股票) B_Book.asset.market_value: 33087.18
持仓盈亏(期货) B_Book.asset.position_pnl: 0.0
累计收益 B_Book.asset.realized_pnl: 0.0
静态权益 B_Book.asset.static_equity: 0.0
交易日 B_Book.asset.trading_day: 20300424
未实现盈亏 B_Book.asset.unrealized_pnl: 1934.8300000000013
更新时间(功夫时间) B_Book.asset.update_time: 1668603493982232838
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
TIP
新手初学者朋友,尽量先运用 一个策略仅管理自己本策略的订单信息为好,即用好 context.book 对象。之后熟练了,再考虑管理帐户的全部订单、甚至多个帐户的订单。先玩好简单的,再玩复杂的。不要一开始就想飞!
# 5.3、 资产信息 asset 对象(Object)
先来个简单的吧, asset 这个对象(Object) 中包含多个属性(变量),都是有关帐户的一些信息。请看下面这个例子吧:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source = "sim"
account_str = "123456"
def pre_start(context):
context.log.info('程序开始 ')
context.add_account(source, account_str )
def post_start(context ):
context.log.info('context.book.asset 中包括的内容如下:')
context.log.info("\n 累计手续费 context.book.asset.accumulated_fee: {} \
\n 可用资金 context.book.asset.avail: {} \
\n 平仓盈亏(期货) context.book.asset.close_pnl: {} \
\n 动态权益 context.book.asset.dynamic_equity: {} \
\n 冻结资金 context.book.asset.frozen_cash: {} \
\n 冻结手续费(期货) context.book.asset.frozen_fee: {} \
\n 冻结保证金(期货) context.book.asset.frozen_margin: {} \
\n 持有人ID context.book.asset.holder_uid: {} \
\n 期初权益 context.book.asset.initial_equity: {} \
\n 当日手续费 context.book.asset.intraday_fee: {} \
\n 账户类别 context.book.asset.ledger_category: {} \
\n 保证金(期货) context.book.asset.margin: {} \
\n 市值(股票) context.book.asset.market_value: {} \
\n 持仓盈亏(期货) context.book.asset.position_pnl: {} \
\n 累计收益 context.book.asset.realized_pnl: {} \
\n 静态权益 context.book.asset.static_equity: {} \
\n 交易日 context.book.asset.trading_day: {} \
\n 未实现盈亏 context.book.asset.unrealized_pnl: {} \
\n 更新时间(功夫时间) context.book.asset.update_time: {} \
".format(context.book.asset.accumulated_fee,
context.book.asset.avail,
context.book.asset.close_pnl,
context.book.asset.dynamic_equity,
context.book.asset.frozen_cash,
context.book.asset.frozen_fee,
context.book.asset.frozen_margin,
context.book.asset.holder_uid,
context.book.asset.initial_equity,
context.book.asset.intraday_fee,
context.book.asset.ledger_category,
context.book.asset.margin,
context.book.asset.market_value,
context.book.asset.position_pnl,
context.book.asset.realized_pnl,
context.book.asset.static_equity,
context.book.asset.trading_day,
context.book.asset.unrealized_pnl,
context.book.asset.update_time
)
)
"""运行结果
context.book.asset 中包括的内容如下:
累计手续费 context.book.asset.accumulated_fee: 0.0
可用资金 context.book.asset.avail: 0.0
平仓盈亏(期货) context.book.asset.close_pnl: 0.0
动态权益 context.book.asset.dynamic_equity: 0.0
冻结资金 context.book.asset.frozen_cash: 0.0
冻结手续费(期货) context.book.asset.frozen_fee: 0.0
冻结保证金(期货) context.book.asset.frozen_margin: 0.0
持有人ID context.book.asset.holder_uid: 1666259557
期初权益 context.book.asset.initial_equity: 0.0
当日手续费 context.book.asset.intraday_fee: 0.0
账户类别 context.book.asset.ledger_category: LedgerCategory.Strategy
保证金(期货) context.book.asset.margin: 0.0
市值(股票) context.book.asset.market_value: 0.0
持仓盈亏(期货) context.book.asset.position_pnl: 0.0
累计收益 context.book.asset.realized_pnl: 0.0
静态权益 context.book.asset.static_equity: 0.0
交易日 context.book.asset.trading_day: 20221116
未实现盈亏 context.book.asset.unrealized_pnl: 0.0
更新时间(功夫时间) context.book.asset.update_time: 1668598940651068700
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
如果你用本地 'sim' 帐户来运行上例,结果也会如示例中一样,很多属性(变量)的值都是 0,显然这与真实情况有所差距。因此强烈建议:至少用某家券商的模拟帐户来理解有关帐户信息的描述,这样更接近真实帐户的情况,也更有帮助。并且,模拟帐户全都是免费申请的,无需付出额外的成本。
由上例可见, asset 这个对象(Object) 中所包含的都是帐户相关的一些信息。其中还有一些是用于期货帐户的,实际运用中请根据自己的需求取用吧。
# 5.4、 手续费 commissions 对象(Object)
手续费 是股民朋友们除了行情之外,最关心的话题之二了。这要是都没算明白,那就是一笔糊涂帐。所以必须有个 对象(Object) 是用来描述手续费啊,就是这个 commissions 。很明显它带有很多的 属性(变量),用来对应有关手续费的数据信息。它也是一个 可迭代 的对象,Python程序猿秒懂了。因此用 for 循环来遍历其中的每一项内容,就知道其中都有什么信息了。请参考下例:(前提是你的帐户里交易过单子啊。一单都没有成交的全新帐户,程序也读取不到手续费啊。)
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source_TD = "tora"
source_MD = "ToraL2"
account_str = "00368920" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
# 第一件事,接入 柜台、帐号
context.add_account(source_TD, account_str )
def post_start(context ):
comm = context.book.commissions # 访问 手续费 对象(Object)
for key in comm: # 遍历 手续费 对象(Object) 中的每一项
ccc = comm[key]
context.log.info("手续费的信息数据如下:")
context.log.info("\n 平仓费率 close_ratio: {} \
\n 平今费率 close_today_ratio: {} \
\n 交易所 exchange_id: {} \
\n 品种类型 instrument_type: {} \
\n 最小手续费 min_commission: {} \
\n 手续费模式 mode: {} (按交易额/交易量计算) \
\n 开仓费率 open_ratio: {} \
\n 品种ID product_id: {} \
".format(ccc.close_ratio,
ccc.close_today_ratio,
ccc.exchange_id,
ccc.instrument_type,
ccc.min_commission,
ccc.mode,
ccc.open_ratio,
ccc.product_id,
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
其中股民朋友们最关心的就是 “最小手续费”吧,谈到什么条件了呢? “万一免5” 了吗?(🐕)
# 5.5、 交易品种 instruments 对象(Object)
每天都盯盘,看得就是交易品种,这个太熟悉不过了。买入持有了,帐户中就有对应的订单,当然也有一个与之对应的 对象(Object)来描述这些信息。同上一节类似,也是一个可迭代 的对象,包括若干属性(变量),保存着相关的信息和数据。请参考以下示例:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source_TD = "tora"
source_MD = "ToraL2"
account_str = "00368920" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
# 第一件事,接入 柜台、帐号
context.add_account(source_TD, account_str )
def post_start(context ):
ins = context.book.instruments # 访问 交易品种 对象(Object)
for key in ins: # 遍历 手续费 对象(Object) 中的每一项
ment = ins[key]
context.log.info("帐户中 交易品种 的信息数据如下:")
context.log.info("\n 合约乘数 contract_multiplier: {} \
\n conversion_rate: {} \
\n 创建日期 create_date:{} \
\n 交割月 delivery_month:{} \
\n 交割年 delivery_year:{} \
\n 交易所 exchange_id: {} \
\n 过期时间 expire_date:{} \
\n force_update_ratio:{} \
\n 品种代码 instrument_id:{} \
\n 品种类型 instrument_type: {} \
\n 是否可交易 is_trading:{} \
\n 多单保证金比例 long_margin_ratio:{} \
\n 开盘日期 open_date:{} \
\n 报价步长 price_tick:{} \
\n 产品ID product_id:{} \
\n 空单保证金比例 short_margin_ratio:{} \
".format(ment.contract_multiplier,
ment.conversion_rate,
ment.create_date,
ment.delivery_month,
ment.delivery_year,
ment.exchange_id,
ment.expire_date,
ment.force_update_ratio,
ment.instrument_id,
ment.instrument_type,
ment.is_trading,
ment.long_margin_ratio,
ment.open_date,
ment.price_tick,
ment.product_id,
ment.short_margin_ratio
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
由示例可看出,交易品种的信息有很多是仅适用于期货产品的。A股股票品种相对简单多了,因此了解就好。
# 5.6、持有(多)单列表 long_positions 对象(Object)
所谓多单,就是某一个品种,买入之后,预期后市上涨,才能获利的订单。A股上的品种都是多单品种,只能买涨。如果买入后下跌了,就亏钱了。
反之,预期后市下跌,才能获利的,称为 空单。如期货产品。
本文档只讨论A股品种,因此这个 多单 对象(Object),就十分重要了。它其中就包含了已买入的持有中的订单数据。如下图,股民朋友们一眼就认出来了吧。

那么策略程序中,如何获取到这个列表呢。简单,整个帐户的持有单列表,都放在一个对象(Object)中嘛。
positions = context.get_account_book("tora", "00368920").long_positions
不会这么快就忘了吧?这个方法(函数)表达的意思就是:柜台 tora ,帐号 00368920 中持有的所有多单(long_positions)都取来,放到对象 positions 中。
然后,我们就可以访问这个 positions 了。让我们来个上帝视角的鸟瞰图,好好看看这个 positions
positions
{
984964031 :{
"avg_open_price":128.3660,
"close_pnl":0.0,
"close_price":0.0,
"direction":0,
"exchange_id":"SSE",
"frozen_total":10,
"frozen_yesterday":10,
"holder_uid":679239496,
"instrument_id":"113588",
"instrument_type":3,
"last_price":106.87,
"ledger_category":0,
"margin":0.0,
"position_cost_price":128.3660,
"position_pnl":0.0,
"pre_close_price":0.0,
"pre_settlement_price":0.0,
"realized_pnl":0.0,
"settlement_price":0.0,
"trading_day":"20300513",
"unrealized_pnl":7.560000000000002,
"update_time":1669022154223382592,
"volume":10,
"yesterday_volume":10
}
805988805 :{"avg_open_price":7.9310,
"close_pnl":0.0,
"close_price":0.0,
"direction":0,
"exchange_id":"SSE",
"frozen_total":0,
"frozen_yesterday":0,
"holder_uid":679239496,
"instrument_id":"000039",
"instrument_type":2,
"last_price":7.19,
"ledger_category":0,
"margin":0.0,
"position_cost_price":7.9310,
"position_pnl":0.0,
"pre_close_price":0.0,
"pre_settlement_price":0.0,
"realized_pnl":0.0,
"settlement_price":0.0,
"trading_day":"20300513",
"unrealized_pnl":-6.760000000000019,
"update_time":1669022399305629825,
"volume":1400,
"yesterday_volume":1400
}
3712768376 :{"avg_open_price":131.8495,
"close_pnl":0.0,
"close_price":0.0,
"direction":0,
"exchange_id":"SZE",
"frozen_total":0,
"frozen_yesterday":0,
"holder_uid":679239496,
"instrument_id":"128063",
"instrument_type":3,
"last_price":111.25,
"ledger_category":0,
"margin":0.0,
"position_cost_price":131.8495,
"position_pnl":0.0,
"pre_close_price":0.0,
"pre_settlement_price":0.0,
"realized_pnl":0.0,
"settlement_price":0.0,
"trading_day":"20300513",
"unrealized_pnl":48.760000000000019,
"update_time":1669022399305629825,
"volume":20,
"yesterday_volume":20
}
......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
这是不是就一目了然了,这么明显。这不就是个 json 数据对象吗, 左边这一列,不就是 key 嘛 ,对应右边的,不就是 value 嘛。 key:value 键:值,一对一对的,这下不简单多了。有多少个 Key ,就表示持有多少个 多单 呗,每一个 多单 具体是什么品种、持有量、均价,都在 value 里面呗。
老规矩,上......示例:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source_TD = "tora"
source_MD = "ToraL2"
account_str = "00368920" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
# 第一件事,接入 柜台、帐号
context.add_account(source_TD, account_str )
def post_start(context ):
position = context.get_account_book(source_TD, account_str).long_positions # 访问账户持有的全部多单
# context.book.long_positions # 只访问本策略程序持有的多单
for key in position: # 遍历 多单 对象(Object) 中的每一项
pos = position[key]
context.log.info("帐户中 多单 的信息数据如下:")
context.log.info("\n 开仓均价 avg_open_price: {} \
\n 已平仓盈亏 close_pnl: {} \
\n 平仓价格 close_price:{} \
\n 方向 direction:{} \
\n 交易所 exchange_id: {} \
\n 冻结总量 frozen_total:{} \
\n 昨日冻结量 frozen_yesterday:{} \
\n 持有者ID holder_uid,:{} \
\n 品种代码 instrument_id:{} \
\n 品种类型 instrument_type: {} \
\n 最新价格 last_price:{} \
\n 账户类别 ledger_category:{} \
\n 保证金 margin:{} \
\n 持仓成本 position_cost_price:{} \
\n 持仓盈亏 position_pnl:{} \
\n 昨收盘价 pre_close_price:{} \
\n 昨结算价 pre_settlement_price:{} \
\n 累计收益 realized_pnl:{} \
\n 结算价 settlement_price:{} \
\n 交易日 trading_day:{} \
\n 未实现盈亏 unrealized_pnl:{} \
\n 更新时间 updatgene_time:{} \
\n 持有量 volume:{} \
\n 昨仓量 yesterday_volume:{} \
".format(pos.avg_open_price,
pos.close_pnl,
pos.close_price,
pos.direction,
pos.exchange_id,
pos.frozen_total,
pos.frozen_yesterday,
pos.holder_uid,
pos.instrument_id,
pos.instrument_type,
pos.last_price,
pos.ledger_category,
pos.margin,
pos.position_cost_price,
pos.position_pnl,
pos.pre_close_price,
pos.pre_settlement_price,
pos.realized_pnl,
pos.settlement_price,
pos.trading_day,
pos.unrealized_pnl,
pos.update_time,
pos.volume,
pos.yesterday_volume
)
)
"""
运行结果:
帐户中 多单 的信息数据如下:
开仓均价 avg_open_price: 106.114
已平仓盈亏 close_pnl: 0.0
平仓价格 close_price:0.0
方向 direction:Direction.Long
交易所 exchange_id: SSE
冻结总量 frozen_total:0
昨日冻结量 frozen_yesterday:0
持有者ID holder_uid,:679239496
品种代码 instrument_id:110059
品种类型 instrument_type: InstrumentType.Bond
最新价格 last_price:106.5
账户类别 ledger_category:LedgerCategory.Account
保证金 margin:0.0
持仓成本 position_cost_price:106.114
持仓盈亏 position_pnl:0.0
昨收盘价 pre_close_price:0.0
昨结算价 pre_settlement_price:0.0
累计收益 realized_pnl:0.0
结算价 settlement_price:0.0
交易日 trading_day:20300427
未实现盈亏 unrealized_pnl:3.859999999999957
更新时间 updatgene_time:1668678901921688763
持有量 volume:10
昨仓量 yesterday_volume:10
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
通过上例应该可以看到,long_positions 这个 对象(Object)中包含的信息,还是非常有用的!比如 开仓均价、持有量、持仓成本、最新价、昨收盘价......这都是事关盈亏的重要数据啊,都是钱、都是钱啊————
至于其它那些期货才用到的属性(变量),暂时先放一放吧。
# 5.7、委托单列表 order_inputs/orders 对象(Object)
如果理解了上一节的 持有单 列表对应的 对象(Object),那么对应 委托单 列表的对象(Object) 当然就是 orders 了。那为什么有 2 个? 可以简单的理解为一个是用于 股票 的,一个是用于 期货的。大部分的属性(变量)都是一样的,可以参考前面我们讨论过的 order 报单对象(Object),只有约 1 -- 2 个属性略有不同。
还是看得更清晰,撸成表格对比一下,就一目了然了吧:
| 序号 | order_inputs属性 | orders属性 | 数据类型 | 表达意义 | 备注 |
|---|---|---|---|---|---|
| 01 | order_id | order_id | int | 委托单ID | 一长串数字 |
| 02 | insert_time | insert_time | 长整数 | 报单时间 | 纳秒时间戳 |
| 03 | update_time | 长整数 | 更新时间 | ||
| 04 | trading_day | 字符串 | 交易日 | 如:20201026 | |
| 05 | instrument_id | instrument_id | 字符串 | 报单品种 | |
| 06 | instrument_type | instrument_type | 对象 InstrumentType | 交易品种类型 | |
| 07 | exchange_id | exchange_id | 对象 Exchange | 交易所 | 如:沪市 Exchange.SSE |
| 08 | limit_price | limit_price | 浮点数 | 报单价格 | |
| 09 | frozen_price | frozen_price | 浮点数 | 冻结价格 | |
| 10 | volume | volume | 整数 | 报单数量 | |
| 11 | volume_left | 整数 | 剩余数量 | ||
| 12 | volume_condition | 对象VolumeCondition | 成交量类型 | ||
| 13 | tax | 浮点数 | 税费 | ||
| 14 | commission | 浮点数 | 手续费 | ||
| 15 | status | 对象 OrderStatus | 订单状态 | ||
| 16 | error_id | 整数 | 错误ID | ||
| 17 | error_msg | 字符串 | 错误消息 | ||
| 18 | side | side | 对象 Side | 买卖方向 | |
| 19 | offset | offset | 对象 Offset | 开平方向 | |
| 20 | hedge_flag | hedge_flag | 对象 HedgeFlag | 投机套保标识 | |
| 21 | price_type | price_type | 对象 PriceType | 价格类型 | |
| 22 | time_condition | time_condition | 对象 TimeCondition | 成交时间类型 | |
| 23 | block_id | 整数 | 大宗交易信息id | 非大宗交易则为0 | |
| 24 | is_swap | 布尔型 | 是否互换单 |
通过这个表格即可看出,orders 其实就是 order 的分身嘛,简直就是一模一样,而 order_inputs 就只有几个属性(变量)是特有的,主要适用于期货品种。A股品种基本用不上,就不深入讨论了。
# 5.8、成交单列表 traders 对象(Object)
那么现在用脚想都知道了, traders 对象就是 trade 的分身嘛。不罗嗦,上示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
from pykungfu import wingchun as wc # 需要先导入模块
def pre_start(context):
context.add_account("tora", "00368920" )
context.subscribe_all("ToraL2")
def post_start(context):
context.log.info("开始吧 ")
A_Book = context.get_account_book("tora", "00368920")
A_trades = A_Book.trades
context.log.info("A_trades {}".format(A_trades))
for key in A_trades: # 遍历 多单 对象(Object) 中的每一项
pos = A_trades[key]
context.log.info("pos {}".format(pos))
context.log.info("帐户中 成交单 的信息数据如下:")
context.log.info("\n 品种代码 instrument_id:{}".format(pos.instrument_id)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 5.9、是否持有指定品种的订单 has_position()
如果想查询一下,是否持有某个品种的单子,就可以用到这个方法(函数)了。字面意思就是:“持有这个品种的订单吗?”
语法: has_position(exchange_id, instrument_id)
参数:exchange_id, instrument_id
exchange_id ———— 表达市场、沪市是 "SSE",深市是: "SZE";
instrument_id ———— 就是要查询的具体品种了,如 " 600600 ";
返回值: 布尔值 True / False ,有持仓订单即为 真,否则为 假
请注意这样一个场景:你的帐户中是持有订单 118004 的订单的,但你当前运行的程序是 “test02”,并没有任何持仓单。那么在调用这个方法(函数)时,就要注意它的 父对象(Object) 是 context.book ,还是 context.get_account_book() 返回的 对象(Object)。请参考以下示例加强自己的理解吧:
示例:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source_TD = "tora"
source_MD = "ToraL2"
account_str = "00368920" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
# 第一件事,接入 柜台、帐号
context.add_account(source_TD, account_str )
"""
假设帐户 00368920 中是持有 118004 的订单
但当前运行的这个程序,名称为 test02 ,没有任何持仓
"""
def post_start(context ):
context.log.info("当前策略程序 持有 118004 的 多单吗?:{}"
.format(context.book.has_position("SSE", "118004"))
)
A_Book = context.get_account_book(source_TD, account_str) # 访问账户持有的全部多单 对象(Object)
context.log.info("账户 {} 中持有 118004 的 多单吗?:{}"
.format(account_str,A_Book.has_position("SSE", "118004"))
)
""" 运行结果
当前策略程序 持有 118004 的 订单吗?:False
账户 00368920 中持有 118004 的 订单吗?:True
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 5.10、 是否持有指定品种的多单 has_long_position()
如果已经理解了上一节中介绍的方法(函数),那么现在这个,就不用过多介绍了。就是字面意思:“持有这个品种的多单吗?”。因为本文档只针对做A股品种的,因此肯定都是多单罗。
语法: has_long_position(exchange_id, instrument_id)
参数:exchange_id, instrument_id
exchange_id ———— 表达市场、沪市是 "SSE",深市是: "SZE";
instrument_id ———— 就是要查询的具体品种了,如 " 600600 ";
返回值: 布尔值 True / False ,有多单持仓即为 真,否则为 假
同样也需要注意它的 父对象(Object) 是 context.book ,还是 context.get_account_book() 返回的 对象(Object),分别对应的是当前策略程序的 多单 和 帐户中的 多单。请参考以下示例加强自己的理解吧:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source_TD = "tora"
source_MD = "ToraL2"
account_str = "00368920" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
# 第一件事,接入 柜台、帐号
context.add_account(source_TD, account_str )
"""
假设帐户 00368920 中是持有 118004 的订单
但当前运行的这个程序,名称为 test02 ,没有任何持仓
"""
def post_start(context ):
context.log.info("当前策略程序 持有 118004 的 多单吗?:{}"
.format(context.book.has_long_position("SSE", "118004"))
)
A_Book = context.get_account_book(source_TD, account_str) # 访问账户持有的全部多单 对象(Object)
context.log.info("账户 {} 中持有 118004 的 多单吗?:{}"
.format(account_str,A_Book.has_long_position("SSE", "118004"))
)
""" 运行结果
当前策略程序 持有 118004 的 多单吗?:False
账户 00368920 中持有 118004 的 多单吗?:True
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 5.11、 查询多个品种的持单 has_position_for()
前面介绍的方法(函数),只能用来查询一个指定的品种,看看有没有持单。这太费劲儿了。实际运用中,当然随时都要了解订阅的全部品种,是不是持单了。不仅是一篮子股票,我要全市场的!!!
因此就要用到下面这个方法(函数)了: has_position_for()
语法:
def on_quote(context, quote, location):
context.book.has_position_for(quote)
def on_order(context, order, location):
context.book.has_position_for(order)
def on_trade(context, trade, location):
context.book.has_position_for(trade)
2
3
4
5
6
7
8
9
10
参数:由语法可看出,放在不同的基础方法(函数)中,传入对应的参数 对象(Object)即可;
返回值: 布尔值 True / False ,有多单持仓即为 真,否则为 假
仍然也需要注意它的 父对象(Object) 是 context.book ,还是 context.get_account_book() 返回的 对象(Object),分别对应的是当前策略程序的 多单 和 帐户中的 多单。请参考以下示例:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source_TD = "tora"
source_MD = "ToraL2"
account_str = "00368920" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
# 第一件事,接入 柜台、帐号
context.add_account(source_TD, account_str )
# 订阅了 上海沪市 的三个品种
context.subscribe(source_MD,["600600","118004","113021"],"SSE")
def post_start(context ):
pass
def on_quote(context, quote, location):
context.log.info("收到报价品种 {} 当前策略程序持有这个品种的订单吗? {}"
.format(quote.instrument_id,context.book.has_position_for(quote)))
A_Book = context.get_account_book(source_TD, account_str) # 访问账户持有的全部多单 对象(Object)
context.log.info("收到报价品种 {} 账户 {} 中持有这个品种的订单吗? {}"
.format(quote.instrument_id,account_str,A_Book.has_position_for(quote)))
context.log.info("------------------------------")
"""
运行结果:
收到报价品种 118004 当前策略程序持有这个品种的订单吗? True
收到报价品种 118004 账户 00368920 中持有这个品种的订单吗? True
------------------------------
收到报价品种 600600 当前策略程序持有这个品种的订单吗? False
收到报价品种 600600 账户 00368920 中持有这个品种的订单吗? False
------------------------------
收到报价品种 113021 当前策略程序持有这个品种的订单吗? True
收到报价品种 113021 账户 00368920 中持有这个品种的订单吗? True
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 5.12、 获取持有订单的信息 get_position()
有 Python 语言基础的朋友们,看到前面的 has 就想到了,肯定得有 get 啊。has 是问 “有没有?”, 如果没有就过去了,如果有,那必须得访问一下啊。说人话就是:“有没有那啥?有就给我拿来?”
于是就用到本节介绍的这个方法(函数)了。
语法: get_position(exchange_id, instrument_id)
参数:与 has_position 对应,也同样是 exchange_id, instrument_id
exchange_id ———— 表达市场、沪市是 "SSE",深市是: "SZE";
instrument_id ———— 就是要查询的具体品种了,如 " 600600 ";
同样要注意它的 父对象(Object) 是 context.book ,还是 context.get_account_book() 返回的 对象(Object),分别对应 当前策略程序 和 帐户 的持有订单, 一定要小心在意。
返回值: 用脚想也知道了,当然是一个 对象(Object),前面已经见过了,就是持仓对象 Position ,其中包含的属性(变量)就是用来描述一个订单的各种数据的。
示例:
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
source_TD = "tora"
source_MD = "ToraL2"
account_str = "00368920" # 用户自己的帐号
def pre_start(context):
context.log.info('程序开始 ')
# 第一件事,接入 柜台、帐号
context.add_account(source_TD, account_str )
"""
当前运行的这个程序,名称为 test02 ,持有 118004 的订单
帐户 00368920 中当然也是持有 118004 的订单
但 帐户 00368920 中 还持有 127029
当前运行的这个程序 test02 ,是没有 127029 这个品种的订单的
"""
def post_start(context ):
context.log.info("获取当前程序 {} 持有品种 118004 的订单信息 {}"
.format(context.name,context.book.get_long_position("SSE", "118004")))
context.log.info("获取当前程序 {} 持有品种 127029 的订单信息 {}"
.format(context.name,context.book.get_long_position("SZE", "127029")))
A_Book = context.get_account_book(source_TD, account_str) # 访问账户持有的全部多单 对象(Object)
context.log.info("获取账户 {} 持有品种 118004 的订单信息 {}"
.format(account_str,A_Book.get_long_position("SSE", "118004")))
context.log.info("获取账户 {} 持有品种 127029 的订单信息 {}"
.format(account_str,A_Book.get_long_position("SZE", "127029")))
"""
运行结果
获取当前程序 test02 持有品种 118004 的订单信息 {"avg_open_price":113.26700000000001,"close_pnl":0.0,"close_price":0.0,"direction":0,"exchange_id":"SSE","frozen_total":0,"frozen_yesterday":0,"holder_uid":679239496,"instrument_id":"118004","instrument_type":3,"last_price":127.02,"ledger_category":0,"margin":0.0,"position_cost_price":113.26700000000001,"position_pnl":0.0,"pre_close_price":0.0,"pre_settlement_price":0.0,"realized_pnl":0.0,"settlement_price":0.0,"trading_day":"20300506","unrealized_pnl":137.52999999999986,"update_time":1668859816491027808,"volume":10,"yesterday_volume":10}
获取当前程序 test02 持有品种 127029 的订单信息 {"avg_open_price":0.0,"close_pnl":0.0,"close_price":0.0,"direction":0,"exchange_id":"SZE","frozen_total":0,"frozen_yesterday":0,"holder_uid":1666622677,"instrument_id":"127029","instrument_type":3,"last_price":0.0,"ledger_category":1,"margin":0.0,"position_cost_price":0.0,"position_pnl":0.0,"pre_close_price":0.0,"pre_settlement_price":0.0,"realized_pnl":0.0,"settlement_price":0.0,"trading_day":"20221119","unrealized_pnl":0.0,"update_time":0,"volume":0,"yesterday_volume":0}
获取账户 00368920 持有品种 118004 的订单信息 {"avg_open_price":113.26700000000001,"close_pnl":0.0,"close_price":0.0,"direction":0,"exchange_id":"SSE","frozen_total":0,"frozen_yesterday":0,"holder_uid":679239496,"instrument_id":"118004","instrument_type":3,"last_price":127.02,"ledger_category":0,"margin":0.0,"position_cost_price":113.26700000000001,"position_pnl":0.0,"pre_close_price":0.0,"pre_settlement_price":0.0,"realized_pnl":0.0,"settlement_price":0.0,"trading_day":"20300506","unrealized_pnl":137.52999999999986,"update_time":1668859816491027808,"volume":10,"yesterday_volume":10}
获取账户 00368920 持有品种 127029 的订单信息 {"avg_open_price":125.21499999999999,"close_pnl":0.0,"close_price":0.0,"direction":0,"exchange_id":"SZE","frozen_total":0,"frozen_yesterday":0,"holder_uid":679239496,"instrument_id":"127029","instrument_type":3,"last_price":129.778,"ledger_category":0,"margin":0.0,"position_cost_price":125.21499999999999,"position_pnl":0.0,"pre_close_price":0.0,"pre_settlement_price":0.0,"realized_pnl":0.0,"settlement_price":0.0,"trading_day":"20300506","unrealized_pnl":638.8200000000004,"update_time":1668859816491185597,"volume":140,"yesterday_volume":140}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
由上例可看出,当用 get_position() 去获取某一个指定的品种的订单信息时,会返回一个 对象(Object),其属性(变量)值就对应着订单的数据。值得注意的是这个方法(函数)的父对象,与前面的 has 方法(函数)是一样一样的, context.book 对应 当前策略程序, context.get_account_book() 对应 帐户 ,实际运用中一定要小心在意。
关于返回的这个 Position 对象(Object),也是十分重要的,与前面的 long_positions 对象(Object)中的内容不能说一模一样,那也是一模一样,请参考下表:
# 5.13、 小结
本章的重点就是认识了 订单薄 BOOK 这个对象(Object),注意:它如果绑定在 context 后面,就是表达本策略的 订单薄,如果是绑定在 context.get_account_book() 返回的对象上,那么它就表达 帐户 的订单薄,请注意这二者的区别。强烈建议新手朋友们,一开始一个策略程序就只管理本策略的订单薄,盯着自己下的订单就好。不要和别的程序打搅。以后熟练了,同时运行多个策略程序了,甚至管理多个帐户了,那你想咋飞咋飞,爱咋咋的————
在 BOOK 这个对象(Object)上,拥有多个属性(变量),同时也是对象(Object)。主要是用来描述帐户里的资金啊、订单啊、委托单啊、成交单啊,总之都是和真金白银有关啊。尤其是持有的订单,那每一次的价格跳动,都是真切实际的利益啊,因此一定要理解透彻,仔细小心。
# Position 持仓对象(Object)
按属性名称首字母排序
| 编号 | 属性 | 类型 | 说明 | 备注 |
|---|---|---|---|---|
| 01 | avg_open_price | 浮点型 float | 开仓均价 | |
| 02 | close_pnl | 浮点型 float | 平仓盈亏 | 期货用 |
| 03 | close_price | 浮点型 float | 收盘价 | |
| 04 | direction | Direction 对象 | 持仓方向 | |
| 05 | exchange_id | 字符串 str | 交易所 | |
| 06 | frozen_total | 整数型 int | 冻结总数量 | |
| 07 | frozen_yesterday | 整数型 int | 冻结昨仓 | |
| 08 | holder_uid | 整数型 int | 持有人id | |
| 09 | instrument_id | 字符串型 str | 交易品种 | |
| 10 | instrument_type | InstrumentType 对象 | 交易品种的类型 | |
| 11 | last_price | 浮点型 float | 最新价 | |
| 12 | ledger_category | LedgerCategory 对象 | 账户类别 | |
| 13 | margin | 浮点型 float | 保证金 | 期货用 |
| 14 | position_cost_price | 浮点型 float | 持仓成本 | |
| 15 | position_pnl | 浮点型 float | 持仓盈亏 | 期货用 |
| 16 | pre_close_price | 浮点型 float | 昨收价 | |
| 17 | pre_settlement_price | 浮点型 float | 昨结算价 | |
| 18 | realized_pnl | 浮点型 float | 已实现盈亏 | |
| 19 | settlement_price | 浮点型 float | 结算价 | |
| 20 | trading_day | 字符串型 str | 交易日 | |
| 21 | unrealized_pnl | 浮点型 float | 未实现盈亏 | |
| 22 | update_time | 整数型 int | 更新时间(功夫时间) | |
| 23 | volume | 整数型 int | 总持仓量 | |
| 24 | yesterday_volume | 整数型 int | 昨仓数量 |
WARNING
注意 : 对于 T+0 的品种,比如 可转债 ,当前可交易数量为 volume 总持仓量;而普通的股票品种,是 T+1 的,当前可交易数量为昨仓数量 yesterday_volume。注意还要 减去 冻结总量 frozen_total
# 六、小工具 Utils
本文档到这里,基本接近尾声了。功夫量化 的内容其实也就这么多。如果拿来和 迅投QMT、掘金量化这些比较, 功夫量化 更象一个骨架,还有很大的发展空间。但整个A股的量化交易,目前为止也是一个新兴市场啊,各路豪杰正在入场的过程中,奇技妙招、各种神通都还在路上呢。
功夫量化 也开发了一些实用的小工具来帮助做量化开发的人,实用的、需求多的,也会慢慢增加和补充。比如本章讨论的,可以理解为就是 功夫量化 的一个小工具 模块 或称之为 库。既然是 库,使用之前当然要先 导入 了。
from pykungfu import wingchun as wc
导入之后,就可以调用以下四个方法了:
| 属性 | 类型 | 说明 |
|---|---|---|
| hash_instrument | long | 根据交易品种,获取持仓列表中该品种对应的 key 值 |
| is_valid_price | bool | 判断当前价格是否为有效价格 |
| is_final_status | bool | 判断当前状态是否为最终状态 |
| get_instrument_type | InstrumentType对象 | 获取类型 |
# 6.1、 获取交易品种在持有单列表中的键(Key)值
hash_instrument()
语法: hash_instrument(exchange_id, instrument_id)
参数:
exchange_id ———— 表达市场、沪市是 "Exchange.SSE",深市是: "Exchange.SZE";
instrument_id ———— 就是要查询的具体品种了,如 " 600600 ";
前面已经见过 持有订单列表 的全局样貌了,其中就是典型的 key:value 键:值对 结构嘛。因此只要知道了 键(key),就能准确的取到对应的 值(value)。但是只知道交易品种,不知道 key 怎么办呢?正好就用这个方法(函数)就可以获取到了。
返回值:当然是一个长整型数字了,对应的就是 持有订单列表 中的 键(key)了。
示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
from pykungfu import wingchun as wc # 需要先导入模块
def pre_start(context):
context.add_account("tora", "00368920" ) # 添加自己的帐号
def post_start(context):
# 获取账户 00368920 中持有的多单列表
pos = context.get_account_book("tora", "00368920").long_positions
# 获取 110059 在列表中对应的 key
key = wc.utils.hash_instrument(Exchange.SSE,"110059")
context.log.info("品种 110059 在多单列表中的 key : {}".format(key))
context.log.info("多单列表中 键 key {} 对应的 交易品种是:{}".format(key,pos[key].instrument_id))
"""
运行结果:
品种 110059 在多单列表中的 key : 984964031
多单列表中 键 key 984964031 对应的 交易品种是:110059
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
可见这个方法(函数),主要用于已经知道持有某个品种的情况,直接获取对应这个品种的 Key 值。下一步,就可以根据 Key 值来获取这个订单的详细数据了。但如果传入了错误的参数,比如帐户中根本就没有这个品种的订单,却用这个方法(函数)来获取 Key ,那就要出错了。实际运用中请灵活运用吧。
# 6.2、 检查报价是否有效 is_valid_price()
当从交易所接收到报价时,可以用这个方法(函数)来检验一下报价是否是合法有效的。
语法: is_valid_price(quote.last_price)
参数:
quote.last_price ———— 当然就是接收到的最新报价罗;
返回值:布尔型。如果报价有效,返回值为 真;否则为 假;
示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
from pykungfu import wingchun as wc # 需要先导入模块
def pre_start(context): # pre_start() 方法(函数), 传入参数 context
context.add_account("tora", "00368920" )
context.subscribe_all("ToraL2")
def post_start(context):
pass
def on_quote(context,quote,location):
is_valid_price = wc.utils.is_valid_price(quote.last_price)
context.log.warning("收到品种 {} 的最新报价 {} , 是有效的吗?{}"
.format(quote.instrument_id,quote.last_price,is_valid_price))
"""
运行结果:
收到品种 603995 的最新报价 58.28 , 是有效的吗?True
收到品种 688246 的最新报价 35.31 , 是有效的吗?True
收到品种 515100 的最新报价 1.548 , 是有效的吗?True
收到品种 515170 的最新报价 0.792 , 是有效的吗?True
收到品种 501082 的最新报价 1.748 , 是有效的吗?True
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
当然,这个方法(函数)在 逐笔行情 中,也是可用的,也可以检验一下收到的 逐笔行情 报价,是不是有效的报价。
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
from pykungfu import wingchun as wc # 需要先导入模块
def pre_start(context): # pre_start() 方法(函数), 传入参数 context
context.add_account("tora", "00368920" )
context.subscribe_all("ToraL2")
def post_start(context):
pass
def on_transaction(context, transaction, location):
is_valid_price = wc.utils.is_valid_price(transaction.price)
context.log.warning("收到品种 {} 的最新报价 {} , 是有效的吗?{}"
.format(transaction.instrument_id,transaction.price,is_valid_price))
"""
运行结果:
收到品种 123080 的最新报价 115.148 , 是有效的吗?True
收到品种 300951 的最新报价 87.66 , 是有效的吗?True
收到品种 000993 的最新报价 7.46 , 是有效的吗?True
收到品种 123111 的最新报价 0.0 , 是有效的吗?False
收到品种 123074 的最新报价 0.0 , 是有效的吗?False
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 6.3、 检查报单状态是否为最终状态
is_final_status()
当你开始报单后,也就是你的程序向交易所喊了一嗓子,但这个报单过程可能会被拒、部分成交、或完全成交。很可能会有几个反复的过程。但最终会确定下来。那么用这个方法(函数)就可以用来检查一下,报单是否已经有了确定的结果,即为最终状态。
语法: is_final_status(order.status)
参数:
order.status ———— 是 order 对象中的一个属性,描述了报单的当时当刻的状态,可能是 已提交、已撤单、出错 或 已成交 等;
返回值:布尔型。如果已经是最终状态,返回值为 真;否则为 假;
示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
from pykungfu import wingchun as wc # 需要先导入模块
def pre_start(context): # pre_start() 方法(函数), 传入参数 context
context.add_account("tora", "00368920" )
context.subscribe_all("ToraL2")
def post_start(context):
pass
def on_order(context,order,location):
is_final_status = wc.utils.is_final_status(order.status)
context.log.warning("更新时间 {} 报单品种 {} 的最新状态 {} , 是最终的状态吗?{}"
.format(context.strftime(order.update_time),order.instrument_id,order.status,is_final_status))
"""
运行结果:
更新时间 2020-11-21 10:36:44.652810124 报单品种 110058 的最新状态 OrderStatus.Submitted , 是最终的状态吗?False
更新时间 2020-11-21 10:36:44.658845210 报单品种 110058 的最新状态 OrderStatus.Pending , 是最终的状态吗?False
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
实际运用中,用这个方法(函数)在 on_order(): 中检查报单的最终状态,是十分有用的作法。
# 6.4、 获取交易品种的类型 get_instrument_type()
在 功夫量化 的世界里,A股市场中所有品种是分了类型的,比如普通的沪市深市品种,如果 “600600”,“003775” 等,就是股票(Stock),可转债的类型是债券(Bond )。如果你不确定某一个品种被分在了哪个类型,就可以用这个方法(函数)来查询一下了。
语法: get_instrument_type(exchange_id, instrument_id)
参数:
exchange_id ———— 表达市场、沪市是 "Exchange.SSE",深市是: "Exchange.SZE";
instrument_id ———— 就是要查询的具体品种了,如 " 600600 ";
返回值:InstrumentType 交易品种的类型。
示例:
# -*- coding: UTF-8 -*-
import kungfu.yijinjing.time as kft
from kungfu.wingchun.constants import *
from pykungfu import wingchun as wc # 需要先导入模块
def pre_start(context): # pre_start() 方法(函数), 传入参数 context
context.add_account("tora", "00368920" )
context.subscribe_all("ToraL2")
def post_start(context):
type = wc.utils.get_instrument_type(Exchange.SSE,"110059")
context.log.info("品种 110059 的类型是 : {}".format(type))
type = wc.utils.get_instrument_type(Exchange.SSE,"600600")
context.log.info("品种 600600 的类型是 : {}".format(type))
"""
运行结果:
品种 110059 的类型是 : InstrumentType.Bond
品种 600600 的类型是 : InstrumentType.Stock
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 后记
到此,功夫量化的 Python 接口基本都介绍完了。因各家券商不同、不有服务器不同,因此在获取数据的细节方面,略有差异。因此,上手之后还是要根据各券商的具体情况稍做调整,但大致模型就是如此了。顺祝各位学有所用、交易获利。