用户手册

本页面给用户提供了一个简洁清晰的入门指南,涵盖各个功能模块

Overview

框架概览:

  • 使用数据API,轻松获取研究数据
  • 根据策略模板,编写自己的量化策略
  • 使用回测框架,对策略进行回测和验证

数据API

本产品提供了金融数据api,方便用户调用接口获取各种数据,通过python的api调用接口,返回DataFrame格式的数据和消息,以下是用法

导入接口

在python程序里面导入module,然后用注册的用户帐号登录就可以使用行情和参考数据的接口来获取数据了

引入模块

from jaqs.data.dataapi import DataApi

登录数据服务器

api = DataApi(addr='tcp://data.quantos.org:8910')
api.login("phone", "token")

调用数据接口

主要数据主要分为两大类:

  • 市场数据,目前可使用的数据包括日线,分钟线
  • 参考数据,包括财务数据、公司行为数据、指数成份数据等

行情数据获取

获取实时行情

使用quote()函数查询最新市场行情。

输入参数:

字段 类型 说明 缺省值
symbol string 标的代码,支持多标的查询 不可缺省
fields string 需要返回字段,多字段以’,’隔开;为”“时返回所有字段 “”

使用示例:

df,msg = api.quote("000001.SH, cu1709.SHF", fields="open,high,low,last,volume")

输出字段:

字段 类型 说明
symbol string 标的代码
code string 交易所原始代码
date int 自然日,YYYYMMDD格式,如20170823
time int 时间,精确到毫秒,如14:21:05.330记为142105330
trade_date int YYYYMMDD格式,如20170823
open double 开盘价
high double 最高价
low double 最低价
last double 最新价
close double 收盘价
volume double 成交量(总)
turnover double 成交金额(总)
vwap double 截止到行情时间的日内成交均价
oi double 持仓总量
settle double 今结算价
iopv double 净值估值
limit_up double 涨停价
limit_down double 跌停价
preclose double 昨收盘价
presettle double 昨结算价
preoi double 昨持仓
askprice1 double 申卖价1
askprice2 double 申卖价2
askprice3 double 申卖价3
askprice4 double 申卖价4
askprice5 double 申卖价5
bidprice1 double 申买价1
bidprice2 double 申买价2
bidprice3 double 申买价3
bidprice4 double 申买价4
bidprice5 double 申买价5
askvolume1 double 申卖量1
askvolume2 double 申卖量2
askvolume3 double 申卖量3
askvolume4 double 申卖量4
askvolume5 double 申卖量5
bidvolume1 double 申买量1
bidvolume2 double 申买量2
bidvolume3 double 申买量3
bidvolume4 double 申买量4
bidvolume5 double 申买量5

获取日线行情

代码示例:

df, msg = api.daily(
                symbol="600832.SH, 600030.SH",
                start_date=20121026,
                end_date=20121130,
                fields="",
                adjust_mode="post")

结果示例(前5条记录):

close code high low oi open settle symbol trade_date trade_status turnover volume vwap
5.09 600832 5.24 5.08 NaN 5.23 NaN 600832.SH 20121026 交易 2.779057e+07 5381800 5.16
5.10 600832 5.15 5.08 NaN 5.11 NaN 600832.SH 20121029 交易 1.320333e+07 2582557 5.11
5.11 600832 5.18 5.08 NaN 5.12 NaN 600832.SH 20121030 交易 1.622705e+07 3170615 5.12
5.11 600832 5.14 5.09 NaN 5.12 NaN 600832.SH 20121031 交易 1.072007e+07 2097770 5.11
5.18 600832 5.20 5.12 NaN 5.12 NaN 600832.SH 20121101 交易 1.972100e+07 3814712 5.17

获取分钟线行情(不含ask,bid信息)

代码示例:

df,msg = api.bar(
            symbol="600030.SH",
            trade_date=20170928,
            freq="5M",
            start_time= 90000,
            end_time= 160000,
            fields="")

结果示例(前5条记录):

close code date freq high low oi open settle symbol time trade_date turnover volume vwap
18.05 600030 20170928 5M 18.08 18.00 NaN 18.01 NaN 600030.SH 93500 20170928 13576973.0 752900 18.032903
18.03 600030 20170928 5M 18.06 18.01 NaN 18.04 NaN 600030.SH 94000 20170928 16145566.0 895110 18.037522
18.04 600030 20170928 5M 18.05 18.02 NaN 18.03 NaN 600030.SH 94500 20170928 11024829.0 611400 18.032105
17.99 600030 20170928 5M 18.05 17.97 NaN 18.04 NaN 600030.SH 95000 20170928 30021003.0 1667190 18.006948
18.02 600030 20170928 5M 18.03 17.97 NaN 17.98 NaN 600030.SH 95500 20170928 13691203.0 761161 17.987263

获取分钟线行情(包含ask,bid信息)

代码示例:

df,msg = api.bar_quote(
                    symbol="000001.SH,cu1709.SHF",
                    start_time = 95600,
                    end_time=135600,
                    trade_date=20170823,
                    freq= "5M",
                    fields="open,high,low,last,volume")

结果示例(前5条记录):

high low symbol time trade_date volume
3294.3371 3291.7666 000001.SH 100000 20170823 493058300
3292.3162 3289.5202 000001.SH 100500 20170823 492695100
3290.4118 3288.3906 000001.SH 101000 20170823 458298100
3289.2133 3285.9129 000001.SH 101500 20170823 535085000
3287.4892 3284.6076 000001.SH 102000 20170823 426738700

基本数据获取

获取证券基础信息

代码示例:

df, msg = api.query(view="jz.instrumentInfo",
                 fields="list_date,delist_date,symbol,market",
                 filter="inst_type=1&trade_date=20171219")

结果示例(前5条记录):

delist_date list_date market name symbol
99999999 19991110 SH 浦发银行 600000.SH
99999999 20001219 SH 民生银行 600016.SH
99999999 20030106 SH 中信证券 600030.SH
99999999 20021009 SH 中国联通 600050.SH
99999999 19970807 SH 国金证券 600109.SH
99999999 20000526 SH 广汇能源 600256.SH

获取指数基本信息

代码示例:

df, msg = api.query(
                view="lb.indexInfo",
                fields="",
                filter="",
                data_format='pandas')

结果示例(前5条记录):

| symbol |
| — |
| 000001.SH |
| 000002.SH |
| 000003.SH |
| 000004.SH |
| 000005.SH |

获取交易日历

代码示例:

df, msg = api.query(
                view="jz.secTradeCal",
                fields="date,istradeday,isweekday,isholiday",
                filter="start_date=20170101&end_date=20170801")

结果示例(前5条记录):

isholiday istradeday isweekday trade_date
F T T 20170103
F T T 20170104
F T T 20170105
F T T 20170106
F T T 20170109

获取分配除权信息

代码示例:

df, msg = api.query(
                view="lb.secDividend",
                fields="",
                filter="start_date=20170101&end_date=20170801",
                data_format='pandas')

结果示例(前5条记录):

ann_date bonus_list_date cash cash_tax cashpay_date end_date exdiv_date publish_date record_date share_ratio share_trans_ratio symbol
20161025   0.20000000 0.20000000 20170103 20160930 20170103 20161227 20161230 0.0 0.000000 002059.SZ
20170117 20170217 3.60000000 3.60000000 20170217 20161231 20170217 20170210 20170216 0.0 5.000000 300561.SZ
20161105   0.50000000 0.50000000 20170216 20160630 20170216 20170210 20170215 0.0 0.000000 601900.SH
20170120   4.50000000 4.50000000 20170303 20161231 20170303 20170224 20170302 0.0 0.000000 603025.SH
20170125 20170307 6.00000000 6.00000000 20170306 20161231 20170306 20170227 20170303 0.0 12.000000 600816.SH

获取复权因子

代码示例:

df, msg = api.query(
                view="lb.secAdjFactor",
                fields="",
                filter="symbol=002059&start_date=20170101&end_date=20170801",
                data_format='pandas')

结果示例(前5条记录):

adjust_factor symbol trade_date
2.077892 002059.SZ 20170103
2.077892 002059.SZ 20170104
2.077892 002059.SZ 20170105
2.077892 002059.SZ 20170106
2.077892 002059.SZ 20170109

获取停牌信息

代码示例:

df, msg = api.query(
                view="lb.secSusp",
                fields="susp_time",
                filter="symbol=002059",
                data_format='pandas')

结果示例(前5条记录):

ann_date resu_date susp_date susp_reason susp_time symbol
20080408 20080409 20080408 召开股东大会 9:30:00 002059.SZ
20080612 20080613 20080612 召开股东大会 9:30:00 002059.SZ
20080922 20080922 20080922 异常波动 9:30:00 002059.SZ
20090220 20090223 20090220 召开股东大会 9:30:00 002059.SZ
20090417 20090420 20090417 召开股东大会 9:30:00 002059.SZ

获取行业分类

代码示例:

df, msg = api.query(
                view="lb.secIndustry",
                fields="",
                filter="industry1_name=金融&industry2_name=金融&industry_src=中证",
                data_format='pandas')

结果示例(前5条记录):

