vn.py量化社区
By Traders, For Traders.

置顶主题

挖个小坑,利用Scikit-learn机器学习库的特征分类进行VnTrade期货量化交易

这个算是一个小坑,因为我也还在学习过程中,代码慢慢完善。一开始在python 2.7,vnpy 1.9.2环境中实现。后面在python 3.7也基本实现,增加支持了xgboost。代码写的繁杂,多多抱歉。

首先说说Scikit-learn是Python语言中专门针对机器学习应用而发展起来的一款开源框架,相对于现在深度学习库tensorflow,由于Scikit-learn本身不支持深度学习,也不支持GPU加速,但是相对于tensorflow那种近乎黑箱的多层神经网络,还是比较好从数学来解释分析。

分类是指识别给定对象的所属类别,属于监督学习的范畴,最常见的应用场景包括垃圾邮件检测和图像识别等。目前Scikit-learn已经实现的算法包括:支持向量机(SVM),最近邻,逻辑回归,随机森林,决策树以及多层感知器(MLP)神经网络等等。这里会使用逻辑回归,决策数, MLP神经网络和SVM向量机,其实都是两句代码事情。

不考虑内部的复杂数学逻辑,这里功能性的使用Scikit-learn特征分类的功能。
1) 选取的特征值,为了避免数据值非逻辑化,这里不直接使用点位最高点这些,而使用那些和具体点位无关的指标,比如atr,cci,rsi,std和bar的涨跌百分比。
2) 利用这些特征值进行分类,这里对于期货走势就是三类,1 是之后上涨,0是之后无规律,-1是之后下跌,这里使用线性回归分析当前时点之后6根K线的走势,如果斜度下,那么当前时点归为-1下跌类,斜度上,为1上涨,如果取信p值不够,或者上下斜率不大,为0。
那么特征值就是atr,cci,rsi,std和bar的涨跌百分比,当然你可以加入KDJ,MACD更多;类别就是三类1,0,-1,利用机器学习,找出特征值和类别的隐含逻辑,进而指导交易,非常粗糙。

整体代码逻辑如下,这里大量借鉴这个 https://mp.weixin.qq.com/s?__biz=MjM5MDEzNDAyNQ==&mid=2650314212&idx=1&sn=0f04627d34f4305e0386fc7562563bff&chksm=be454f828932c694f8ce107249457e0ffba705e6e9531807e86a1f468a3a549001c00bb5389e&scene=21

一,期货K线数据, 导入k线数据,并整理加入特性和类属性
• 利用之前做的DataAnalyzer,读取Mongodb或者csv的1分钟k数据, 放入Dataframe来处理,按照定义合并出n分钟k线,这里n为10
• 还是利用DataAnalyzer,利用ta-lib方法。给Dataframe加入atr,cci,rsi,std,macd和涨跌百分比。
• 使用新方法addTrend,利用scipy.stats的线性回归求出当前时点之后的斜率,给与分类值-1,0,1.

二,数据处理,为了后面机器学习,把特征数组和类数组划分出来,并且划分训练机和测试集。
(1)划分出特征数组 X,和类别数组y
(2)划分训练集和测试集
• model_selection.train_test_split()

三,特征工程,之前需求很多特征,其实有些并没有体现规律,或者完全随机,那么没有意义,可以删除
• 根据P值选:feature_selection.SelectFpr()
• 按照百分比选出最高分特征:feature_selection.SelectPercentile()
这里使用SelectPercentile,

四,模型定义调参/选择
这里使用下面模式进行分析,然后利用网格调参
1)LogisticRegression 逻辑回归
2)DecisionTreeClassifier 决策树
3)SVC 支持向量分类
4)MLP 神经网络
• 交叉验证+网格搜索:model_selection.GridSearchCV()

五,模型测试和评价,使用选取最好的模型,进行测试看看拼接
• 模型预测:model.predict()
• Accuracy:metrics.accuracy_score()
• Presicion:metrics.precision_score()
• Recall:metrics.recall_score()

六,模型保存和调用
• 模型保存:joblib.dump()
• 模型调用:joblib.load(),这里就可以在vnpy中使用了。

后面代码整理放在后面,最后,只做参考,我发现虽然准确率有差不多70%,但是都是要你空仓,大智慧。。。



Mac 下安装运行 vnpy 2.0

官网上对 Mac 下运行 vnpy 介绍的比较少, 自己尝试安装了下, 把过程简单记录下来, 希望能帮到大家.

