CTP&IB 1.0.0Beta

"Python, Program Trading, VNPY"

Posted by Simon on April 5, 2017

“Yeah It’s on. ”

前言

基于VNPY的CTP/IB 双接口内外套利策略 1.0.0 。

vn.py项目起源于国内私募的自主交易系统,2015年初启动时只是单纯的交易API接口的Python封装。随着业内关注度的上升和社区不断的贡献,目前已经一步步成长为一套全面的交易程序开发框架,用户群体也日渐多样化,包括私募基金、证券自营和资管、期货资管和子公司、高校研究机构、个人投资者等。

我个人接触VNPY也有近半年的时间了,对VNPY交易框架摸得越来越熟,对CTP接口的开发也已经日趋完善,今天开启IB接口的新篇章~~

当前只实现了在策略内能获取不同接口合约的tick信息,未涉及交易部分。


踩过的坑

1.由于IB不对外直接开放接口,程序化交易只能通过本地TWS来间接交易,所以本地TWS的配置一定要对,步骤如下

  • 在TWS的Edit > Global Configuration菜单打开的对话框中,左边栏选择API > Settings。
  • 确认允许ActiveX和socket客户端;确认端口号和VN.PY的IB_connect.json中的设置一致。
  • 可以设置可信的API应用的IP地址列表。

2.直接从VNPY作者Github上Down下来的代码是没有对IB做编译的,需要自己编译,步骤如下

  • 先进入vn.ib/ibapi/linux文件夹下运行bash build.sh生成twsapi.so
  • 然后回到vn.ib文件夹下运行bash build.sh生成vnib.so
  • 最后将生成的两个.so文件放入 IBGateway文件夹下

3.在使用VNPY订阅合约时需要一个localSymbol,这个代码需要在TWS查的

  • 首先在TWS界面订阅想要的合约
  • 右键该合约,查看合约描述,在弹出的窗口里找到localSymbol,exchange,currency,secType几个字段
  • 如CL合约一次为CLK7,NYMEX,USD,FUT,填入VNPY界面敲回车即可订阅行情

代码逻辑

在原版CtaEngine.py中使用如下方法添加策略,同时建立合约策略的映射


    def loadStrategy(self, setting):
        """载入策略"""
        try:
            name = setting['name']
            className = setting['className']
        except Exception, e:
            self.writeCtaLog(u'载入策略出错:%s' %e)
            return
        
        # 获取策略类
        strategyClass = STRATEGY_CLASS.get(className, None)
        if not strategyClass:
            self.writeCtaLog(u'找不到策略类:%s' %className)
            return
        
        # 防止策略重名
        if name in self.strategyDict:
            self.writeCtaLog(u'策略实例重名:%s' %name)
        else:
            # 创建策略实例
            strategy = strategyClass(self, setting)  
            self.strategyDict[name] = strategy
            
            # 保存Tick映射关系
            if strategy.vtSymbol in self.tickStrategyDict:
                l = self.tickStrategyDict[strategy.vtSymbol]
            else:
                l = []
                self.tickStrategyDict[strategy.vtSymbol] = l
            l.append(strategy)
            
            # 订阅合约
            contract = self.mainEngine.getContract(strategy.vtSymbol)
            if contract:
                req = VtSubscribeReq()
                req.symbol = contract.symbol
                req.exchange = contract.exchange
                
                # 对于IB接口订阅行情时所需的货币和产品类型,从策略属性中获取
                req.currency = strategy.currency
                req.productClass = strategy.productClass
                
                self.mainEngine.subscribe(req, contract.gatewayName)
            else:
                self.writeCtaLog(u'%s的交易合约%s无法找到' %(name, strategy.vtSymbol))   

代码中的tickStrategyDict是一个保存了合约和策略映射关系的字典,通过这个字典,来自底层的tick会知道有哪些策略关心它,从而执行这些策略的onTick

但是这里有一个问题,在订阅合约的时候productClass,currency字段只考虑了策略类中的属性,当多个合约不一样时就会出问题,所以我增加了如下代码来单独订阅IB合约

if "." in vtSymbol:
    req = VtSubscribeReq()
    req.symbol = vtSymbol[:vtSymbol.index('.')]
    req.exchange = vtSymbol[vtSymbol.index('.')+1:]
                
		# 对于IB接口订阅行情时所需的货币和产品类型,从策略属性中获取
    req.currency = strategy.currency
    req.productClass = strategy.productClass
    self.mainEngine.subscribe(req, "IB")
    continue      

由于IB合约在填写的时候格式为symbol.exchange,所以如果合约代码中有 ‘.’ 就会单独订阅它,不影响另外一个CTP合约

对于策略配置窗口,我在uiCtaWidget.py文件中增加了 class strategyWindow来管理不同策略类的配置窗口

class strategyWindow(QtGui.QMainWindow):

    def __init__(self,ParamWindow,ParamWindow2,ParamWindow3,CtaEngineManager=None):
        super(strategyWindow,self).__init__()
        self.setWindowTitle(u"策略类型")
        self.pw = ParamWindow
        self.gt = ParamWindow2
        self.cai = ParamWindow3
        self.ce = CtaEngineManager      

其中pw,gt,cai三个成员分别对应当前的三个策略类,这三个类分别在各自的策略文件中定义,在class CtaEngineManager初始化

class CtaEngineManager(QtGui.QWidget):
    """CTA引擎管理组件"""
    signal = QtCore.pyqtSignal(type(Event()))

    #----------------------------------------------------------------------
    def __init__(self, ctaEngine, eventEngine, parent=None):
        """Constructor"""
        super(CtaEngineManager, self).__init__(parent)
        
        self.ctaEngine = ctaEngine
        self.eventEngine = eventEngine
        self.strategyLoaded = False
        self.pw = ParamWindow("","","",self)
        self.gt = ParamWindow2("","","",self)
        self.cai = ParamWindow3("","","",self)
        self.sw = strategyWindow(self.pw, self.gt,self.cai)
        self.initUi()
        self.registerEvent()
        # 记录日志
        self.ctaEngine.writeCtaLog(u'CTA引擎启动成功')           

本次更新还涉及增加IB交易所和原有的两个策略的BUG修复等,就不在一一赘述了,更新详见我的Github

后记

—— Simon 于2017.4