in_date industry1_code industry1_name industry2_code industry2_name industry3_code industry3_name industry4_code industry4_name industry_src is_new out_date symbol
20130219 J 金融业 J66 货币金融服务         中证指数有限公司 Y   000001.SZ
20130219 J 金融业 J69 其他金融业         中证指数有限公司 Y   000563.SZ
20130219 J 金融业 J66 货币金融服务         中证指数有限公司 Y   600000.SH
20130219 J 金融业 J66 货币金融服务         中证指数有限公司 Y   600015.SH
20130219 J 金融业 J66 货币金融服务         中证指数有限公司 Y   600016.SH

获取指数成份

代码示例:

df, msg = api.query(
                view="lb.indexCons",
                fields="",
                filter="index_code=399001&is_new=Y",
                data_format='pandas')

结果示例(前5条记录):

in_date index_code out_date symbol
20140814 000001.SH   603126.SH
20140815 000001.SH   603111.SH
20090511 000001.SH   600372.SH
20140819 000001.SH   603100.SH
20140822 000001.SH   603609.SH

获取常量参数

代码示例:

df, msg = api.query(
                view="jz.sysConstants",
                fields="",
                filter="code_type=symbol_type",
                data_format='pandas')

结果示例(前5条记录):

code code_type value
1 inst_type 股票
10 inst_type 回购
100 inst_type 指数
101 inst_type 股指期货
102 inst_type 国债期货

获取日行情估值

代码示例:

df, msg = api.query(
                view="lb.secDailyIndicator",
                fields='pb,net_assets,ncf,price_level',
                filter='symbol=000063.SZ&start_date=20170605&end_date=20170701')

结果示例(前5条记录):

close_price float_market_value high_52w low_52w net_assets pb pe price_level share_float_free symbol total_market_value trade_date
19.62 6.726969e+06 20.05 13.07 3.659734e+10 2.2457 0.0 0 215879.8077 000063.SZ 8.218724e+06 20170605
19.81 6.792113e+06 20.05 13.07 3.659734e+10 2.2675 0.0 0 215879.8077 000063.SZ 8.298314e+06 20170606
20.59 7.059546e+06 20.80 13.07 3.659734e+10 2.3567 0.0 0 215879.8077 000063.SZ 8.625052e+06 20170607
20.63 7.073260e+06 21.05 13.07 3.659734e+10 2.3613 0.0 0 215879.8077 000063.SZ 8.641808e+06 20170608
20.98 7.193262e+06 21.09 13.07 3.659734e+10 2.4014 0.0 0 215879.8077 000063.SZ 8.788421e+06 20170609

获取资产负债表

代码示例:

df, msg = api.query(
                view="lb.balanceSheet",
                fields="",
                filter="symbol=002636.SZ",
                data_format='pandas')

结果示例(前5条记录):

acct_rcv ann_date inventories notes_rcv report_date report_type symbol tot_cur_assets
2.035835e+08 20130318 7.627147e+07 1.737082e+08 20121231 408006000 002636.SZ 1.074759e+09
7.050691e+08 20130425 1.685824e+08 2.460369e+08 20130331 408001000 002636.SZ 1.890115e+09
2.436788e+08 20120421 6.736024e+07 5.982293e+07 20101231 408009000 002636.SZ 4.718200e+08
2.495033e+08 20120424 1.077278e+08 1.173246e+08 20120331 408006000 002636.SZ 1.133775e+09
2.035835e+08 20140422 7.627147e+07 1.737082e+08 20121231 408009000 002636.SZ 1.074759e+09

获取利润表

代码示例:

df, msg = api.query(
                view="lb.income",
                fields="",
                filter="symbol=600030.SH,000063.SZ,000001.SZ&report_type=408002000&start_date=20160601&end_date=20170601",
                data_format='pandas')

结果示例(前5条记录):

ann_date int_income less_handling_chrg_comm_exp net_int_income oper_exp oper_profit oper_rev report_date symbol tot_oper_cost tot_profit total_oper_rev
20160812 3.120900e+10 857000000.0 1.779800e+10 1.909500e+10 8.142000e+09 2.723700e+10 20160630 000001.SZ 1.909500e+10 8.125000e+09 2.723700e+10
20160825 0.000000e+00 0.0 6.524571e+08 5.588709e+09 4.970444e+09 1.055915e+10 20160630 600030.SH 5.588709e+09 4.917090e+09 1.055915e+10
20160826 0.000000e+00 0.0 0.000000e+00 0.000000e+00 1.811750e+08 2.589879e+10 20160630 000063.SZ 2.615474e+10 1.336791e+09 2.589879e+10
20161029 0.000000e+00 0.0 7.365511e+08 5.237163e+09 3.643600e+09 8.880763e+09 20160930 600030.SH 5.237163e+09 3.659715e+09 8.880763e+09
20161021 3.200700e+10 863000000.0 1.836700e+10 1.881000e+10 8.389000e+09 2.719900e+10 20160930 000001.SZ 1.881000e+10 8.406000e+09 2.719900e+10

获取现金流量表

代码示例:

df, msg = api.query(
                view="lb.cashFlow",
                fields="",
                filter="symbol=002548.SZ",
                data_format='pandas')

结果示例(前5条记录):

ann_date cash_recp_prem_orig_inco cash_recp_return_invest cash_recp_sg_and_rs incl_dvd_profit_paid_sc_ms net_cash_flows_inv_act net_cash_received_reinsu_bus net_incr_dep_cob net_incr_disp_tfa net_incr_fund_borr_ofi net_incr_int_handling_chrg net_incr_loans_central_bank other_cash_recp_ral_fnc_act other_cash_recp_ral_oper_act recp_tax_rends report_date report_type stot_cash_inflows_oper_act stot_cash_outflows_oper_act symbol
20140815 0.0 1071150.68 4.747366e+08 0.0 3.387516e+05 0.0 0.0 0.0 0.0 0.0 0.0 0.000000e+00 2.372317e+07 0.00 20130630 408003000 4.984598e+08 4.938527e+08 002548.SZ
20140815 0.0 492274.24 4.574233e+08 0.0 -5.160987e+06 0.0 0.0 0.0 0.0 0.0 0.0 0.000000e+00 -2.986875e+05 0.00 20140630 408002000 4.571247e+08 4.261462e+08 002548.SZ
20140815 0.0 37071150.68 1.294270e+08 0.0 3.893878e+07 0.0 0.0 0.0 0.0 0.0 0.0 0.000000e+00 -7.582736e+06 0.00 20130630 408008000 1.218442e+08 1.523719e+08 002548.SZ
20140815 0.0 492274.24 1.098741e+08 0.0 -4.613494e+07 0.0 0.0 0.0 0.0 0.0 0.0 0.000000e+00 2.659082e+08 0.00 20140630 408007000 3.757823e+08 3.125591e+08 002548.SZ
20170429 0.0 19237803.19 2.754383e+09 2450000.0 -7.779336e+08 0.0 0.0 0.0 0.0 0.0 0.0 1.547990e+08 4.374965e+07 0.00 20161231 408001000 2.798133e+09 2.846801e+09 002548.SZ

获取业绩快报

代码示例:

df, msg = api.query(
                view="lb.profitExpress",
                fields="",
                filter="start_anndate=20170101",
                data_format='pandas')

结果示例(前5条记录):

ann_date net_profit_int_inc oper_profit oper_rev report_date symbol total_assets total_profit
20170207 1.054700e+10 1.227300e+10 9.844400e+10 20161231 601633.SH 9.214600e+10 1.248000e+10
20170713 1.493567e+08 1.902676e+08 1.218885e+09 20170630 002258.SZ 3.658932e+09 1.890177e+08
20170228 1.177647e+08 1.142228e+08 1.023947e+09 20161231 002406.SZ 2.538539e+09 1.389901e+08
20170228 2.148007e+08 1.276432e+08 4.011206e+09 20161231 002087.SZ 7.744742e+09 2.504674e+08
20170228 1.621291e+08 1.944727e+08 1.480000e+09 20161231 002688.SZ 2.713363e+09 2.025310e+08

获取限售股解禁表

代码示例:

df, msg = api.query(
                view="lb.secRestricted",
                fields="",
                filter="list_date=20170925",
                data_format='pandas')

结果示例(前5条记录):

lifted_shares list_date symbol
7.597341e+08 20171011 000536.SZ
7.586547e+08 20171011 000813.SZ
2.776560e+08 20171011 002701.SZ
1.151995e+08 20171010 603010.SH
5.385944e+06 20171010 300190.SZ

数据视图(DataView)

数据视图(DataView)将各种行情数据和参考数据进行了封装,方便用户使用数据。

DataView做什么

将频繁使用的DataFrame操作自动化,使用者操作数据时尽量只考虑业务需求而不是技术实现:

  1. 根据字段名,自动从不同的数据api获取数据
  2. 按时间、标的整理对齐(财务数据按发布日期对齐)
  3. 在已有数据基础上,添加字段、加入自定义数据或根据公式计算新数据
  4. 数据查询
  5. 本地存储

初始化

DataView初始化工作主要包括创建DataView和DataService、初始化配置、数据准备三步。

创建DataView和DataService

DataService提供原始的数据,目前jaqs已经提供远程数据服务类(RemoteDataService),可以通过互联网获取行情数据和参考数据。

from jaqs.data.dataservice import RemoteDataService
from jaqs.data.dataview import DataView
dv = DataView()
ds = RemoteDataService()

初始化配置

通过init_from_config函数进行初始化配置,配置参数如下表所示:

字段 类型 说明 缺省值
symbol string universe标的代码,多标的以’,’隔开,如‘000001.SH, 600300.SH’,指数代码不会被展开成对应成员股票代码 不可缺失,symbol与universe二选一
universe string 指数代码,单标的,将该指数的成员作为universe 不可缺省,symbol与universe二选一
start_date int 开始日期 不可缺省
end_date int 结束日期 不可缺省
fields string 数据字段,多字段以’,’隔开,如’open,close,high,low’ 不可缺省
freq int 数据类型,目前只支持1,表示日线数据 1

示例代码:

dv = DataView()
ds = RemoteDataService()

secs = '600030.SH,000063.SZ,000001.SZ'
props = {'start_date': 20160601, 'end_date': 20170601, 'symbol': secs,
       'fields': 'open,close,high,low,volume,pb,net_assets,eps_basic',
       'freq': 1}
dv.init_from_config(props, data_api=ds)

数据准备

从数据服务获取数据:

dv.prepare_data()

获取数据

数据结构说明

DataView用一个三维的数据结构保存的所需数据,其三维数据轴分别为:

  1. 标的代码,如 600030.SH, 000002.SH
  2. 交易日期,如 20150202, 20150203
  3. 数据字段,如 open, high, low, close
如下图所示:
dataview

根据日期获取数据:

使用get_snapshot()函数来获取某日的数据快照(在时间轴切片),输入参数见下表:

字段 类型 说明 缺省值
snapshot_date int 交易日 不可缺省
symbol string 标的代码,多标的以’,’隔开,如‘000001.SH, 600300.SH’ 不可缺省
fields string 数据字段,多字段以’,’隔开,如’open,close,high,low’ 不可缺省

返回在结果格式为pandas DataFrame,标的代码作为DataFrame的index,数据字段作为DataFrame的column。

示例代码:

snap1 = dv.get_snapshot(20170504, symbol='600030.SH,000063.SZ', fields='close,pb')

返回结果示例:

根据数据字段获取数据

使用get_ts()函数获取某个数据字段的时间序列(在字段轴切片),输入参数见下表:

字段 类型 说明 缺省值
field string 数据字段,多字段以’,’隔开 不可缺省
symbol string “标的代码,多标的以’,’隔开,如”“000001.SH, 600300.SH”“” 不可缺省
start_date int 开始日期 不可缺省
end_date int 结束日期 不可缺省

返回结果格式为pandas DataFrame,交易日作为DataFrame的index,标的代码作为DataFrame的column

示例代码:

ts1 = dv.get_ts('close', symbol='600030.SH,000063.SZ',
            start_date=20170101, end_date=20170302)

数据视图及保存

  • 可以读取修改后继续存储
  • 默认覆盖

保存DataView到文件

使用save_dataview()函数将当前数据视图保存到指定文件夹,保存格式为h5文件。函数输入参数如下:

字段 类型 说明 缺省值
folder_path string 文件保存主目录 不可缺省
sub_folder string 文件保存子目录,缺省为’{start_date}*{end*date}freq={freq}D’,例如,若DataView初始参数为startdate=20120101,end_date=20120110,freq=1时,sub_folder为‘20120101_20120110_freq=1D’ ‘{start_date}*{end*date}_freq={freq}D’|

示例代码:

dv.save_dataview('prepared', 'demo')
Store data...
Dataview has been successfully saved to:
/home/user/prepared/demo

You can load it with load_dataview('/home/user/prepared/demo')

读取已经保存的DataView

利用load_dataview()函数,DataView可以不经初始化,直接读取已经保存的DataView数据。函数输入参数如下所示:

字段 类型 说明 缺省值
folder string DataView文件保存目录 不可缺省

示例代码:

dv = DataView()
dv.load_dataview('/home/user/prepared/demo')
Dataview loaded successfully.

添加数据

  • 从DataApi获取更多字段: dv.add_field('roe')
  • 加入自定义DataFrame: dv.append_df(name, df)
  • 根据公式计算衍生指标: dv.add_formula(name, formula, is_quarterly=False)

添加字段

利用add_field()函数可以添加当前DataView没有包含的数据,输入参数如下:

字段 类型 说明 缺省值
field_name string 需要添加的字段名称 不可缺省
data_api BaseDataServer 缺省时为None,即利用DataView初始化时传入的DataService添加数据;当DataView是从文件中读取得到时,该DataView没有DataService,需要外部传入一个DataService以添加数据。 None

示例代码:

.

添加自定义公式数据

利用add_formula()函数可以添加当前DataView添加自定义公式数据字段,输入参数如下所示:

字段 类型 说明 缺省
field_name string 字段名称 不可缺省
formula string 公式表达式 不可缺省
is_quarterly bool 是否为季度数据,如财务季报数据 不可缺省
formula_func_name_style string 函数名大小写识别模式,’upper’:使用默认函数名,’lower’:formular里所有函数名都为应为小写。 ‘upper’
data_api BaseDataServer 数据服务 None

示例代码:

## 日频0/1指标:是否接近涨跌停
dv.add_formula('limit_reached', 'Abs((open - Delay(close, 1)) / Delay(close, 1)) > 0.095', is_quarterly=False)
dv.get_ts('limit_reached').iloc[:, 100:].head(2)
## 日频指标:与52周高点的百分比
dv.add_formula('how_high_52w', 'close_adj / Ts_Max(close_adj, 252)', is_quarterly=False)
dv.get_ts('how_high_52w').tail().applymap(lambda x: round(100*x, 1))
## 日频指标:量价背离
dv.add_formula('price_volume_divert', 'Correlation(vwap_adj, volume, 10)', is_quarterly=False)
dv.get_snapshot(20171009, fields='price_volume_divert')
## 季频指标:eps增长率
dv.add_formula('eps_growth', 'Return(eps_basic, 4)', is_quarterly=True)
dv.get_ts('eps_growth', start_date=20160810).head()
ds = RemoteDataService()
dv.add_field('total_share', ds)

目前支持的公式如下表所示:

公式 说明 示例
+ 加法运算 close + open
- 减法运算 close - open
* 乘法运算 vwap * volume
/ 除法运算 close / open
^ 幂函数 close ^ 2
% 取余函数 oi % 10
== 判断是否相等 close == open
!= 判断是否不等 close != open
> 大于 close > open
< 小于 close < open
>= 大于等于 close >= open
<= 小于等于 close <= open
&& 逻辑与 (close > open) && (close > vwap)
|| 逻辑或 (close > open) ||(close > vwap)
! 逻辑非 !(close>open)
Sin(x) 正弦函数 Sin(close/open)
Cos(x) 余弦函数 Cos(close/open)
Tan(x) 正切函数 Tan(close/open)
Sqrt(x) 开平方函数 Sqrt(close^2 + open^2)
Abs(x) 绝对值函数 Abs(close-open)
Log(x) 自然对数 Log(close/open)
Ceil(x) 向上取整 Ceil(high)
Floor(x) 向下取整 Floor(low)
Round(x) 四舍五入 Round(close)
Sign(x) 取 x 正负号,返回以-1,0和1标志 Sign(close-open)
-x 对x取负 -close
Max(x,y) 取 x 和 y 同位置上的较大值组成新的DataFrame返回 Max(close, open)
Min(x,y) 取 x 和 y 同位置上的较小值组成新的DataFrame返回 Min(close,open)
Delay(x,n) 时间序列函数, n 天前 x 的值 Delay(close,1) 表示前一天收盘价
Rank(x) 各标的根据给出的指标x的值,在横截面方向排名 Rank( close/Delay(close,1)-1 ) 表示按日收益率进行排名
GroupRank(x,g) 各标的根据指标 x 的值,在横截面方向进行按分组 g 进行分组排名。分组 DataFrame g 以int数据标志分组,例如三个标的在某一天的截面上的分组值都为2,则表示这三个标的在同一组 GroupRank(close/Delay(close,1)-1, g) 表示按分组g根据日收益率进行分组排名
ConditionRank(x,cond) 各标的根据条件 DataFrame cond,按照给出的指标 x 的值,在横截面方向排名,只有 cond 中值为True的标的参与排名。 GroupRank(close/Delay(close,1)-1, cond) 表示按条件cond根据日收益率进行分组排名
Quantile(x,n) 各标的按根据指标 x 的值,在横截面方向上进行分档,每档标的数量相同 Quantile( close/Delay(close,1)-1,5)表示按日收益率分为5档
GroupQuantile(x,g,n) 各标的根据指标 x 的值,在横截面方向上按分组 g 进行分组分档,分组 DataFrame g 以int数据标志分组,例如三个标的在某一天的截面上的分组值都为2,则表示这三个标的在同一组 GroupQuantile(close/Delay(close,1)-1,g,5) 表示按日收益率和分组g进行分档,每组分为5档
Standardize(x) 标准化,x值在横截面上减去平均值后再除以标准差 Standardize(close/Delay(close,1)-1) 表示日收益率的标准化
Cutoff(x,z_score) x值在横截面上去极值,用MAD方法 Cutoff(close,3) 表示去掉z_score大于3的极值
Sum(x,n) 时间序列函数,x 指标在过去n天的和,类似于pandas的rolling_sum()函数 Sum(volume,5) 表示一周成交量
Product(x,n) 时间序列函数,计算 x 中的值在过去 n 天的积 Product(close/Delay(close,1),5) - 1 表示过去5天累计收益
CountNans(x,n) 时间序列函数,计算 x 中的值在过去 n 天中为 nan (非数字)的次数 CountNans((close-open)^0.5, 10) 表示过去10天内有几天close小于open
Ewma(x,halflife) 指数移动平均,以halflife的衰减对x进行指数移动平均 Ewma(x,3)
StdDev(x,n) 时间序列函数,计算 x 中的值在过去n天的标准差 StdDev(close/Delay(close,1)-1, 10)
Covariance(x,y,n) 时间序列函数,计算 x 中的值在过去n天的协方差 Covariance(close, open, 10)
Correlation(x,y,n) 时间序列函数,计算 x 中的值在过去n天的相关系数 Correlation(close,open, 10)
Delta(x,n) 时间序列函数,计算 x 当前值与n天前的值的差 Delta(close,5)
Return(x,n,log) 时间序列函数,计算x值n天的增长率,当log为False时,计算线性增长;当log为True时,计算对数增长 Return(close,5,True)计算一周对数收益
Ts_Mean(x,n) 时间序列函数,计算 x 中的值在过去n天的平均值 Ts_Mean(close,5)
Ts_Min(x,n) 时间序列函数,计算 x 中的值在过去n天的最小值 Ts_Min(close,5)
Ts_Max(x,n) 时间序列函数,计算 x 中的值在过去n天的最大值 Ts_Max(close,5)
Ts_Skewness(x,n) 时间序列函数,计算 x 中的值在过去n天的偏度 Ts_Skewness(close,20)
Ts_Kurtosis(x,n) 时间序列函数,计算 x 中的值在过去n天的峰度 Ts_Kurtosis(close,20)
Tail(x, lower, upper, newval) 如果 x 的值介于 lower 和 upper,则将其设定为 newval Tail(close/open, 0.99, 1.01, 1.0)
Step(n) Step(n) 为每个标的创建一个向量,向量中 n 代表最新日期,n-1 代表前一天,以此类推。 Step(30)
Decay_linear(x,n) 时间序列函数,过去n天的线性衰减函数。Decay_linear(x, n) = (x[date] * n + x[date - 1] * (n - 1) + … + x[date – n - 1]) / (n + (n - 1) + … + 1) Decay_linear(close,15)
Decay_exp(x,f,n) 时间序列函数, 过去 n 天的指数衰减函数,其中 f 是平滑因子。这里 f 是平滑因子,可以赋一个小于 1 的值。Decay_exp(x, f, n) = (x[date] + x[date - 1] * f + … +x[date – n - 1] * (f ^ (n – 1))) / (1 + f + … + f ^ (n - 1)) Decay_exp(close,0.9,10)
Pow(x,y) 幂函数x^y Pow(close,2)
SignedPower(x,e) 等价于Sign(x) * (Abs(x)^e) SignedPower(close-open, 0.5)
If(cond,x,y) cond为True取x的值,反之取y的值 If(close > open, close, open) 表示取open和close的较大值