我并不准备在 Mac 进行实盘交易, 因为刚准备开始量化, 所以就打算先熟悉下框架用用回测然后对 A股 进行模拟交易, 所以无所谓 CTP 接口是否可用.

下面以跑通 okex 来说明.

安装步骤:

  • 安装 Anaconda, 这个到官网下载 Python 3.7 的版本即可: https://www.anaconda.com/distribution/
  • git clone vnpy 代码到本地
  • 切换到 vnpy 代码目录
  • 运行 conda create --name test-vnpy python=3.7, 创建 conda 环境, 名字随便取的就叫 test-vnpy 了
  • 运行 conda activate test-vnpy, 激活环境
  • 安装 python.app (Mac 下运行 Matplotlib 需要使用到): conda install python.app
  • 安装 ta-lib, 这个直接通过 brew install ta-lib 安装
  • pip 安装依赖的时候发现报 pg_config executable not found 错误, 安装 PostgreSQL 解决, 运行 brew install postgresql 安装即可
  • 安装 rqdatac: pip install --pre --extra-index-url https://rquser:ricequant99@py.ricequant.com/simple/ rqdatac
  • 安装 ibapi: pip install https://vnpy-pip.oss-cn-shanghai.aliyuncs.com/colletion/ibapi-9.75.1-py3-none-any.whl
  • 安装 requirements 中依赖: pip install -r requirements.txt
  • 安装 vnpy: pip install .
  • 创建 run.py, 内容如下:

    from vnpy.event import EventEngine
    from vnpy.trader.engine import MainEngine
    from vnpy.trader.ui import MainWindow, create_qapp
    from vnpy.gateway.okex import OkexGateway
    from vnpy.app.cta_strategy import CtaStrategyApp
    
    def main():
        """Start VN Trader"""
        qapp = create_qapp()
    
        event_engine = EventEngine()
        main_engine = MainEngine(event_engine)
    
        main_engine.add_gateway(OkexGateway)
        main_engine.add_app(CtaStrategyApp)
    
        main_window = MainWindow(main_engine, event_engine)
        main_window.showMaximized()
    
        qapp.exec()
    
    if __name__ == "__main__":
        main()
  • 使用 pythonw run.py 运行

这样就成功启动界面了, 点击菜单连接 API 即可!

还不是很清楚 VN Trader 的用法, 不过看样子功能都正常了, 今天就先到这里了, 明天再来研究研究.



Database源码阅读笔记+配置教程

最近在看文档的数据库部分,碰见了很多的数据库类型,干脆把各个数据库的配置记录一下,了解一下vnpy的数据库部分结构。
本文涉及的数据库包括MySQL,SQLite,MongoDB,(PostgreSQL就不写了,和MySQL差不多)。
第一次发长文,排版不好或是认知有偏差敬请指出。本人也算小白,仅当记录自己的心得了。

Vnpy的database

 
database模块一般用于CsvLoader模块中的数据持久化(导入)和CtaBacktester模块中的数据导出,在这两个模块中,database这个module是以BaseDatabaseManager类对象出现的,并且会直接调用这个类对象的导入导出方法。下面概括下这个类对象是如何生成的。
 
description
上图是database简略框架图。
 
在导入database这个module的时候,可以分成三层结构

  • 自动运行init.py文件,读取.vntrader/vt_setting.json中的数据库配置信息(也就是GUI界面点击菜单栏中”配置“出现的那个),然后调用.initialize中的init方法。
  • 在settings中保存着数据库driver类型,根据不同的类型又调用同一文件中的init_sql或者init_nosql。以init_sql为例,函数内部从database_sql.py中调用真正的init函数。目前为止并没有实质的内容,只是不断的在分层选择调用不同的函数。
  • 接下来以sqlite为例,并且会涉及peewee包。
     
    peewee包的作用我理解是,用统一的形式对sql数据库进行管理,开放上层接口给用户使用。(有误请指出,谢谢)
    在peewee中,一个model类对象视为一张表,相应类实例可以视为一条记录(row),一个field实例可以视为一列。先使用peewee提供的数据库引擎类实例化,建立与数据文件的连接,然后定义一个model类用于表示表,最后将model类添加到数据库引擎类(即生成数据表)。此时,db即可表示一个与数据文件连接着的数据引擎实例,上面添加到db的model类即可以表示db中的一张张表,可以取出来继续单独使用(原理是在定义model类时,使用了class meta吧我猜)
     
    那么在database_sql.py中,整个逻辑过程就是:在init中调用peewee的Database Engine(SQLiteDatabase)生成实例表示与db文件建立连接,将该实例对象称为db。调用init_models函数生成model类同时将model类添加到db中,然后将两张表返回(DbTickData和DBBarData)。最后,将这两张表(类)添加到SqlManager中,生成统一的BaseDatabaseManager,并提供给外界调用。
    大概逻辑图长这样:
    description
     
     

