使用jqdata和hikyuu平台进行C /python混合策略编写的方法

很多时候为了运行复杂的策略用python速度会很慢,而核心部分用C 编写可以大幅提升策略的运行速度。另外通达信、金字塔等主流证券软件都支持C 的dll库,而且可以很方便地图形化展示策略结果,那么策略核心部分用C 编写成dll库也是一种通用的跨平台方案。

而传统的python对C 库调用方法,需要自己编写很多封装函数,且聚宽的策略回测平台本身也不支持调用本地的C 库。
这时可以借助一个开源的第三方平台hikyuu来方便地完成该需求。将jqdata与hikyuu整合起来实现C /python混合编程。
首先我们需要在hikyuu的C 工程文件中添加自己的策略代码,自己的策略代码可以作为自定义指标的一部分。

在hikyuu_msvc10工程下增加一个指标,首先在indicator/crt目录下增加一个策略的包装头文件,例如一个移动平均线的策略:

 
  1. #ifndef EMA_H_

  2. #define EMA_H_

  3.  
  4. #include "../Indicator.h"

  5.  
  6. namespace hku {

  7. /**

  8.  * 指数移动平均线(Exponential Moving Average)

  9. * @param n 计算均值的周期窗口,必须为大于0的整数

  10.  * @ingroup Indicator

  11.  */

  12.  
  13. Indicator HKU_API EMA(int n = 22);

  14.  
  15. /**

  16.  * 指数移动平均线(Exponential Moving Average)

  17.  * @param data 待计算的源数据

  18.  * @param n 计算均值的周期窗口,必须为大于0的整数

  19.  * @ingroup Indicator

  20.  */

  21.  
  22. Indicator HKU_API EMA(const Indicator& data, int n = 22);

  23. } /* namespace */

  24.  
  25. #endif /* EMA_H_ */

  26.  
  27.  
  28.  
  29. 然后在indicator/imp目录中增加这个策略的实现类,包含头文件和实现文件:

  30.  
  31. #ifndef EMA_H_

  32. #define EMA_H_

  33.  
  34. #include "../Indicator.h"

  35.  
  36. namespace hku {

  37.  
  38. /*

  39.  * 指数移动平均线(Exponential Moving Average)

  40.  * 参数: n: 计算均值的周期窗口,必须为大于0的整数

  41.  * 抛弃数 = 0

  42.  */

  43.  
  44. class Ema: public IndicatorImp {

  45.     INDICATOR_IMP(Ema)

  46.     INDICATOR_IMP_NO_PRIVATE_MEMBER_SERIALIZATION

  47. public:

  48.     Ema();

  49.     virtual ~Ema();

  50. };

  51.  
  52. } /* namespace hku */

  53.  
  54. #endif /* EMA_H_ */

  55.  
  56.  
  57.  
  58. #include "Ema.h"

  59.  
  60. namespace hku {

  61. Ema::Ema(): IndicatorImp("EMA", 1) {

  62.     setParam("n", 22);

  63. }

  64.  
  65. Ema::~Ema() {

  66.  
  67. }

  68.  
  69. bool Ema::check() {

  70.     int n = getParam("n");

  71. &nbsp;&nbsp;&nbsp; if (n <= 0) {

  72. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HKU_ERROR("Invalid param[n] must > 0 ! [Ema::check]");

  73. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;

  74. &nbsp;&nbsp;&nbsp; }

  75.  
  76. &nbsp;&nbsp;&nbsp; return true;

  77. }

  78.  
  79. void Ema::_calculate(const Indicator&amp; indicator) {

  80. &nbsp;&nbsp;&nbsp; size_t total = indicator.size();

  81. &nbsp;&nbsp;&nbsp; int n = getParam("n");

  82. &nbsp;&nbsp;&nbsp; m_discard = indicator.discard();

  83. &nbsp;&nbsp;&nbsp; if (total <= m_discard) {

  84. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;

  85. &nbsp;&nbsp;&nbsp; }

  86.  
  87. &nbsp;&nbsp;&nbsp; size_t startPos = discard();

  88. &nbsp;&nbsp;&nbsp; price_t ema = indicator[startPos];

  89. &nbsp;&nbsp;&nbsp; _set(ema, startPos);

  90. &nbsp;&nbsp;&nbsp; price_t multiplier = 2.0 / (n 1);

  91. &nbsp;&nbsp;&nbsp; for (size_t i = startPos 1; i < total; i) {

  92. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ema = (indicator[i] - ema) * multiplier ema;

  93. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _set(ema, i);

  94. &nbsp;&nbsp;&nbsp; }

  95. }

  96.  
  97. Indicator HKU_API EMA(int n) {

  98. &nbsp;&nbsp;&nbsp; IndicatorImpPtr p = make_shared();

  99. &nbsp;&nbsp;&nbsp; p->setParam("n", n);

  100. &nbsp;&nbsp;&nbsp; return Indicator(p);

  101. }

  102.  
  103. Indicator HKU_API EMA(const Indicator&amp; data, int n) {

  104. &nbsp;&nbsp;&nbsp; IndicatorImpPtr p = make_shared();

  105. &nbsp;&nbsp;&nbsp; p->setParam("n", n);

  106. &nbsp;&nbsp;&nbsp; p->calculate(data);

  107. &nbsp;&nbsp;&nbsp; return Indicator(p);

  108. }

  109.  
  110. } /* namespace hku */

  111.  