研究

信号研究与回测: SignalDigger模块

功能

  • 收益分析:分组收益、加权组合收益等
  • 相关性分析:每日IC、IC分布等
  • 排除涨跌停、停牌、非指数成分等

特性

  • 计算与绘图分离
  • 绘图输出格式可选、可关闭,数据计算结果可返回

完整代码及样例见这里,安装JAQS后即可直接运行。请勿直接复制下方代码运行

测试量价背离因子

  • 输入:两个DataFrame:因子值,标的价格/收益
  • 设置:period,quantile个数
factor = -dv.get_ts('price_volume_divert').shift(1, axis=0)  # avoid look-ahead bias
price = dv.get_ts('close_adj')
price_bench = dv.data_benchmark

my_period = 5
obj = SignalDigger(output_folder='.', output_format='plot')
obj.process_signal_before_analysis(factor, price=price,
                                   mask=mask_all,
                                   n_quantiles=5, period=my_period,
                                   benchmark_price=price_bench,
                                   )
res = obj.create_full_report()

returnsreport

icreport

利用输出数据做进一步分析

def performance(ret):
    cum = ret.add(1.0).cumprod(axis=0)
    std = np.std(ret)

    start = pd.to_datetime(ser.index[0], format="%Y%m%d")
    end = pd.to_datetime(ser.index[-1], format="%Y%m%d")
    years = (end - start).days / 365.0

    yearly_return = np.power(cum.values[-1], 1. / years) - 1
    yearly_vol = std * np.sqrt(225.)
    # beta = np.corrcoef(df_returns.loc[:, 'bench'], df_returns.loc[:, 'strat'])[0, 1]
    sharpe = yearly_return / yearly_vol
    print "ann. ret = {:.1f}%; ann. vol = {:.1f}%, sharpe = {:.2f}".format(yearly_return*100, yearly_vol*100, sharpe)
ser = res['quantile_active_ret_correct'][1]['mean']#.iloc[90:]
print ser.index[0], ser.index[-1]
plt.figure(figsize=(14, 5))
plt.plot(ser.add(1.0).cumprod().values)
performance(ser)
20160105 20171013
ann. ret = -17.2%; ann. vol = 3.7%, sharpe = -4.63

furtheranalysis

回测

JAQS支持Alpha选股策略事件驱动择时策略,两种策略使用不同方法回测。

对于入门用户,推荐首先查看快速入门,关于JAQS策略系统的介绍,见这里。本文在以上教程的基础上,举出更多策略样例。

*注*:本文所用例子的完整代码见这里,安装JAQS后即可直接运行。请勿直接复制下方代码运行

格雷厄姆选股策略

本策略完整实现代码见 这里

主要介绍基于回测框架实现格雷厄姆模型。格雷厄姆模型分为两步,首先是条件选股,其次按照市值从小到大排序,选出排名前五的股票。

一. 数据准备

我们选择如下指标,对全市场的股票进行筛选,实现过程如下:

a. 首先在数据准备模块save_dataview()中通过props设置数据起止日期,股票版块,以及所需变量

props = {
'start_date': 20150101,
'end_date': 20170930,
'universe':'000905.SH',
'fields': ('tot_cur_assets,tot_cur_liab,inventories,pre_pay,deferred_exp, eps_basic,ebit,pe,pb,float_mv,sw1'),
'freq': 1
}

b. 接着创建0-1变量表示某只股票是否被选中,并通过add_formula将变量添加到dataview中

  • 市盈率(pe ratio)低于 20
  • 市净率(pb ratio)低于 2
  • 同比每股收益增长率(inc_earning_per_share)大于 0
  • 税前同比利润增长率(inc_profit_before_tax)大于 0
  • 流动比率(current_ratio)大于 2
  • 速动比率(quick_ratio)大于 1
factor_formula = 'pe < 20'
dv.add_formula('pe_condition', factor_formula, is_quarterly=False)
factor_formula = 'pb < 2'
dv.add_formula('pb_condition', factor_formula, is_quarterly=False)
factor_formula = 'Return(eps_basic, 4) > 0'
dv.add_formula('eps_condition', factor_formula, is_quarterly=True)
factor_formula = 'Return(ebit, 4) > 0'
dv.add_formula('ebit_condition', factor_formula, is_quarterly=True)
factor_formula = 'tot_cur_assets/tot_cur_liab > 2'
dv.add_formula('current_condition', factor_formula, is_quarterly=True)
factor_formula = '(tot_cur_assets - inventories - pre_pay - deferred_exp)/tot_cur_liab > 1'
dv.add_formula('quick_condition', factor_formula, is_quarterly=True)

需要注意的是,涉及到的财务数据若不在secDailyIndicator表中,需将is_quarterly设置为True,表示该变量为季度数据。

  1. 由于第二步中需要按流通市值排序,我们将这一变量也放入dataview中
dv.add_formula('mv_rank', 'Rank(float_mv)', is_quarterly=False)

二. 条件选股

条件选股在my_selector函数中完成:

  • 首先我们将上一步计算出的0/1变量提取出来,格式为Series
  • 接着我们对所有变量取交集,选中的股票设为1,未选中的设为0,并将结果通过DataFrame形式返回
def my_selector(context, user_options=None):
    #
    pb_selector      = context.snapshot['pb_condition']
    pe_selector      = context.snapshot['pe_condition']
    eps_selector     = context.snapshot['eps_condition']
    ebit_selector    = context.snapshot['ebit_condition']
    current_selector = context.snapshot['current_condition']
    quick_selector   = context.snapshot['quick_condition']
    #
    merge = pd.concat([pb_selector, pe_selector, eps_selector,     ebit_selector, current_selector, quick_selector], axis=1)

    result = np.all(merge, axis=1)
    mask = np.all(merge.isnull().values, axis=1)
    result[mask] = False
    return pd.DataFrame(result, index=merge.index, columns=['selector'])

三、按市值排序

按市值排序功能在signal_size函数中完成。我们根据流通市值排序变量’mv_rank’对所有股票进行排序,并选出市值最小的5只股票。

def signal_size(context, user_options = None):
    mv_rank = context.snapshot_sub['mv_rank']
    s = np.sort(mv_rank.values)[::-1]
    if len(s) > 0:
        critical = s[-5] if len(s) > 5 else np.min(s)
        mask = mv_rank < critical
        mv_rank[mask] = 0.0
        mv_rank[~mask] = 1.0
    return mv_rank

四、回测

我们在test_alpha_strategy_dataview()模块中实现回测功能

1. 载入dataview,设置回测参数

该模块首先载入dataview并允许用户设置回测参数,比如基准指数,起止日期,换仓周期等。