数据库配置

上面大致介绍了BaseDataManager的生成过程,现在介绍不同数据库具体使用时候setting如何填写。

  • #### Sqlite
    Sqlite是vnpy默认的数据库,配置不用特意去改,同时,Sqlite的数据库文件在.vntrader\\vt_setting.json中(以"database."为前缀的那几个)。
    
    额外注:在内部运行过程中,其实只用到了database.database这条配置,它的值也就是数据库文件的名字。其余都没有用到(不需要)
  • #### MySQL
    MySQL使用到的配置信息包括:database,user,password,host,port。
    
    peewee在连接本地mysql数据库时,实际调用的是MySQLdb或者是pymysql包。实际必须的只有数据库名称,用户和密码,后面两个都是默认有的(pymysql)
    使用MySQL除此之外还需特别注意,peewee不能为本地创建不存在的database,这意味着需要使用vnpy+MySQL的用户需要自己额外创建一个database,假如是”vnpyDb”,那么在配置栏就写vnpyDb"。
  • #### MongoDB
     后续补上,写太长了


CTP封装过程记录 (四)

接下来是比较核心的部分

CtpTdApi(TdApi)

缓存变量

  • 状态变量:包括控制连接、登录、授权、连接失败,四个控制变量,比如connect_status
  • 用户信息:比如userid
  • frontid;sessionid[用于形成唯一order序号,由Thost回调提供]
  • reqid;order_ref[vnpy内部维护,用于发单等编号]
  • order_data;trade_data[这两个list用于保存第一次私有流委托等回报的data]
  • sysid_orderid_map用于
  • symbol_\exchange_map;symbol_name_map;symbol_size_map[这三个是全局dict,用于在合约代码、交易所、合约名称、合约大小之间形成映射]

逻辑流程

先明确作为一个交易接口,TdApi需要实现什么功能:

  • 首先tdapi需要能够正常连接到ctp服务器,即执行连接、登录操作
  • 其次tdapi需要在第一次连接后查询各个交易所可交易合约并保存到vnpy本地;查询账户资金情况;查询持仓情况;查询当前报单情况;查询当前成交情况;
  • 最后就是通过交易接口进行一些基础的下单操作,并且得收到服务器多次响应。
     
    其次要明确CTP交易接口如何实现这些功能
    由于客户端和期货公司CTP服务器在通讯过程中设计多种模式,先初步介绍一下CTP通讯模式
    CTP-API涉及的通讯模式有三种:对话通讯模式;私有通讯模式;广播通讯模式。而不同的通讯模式又支持不同的数据流,具体体现在函数命名规则上
  • 对话通讯模式:客户端主动发起请求,服务器端给予相应回应,该模式支持对话数据流和查询数据流。前者指的是客户端向服务器发送交易请求,包括登录、报撤单等,Api命名规则为Reqxxx;后者指的是客户端向服务器发送查询请求,比如报单查询、合约查询等,命名规则为ReqQryxxx。
  • 私有通讯模式:CTP后台主动向某个特定客户端发出信息,该模式支持私有数据流,并且仅有响应接口OnRtnxxx,没有请求接口,包含成交回报报单回报各种错误回报。成交回报指的是报单状态信息变化后CTP主动向客户端发送回报信息。
  • 广播通讯模式:CTP后台主动向所有客户端发出信息,比如合约交易状态通知
    私有流和交易接口又相关关联,比如通过对话流进行报单录入,不仅会收到报单相应OnRspOrderInsert,当报单交易状态发生变化时,又会自动调用OnRtnOrder
     