最后在indicator目录下的build_in.h文件中增加包含关系:

#include "crt/EMA.h"

在indicator目录下的export.cpp文件中导出策略类:

 
  1. #include "imp/Ema.h"

  2. BOOST_CLASS_EXPORT(hku::Ema)

最后编译整个hikyuu_msvc10工程得到一个新的dll库,直接代替原hikyuu相应的dll库就实现了C 策略类的导出。

第二步就是在python中直接使用这个新的移动平均线策略指标。
首先导入jqdata和hikyuu

 
  1. from jqdatasdk import *

  2. from hikyuu.interactive.interactive import *

然后封装一个由jqdata作为数据源的自定义数据源类,具体的要实现的接口可以参考hikyuu平台的文档。这个封装只需要编写一次即可,不需要每个策略都编写。
封装类如下:

 
  1. from .._hikyuu import KDataDriver, DataDriverFactory

  2. from hikyuu import KRecord, Query, Datetime, Parameter

  3.  
  4. from jqdatasdk import *

  5. from datetime import *

  6.  
  7. class jqdataKDataDriver(KDataDriver):

  8. def __init__(self):

  9. super(jqdataKDataDriver, self).__init__('jqdata')

  10.  
  11. def _init(self):

  12. """【重载接口】(可选)初始化子类私有变量"""

  13. self._max = {Query.DAY:10,

  14. Query.WEEK:2,

  15. Query.MONTH:1,

  16. Query.QUARTER:1,

  17. #Query.HALFYEAR:1,

  18. Query.YEAR:1,

  19. Query.MIN:25,

  20. Query.MIN5:25,

  21. Query.MIN15:25,

  22. Query.MIN30:25,

  23. Query.MIN60:25}

  24. return

  25.  
  26. def loadKData(self, market, code, ktype, start_ix, end_ix, out_buffer):

  27. """

  28. 【重载接口】(必须)按指定的位置[start_ix, end_ix)读取K线数据至out_buffer

  29. :param str market: 市场标识

  30. :param str code: 证券代码

  31. :param KQuery.KType ktype: K线类型

  32. :param int start_ix: 起始位置

  33. :param int end_ix: 结束位置

  34. :param KRecordListPtr out_buffer: 传入的数据缓存,读取数据后使用

  35. out_buffer.append(krecord) 加入数据

  36. """

  37. if start_ix >= end_ix or start_ix <0 or end_ix <0:

  38. return

  39.  
  40. data = self._get_bars(market, code, ktype)

  41.  
  42. if len(data) < start_ix:

  43. return

  44.  
  45. total = end_ix if end_ix < len(data) else len(data)

  46. for i in range(start_ix, total):

  47. record = KRecord()

  48. record.datetime = Datetime(data.index[i])

  49. record.openPrice = data['open'][i]

  50. record.highPrice = data['high'][i]

  51. record.lowPrice = data['low'][i]

  52. record.closePrice = data['close'][i]

  53. record.transAmount = data['money'][i]

  54. record.transCount = data['volume'][i]

  55. out_buffer.append(record)

  56.  
  57.  
  58. def getCount(self, market, code, ktype):

  59. """

  60. 【重载接口】(必须)获取K线数量

  61. :param str market: 市场标识

  62. :param str code: 证券代码

  63. :param KQuery.KType ktype: K线类型

  64. """

  65. data = self._get_bars(market, code, ktype)

  66. return len(data)

  67.  
  68. def _getIndexRangeByDate(self, market, code, query):

  69. """

  70. 【重载接口】(必须)按日期获取指定的K线数据

  71. :param str market: 市场标识

  72. :param str code: 证券代码

  73. :param KQuery query: 日期查询条件(QueryByDate)

  74. """

  75. print("getIndexRangeByDate")

  76.  
  77. if query.queryType != Query.DATE:

  78. return (0, 0)

  79.  
  80. start_datetime = query.startDatetime

  81. end_datetime = query.endDatetime

  82. if start_datetime >= end_datetime or start_datetime > Datetime.max():

  83. return (0, 0)

  84.  
  85. data = self._get_bars(market, code, query.kType)

  86. total = len(data)

  87. if total == 0:

  88. return (0, 0)

  89.  
  90. mid, low = 0, 0

  91. high = total-1

  92. while low <= high:

  93. tmp_datetime = Datetime(data.index[high])

  94. if start_datetime > tmp_datetime:

  95. mid = high 1

  96. break

  97.  
  98. tmp_datetime = Datetime(data.index[low])

  99. if tmp_datetime >= start_datetime:

  100. mid = low

  101. break

  102.  
  103. mid = (low high) // 2

  104. tmp_datetime = Datetime(data.index[mid])

  105. if start_datetime > tmp_datetime:

  106. low = mid 1

  107. else:

  108. high = mid - 1

  109.  
  110. if mid >= total:

  111. return (0, 0)

  112.  
  113. start_pos = mid

  114. low = mid

  115. high = total - 1

  116. while low <= high:

  117. tmp_datetime = Datetime(data.index[high])

  118. if end_datetime > tmp_datetime:

  119. mid = high 1

  120. break

  121.  
  122. tmp_datetime = Datetime(data.index[low])

  123. if tmp_datetime >= end_datetime:

  124. mid = low

  125. break

  126.  
  127. mid = (low high) // 2

  128. tmp_datetime = Datetime(data.index[mid])

  129. if end_datetime > tmp_datetime:

  130. low = mid 1

  131. else:

  132. high = mid - 1

  133.  
  134. end_pos = total if mid >= total else mid

  135. if start_pos >= end_pos:

  136. return (0,0)

  137.  
  138. return (start_pos, end_pos)

  139.  
  140.  
  141. def getKRecord(self, market, code, pos, ktype):

  142. """

  143. 【重载接口】(必须)获取指定位置的K线记录

  144. :param str market: 市场标识

  145. :param str code: 证券代码

  146. :param int pos: 指定位置(大于等于0)

  147. :param KQuery.KType ktype: K线类型

  148. """

  149. record = KRecord()

  150. if pos < 0:

  151. return record

  152.  
  153. data = self._get_bars(market, code, ktype)

  154. if data is None:

  155. return record

  156.  
  157. if pos < len(data):

  158. record.datetime = Datetime(data.index[pos])

  159. record.openPrice = data['open'][pos]

  160. record.highPrice = data['high'][pos]

  161. record.lowPrice = data['low'][pos]

  162. record.closePrice = data['close'][pos]

  163. record.transAmount = data['money'][pos]

  164. record.transCount = data['volume'][pos]

  165.  
  166. return record

  167.  
  168.  
  169. def _trans_ktype(self, ktype): #此处的周月季年数据只是近似的,目前jqdata未提供聚宽网络平台上的get_bar函数,不能直接取,需要自行用日线数据拼装

  170. ktype_map = {Query.MIN: '1m',

  171. Query.MIN5: '5m',

  172. Query.MIN15: '15m',

  173. Query.MIN30: '30m',

  174. Query.MIN60: '60m',

  175. Query.DAY: '1d',

  176. Query.WEEK: '7d',

  177. Query.MONTH: '30d',

  178. Query.QUARTER: '90d',

  179. Query.YEAR: '365d'}

  180. return ktype_map.get(ktype)

  181.  
  182. def _get_bars(self, market, code, ktype):

  183. data = []

  184. username = self.getParam('username')

  185. password = self.getParam('password')

  186. auth(username, password)

  187.  
  188. jqdataCode = normalize_code(code)

  189. jqdata_ktype = self._trans_ktype(ktype)

  190.  
  191. if jqdata_ktype is None:

  192. print("jqdata_ktype == None")

  193. return data

  194.  
  195. print(jqdataCode)

  196. security_info = get_security_info(jqdataCode)

  197.  
  198. if security_info is None: #有可能取不到任何信息

  199. return data

  200. #print(security_info)

  201.  
  202. data = get_price(jqdataCode, security_info.start_date, datetime.now(), jqdata_ktype)

  203.  
  204. return data