dv = DataView()

fullpath = fileio.join_relative_path('../output/prepared', dv_subfolder_name)
dv.load_dataview(folder=fullpath)

props = {
    "benchmark": "000905.SH",
    "universe": ','.join(dv.symbol),

    "start_date": dv.start_date,
    "end_date": dv.end_date,

    "period": "week",
    "days_delay": 0,

    "init_balance": 1e8,
    "position_ratio": 1.0,
}
2. StockSelector选股模块

接着我们使用StockSelector选股模块,将之前定义的my_selector载入

stock_selector = model.StockSelector
stock_selector.add_filter(name='myselector', func=my_selector)
3. FactorSignalModel模块

在进行条件选股后,使用FactorSignalModel模块对所选股票进行排序

signal_model = model.FactorSignalModel(context)
signal_model.add_signal(name='signalsize', func = signal_size)
4. 策略回测模块

将上面定义的stockSelector和FactorSignalModel载入AlphaStrategy函数进行回测

strategy = AlphaStrategy(
            stock_selector=stock_selector,
            signal_model=signal_model,
            pc_method='factor_value_weight')
5. 启动数据准备及回测模块
t_start = time.time()

test_save_dataview()
test_alpha_strategy_dataview()
test_backtest_analyze()

t3 = time.time() - t_start
print "\n\n\nTime lapsed in total: {:.1f}".format(t3)

五、回测结果

回测的参数如下:

指标
Beta 0.87
Annual Return 0.08
Annual Volatility 0.29
Sharpe Ratio 0.28

回测的净值曲线图如下:

backtestgraham

基于因子IC的多因子选股模型

本策略完整实现代码见 这里

主要介绍基于回测框架实现基于因子IC的因子权重优化模型。

一. 因子IC定义及优化模型

1. 因子IC的定义方法
首先介绍一下因子IC (Information

Coefficient)的定义。传统意义上,因子在某一期的IC为该期因子与股票下期收益率的秩相关系数,即: | $$IC_t = RankCorrelation(\vec{f_t}, \vec{r_{t+1}})$$ | 其中$\vec{f_t}$为所有股票在t期的因子值向量,$\vec{r_{t+1}}$为所有股票在t到t+1期的收益率向量。秩相关系数直接反映了因子的预测能力:IC越高,说明该因子对接下里一期股票收益的预测能力越强。

2. 因子的获取及计算方法

在本示例中我们简单选取了几个因子,更多的因子可以在股票因子数据中找到:

  • Turnover, 换手率
  • BP, Book-to-Market Ratio
  • MOM20, 过去20天收益率
  • LFMV, 对数流通市值

实现过程如下:

a. 首先在数据准备模块save_dataview()中通过props设置数据起止日期,股票版块,以及所需变量

props = {'start_date': 20150101, 'end_date': 20170930, 'universe':
'000905.SH', 'fields': ('turnover,float_mv,close_adj,pe'), 'freq': 1}

b. 接着计算因子,进行标准化和去极值处理后通过add_formula()将因子添加到变量列表中

factor_formula = 'Cutoff(Standardize(turnover / 10000 / float_mv), 2)'
dv.add_formula('TO', factor_formula, is_quarterly=False)

factor_formula = 'Cutoff(Standardize(1/pb), 2)'
dv.add_formula('BP', factor_formula, is_quarterly = False)

factor_formula = 'Cutoff(Standardize(Return(close_adj, 20)), 2)'
dv.add_formula('REVS20', factor_formula, is_quarterly=False)

factor_formula = 'Cutoff(Standardize(Log(float_mv)), 2)'
dv.add_formula('float_mv_factor', factor_formula, is_quarterly=False)
其中Standardize()和Cutoff()均为内置函数。Standardize作用是将序列做去均值并除以标准差的标准化处理,Cutoff作用是将序列中的极值拉回正常范围内。
之后将因子名称保存在外部文件中,以便后续计算使用
factorList = ['TO', 'BP', 'REVS20', 'float_mv_factor']
factorList_adj = [x + '_adj' for x in factorList]
from jaqs.util import fileio
fileio.save_json(factorList_adj, '.../myCustomData.json')

c. 由于多个因子间可能存在多重共线性,我们对因子进行施密特正交化处理,并将处理后的因子添加到变量列表中。

### add the orthogonalized factor to dataview
for trade_date in dv.dates:
    snapshot = dv.get_snapshot(trade_date)
    factorPanel = snapshot[factorList]
    factorPanel = factorPanel.dropna()

    if len(factorPanel) != 0:
        orthfactorPanel = Schmidt(factorPanel)
        orthfactorPanel.columns = [x + '_adj' for x in factorList]

        snapshot = pd.merge(left = snapshot, right = orthfactorPanel,
                            left_index = True, right_index = True, how = 'left')

        for factor in factorList:
            orthFactor_dic[factor][trade_date] = snapshot[factor]

for factor in factorList:
    dv.append_df(pd.DataFrame(orthFactor_dic[factor]).T, field_name = factor + '_adj', is_quarterly=False)
3. 计算因子IC

从dataview中提取所有交易日,在每个交易日计算每个因子的IC

def get_ic(dv):
    """
    Calculate factor IC on all dates and save it in a DataFrame
    :param dv:
    :return: DataFrame recording factor IC on all dates
    """
    factorList = fileio.read_json('.../myCustomData.json')
    ICPanel = {}
    for singleDate in dv.dates:
        singleSnapshot = dv.get_snapshot(singleDate)
        ICPanel[singleDate] = ic_calculation(singleSnapshot, factorList)

    ICPanel = pd.DataFrame(ICPanel).T
    return ICPanel

其中计算IC的函数为ic_calculation()

def ic_calculation(snapshot, factorList):
    """
    Calculate factor IC on single date
    :param snapshot:
    :return: factor IC on single date
    """
    ICresult = []
    for factor in factorList:
        # drop na
        factorPanel = snapshot[[factor, 'NextRet']]
        factorPanel = factorPanel.dropna()
        ic, _ = stats.spearmanr(factorPanel[factor], factorPanel['NextRet'])
        ICresult.append(ic)
    return ICresult
4. 因子权重优化
我们将因子IR设为因子权重优化的目标,因子IR(信息比)定义为因子IC的均值与因子IC的标准差的比值,IR值越高越好。假设我们有k个因子,其IC的均值向量为$\vec{IC}=(\overline{IC_1},

\overline{IC_2}, \cdots, \overline{IC_k},)’$,相应协方差矩阵为$\Sigma$,因子的权重向量为$\vec{v}=(\overline{V_1}, \overline{V_2},\cdots, \overline{V_k})’$。则所有因子的复合IR值为 | $$IR = \frac{\vec{v}’\vec{IC}}{\sqrt{\vec{v}’ \Sigma \vec{v}}}$$ | 我们的目标是通过调整$\vec{v}$使IR最大化。经简单计算我们可以直接求出$\vec{v}$的解析解,则最优权重向量为: | $$\vec{v}^* = \Sigma^{-1}\vec{IC}$$ | 具体实现过程如下:

def store_ic_weight():
    """
    Calculate IC weight and save it to file
    """
    dv = DataView()
    fullpath = fileio.join_relative_path('../output/prepared', dv_subfolder_name)
    dv.load_dataview(folder=fullpath)

    w = get_ic_weight(dv)

    store = pd.HDFStore('/home/lli/ic_weight.hd5')
    store['ic_weight'] = w
    store.close()

其中使用到了get_ic_weight()函数,其作用是计算每个因子IC对应的weight

def get_ic_weight(dv):
    """
    Calculate factor IC weight on all dates and save it in a DataFrame
    :param dv: dataview
    :return: DataFrame containing the factor IC weight, with trading date as index and factor name as columns
    """
    ICPanel = get_ic(dv)
    ICPanel = ICPanel.dropna()
    N = 10
    IC_weight_Panel = {}
    for i in range(N, len(ICPanel)):
        ICPanel_sub = ICPanel.iloc[i-N:i, :]
        ic_weight = ic_weight_calculation(ICPanel_sub)
        IC_weight_Panel[ICPanel.index[i]] = ic_weight
    IC_weight_Panel = pd.DataFrame(IC_weight_Panel).T
    return IC_weight_Panel

我们在计算weight时需要确定一个rolling window,这里选择N=10。

def ic_weight_calculation(icpanel):
    """
    Calculate factor IC weight on single date
    :param icpanel:
    :return: a vector containing all factor IC weight
    """
    mat = np.mat(icpanel.cov())
    mat = nlg.inv(mat)
    weight = mat * np.mat(icpanel.mean()).reshape(len(mat), 1)
    weight = np.array(weight.reshape(len(weight), ))[0]
    return weight

二. 基于因子IC及相应权重的选股模型

在介绍选股模型的具体实现之前,我们首先熟悉一下策略模块test_alpha_strategy_dataview()。该模块的功能是基于dataview对具体策略进行回测。

1. 载入dataview,设置回测参数

该模块首先载入dataview并允许用户设置回测参数,比如基准指数,起止日期,换仓周期等。

dv = DataView()

fullpath = fileio.join_relative_path('../output/prepared', dv_subfolder_name)
dv.load_dataview(folder=fullpath)