CTP实现一个交易接口的功能可以分成几部分:初始化、初次查询、报单录入、撤单、委托成交回报

  • 初始化
    connect(初始化连接)--> onFrontConnected(连接响应,嵌套authenticate) --> onRspAuthenticate(授权成功,嵌套login) --> login(实际触发reqUserLogin) --> onRspUserLogin(登录回报,嵌套投资者结算结果确认reqSettlementInfoConfirm,交易接口和行情接口区别之一在于交易接口在客户端成功登录ctp后会主动进行私有流\公有流回报OnRtnxxx,并且比投资者结算结果响应还要早)
    完成上述操作后交易接口初始化完成,但仍然不适合直接进行交易,还需要进行初次查询,而vnpy直接将这两种操作互相嵌套了。
    :初始化最后会自动调用onRtnOrder和onRtnTrade作为私有流回报。原生OnRtnOrder只用于返回报单信息,通知客户端报单的最新状态.vnpy重载形成onRtnOrder,想要实现的功能包括第一次连入Thost后将挂单缓存到本地,vnpy发出的报单得到的委托成交回报也缓存到本地。
    具体逻辑为 :和order相关的vnpy内部数据结构有OrderRequest,OrderData.OrderData向vnpy上层事件引擎推送信息,来源于Thost回调的data数据,通过预定义的dict映射成OrderData.OrderRequest是策略层下底层发单用到的对象,和OrderData区别在于不包含接口信息。由于Thost的委托回报自身没有交易所信息(空字段),并且vnpy三个映射字典是通过reqQryInstrument完成的(比第一次私有流回报晚),因此OnRtnOrder函数需要同时考虑几种情况
      - 第一次连入Thost:将onRtnOrder返回的CThostOrderField数据缓存在self.order\_data中
      - 在onRspQryInstrument内部:在最后一次查询合约完成后,vnpy内部三个映射dict已经全部缓存完成,这时将order\_data中的各个CThostOrderField手工推送到onRtnOrder中
      **onRtnOrder完成的功能**:将Thost推送的委托回报数据包装成OrderData推送到gateway中;将委托回报的orderid和OrderSysID记录成映射
    
     
  • 初次查询
    除了嵌套在初始化中的查询合约,vnpy还对账户信息和仓位进行轮询。查询结果全部封成vnpy内部数据结构推送给gateway。
     
  • 报单
    在vnpy系统里,上层传到底层的报单对象为OrderRequest,然后在gateway层将其转化为ctp所需要的dict对象(pybind会把dict变成C++里的struct)。
    报单指令是ReqOrderInsert,CTP所需要的报单对象有如下成员:
      - InstrumentID,ExchangeID,LimitPrice,VolumeTotalOriginal(数量)
      - OrderPriceType(订单价格类型,比如限价等,vnpy支持限价、市价、stoop、FAKFOK)
      - Direction(买卖方向)
      - CombOffsetFlag(组合开平标志,一般用不到)
      - OrderRef(报单引用,作为报单的标识之一,vnpy内部会维护)
      - InvestorID(投资者代码),BrokerID(经纪商代码)
      - ComHedgeFlag(组合投机套保标志,一般用不到)
      - ContingentCondition(触发条件,比如立即、止损等,vnpy默认使用“立即”)
      - IsAutoSuspend(自动挂起标志,bool类型,默认是False,不知道干啥的,不重要)
      - TimeCondition(有效期类型,包含FOK、当日有效、本节有效、指定日期前有效、撤销前有效、集合竞价有效,vnpy发单默认使用“当日有效”)
      - VolumeCondition(成交量类型,vnpy默认使用任何数量)
      - MinVolume(vnpy默认最小成交数量是1)
    
    以上是报单基础类型,可以根据OrderRequest.type进行diy,比如变成FAK或者FOK,只需要更改TimeCondition和VolumeCondition
      - FAK = 立即完成所有数量否则撤销
      - FOK = 立即完成任何数量否则撤销
    
    报单涉及到一组唯一序号,由FrontID、SessionID、OrderRef组成,前两个是在登录后ctp默认分发的,vnpy会自动缓存,第三个vnpy在每次报单操作后会自动++1递增。这三个成员组成了某一个接口中独一无二的order编号orderid。而在往上层推送的是利用OrderRequest.create_order_data生成的OrderData对象,多出了vt_symbol,vt_orderid等信息。
    至此,一次报单请求操作已经完成。


海龟策略深入研究-策略回测系列-17 基于遗传算法的信号优化

海龟策略的信号来源于唐奇安通道突破。简单的说就是若突破上轨则做多,突破下轨则做空。我们可以对信号进行改良,如换成布林带通道,金肯特纳通道等等,并且增加过滤条件和离场条件。

但是呢?新的问题又来了:若用了新的指标,需要通过不断调试来得到“最优”参数,这样会耗费大量的时间。

那么,有没有办法在尽量少得时间内,尽可能得到全局最优解或者次优解呢?

答案就是遗传算法啦!

 
 
 

遗传算法原理


具体原理详见:
遗传算法原理简介

一文读懂遗传算法工作原理(附Python实现)

 