在interactive.py文件中替换原来的数据源即可

 
  1. DataDriverFactory.regKDataDriver(jqdataKDataDriver())

  2.  
  3. jqdata_param = Parameter()

  4. jqdata_param.set('type', 'jqdata')

  5. jqdata_param.set('username', '用户名')

  6. jqdata_param.set('password', '密码')

  7.  
  8. base_param = sm.getBaseInfoDriverParameter()

  9. block_param = sm.getBlockDriverParameter()

  10. kdata_param = sm.getKDataDriverParameter()

  11. preload_param = sm.getPreloadParameter()

  12. hku_param = sm.getHikyuuParameter()

  13.  
  14. #切换K线数据驱动,重新初始化

  15. sm.init(base_param, block_param, jqdata_param, preload_param, hku_param)

最后一步就是在python中直接使用jqdata数据源调用C 编写的指标了

 
  1. s = sm['sz000001']

  2. k = s.getKData(Query(-200))

  3. #抽取K线收盘价指标,一般指标计算参数只能是指标类型,所以必须先将K线数据生成指标类型

  4. c = CLOSE(k)

  5. #调用自定义的C 均线策略计算收盘价的EMA指标

  6. a = EMA(c)

  7. #绘制指标

  8. c.plot(legend_on=True)

  9. a.plot(new=False, legend_on=True)

  10. #绘制柱状图

  11. a.bar()

以上三步,中最复杂的第二步,写一次后就可以通用,这样可以大大简化,python中,调用C 策略库的难度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值