props = {
    "benchmark": "000905.SH",
    "universe": ','.join(dv.symbol),

    "start_date": dv.start_date,
    "end_date": dv.end_date,

    "period": "week",
    "days_delay": 0,

    "init_balance": 1e8,
    "position_ratio": 1.0,
}
2. 载入context

context是一个类用来保存一些中间结果,可在程序中任意位置调用,并将之前算出的ic_weight放入context中。

context = model.Context(dataview=dv, gateway=gateway)
store = pd.HDFStore('.../ic_weight.hd5')
context.ic_weight = store['ic_weight']
store.close()
3. StockSelector选股模块

接着我们使用StockSelector选股模块。基于因子IC及相应权重的选股过程在my_selector中实现。

stock_selector = model.StockSelector(context)
stock_selector.add_filter(name='myselector', func=my_selector)

a.首先载入因子ic的权重context.ic_weight,回测日期列表context.trade_date记忆因子名称列表factorList

ic_weight = context.ic_weight
t_date = context.trade_date
current_ic_weight = np.mat(ic_weight.loc[t_date,]).reshape(-1,1)
factorList = fileio.read_json('.../myCustomData.json')

factorPanel = {}
for factor in factorList:
    factorPanel[factor] = context.snapshot[factor]

factorPanel = pd.DataFrame(factorPanel)

b.接着根据各因子IC的权重,对当天各股票的IC值进行加权求和,选出得分最高的前30只股票。最后返回一个列表,1代表选中,0代表未选中。

factorResult = pd.DataFrame(np.mat(factorPanel) * np.mat(current_ic_weight), index = factorPanel.index)

factorResult = factorResult.fillna(-9999)
s = factorResult.sort_values(0)[::-1]

critical = s.values[30]
mask = factorResult > critical
factorResult[mask] = 1.0
factorResult[~mask] = 0.0
4. 启动数据准备及回测模块
t_start = time.time()

test_save_dataview()
store_ic_weight()
test_alpha_strategy_dataview()
test_backtest_analyze()

t3 = time.time() - t_start
print "\n\n\nTime lapsed in total: {:.1f}".format(t3)

三、回测结果

回测的参数如下:

指标
Beta 0.92
Annual Return 0.19
Annual Volatility 0.16
Sharpe Ratio 1.21
回测的净值曲线图如下:
backtesticmodel

四、参考文献

  1. 基于因子IC的多因子模型
  2. 《安信证券-多因子系列报告之一:基于因子IC的多因子模型》

Calendar Spread交易策略

本策略完整实现代码见 这里

本帖主要介绍了基于事件驱动回测框架实现calendar spread交易策略。

一. 策略介绍

在商品期货市场中,同一期货品种不同到期月份合约间的价格在短期内的相关性较稳定。该策略就利用这一特性,在跨期基差稳定上升时进场做多基差,反之做空基差。
在本文中我们选择了天然橡胶作为交易品种,时间范围从2017年7月到2017年11月,选择的合约为RU1801.SHF和RU1805.SHF,将基差定义为近期合约价格减去远期合约价格。

二. 参数准备

我们在test_spread_commodity.py文件中的test_spread_trading()函数中设置策略所需参数,例如交易标的,策略开始日期,终止日期,换仓频率等。

props = {
         "symbol"                : "ru1801.SHF,ru1805.SHF",
         "start_date"            : 20170701,
         "end_date"              : 20171109,
         "bar_type"              : "DAILY",
         "init_balance"          : 2e4,
         "bufferSize"            : 20,
         "future_commission_rate": 0.00002,
         "stock_commission_rate" : 0.0001,
         "stock_tax_rate"        : 0.0000
         }

三. 策略实现

策略实现全部在spread_commodity.py中完成,创建名为SpreadCommodity()的class继承EventDrivenStrategy,具体分为以下几个步骤:

1. 策略初始化

这里将后续步骤所需要的变量都创建好并初始化。

def __init__(self):
    EventDrivenStrategy.__init__(self)

    self.symbol      = ''
    self.s1          = ''
    self.s2          = ''
    self.quote1      = None
    self.quote2      = None

    self.bufferSize  = 0
    self.bufferCount = 0
    self.spreadList  = ''
2. 从props中得到变量值

这里将props中设置的参数传入。其中,self.spreadList记录了最近$n$天的spread值,$n$是由self.bufferSize确定的。

def init_from_config(self, props):
    super(SpreadCommodity, self).init_from_config(props)
    self.symbol       = props.get('symbol')
    self.init_balance = props.get('init_balance')
    self.bufferSize   = props.get('bufferSize')
    self.s1, self.s2  = self.symbol.split(',')
    self.spreadList = np.zeros(self.bufferSize)
3. 策略实现
策略的主体部分在on_bar()函数中实现。因为我们选择每日调仓,所以会在每天调用on_bar()函数。
首先将两个合约的quote放入self.quote1和self.quote2中,并计算当天的spread
q1 = quote_dic.get(self.s1)
q2 = quote_dic.get(self.s2)
self.quote1 = q1
self.quote2 = q2
spread = q1.close - q2.close

接着更新self.spreadList。因为self.spreadList为固定长度,更新方法为将第2个到最后1个元素向左平移1位,并将当前的spread放在队列末尾。

self.spreadList[0:self.bufferSize - 1] = self.spreadList[1:self.bufferSize]
self.spreadList[-1] = spread
self.bufferCount += 1

接着将self.spreadList中的数据对其对应的编号(例如从1到20)做regression,观察回归系数的pvalue是否显著,比如小于0.05。如果结果不显著,则不对仓位进行操作;如果结果显著,再判断系数符号,如果系数大于0则做多spread,反之做空spread。

X, y = np.array(range(self.bufferSize)), np.array(self.spreadList)
X = X.reshape(-1, 1)
y = y.reshape(-1, 1)
X = sm.add_constant(X)

est = sm.OLS(y, X)
est = est.fit()

if est.pvalues[1] < 0.05:
    if est.params[1] < 0:
        self.short_spread(q1, q2)
    else:
        self.long_spread(q1, q2)

四. 回测结果

calendarspreadresult

商品期货的Dual Thrust日内交易策略

本策略完整实现代码见 这里

本帖主要介绍了基于事件驱动回测框架实现Dual Thrust日内交易策略。

一. 策略介绍

Dual

Thrust是一个趋势跟踪策略,具有简单易用、适用度广的特点,其思路简单、参数较少,配合不同的参数、止盈止损和仓位管理,可以为投资者带来长期稳定的收益,被投资者广泛应用于股票、货币、贵金属、债券、能源及股指期货市场等。 | 在本文中,我们将Dual Thrust应用于商品期货市场中。 | 简而言之,该策略的逻辑原型是较为常见的开盘区间突破策略,以今日开盘价加减一定比例确定上下轨。日内突破上轨时平空做多,突破下轨时平多做空。 | 在Dual Thrust交易系统中,对于震荡区间的定义非常关键,这也是该交易系统的核心和精髓。Dual Thrust系统使用 | $$Range = Max(HH-LC,HC-LL)$$ | 来描述震荡区间的大小。其中HH是过去N日High的最大值,LC是N日Close的最小值,HC是N日Close的最大值,LL是N日Low的最小值。

二. 参数准备

我们在test_spread_commodity.py文件中的test_spread_trading()函数中设置策略所需参数,例如交易标的,策略开始日期,终止日期,换仓频率等,其中$k1,k2$为确定突破区间上下限的参数。

props = {
         "symbol"                : "rb1710.SHF",
         "start_date"            : 20170510,
         "end_date"              : 20170930,
         "buffersize"            : 2,
         "k1"                    : 0.7,
         "k2"                    : 0.7,
         "bar_type"              : "MIN",
         "init_balance"          : 1e5,
         "future_commission_rate": 0.00002,
         "stock_commission_rate" : 0.0001,
         "stock_tax_rate"        : 0.0000
         }

三. 策略实现

策略实现全部在DualThrust.py中完成,创建名为DualThrustStrategy()的class继承EventDrivenStrategy,具体分为以下几个步骤:

1. 策略初始化

这里将后续步骤所需要的变量都创建好并初始化。其中self.bufferSize为窗口期长度,self.pos记录了实时仓位,self.Upper和self.Lower记录了突破区间上下限。

def __init__(self):
    EventDrivenStrategy.__init__(self)
    self.symbol      = ''
    self.quote       = None
    self.bufferCount = 0
    self.bufferSize  = ''
    self.high_list   = ''
    self.close_list  = ''
    self.low_list    = ''
    self.open_list   = ''
    self.k1          = ''
    self.k2          = ''
    self.pos         = 0
    self.Upper       = 0.0
    self.Lower       = 0.0
2. 从props中得到变量值

这里将props中设置的参数传入。其中,self.high_list为固定长度的list,保存了最近$N$天的日最高价,其他变量类似。

def init_from_config(self, props):
    super(DualThrustStrategy, self).init_from_config(props)

    self.symbol       = props.get('symbol')
    self.init_balance = props.get('init_balance')
    self.bufferSize   = props.get('buffersize')
    self.k1           = props.get('k1')
    self.k2           = props.get('k2')
    self.high_list    = np.zeros(self.bufferSize)
    self.close_list   = np.zeros(self.bufferSize)
    self.low_list     = np.zeros(self.bufferSize)
    self.open_list    = np.zeros(self.bufferSize)
3. 策略实现

在每天开始时,首先调用initialize()函数,得到当天的open,close,high和low的值,并对应放入list中。