遗传算法要做的事情并不复杂:

  1. 随机生成一大推策略参数(称之为族群,族群内的个体对应某一组策略参数)
  2. 族群内个体间进行3类活动:个体间两两交叉互换;个体某个参数发生变异;个体繁殖(即直接复制参数)
  3. 形成子代
  4. 通过目标函数(如最大化夏普比率,最大化总盈亏等)对母代族群和子代族群进行评分
  5. 通过特点筛选标准(如NSGA-Ⅱ)从母代和子代中筛选个体,形成第二代族群。(类似进化论中的“自然选择”)
  6. 新的族群在特定的评分标准和筛选标准中不断迭代(即重复1-5步骤),得到最优解/次优解。

 
 
 

遗传算法代码示例


1.随机生成待优化的策略参数

def parameter_generate():
    '''
    根据设置的起始值,终止值和步进,随机生成待优化的策略参数
    '''
    parameter_list = []
    p1 = random.randrange(4,50,2)      #入场窗口
    p2 = random.randrange(4,50,2)      #出场窗口
    p3 = random.randrange(4,50,2)      #基于ATR窗口止损窗
    p4 = random.randrange(18,40,2)     #基于ATR的动态调仓 

    parameter_list.append(p1)
    parameter_list.append(p2)
    parameter_list.append(p3)
    parameter_list.append(p4)

    return parameter_list

 

  1. 设置目标优化函数(收益回撤比和夏普比率)
def object_func(strategy_avg):
    """
    本函数为优化目标函数,根据随机生成的策略参数,运行回测后自动返回2个结果指标:收益回撤比和夏普比率
    """
    # 创建回测引擎对象
    engine = BacktestingEngine()
    # 设置回测使用的数据                       
    engine.setBacktestingMode(engine.BAR_MODE)      # 设置引擎的回测模式为K线
    engine.setDatabase("VnTrader_Daily_Db", 'XBTHOUR')  # 设置使用的历史数据库
    engine.setStartDate('20170401')                 # 设置回测用的数据起始日期
    engine.setEndDate('20181230')                   # 设置回测用的数据起始日期

    # 配置回测引擎参数
    engine.setSlippage(0.5)                        
    engine.setRate(0.2/100)                     
    engine.setSize(10)                            
    engine.setPriceTick(0.5)                      
    engine.setCapital(1000000) 

    setting = {'entryWindow': strategy_avg[0],       #布林带窗口
               'exitWindow': strategy_avg[1],        #布林带通道阈值
               'atrWindow': strategy_avg[2],         #CCI窗口
               'artWindowUnit': strategy_avg[3],}    #ATR窗口               

    #加载策略          
    engine.initStrategy(TurtleTradingStrategy, setting)    
    # 运行回测,返回指定的结果指标   
    engine.runBacktesting()          # 运行回测
    #逐日回测   
    engine.calculateDailyResult()
    backresult = engine.calculateDailyStatistics()[1] 

    returnDrawdownRatio = round(backresult['returnDrawdownRatio'],2)  #收益回撤比
    sharpeRatio= round(backresult['sharpeRatio'],2)                   #夏普比率
    return returnDrawdownRatio , sharpeRatio

 

3.运行基于Deap库的遗传算法(具体步骤看代码中文注释)

#设置优化方向:最大化收益回撤比,最大化夏普比率
creator.create("FitnessMulti", base.Fitness, weights=(1.0, 1.0)) # 1.0 求最大值;-1.0 求最小值
creator.create("Individual", list, fitness=creator.FitnessMulti)

def optimize():
    """"""   
    toolbox = base.Toolbox()  #Toolbox是deap库内置的工具箱,里面包含遗传算法中所用到的各种函数

    # 初始化     
    toolbox.register("individual", tools.initIterate, creator.Individual,parameter_generate) # 注册个体:随机生成的策略参数parameter_generate()                                          
    toolbox.register("population", tools.initRepeat, list, toolbox.individual)               #注册种群:个体形成种群                                    
    toolbox.register("mate", tools.cxTwoPoint)                                               #注册交叉:两点交叉  
    toolbox.register("mutate", tools.mutUniformInt,low = 4,up = 40,indpb=0.6)                #注册变异:随机生成一定区间内的整数
    toolbox.register("evaluate", object_func)                                                #注册评估:优化目标函数object_func()    
    toolbox.register("select", tools.selNSGA2)                                               #注册选择:NSGA-II(带精英策略的非支配排序的遗传算法)


    #遗传算法参数设置
    MU = 40                                  #设置每一代选择的个体数
    LAMBDA = 160                             #设置每一代产生的子女数
    pop = toolbox.population(400)            #设置族群里面的个体数量
    CXPB, MUTPB, NGEN = 0.5, 0.35, 40        #分别为种群内部个体的交叉概率、变异概率、产生种群代数
    hof = tools.ParetoFront()                #解的集合:帕累托前沿(非占优最优集)

    #解的集合的描述统计信息
    #集合内平均值,标准差,最小值,最大值可以体现集合的收敛程度
    #收敛程度低可以增加算法的迭代次数
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    np.set_printoptions(suppress=True)            #对numpy默认输出的科学计数法转换
    stats.register("mean", np.mean, axis=0)       #统计目标优化函数结果的平均值
    stats.register("std", np.std, axis=0)         #统计目标优化函数结果的标准差
    stats.register("min", np.min, axis=0)         #统计目标优化函数结果的最小值
    stats.register("max", np.max, axis=0)         #统计目标优化函数结果的最大值

    #运行算法
    algorithms.eaMuPlusLambda(pop, toolbox, MU, LAMBDA, CXPB, MUTPB, NGEN, stats,
                              halloffame=hof)     #esMuPlusLambda是一种基于(μ+λ)选择策略的多目标优化分段遗传算法

    return pop

 
 
 

遗传算法效果


夏普比率1.9,总收益率1958%,最大百分比回撤37%,收益回撤比达53。

enter image description here
其解集收敛程度如下:
enter image description here

在得到一个好的曲线后,还要检查一下这些参数是否符合市场逻辑,尽量去避免过拟合的情况。下面举个反例:

这里使用金肯特纳通道+基于固定百分比移动止损策略。不管从曲线的形态和收敛程度来看都是挺正常的

enter image description here
enter image description here
但是在这种优化后参数中我们观察到trailingPercent=18%,这意味着价格从最高点回落18个点才平仓离场。在正常情况,这会带来非常糟糕的盈亏比。
enter image description here

 
 
 

总结

遗传算法本质上是一种加快策略研究的技术,相对于暴力穷举,它可以大大节省电脑运算时间。我们可以使用它,但不能过度依赖它,因为有可能输出的仅仅是一些很巧合的参数。所以,针对这些参数,需要做更加细致的回测。

毕竟,在策略研究中,细心与耐心也是非常重要的。



为VNPY的K线序列管理工具ArrayManager增加对数收益率队列

这段文字说明特别清楚,引用过来

如果我们考察单一投资品在总共 T 期内的表现,那应该用对数收益率,而非算数收益率。算术平均值不能正确的反应一个投资品的收益率。比如一个投资品今年涨了 50%,明年跌了 50%,它的算数平均收益率为0;但事实上,两年后该投资品亏损了最初资金的 25%。相反的,对数收益率由于具备可加性,它的均值可以正确反映出该投资品的真实收益率。比如这两年的对数收益率分别为 40.5% 和 -69.3%,平均值为-28.77%,转换为百分比亏损就是 exp{-28.77%} - 1 = -25%。
对数收益率的时序可加性让我们能够使用另外两个利器:“中心极限定理”和“大数定律”。假设初始资金 X_0(假设等于 1),ln(X_T) = ln(X_T/X_0) 就是整个 T 期的对数收益率。对数收益率的最大好处是它的可加性,把单期的对数收益率相加就得到整体的对数收益率。

在做策略建模的时候,经常需要把K线转换为可以正态分布数据,这样可以使用那些很牛吼吼的数学模型进行挖掘。

实现很简单

c = ln(t1/t0)
像相信研究可以看看这个 https://www.zhihu.com/question/30113132

在VNPY的K线序列管理工具ArrayManager,可以加入下面代码。按照属性返回对数收益率序列

@property
def percentLog(self):
    """获取对数收益序列"""
    arrayold = self.closeArray[0:self.size - 1]
    arraynew = self.closeArray[1:self.size]
    return np.log(arraynew/arrayold)*100.0

传统close 曲线
enter image description here
对数收益率后,把几个突变极大极小值忽略后,就是一个很标准正态分布,然后就可以用一堆模型来套用了
enter image description here



VNPY参数优化功能v1版本中的更新参数批量生成方法

NPY的参数优化功能,是策略优化的重要功能。主要就是按照范围生成批量的参数组合,然后成批跑完,选出最优的方法的。