def initialize(self):
    self.bufferCount += 1

    # get the trading date
    td = self.ctx.trade_date
    ds = self.ctx.data_api

    # get the daily data
    df, msg = ds.daily(symbol=self.symbol, start_date=td, end_date=td)

    # put the daily value into the corresponding list
    self.open_list[0:self.bufferSize - 1] =
                   self.open_list[1:self.bufferSize]
    self.open_list[-1] = df.high
    self.high_list[0:self.bufferSize - 1] =
                   self.high_list[1:self.bufferSize]
    self.high_list[-1] = df.high
    self.close_list[0:self.bufferSize - 1] =
                   self.close_list[1:self.bufferSize]
    self.close_list[-1] = df.close
    self.low_list[0:self.bufferSize - 1] =
                   self.low_list[1:self.bufferSize]
    self.low_list[-1] = df.low

策略的主体部分在on_bar()函数中实现。因为我们选择分钟级回测,所以会在每分钟调用on_bar()函数。

首先取到当日的quote,并计算过去$N$天的HH,HC,LC和LL,并据此计算Range和上下限Upper,Lower

HH = max(self.high_list[:-1])
HC = max(self.close_list[:-1])
LC = min(self.close_list[:-1])
LL = min(self.low_list[:-1])

Range = max(HH - LC, HC - LL)
Upper = self.open_list[-1] + self.k1 * Range
Lower = self.open_list[-1] - self.k2 * Range
几个关键变量的意义如下图所示:
illustrationdual

我们的交易时间段为早上9:01:00到下午14:28:00,交易的逻辑为:

  1. 当分钟Bar的open向上突破上轨时,如果当时持有空单,则先平仓,再开多单;如果没有仓位,则直接开多单;

  2. 当分钟Bar的open向下突破下轨时,如果当时持有多单,则先平仓,再开空单;如果没有仓位,则直接开空单;

    if self.pos == 0:
        if self.quote.open > Upper:
            self.short(self.quote, self.quote.close, 1)
        elif self.quote.open < Lower:
            self.buy(self.quote, self.quote.close, 1)
    elif self.pos < 0:
        if self.quote.open < Lower:
            self.cover(self.quote, self.quote.close, 1)
            self.long(self.quote, self.quote.close, 1)
    else:
        if self.quote.open > Upper:
            self.sell(self.quote, self.quote.close, 1)
            self.short(self.quote, self.quote.close, 1)
    

    由于我们限制该策略为日内策略,故当交易时间超过14:28:00时,进行强行平仓。

    elif self.quote.time > 142800:
        if self.pos > 0:
            self.sell(self.quote, self.quote.close, 1)
        elif self.pos < 0:
            self.cover(self.quote, self.quote.close, 1)
    

    我们在下单后,可能由于市场剧烈变动导致未成交,因此在on_trade_ind()函数中记录具体成交情况,当空单成交时,self.pos减一,当多单成交时,self.pos加一。

    def on_trade_ind(self, ind):
        if ind.entrust_action == 'sell' or ind.entrust_action == 'short':
            self.pos -= 1
        elif ind.entrust_action == 'buy' or ind.entrust_action == 'cover':
            self.pos += 1
        print(ind)
    

四. 回测结果

回测结果如下图所示:
dualthrustresult

五、参考文献

版块内股票轮动策略

本策略完整实现代码见 这里

本帖主要介绍了基于事件驱动回测框架实现版块内股票轮动策略。

一. 策略介绍

该轮动策略如下:在策略开始执行时等价值买入版块内所有股票,每天 $t$

计算各股在过去$m$天相对板块指数的收益率 | $$R^A_{i,t} = (lnP_{i,t}-lnP_{i,t-m})-(lnP_{B,t}-lnP_{B,t-m})$$ | 其中$P_{i,t}$为股票$i$在$t$天的收盘价,$P_{B,t}$为板块指数在$t$天的收盘价。每天检查持仓,若持仓股$R^A_{i,t}$超过过去$n$天均值加$k$倍标准差,则卖出;反之,若有未持仓股$R^A_{i,t}$小于过去$n$天均值减$k$倍标准差,则买入。

二. 参数准备

我们在test_roll_trading.py文件中的test_strategy()函数中设置策略所需参数。首先确定策略开始日期,终止日期以及板块指数。在本文中,我们选择券商指数399975.SZ,并听过data_service得到该指数中所有成份股。

start_date = 20150901
end_date = 20171030
index = '399975.SZ'
data_service = RemoteDataService()
symbol_list = data_service.get_index_comp(index, start_date, start_date)

接着在props中设置参数

symbol_list.append(index)
props = {"symbol": ','.join(symbol_list),
         "start_date": start_date,
         "end_date": end_date,
         "bar_type": "DAILY",
         "init_balance": 1e7,
         "std multiplier": 1.5,
         "m": 10,
         "n": 60,
         "future_commission_rate": 0.00002,
         "stock_commission_rate": 0.0001,
         "stock_tax_rate": 0.0000}

我们可以在bar_type中设置换仓周期,现在支持分钟和日换仓,本例中选择每日调仓。

三. 策略实现

策略实现全部在roll.py中完成,创建名为RollStrategy()的class继承EventDrivenStrategy,具体分为以下几个步骤:

1. 策略初始化

这里将后续步骤所需要的变量都创建好并初始化。

def __init__(self):
    EventDrivenStrategy.__init__(self)
    self.symbol = ''
    self.benchmark_symbol = ''
    self.quotelist = ''
    self.startdate = ''
    self.bufferSize = 0
    self.rollingWindow = 0
    self.bufferCount = 0
    self.bufferCount2 = 0
    self.closeArray = {}
    self.activeReturnArray = {}
    self.std = ''
    self.balance = ''
    self.multiplier = 1.0
    self.std_multiplier = 0.0
2. 从props中得到变量值

这里将props中设置的参数传入。其中,self.closeArray和self.activeReturnArray数据类型为dict,key为股票代码,value分别为最近$m$天的收盘价和最近$n$天的active return。

def init_from_config(self, props):
    super(RollStrategy, self).init_from_config(props)
    self.symbol = props.get('symbol').split(',')
    self.init_balance = props.get('init_balance')
    self.startdate = props.get('start_date')
    self.std_multiplier = props.get('std multiplier')
    self.bufferSize = props.get('n')
    self.rollingWindow = props.get('m')
    self.benchmark_symbol = self.symbol[-1]
    self.balance = self.init_balance

    for s in self.symbol:
        self.closeArray[s] = np.zeros(self.rollingWindow)
        self.activeReturnArray[s] = np.zeros(self.bufferSize)
3. 策略实现
策略的主体部分在on_bar()函数中实现。因为我们选择每日调仓,所以会在每天调用on_bar()函数。
首先将版块内所有股票的quote放入self.quotelist中,
self.quotelist = []
for s in self.symbol:
    self.quotelist.append(quote_dic.get(s))

接着对每只股票更新self.closeArray。因为self.closeArray为固定长度,更新方法为将第2个到最后1个元素向左平移1位,并将当前quote中最新的close放在末尾。

for stock in self.quotelist:
    self.closeArray[stock.symbol][0:self.rollingWindow - 1] =  self.closeArray[stock.symbol][1:self.rollingWindow]
    self.closeArray[stock.symbol][-1] = stock.close

计算每只股票在过去$m$天的active return,存入self.activeReturnArray。

### calculate active return for each stock
benchmarkReturn = np.log(self.closeArray[self.benchmark_symbol][-1])
                 -np.log(self.closeArray[self.benchmark_symbol][0])
for stock in self.quotelist:
    stockReturn = np.log(self.closeArray[stock.symbol][-1])
                 -np.log(self.closeArray[stock.symbol][0])
    activeReturn = stockReturn - benchmarkReturn
    self.activeReturnArray[stock.symbol][0:self.bufferSize - 1]
                 = self.activeReturnArray[stock.symbol][1:self.bufferSize]
    self.activeReturnArray[stock.symbol][-1] = activeReturn

在策略首次执行时,默认等价值持有版块中所有的股票。

### On the first day of strategy, buy in equal value stock in the universe
stockvalue = self.balance/len(self.symbol)
for stock in self.quotelist:
    if stock.symbol != self.benchmark_symbol:
        self.buy(stock, stock.close,
                 np.floor(stockvalue/stock.close/self.multiplier))

在其他日期,当策略开始执行时,首先通过self.pm.holding_securities检查持有的股票代码,并与版块成分比较确定未持有的股票代码。

stockholdings = self.pm.holding_securities
noholdings = set(self.symbol) - stockholdings
stockvalue = self.balance/len(noholdings)

对于已持有的股票,计算最近$m$天的active return,若超过self.activeReturnArray均值的一定范围,就将该股票卖出。

for stock in list(stockholdings):
    curRet = self.activeReturnArray[stock][-1]
    avgRet = np.mean(self.activeReturnArray[stock][:-1])
    stdRet = np.std(self.activeReturnArray[stock][:-1])
    if curRet >= avgRet + self.std_multiplier * stdRet:
        curPosition = self.pm.positions[stock].curr_size
        stock_quote = quote_dic.get(stock)
        self.sell(stock_quote, stock_quote.close, curPosition)

反之,对于未持有的股票,若其active return低于均值的一定范围,就将其买入。

for stock in list(noholdings):
    curRet = self.activeReturnArray[stock][-1]
    avgRet = np.mean(self.activeReturnArray[stock][:-1])
    stdRet = np.std(self.activeReturnArray[stock][:-1])
    if curRet < avgRet - self.std_multiplier * stdRet:
        stock_quote = quote_dic.get(stock)
        self.buy(stock_quote, stock_quote.close,
                 np.floor(stockvalue/stock_quote.close/self.multiplier))

此外,我们在框架中on_trade_ind()中实现了仓位管理。在策略初始化时,我们将组合中的现金设为初始资金。

self.init_balance = props.get('init_balance')
self.balance = self.init_balance

此后,每买入一只股票,我们将self.balance减去相应市值;每卖出一只股票,将self.balance加上相应市值。

def on_trade_ind(self, ind):
    if ind.entrust_action == 'buy':
        self.balance -= ind.fill_price * ind.fill_size * self.multiplier
    elif ind.entrust_action == 'sell':
        self.balance += ind.fill_price * ind.fill_size * self.multiplier
    print(ind)

四. 回测结果

该策略的回测结果如下图所示:
rollwithinsectorresult
回测的参数如下:
| 指标 | 值 |
| ——– | –: |
| Beta | 0.70 |
| Annual Return | 0.05 |
| Annual Volatility| 0.17 |
| Sharpe Ratio | 0.29 |

交易API

本产品提供了交易API,可用于委托、撤单、查询账户、查询持仓、查询委托、查询成交、批量下单等,以下是用法

导入接口

在python程序里面导入module,然后用注册的用户帐号登录就可以使用交易接口进行交易。

引入模块

from jaqs.trade.tradeapi import TradeApi

登录

tapi = TradeApi(addr="tcp://gw.quantos.org:8901") # tcp://gw.quantos.org:8901是仿真系统地址
user_info, msg = tapi.login("demo", "666666")     # 示例账户,用户需要改为自己注册的账户

使用用户名、密码登陆, 如果成功,返回用户可用的策略帐号列表

定义并设置回调接口

TradeApi通过回调函数方式通知用户事件。事件包括三种:订单状态、成交回报、委托任务执行状态。

  • 订单状态推送

    def on_orderstatus(order):
        print "on_orderstatus:" #, order
        for key in order:    print "%20s : %s" % (key, str(order[key]))
        print ""
    
  • 成交回报推送

    def on_trade(trade):
        print "on_trade:"
        for key in trade:    print "%20s : %s" % (key, str(trade[key]))
        print ""
    
  • 委托任务执行状态推送,通常可以忽略该回调函数

    def on_taskstatus(task):
        print "on_taskstatus:"
        for key in task:    print "%20s : %s" % (key, str(task[key]))
        print ""
    

设置回调函数

tapi.set_ordstatus_callback(on_orderstatus)
tapi.set_trade_callback(on_trade)
tapi.set_task_callback(on_taskstatus)

选择使用的策略帐号

该函数成功后,下单、查持仓等和策略帐号有关的操作都和该策略帐号绑定。
没有必要每次下单、查询都调用该函数。重复调用该函数可以选择新的策略帐号。
如果成功,返回(strategy_id, msg)
否则返回 (0, err_msg)
sid, msg = tapi.use_strategy(1)
print "msg: ", msg
print "sid: ", sid

查询账户信息

返回当前的策略帐号的账户资金信息。

df, msg = tapi.query_account()
print "msg: ", msg
print df

查询Portfolio

返回当前的策略帐号的Universe中所有标的的净持仓,包括持仓为0的标的。

df, msg = tapi.query_portfolio()
print "msg: ", msg
print df

查询当前策略帐号的所有持仓

和 query_portfolio接口不一样。如果莫个期货合约 Long,

Short两个方向都有持仓,这里是返回两条记录 | 返回的 size 不带方向,全部为 正

df, msg = tapi.query_position()
print "msg: ", msg
print df

单标的下单

task_id, msg = place_order(code, action, price, size )
action: Buy, Short, Cover, Sell, CoverToday, CoverYesterday,

SellToday, SellYesterday | 返回 task_id

task_id, msg = tapi.place_order("000025.SZ", "Buy", 57, 100)
print "msg:", msg
print "task_id:", task_id

撤单

cancel_order(task_id)

tapi.cancel_order(task_id)

查询委托

返回委托信息

df, msg = tapi.query_order(task_id = task_id, format = 'pandas')

查询成交

返回成交信息

df, msg = tapi.query_trade(task_id = task_id, format = 'pandas')

目标持仓下单

#  goal_protfolio
#  参数:目标持仓
#  返回:(result, msg)
#     result:  成功或失败
#     msg:     错误原因
#  注意:目标持仓中必须包括所有的代码的持仓,即使不修改

# 先查询当前的持仓,
portfolio, msg = tapi.goal_portfolio(goal, algo, algo_param)
print "msg", msg
print "portfolio", portfolio

portfolio撤单

# stop_portfolio
# 撤单, 撤销所有portfolio订单
tapi.stop_portfolio()

批量下单(1)

place_batch_order,指定绝对size和交易类型

# place_batch_order
# 返回task_id, msg。
orders = [
    {"security":"600030.SH", "action" : "Buy", "price": 16, "size":1000},
    {"security":"600519.SH", "action" : "Buy", "price": 320, "size":1000},
    ]

task_id, msg = tapi.place_batch_order(orders)
print task_id
print msg

批量下单(2)

basket_order,指定变化量,不指定交易方向,由系统根据正负号来确定

# 批量下单2:basket_order
#
# 返回task_id, msg。
orders = [
    {"security":"601857.SH", "ref_price": 8.40, "inc_size":1000},
    {"security":"601997.SH",  "ref_price": 14.540, "inc_size":20000},
    ]

task_id, msg = tapi.basket_order(orders)
print task_id
print msg

实盘交易

使用JAQS进行回测与实盘运行的代码具有高一致性,回测满意后,只需以下几点改动,即可接入实盘/模拟撮合:

  1. 使用实盘交易的交易接口:将BacktestTradeApi替换为RealTimeTradeApi
  2. 使用实盘交易主程序:将EventBacktestInstance替换为EventLiveTradeInstance
  3. 在数据接口RemoteDataService中订阅所要交易品种的行情
  4. 在主程序最后添加time.sleep(9999999). 保证在事件循环运行中,主程序不会提前终止
  5. 实时行情均为逐笔或Tick数据,即使订阅多个品种,行情数据仍会逐个到达strategy.on_tick()函数

这里我们以双均线策略为例,展示实盘运行的代码:

props = {'symbol': 'rb1801.SHF'}
tapi = RealTimeTradeApi()
ins = EventLiveTradeInstance()

tapi.use_strategy(3)

ds = RemoteDataService()
strat = DoubleMaStrategy()
pm = PortfolioManager()

context = model.Context(data_api=ds, trade_api=tapi,
                        instance=ins, strategy=strat, pm=pm)

ins.init_from_config(props)
ds.subscribe(props['symbol'])

ins.run()

time.sleep(999999)

程序运行后,行情、策略、交易会在不同的线程内运行,我们只需要在on_tick中进行下单,在on_trade, on_order_status中处理交易回报即可。

正常收到行情如下:

rb1801.SHF  20171116-211319500       (BID)    229@3889.00 | 3891.00@12     (ASK)
Fast MA = 3889.89     Slow MA = 3889.81
rb1801.SHF  20171116-211320000       (BID)    224@3889.00 | 3891.00@13     (ASK)
Fast MA = 3889.93     Slow MA = 3889.83
rb1801.SHF  20171116-211320500       (BID)    223@3889.00 | 3891.00@5      (ASK)
Fast MA = 3889.89     Slow MA = 3889.85
rb1801.SHF  20171116-211321000       (BID)    223@3889.00 | 3891.00@5      (ASK)
Fast MA = 3889.93     Slow MA = 3889.88
rb1801.SHF  20171116-211321500       (BID)     24@3890.00 | 3891.00@5      (ASK)
Fast MA = 3890.00     Slow MA = 3889.92

如果发生下单、成交,则会收到如下回报:

rb1801.SHF  20171116-211319000       (BID)    230@3889.00 | 3891.00@6      (ASK)
Fast MA = 3889.93     Slow MA = 3889.79

Strategy on order status:
New       |  20171115(  211319) Buy    rb1801.SHF@3.000  size = 1

Strategy on order status:
Accepted  |  20171115(  211319) Buy    rb1801.SHF@3.000  size = 1

Strategy on trade:
20171115(  211319) Buy    rb1801.SHF@3.000  size = 1

Strategy on order status:
Filled    |  20171115(  211319) Buy    rb1801.SHF@3.000  size = 1

Strategy on task ind:
task_id = 81116000001  |  task_status = Stopped  |  task_algo =
task_msg =
DONE Execution Report (00'00"039):
+---------+------+----------+------+----+---------+-----+----------+----+---------+------------+-------+-------+--------+---------+
|underlyer| ba_id|    symbol|jzcode|side|is_filled|price|fill_price|size|fill_size|pending_size|cancels|rejects|entrusts| duration|
+---------+------+----------+------+----+---------+-----+----------+----+---------+------------+-------+-------+--------+---------+
|        0|800000|rb1801.SHF| 27509|Long|        Y|  3.0|    3.0000|   1|        1|           0|      0|      0|       1|00'00"033|
+---------+------+----------+------+----+---------+-----+----------+----+---------+------------+-------+-------+--------+---------+