在ctaBaclesting.py中的addParameter方法提供了批量导入参数的方法。就是这样一个参数一个参数填入。addParameter会按照1为初始,5为结束,2为步进,生成[1,3,5]参数队列 。
setting.addParameter('barMins', 1,5,2)

但是有时候参数比较多,如果参数是已经维护list这样情况,就不好支持。我写了个v2版本。传入的是key,和一个参数定义,可以是tuple元祖,队列list,或者单个值。

    def addParameterV2(self, key, value):
        """增加优化参数"""
        if isinstance(value, tuple):
                if len(value) == 4:
                    if value[3] == "int":
                        self.paramDict[key] = np.linspace(value[0], value[1], value[2]).astype(int).tolist()
                    else:
                        self.paramDict[key] = np.linspace(value[0], value[1], value[2]).tolist()
                elif len(value) ==3:
                    l = []
                    param =  value[0]
                    while param <=  value[1]:
                        l.append(param)
                        param +=  value[2]
                    self.paramDict[key] = l
        elif isinstance(value, list):
                self.paramDict[key] = value
        else:

使用方法:
1,起始点,终结点,和步进 比如,那么就和之前一样的返回
setting.addParameterV2('barMins', (1,5,2))
setting.addParameterV2('barMins', (0,5,0.2))
2, 起始点,终结点,参数个数和是否int 比如
setting.addParameterV2('barMins', 1,5,3, "int") 会生成有3个整数参数的参数队列
[1, 3, 5];
setting.addParameterV2('barMins', 1,5,4, "float")会生成浮点数参数组
[1.0, 2.333333333333333, 3.6666666666666665, 5.0]
3,数列,就直接使用数列

setting.addParameterV2('barMins', [3.5.6])
4,单个数字,就直接使用单个数字

setting.addParameterV2('barMins',4)

这样修改后,就可以不用一个一个加入,如下批量加入。

paradict = {
    'lWindow':40,
    'llDev': 8.0,
    'MAWindows':(5,12,5,"int"),
    'atrWindow':25,
    'slMultiplier':(3.0,6.0,3),
    'pRate':(0.002,0.010,5,"float"),
    'bMins':[3,5,10],
    'CDate':3,
    'endsize':(1,4,1),
    'endplus':(0,3,1)
}
for key,value in paradict.items():
    setting.addParameterV2(key,value)


CTP封装过程记录 (一)

CTP接口封装流程记录

这个帖子仅当记录自己在ctp接口封装时的经历以及经验,如果有错误敬请指出,如果对你有帮助那是最好的

最初的文件:

  • thostmduserapi_se.dll:行情接口动态库,主要对ThostFtdcMdApi和Spi进行实现
  • thosttraderapi_se.dll:交易接口动态库,主要对ThostFtdcTdApi和Spi进行实现
  • thostmduserapi_se.lib:行情接口静态库,主要包含一些索引信息
  • thosttraderapi_se.lib:交易接口静态库,主要包含一些索引信息
  • ThostFtdcMdApi.h:声明行情接口类及成员函数
  • ThostFtdcTraderApi.h:声明交易接口类及成员函数
  • ThostFtdcUserApiDataType.h:自定义了接口类中所需的变量类型,通过ctp接口发单需要转换成这里面的格式
  • ThostFtdcUserApiStruct.h:定义了接口类中所需的结构体如各种field

 
vnpy用python封装ctp,意思就是把原生CtpApi使用C++继承后,利用pybind11生成python可用的pyd库,里面包含了可以在python里直接调用的行情交易接口类
 

使用Ctp接口,一般是重载Spi类生成用户自定义的类,例如UserApi,同时定义成员变量api(利用Api类的静态方法生成),在使用时,先生成UserApi,然后通过调用成员变量api的初始化主动函数,将UserApi注册进入api,此时调用api的函数,最后会触发UserApi的回调函数,这也是为什么要继承定义Spi类。

原生CTP文件细节

  • ThostFtdcUserApiStruct.h中定义了各种结构体,在调用ctp接口时需要将参数构造成该文件里的struct-
  • ThostFtdcUserApiDataType.h中有两种定义:
    • typedef char TThostFtdcTimeConditionType;定义了变量类型,比如这是下单时间类型
    • #define THOST_FTDC_TC_IOC '1';定义了上面变量类型可选参数,比如这里表示“立即成交否则撤销”

Api的初始化主动函数简介:(md和td共有的)

  • CreateFtdcMdApi(const char *pszFlowPath="",...):静态成员函数,通过CThostFtdcMdApi类进行调用,创建一个指向CThostFtdcMdApi的类对象,可以视作是UserApi
  • RegisterSpi(CThostFtdcMdSpi *pSpi):将pSpi注册到UserApi对象中,此时UserApi调用的后续主动函数会自动触发pSpi中对应的回调函数
  • RegisterFront(char *pszFrontAddress):将UserApi连接到前置机网络地址上
  • Init():初始化函数,属于初始化最后一步,成功运行后会调用Spi的OnFrontConnected()
  • ReqUserLogin(CThostFtdcReqUserLoginField *pReqUserLoginField,int nRequestID):进行登录操作,会自动触发OnRspUserLogin(..)


CTP封装过程记录 (三)

BaseGateway

BaseGateway是抽象出来的网关基类,有两大类函数构成:

  • on_xxx类型:回调内容包括事件、tick数据、成交数据、订单数据、持仓数据、账户数据、日志数据、合约数据,用于向上层推送已经按照vnpy内部标准封装好的数据对象
  • 抽象函数:必须继承实现
    • connect(self,setting:dict):连接接口,如果已经连接,则日志输出,成功连接后进行必要的查询操作
    • close(self):
    • subscribe(self,req:SubscribeRequest):订阅时必须传入vnpy标准订阅请求类参数
    • send_order(self,req:OrderRequest):发单必须传入vnpy标准报单请求类参数,对于报单,有额外的要求
      • 利用OrderRequest生成OrderData,即从报单请求生成报单数据
      • 给OrderData赋值一个与接口类有关的unique_id
      • 如果order已经发出,则更改报单状态为“已提交”,如果发送失败,则应该为“拒单”
      • 利用on_order回调,send_order应返回OrderData.vt_orderid.
         
    • cancel_order(self,reqs:Sequence[CancelRequest]):撤销一系列订单
    • query_account(self):查询账户信息
    • query_position(self):查询持仓信息
    • query_history(self):查询历史信息?
    • get_default_setting(self):返回默认配置。

 

整体逻辑

首先定义一个接口网关,将基类中的抽象方法实现一遍,不过这个接口网关实质是一个上层入口,实际功能包括连接等,需要由缓存着的真正的交易接口句柄实现。

  • 初始化,缓存的对象包括,事件引擎、接口名称、外部交易接口
  • connect(self,setting:dict):将setting中的信息读取,调用底层接口进行connect并初始化查询(持仓、账户信息、
  • subscribe(self,req:SubscribeRequest):利用行情接口订阅行情。
  • send_order(self,req:OrderReuquest)和cancel_order(self,CancelRequest):利用交易接口撤单
  • query_account(self),query_position(self)
  • close(self):关闭底层接口
  • init_query(self):利用事件引擎定时轮询
     


CTP封装过程记录 (二)

python封装ctp

具体流程:
1、需要将两个定义了数据类型等的头文件转换成python的格式,方便后续gateway映射使用
2、将原生ctpapi接口用C++继承实现一次
3、利用pybind11对C++继承好的类进行转换

vnpy整个处理逻辑是:将MdApi分成四大部分

  • 继承原生CTP接口的Onxxxxx函数
  • 额外开一个task线程,用来运行process特定的任务
  • 额外声明onxxxxx响应函数
  • 额外声明包括createFtdcxxx等主动函数

运行的时候,先调用createFtdcxxx(实际调用的是原生CTP定义好的函数),进行初始化后,远端Thost服务器响应调用Onxxxx,将响应包装成一个事件推送到任务处理队列中,由process一系列函数挑选处理,推送到相应的onxxxx函数里。这样做的好处很明显,一是利用了一个无限长的任务处理队列,不用担心阻塞了CTP回调;二是只需要将onxxxx暴露给vnpy,个人感觉比较简洁?
 
对原生接口进行继承重写,分成两部分:
1、头文件:继承CThostFtdcMdSpi生成MdApi,其中MdApi类其中包括了原生Api对象(原生Spi只提供回调函数,原生Api只提供主动请求函数),继承定义的MdApi将两者合二为一,按照上面的处理逻辑声明特定的函数即可
2、源文件:将上述声明的类方法都实现一遍

  • 原生API回调函数Onxxxx:负责生成Task类实例推送给API的工作线程队列。
  • 回调处理函数processxxxx
  • onxxxx
  • 初始化和主动请求函数(包括createFtdcMdApi(...))

统计

主题
1180
帖子
4282
已注册用户
5272
最新用户
在线用户
78
在线来宾用户
164
© 2015-2019 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号-3