vnpy量化平台学习过程中的经验分享

头像
玉米棉花糖
帖子: 92
注册时间: 2018年 1月 11日 17:13

Re: vnpy量化平台学习过程中的经验分享

#11

帖子 玉米棉花糖 » 2018年 1月 12日 22:14

查询委托,查询成交
TdApi中增加报单回报和成交回报的回调函数

代码: 全选

    #----------------------------------------------------------------------
    def onRtnOrder(self, data):
        """报单回报"""
        # 更新最大报单编号
        newref = data['OrderRef']
        self.orderRef = max(self.orderRef, int(newref))

        # 常规报单事件
        event1 = Event(type_=EVENT_ORDER)
        event1.dict_['data'] = data
        self.__eventEngine.put(event1)

    #----------------------------------------------------------------------
    def onRtnTrade(self, data):
        """成交回报"""
        event = Event(type_=EVENT_TRADE)
        event.dict_['data'] = data
        self.__eventEngine.put(event)

mainEngine中增加相应的委托和成交的函数,也是先简单地打印出来

代码: 全选

    def order(self, event):
        data = event.dict_['data']
        print(data)

    def trade(self, event):
        data = event.dict_['data']
        print(data)

在__init__把这两个方法注册到事件引擎

代码: 全选

        self.ee.register (EVENT_TRADE, self.trade)
        self.ee.register (EVENT_ORDER, self.order)

运行正常,因为是周末没有委托成交信息

代码: 全选

行情服务器连接成功
交易服务器连接成功
行情服务器登录完成
100897交易服务器登录完成
结算信息确认完成
合约信息查询完成
合约信息已经保存
合约截面数据查询完成
合约截面数据已经保存
最新价: 3787.0
收到查询资金的回报
收到查询持仓的回报
收到查询持仓的回报
收到查询持仓的回报

报单、撤单
先从我的github下载ctp_data_type.py和constant.py,增加到程序所在的目录。ctp_data_type这个模块中保存着CTP系统中用到的数据类型和常量定义。举个例子,服务器推送过来报单的状态是用0、1、2、3这样的数字表示的,我们没办法记住每个数字代表的意思,直接写数字也会导致出错时无从改起。因此导入这个模块,然后用THOST_FTDC_OST_AllTraded这种从名字上能看出信息的方式来写。constant里面类似地保存着一些常用的常量。

代码: 全选

#全部成交
defineDict["THOST_FTDC_OST_AllTraded"] = '0'
#部分成交还在队列中
defineDict["THOST_FTDC_OST_PartTradedQueueing"] = '1'
#部分成交不在队列中
defineDict["THOST_FTDC_OST_PartTradedNotQueueing"] = '2'
#未成交还在队列中
defineDict["THOST_FTDC_OST_NoTradeQueueing"] = '3'
#未成交不在队列中
defineDict["THOST_FTDC_OST_NoTradeNotQueueing"] = '4'
#撤单
defineDict["THOST_FTDC_OST_Canceled"] = '5'
#未知
defineDict["THOST_FTDC_OST_Unknown"] = 'a'
增加类型映射字典

代码: 全选

# 价格类型映射
priceTypeMap = {}
priceTypeMap[PRICETYPE_LIMITPRICE] = defineDict["THOST_FTDC_OPT_LimitPrice"]
priceTypeMap[PRICETYPE_MARKETPRICE] = defineDict["THOST_FTDC_OPT_AnyPrice"]
priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} 

# 方向类型映射
directionMap = {}
directionMap[DIRECTION_LONG] = defineDict['THOST_FTDC_D_Buy']
directionMap[DIRECTION_SHORT] = defineDict['THOST_FTDC_D_Sell']
directionMapReverse = {v: k for k, v in directionMap.items()}

# 开平类型映射
offsetMap = {}
offsetMap[OFFSET_OPEN] = defineDict['THOST_FTDC_OF_Open']
offsetMap[OFFSET_CLOSE] = defineDict['THOST_FTDC_OF_Close']
offsetMap[OFFSET_CLOSETODAY] = defineDict['THOST_FTDC_OF_CloseToday']
offsetMap[OFFSET_CLOSEYESTERDAY] = defineDict['THOST_FTDC_OF_CloseYesterday']
offsetMapReverse = {v:k for k,v in offsetMap.items()}

从vnpy搬运委托对象

代码: 全选

class CtaOrderReq(object):
    """发单时传入的对象类"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.symbol = EMPTY_STRING              # 代码
        self.exchange = EMPTY_STRING            # 交易所
        self.price = EMPTY_FLOAT                # 价格
        self.volume = EMPTY_INT                 # 数量
    
        self.priceType = EMPTY_STRING           # 价格类型
        self.direction = EMPTY_STRING           # 买卖
        self.offset = EMPTY_STRING              # 开平
发单

代码: 全选

    def sendOrder(self, orderReq):
        """发单"""
        self.reqID += 1
        self.orderRef += 1
            
        req = {}
        
        req['InstrumentID'] = orderReq.symbol # 合约代码
        req['LimitPrice'] = orderReq.price # 价格
        req['VolumeTotalOriginal'] = orderReq.volume # 数量
        
        # 下面如果由于传入的类型本接口不支持,则会返回空字符串
        req['OrderPriceType'] = priceTypeMap.get(orderReq.priceType, '') # 价格类型
        req['Direction'] = directionMap.get(orderReq.direction, '') # 方向
        req['CombOffsetFlag'] = offsetMap.get(orderReq.offset, '') # 组合标志
            
        req['OrderRef'] = str(self.orderRef) # 报单引用
        req['InvestorID'] = self.userID # 投资者代码
        req['UserID'] = self.userID # 账号
        req['BrokerID'] = self.brokerID # 经纪商代码
        
        req['CombHedgeFlag'] = defineDict['THOST_FTDC_HF_Speculation']       # 投机单
        req['ContingentCondition'] = defineDict['THOST_FTDC_CC_Immediately'] # 立即发单
        req['ForceCloseReason'] = defineDict['THOST_FTDC_FCC_NotForceClose'] # 非强平
        req['IsAutoSuspend'] = 0                                             # 非自动挂起
        req['TimeCondition'] = defineDict['THOST_FTDC_TC_GFD']               # 今日有效
        req['VolumeCondition'] = defineDict['THOST_FTDC_VC_AV']              # 任意成交量
        req['MinVolume'] = 1                                                 # 最小成交量为1
        
        # 判断FAK和FOK
        if orderReq.priceType == PRICETYPE_FAK:
            req['OrderPriceType'] = defineDict["THOST_FTDC_OPT_LimitPrice"]
            req['TimeCondition'] = defineDict['THOST_FTDC_TC_IOC']
            req['VolumeCondition'] = defineDict['THOST_FTDC_VC_AV']
        if orderReq.priceType == PRICETYPE_FOK:
            req['OrderPriceType'] = defineDict["THOST_FTDC_OPT_LimitPrice"]
            req['TimeCondition'] = defineDict['THOST_FTDC_TC_IOC']
            req['VolumeCondition'] = defineDict['THOST_FTDC_VC_CV']        
        
        self.reqOrderInsert(req, self.reqID)
        
        # 返回订单号(字符串),便于某些算法进行动态管理
        return str(self.orderRef)
撤单

代码: 全选

    def cancelOrder(self, cancelOrderReq):
        """撤单"""
        self.reqID += 1

        req = {}
        
        req['ExchangeID'] = cancelOrderReq.exchange
        req['OrderSysID'] = cancelOrderReq.OrderSysID
        
        req['ActionFlag'] = defineDict['THOST_FTDC_AF_Delete']
        req['BrokerID'] = self.brokerID
        req['InvestorID'] = self.userID
        
        self.reqOrderAction(req, self.reqID)
多开多平、空开孔平、多平今空平今

代码: 全选

    #----------------------------------------------------------------------
    def buy(self, symbol, price, vol):  # 多开
        orderReq = CtaOrderReq()
        orderReq.symbol = symbol              # 代码
        orderReq.price = price                # 价格
        orderReq.volume = vol                 # 数量
    
        orderReq.priceType = PRICETYPE_LIMITPRICE           # 价格类型
        orderReq.direction = DIRECTION_LONG           # 买卖
        orderReq.offset = OFFSET_OPEN              # 开平
        
        self.sendOrder(orderReq)

    def sell(self, symbol, price, vol):  # 多平
        orderReq = CtaOrderReq()
        orderReq.symbol = symbol              # 代码
        orderReq.price = price                # 价格
        orderReq.volume = vol                 # 数量
    
        orderReq.priceType = PRICETYPE_LIMITPRICE           # 价格类型
        orderReq.direction = DIRECTION_SHORT           # 买卖
        orderReq.offset = OFFSET_CLOSE              # 开平

        self.sendOrder(orderReq)
        
    def selltoday(self, symbol, price, vol):  # 多头平今
        orderReq = CtaOrderReq()
        orderReq.symbol = symbol              # 代码
        orderReq.price = price                # 价格
        orderReq.volume = vol                 # 数量
    
        orderReq.priceType = PRICETYPE_LIMITPRICE           # 价格类型
        orderReq.direction = DIRECTION_SHORT           # 买卖
        orderReq.offset = OFFSET_CLOSETODAY              # 开平

        self.sendOrder(orderReq)

    def short(self, symbol, price, vol):  # 空开
        orderReq = CtaOrderReq()
        orderReq.symbol = symbol              # 代码
        orderReq.price = price                # 价格
        orderReq.volume = vol                 # 数量
    
        orderReq.priceType = PRICETYPE_LIMITPRICE           # 价格类型
        orderReq.direction = DIRECTION_SHORT           # 买卖
        orderReq.offset = OFFSET_OPEN              # 开平
        
        self.sendOrder(orderReq)

    def cover(self, symbol, price, vol):  # 空平
        orderReq = CtaOrderReq()
        orderReq.symbol = symbol              # 代码
        orderReq.price = price                # 价格
        orderReq.volume = vol                 # 数量
    
        orderReq.priceType = PRICETYPE_LIMITPRICE           # 价格类型
        orderReq.direction = DIRECTION_LONG           # 买卖
        orderReq.offset = OFFSET_CLOSE              # 开平

        self.sendOrder(orderReq)

    def covertoday(self, symbol, price, vol):  # 空头平今
        orderReq = CtaOrderReq()
        orderReq.symbol = symbol              # 代码
        orderReq.price = price                # 价格
        orderReq.volume = vol                 # 数量
    
        orderReq.priceType = PRICETYPE_LIMITPRICE           # 价格类型
        orderReq.direction = DIRECTION_LONG           # 买卖
        orderReq.offset = OFFSET_CLOSETODAY              # 开平

        self.sendOrder(orderReq)
mainEngine中也相应封装这些函数

代码: 全选

    # ----------------------------------------------------------------------
    def buy(self, symbol, price, vol):  # 买开多开
        self.td.buy(symbol, price, vol)

    def sell(self, symbol, price, vol):  # 多平
        self.td.sell(symbol, price, vol)

    def selltoday(self, symbol, price, vol):  # 多平今
        self.td.selltoday(symbol, price, vol)

    def short(self, symbol, price, vol):  # 空开
        self.td.short(symbol, price, vol)

    def cover(self, symbol, price, vol):  # 空平
        self.td.cover(symbol, price, vol)

    def covertoday(self, symbol, price, vol):  # 空平今
        self.td.covertoday(symbol, price, vol)
    # ----------------------------------------------------------------------
    def cancelOrder(self, req):#撤单
        self.td.cancelOrder(req)        
代码见附件
附件
demo_order.rar
(37.41 KiB) 下载 631 次
上次由 玉米棉花糖 在 2018年 1月 14日 17:00,总共编辑 6 次。

头像
博弈
帖子: 241
注册时间: 2018年 1月 11日 15:35

Re: vnpy量化平台学习过程中的经验分享

#12

帖子 博弈 » 2018年 1月 12日 23:40

玉米棉花糖 写了:
2018年 1月 12日 10:25
运行结果如下:

代码: 全选

交易服务器连接成功
行情服务器连接成功
100897交易服务器登录完成
TypeError: 'NoneType' object is not callable
TypeError: 'NoneType' object is not callable
再咋整呢?
一屏的TypeError: 'NoneType' object is not callable
附件
2018-01-12_233703.png
2018-01-12_233703.png (8.58 KiB) 查看 17055 次

头像
玉米棉花糖
帖子: 92
注册时间: 2018年 1月 11日 17:13

Re: vnpy量化平台学习过程中的经验分享

#13

帖子 玉米棉花糖 » 2018年 1月 13日 07:21

我写的跟不上你进度了,哈哈。你直接看我github那个吧,那个是写到基本查询功能和界面的。这里我慢慢更新

chunk998
帖子: 4
注册时间: 2018年 1月 11日 18:38

Re: vnpy量化平台学习过程中的经验分享

#14

帖子 chunk998 » 2018年 1月 13日 15:46

辛苦

头像
玉米棉花糖
帖子: 92
注册时间: 2018年 1月 11日 17:13

Re: vnpy量化平台学习过程中的经验分享

#15

帖子 玉米棉花糖 » 2018年 1月 13日 18:03

这个分享主要是一个从无到有、从少到多的过程,解决方案并不是很好。目的是让看的人能知道开发过程功能是怎样一个个增加的,每一个部分虽然简单,但至少是在能运行的基础上再增加新的模块。等功能加的差不多了,你可能就需要重新考虑一下程序的结构了。

我在学习vnpy的过程中,碰到了这样困惑。vnpy的代码写的很好,不同的接口封装成gateway统一处理,api、中间层、gui每个都形成模块,各种常量、数据类有独立的定义。但是对新手来说就比较痛苦,经过封装,业务逻辑被重新组织,而且隐藏在几个层次间绕来绕去。学习者常常在文件间反复跳转只为跟踪一个变量的传递,很难看到业务流程的全貌。

后来有幸看到何先生的文章,又结合vnpy自带的ctpdemo,才算了解了业务逻辑,有种豁然开朗的感觉。后面又花了一个月左右时间改造出了自己的软件,逐步完善。所以这篇分享的目的,也是希望能把有缘人比较快的引进门,后面的开发,还是应该参考vnpy的代码。不但写的规范、功能也多,大部分你能想到的功能都可以从里面直接搬运回去修改。

头像
玉米棉花糖
帖子: 92
注册时间: 2018年 1月 11日 17:13

Re: vnpy量化平台学习过程中的经验分享

#16

帖子 玉米棉花糖 » 2018年 1月 13日 19:06

如果纯粹跑策略,不写图形界面也是可以的。通过在命令行打印一些信息,以及通过日志记录异常,也能运行的很好,还省资源。但是,有逼格的程序员怎么可能不写界面呢 ?:)
图形界面更符合我们的使用习惯,能直观的把我们关系的参数、变量实时显示出来,也能画点K线指标,或者通过菜单按钮来交互。在这里,我们主要使用PYQT来实现图形界面。

第一个图形界面LogMonitor
demo的代码已经很臃肿了,gui新建一个demo_gui.py来写。

代码: 全选

class LogMonitor(QTableWidget):
    """用于显示日志"""
    signal = pyqtSignal(type(Event()))
    #----------------------------------------------------------------------
    def __init__(self,eventEngine, parent=None):
        """Constructor"""
        super(LogMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine
        self.setWindowTitle('日志')
        self.setColumnCount(2)
        self.setHorizontalHeaderLabels(['时间', '日志'])
        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QTableWidget.NoEditTriggers) # 设为不可编辑状态
        self.setColumnWidth(0, 80)
        self.setColumnWidth(1, 800)
        # Qt图形组件的GUI更新必须使用Signal/Slot机制,否则有可能导致程序崩溃
        # 因此这里先将图形更新函数作为Slot,和信号连接起来
        # 然后将信号的触发函数注册到事件驱动引擎中
        self.signal.connect(self.updateLog)
        self.__eventEngine.register(EVENT_LOG, self.signal.emit)

    #----------------------------------------------------------------------
    def updateLog(self, event):
        """更新日志"""
        # 获取当前时间和日志内容
        t = datetime.now()
        t = t.strftime('%H:%M:%S')
        log = event.dict_['log']
        # 在表格最上方插入一行
        self.insertRow(0)
        # 创建单元格
        cellTime = QTableWidgetItem(t)
        cellLog = QTableWidgetItem(log)

        # 将单元格插入表格
        self.setItem(0, 0, cellTime)
        self.setItem(0, 1, cellLog)
mainEngine里面的print_log可以退出历史舞台了,注释掉。导入demo_gui模块,然后添上

代码: 全选

        # GUI
        self.lm = LogMonitor(self.ee)

        self.lm.show()
运行结果
logmonitor.JPG
logmonitor.JPG (41.73 KiB) 查看 17013 次
附件
demo_gui.rar
(7.83 KiB) 下载 650 次
上次由 玉米棉花糖 在 2018年 1月 14日 17:27,总共编辑 2 次。

头像
玉米棉花糖
帖子: 92
注册时间: 2018年 1月 11日 17:13

Re: vnpy量化平台学习过程中的经验分享

#17

帖子 玉米棉花糖 » 2018年 1月 13日 19:07

持仓PositionMonitor。
由于写的太简单,这里其实有个错误,ExchangeID在Tdapi中其实还没获取。我先暂且贴出来,只是理解一下流程。到最后我会把我实际解决的方案传一个到github上。

代码: 全选

########################################################################
class PositionMonitor(QTableWidget):
    """用于显示持仓"""
    signal = pyqtSignal(type(Event()))
    #----------------------------------------------------------------------
    def __init__(self, eventEngine, mainEngine, parent=None):
        """Constructor"""
        super(PositionMonitor, self).__init__(parent)
        self.dictLabels = [
            "合约代码", "合约名称", "持仓方向", "总持仓量", "昨持仓量", "今持仓量", "今冻结", "昨冻结",
            "合约开仓价值", "合约持仓价值", "开仓均价", "持仓盈亏", "开仓盈亏", "风险度"]
        self.__eventEngine = eventEngine
        self.__mainEngine = mainEngine
        self.dict = {}
        self.setWindowTitle('持仓')
        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels)
        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QTableWidget.NoEditTriggers) # 设为不可编辑状态
        self.signal.connect(self.updateposition)
        self.__eventEngine.register(EVENT_POSITION, self.signal.emit)
        self.insertRow(0)# 插入合计的表格
        col = 0
        self.dict['合计'] = {}
        for i in self.dictLabels:
            self.dict['合计'][i] = QTableWidgetItem('')
            self.dict['合计'][i].setTextAlignment(0x0004 | 0x0080)  # 居中
            self.setItem(0, col, self.dict['合计'][i])
            col += 1
        self.dict['合计']["合约代码"].setText('合计')
        
    def updateposition(self, event):
        var = event.dict_['data']
        last = event.dict_['last']
        PreBalance = float(self.__mainEngine.dict_account["静态权益"])
        directionmap = {'多持':DIRECTION_LONG, '空持':DIRECTION_SHORT}
        ExchangeID =var['ExchangeID']
       
        if var["Position"] != 0:#有持仓
            index = var["InstrumentID"] + '.' + var["PosiDirection"]
            if index not in self.dict.keys():#计算持仓数据
                self.dict[index] = {}
                self.dict[index]["合约代码"] = QTableWidgetItem(str(var["InstrumentID"]))
                self.dict[index]["合约名称"] = QTableWidgetItem(str(var['InstrumentName']))
                self.dict[index]["合约持仓价值"] = QTableWidgetItem(str(var["PositionCost"]))
                self.dict[index]["昨结算价"] = QTableWidgetItem(str(var["PreSettlementPrice"]))
                self.dict[index]["乘数"] = var["VolumeMultiple"]
                self.dict[index]["持仓盈亏"] = QTableWidgetItem(str(round(var["PositionProfit"],2)))
                self.dict[index]["合约开仓价值"] = QTableWidgetItem(str(var["OpenCost"]))
                self.dict[index]["开仓均价"] = QTableWidgetItem(str(round(var["OpenCost"]/ (self.dict[index]["乘数"] * var["Position"]),2)))
                self.dict[index]["总持仓量"] =  QTableWidgetItem(str(var["Position"]))
                self.dict[index]["今持仓量"] = QTableWidgetItem('0')
                self.dict[index]["昨持仓量"] = QTableWidgetItem('0') 
                self.dict[index]["风险度"] = QTableWidgetItem(str(round(var["OpenCost"] / PreBalance * 100, 2)))

                if var["PosiDirection"] == DIRECTION_LONG:
                    self.dict[index]["持仓方向"] = QTableWidgetItem(str('多持'))
                    self.dict[index]["持仓方向"].setBackground(QColor(255, 0, 0))
                    po = var["PositionProfit"] + var["PositionCost"] - var["OpenCost"]
                    self.dict[index]["开仓盈亏"]  = QTableWidgetItem(str(po))
                else:
                    self.dict[index]["持仓方向"] = QTableWidgetItem(str('空持'))
                    self.dict[index]["持仓方向"].setBackground(QColor(34, 139, 34))
                    po = round(var["PositionProfit"] + var["OpenCost"] - var["PositionCost"], 2)
                    self.dict[index]["开仓盈亏"]  = QTableWidgetItem(str(po))
                if var["PositionProfit"] > 0 :
                    self.dict[index]["持仓盈亏"].setBackground(QColor(255, 0, 0))
                else:
                    self.dict[index]["持仓盈亏"].setBackground(QColor(34, 139, 34))
                if po >0 :
                    self.dict[index]["开仓盈亏"].setBackground(QColor(255, 0, 0))
                else:
                    self.dict[index]["开仓盈亏"].setBackground(QColor(34, 139, 34))
                if ExchangeID == EXCHANGE_SHFE:
                    if var["PositionDate"] == "2":   #1今仓,2昨仓
                        self.dict[index]["昨持仓量"].setText(str(var["Position"]))
                        self.dict[index]["昨冻结"] = QTableWidgetItem(str(var["LongFrozen"] + var["ShortFrozen"]))
                        self.dict[index]["今冻结"] = QTableWidgetItem('')
                    if var["PositionDate"] == "1":  #1今仓,2昨仓
                        self.dict[index]["今持仓量"].setText(str(var["Position"]))
                        self.dict[index]["今冻结"] = QTableWidgetItem(str(var["LongFrozen"] + var["ShortFrozen"]))
                        self.dict[index]["昨冻结"] = QTableWidgetItem('')
                    pt = int(self.dict[index]["今持仓量"].text()) + int(self.dict[index]["昨持仓量"].text())
                    self.dict[index]["总持仓量"].setText(str(pt))
                    self.dict[index]["开仓均价"].setText(str(round(var["OpenCost"]/ (self.dict[index]["乘数"] * pt),2)))
                else:
                # 非上期所的品种都算昨持
                    self.dict[index]["昨持仓量"].setText(str(var["Position"]))
                    self.dict[index]["昨冻结"] = QTableWidgetItem(str(var["LongFrozen"] + var["ShortFrozen"]))
                    self.dict[index]["今冻结"] = QTableWidgetItem('')
                
                self.insertRow(0) # 插入表格第一行
                col = 0 # 列计数
                for label in self.dictLabels:
                    self.dict[index][label].setTextAlignment(0x0004 | 0x0080)  # 居中
                    self.setItem(0, col, self.dict[index][label])
                    col += 1

            else:# 更新可能会变的数据
                self.dict[index]["持仓盈亏"].setText(str(round(var["PositionProfit"],2)))
                if var["PosiDirection"] == DIRECTION_LONG:
                    po = round(var["PositionProfit"] + var["PositionCost"] - var["OpenCost"], 2)
                    self.dict[index]["开仓盈亏"].setText(str(po))
                else:
                    po = round(var["PositionProfit"] + var["OpenCost"] - var["PositionCost"], 2)
                    self.dict[index]["开仓盈亏"].setText(str(po))
                    
                self.dict[index]["总持仓量"] .setText(str(var["Position"]))
                if ExchangeID == EXCHANGE_SHFE:
                    if var["PositionDate"] == "2":   #1今仓,2昨仓
                        self.dict[index]["昨持仓量"].setText(str(var["Position"]))
                        self.dict[index]["昨冻结"].setText(str(var["LongFrozen"] + var["ShortFrozen"]))
                    if var["PositionDate"] == "1":  #1今仓,2昨仓
                        self.dict[index]["今持仓量"].setText(str(var["Position"]))
                        self.dict[index]["今冻结"].setText(str(var["LongFrozen"] + var["ShortFrozen"]))
                    pt = int(self.dict[index]["今持仓量"].text()) + int(self.dict[index]["昨持仓量"].text())
                    self.dict[index]["总持仓量"].setText(str(pt))
                    self.dict[index]["开仓均价"].setText(str(round(var["OpenCost"]/ (self.dict[index]["乘数"] * pt),2)))
                else:
                    self.dict[index]["昨持仓量"].setText(str(var["Position"]))
                    self.dict[index]["昨冻结"].setText(str(var["LongFrozen"] + var["ShortFrozen"]))

                if var["PositionProfit"] > 0:
                    self.dict[index]["持仓盈亏"].setBackground(QColor(255, 0, 0))
                else:
                    self.dict[index]["持仓盈亏"].setBackground(QColor(34, 139, 34))
                if po > 0:
                    self.dict[index]["开仓盈亏"].setBackground(QColor(255, 0, 0))
                else:
                    self.dict[index]["开仓盈亏"].setBackground(QColor(34, 139, 34))
        else :#没有持仓,有2个情况:1,已经全部平仓,2,有开仓挂单
            index = var["InstrumentID"] + '.' + var["PosiDirection"]
            if index in self.dict.keys():#只处理全部平仓的表格
                del self.dict[index]
                r = self.rowCount()
                for i in range(r ):
                    row = r - 1 - i
                    if self.item(row, 0).text() == var["InstrumentID"] and directionmap[self.item(row, 2).text()] == var["PosiDirection"]:
                        self.removeRow(row)#删除表格
                        
        if last == True :#处理合计表格
            row = self.rowCount()
            p = {}
            p["总持仓量"] = 0
            p["昨持仓量"] = 0
            p["今持仓量"] = 0
            p["合约持仓价值"] = float(0)
            p["合约开仓价值"] = float(0)
            p["持仓盈亏"] = float(0)
            p["开仓盈亏"] = float(0)
            p["风险度"] = float(0)

            for i in range(row - 1):
                p["总持仓量"] += int(self.item(i, 3).text())
                p["昨持仓量"] += int(self.item(i, 4).text())
                p["今持仓量"] += int(self.item(i, 5).text())
                p["合约持仓价值"] += float(self.item(i, 8).text())
                p["合约开仓价值"] += float(self.item(i, 9).text())
                p["持仓盈亏"] += float(self.item(i, 11).text())
                p["开仓盈亏"] += float(self.item(i, 12).text())
                p["风险度"] = round(float(self.item(i, 13).text()) + p["风险度"], 2)
            self.dict['合计']['总持仓量'].setText(str(p["总持仓量"]))
            self.dict['合计']['昨持仓量'].setText(str(p["昨持仓量"]))
            self.dict['合计']['今持仓量'].setText(str(p["今持仓量"]))
            self.dict['合计']['合约持仓价值'].setText(str(p["合约持仓价值"]))
            self.dict['合计']['合约开仓价值'].setText(str(p["合约开仓价值"]))
            self.dict['合计']['持仓盈亏'].setText(str(round(p["持仓盈亏"],2) ))
            self.dict['合计']['开仓盈亏'].setText(str(round(p["开仓盈亏"],2)))
            self.dict['合计']['风险度'].setText(str(p["风险度"]))
运行结果
pos.JPG
pos.JPG (48.93 KiB) 查看 17009 次
资金AccountMonitor

代码: 全选

class AccountMonitor(QTableWidget):
    """用于显示账户"""
    signal = pyqtSignal(type(Event()))#这里的TYPE也可以是DICT,需要在注册事件中进行数据格式转换
    #----------------------------------------------------------------------
    def __init__(self, eventEngine, parent=None):
        """Constructor"""
        super(AccountMonitor, self).__init__(parent)
        self.dictLabels = ["动态权益","总保证金","冻结保证金", "手续费","平仓盈亏", "持仓盈亏","可用资金","可取资金"]
        self.__eventEngine = eventEngine
        #self.__mainEngine = mainEngine
        self.list_account = []#保存账户数据的LIST
        self.count = 0  # 账户数据第一次保存记号
        self.dict = {}	    # 用来保存账户对应的单元格
        self.setWindowTitle('账户')
        self.setColumnCount(len(self.dictLabels))#设置列
        self.insertRow(0)#因为只有1行数据,直接初始化
        col=0#表格列计数器
        for i in self.dictLabels:#初始化表格为空格
            self.dict[i] = QTableWidgetItem('')
            self.dict[i].setTextAlignment(0x0004 | 0x0080)  # 居中
            self.setItem(0,col,self.dict[i] )
            col +=1
        self.setHorizontalHeaderLabels(self.dictLabels)
        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QTableWidget.NoEditTriggers) # 设为不可编辑状态
        self.signal.connect(self.updateAccount)
        self.__eventEngine.register(EVENT_ACCOUNT, self.signal.emit)

    def updateAccount(self, event):
        var =self.TradingAccountField(event.dict_['data']) # 这里的dict'keys要包含self.dictLabels,否则会出错。
        self.count += 1 # 也可以每执行一次保存一次,收盘后可以看到账户的曲线。
        if self.count ==1: # 记录一次账户数据,只记录登陆后的第一个数据。
            self.list_account.append(var) # 这个代码可有可无,看个人的使用而言。
        for i in self.dictLabels: # 刷新表格
            value = var[i] # i就是DICT的key
            try:
                value = str(round(value, 2)) # 保留2位小数
            except:
                value = str(value)
            self.dict[i].setText(value) # 刷新单元格数据
    #----------------------------------------------------------------------
    def TradingAccountField(self,var):
        tmp = {}
        tmp["投资者帐号"] = var["AccountID"]
        tmp["静态权益"] = var["PreBalance"]
        tmp["上次存款额"] = var["PreDeposit"]
        tmp["入金金额"] = var["Deposit"]
        tmp["出金金额"] = var["Withdraw"]
        tmp["冻结保证金"] = var["FrozenMargin"]
        tmp["总保证金"] = var["CurrMargin"]
        tmp["手续费"] = var["Commission"]
        tmp["平仓盈亏"] = var["CloseProfit"]
        tmp["持仓盈亏"] = var["PositionProfit"]
        tmp["动态权益"] = var["Balance"]
        tmp["可用资金"] = var["Available"]
        tmp["可取资金"] = var["WithdrawQuota"]
        tmp["交易日"] = var["TradingDay"]
        tmp["时间"] = datetime.now()
        return tmp
运行结果
acc.JPG
acc.JPG (27.5 KiB) 查看 17008 次
成交TradeMonitor

代码: 全选

########################################################################
class TradeMonitor(QTableWidget):
    """用于显示成交记录"""
    signal = pyqtSignal(type(Event()))
    #----------------------------------------------------------------------
    def __init__(self, eventEngine, parent=None):
        """Constructor"""
        super(TradeMonitor, self).__init__(parent)
        self.dictLabels = ["合约名称","合约代码",  "买卖标志","成交时间", "价格", "数量", "成交编号", "报单引用",
                           "报单编号","本地报单编号"]
        self.__eventEngine = eventEngine
        self.setWindowTitle('成交')
        self.c = len(self.dictLabels)
        self.setColumnCount(self.c)
        self.setHorizontalHeaderLabels(self.dictLabels)
        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QTableWidget.NoEditTriggers) # 设为不可编辑状态
        self.signal.connect(self.updateTrade)
        self.__eventEngine.register(EVENT_TRADE, self.signal.emit)
        

    #----------------------------------------------------------------------
    def updateTrade(self, event):
        """"""
        var = event.dict_['data']
        data = self.TradeField(event.dict_['data'])
        #print(data)
        self.insertRow(0)
        for i in range(4,self.c):
            value = str(data[self.dictLabels[i]])
            item = QTableWidgetItem(value)
            self.setItem(0, i, item)
        self.setItem(0, 1, QTableWidgetItem(data["合约代码"]))
        self.setItem(0, 0, QTableWidgetItem(var["InstrumentName"]))
        if data["开平标志"] == OFFSET_OPEN:
            if data['买卖方向'] == DIRECTION_LONG:
                value ='多开'
            else:
                value ='空开'
        else:
            if data['买卖方向'] == DIRECTION_LONG:
                value = '空平'
            else:
                value = '多平'
        self.setItem(0, 2, QTableWidgetItem(value))
        t = data['成交日期']
        value = t[:4] +'-'+t[4:6] +'-'+t[6:8] +' ' + data["成交时间"]
        self.setItem(0, 3, QTableWidgetItem(value))
        self.setColumnWidth(3, 150)

    def TradeField(self, var):
        tmp = {}
        tmp["合约代码"] = var["InstrumentID"]
        tmp["报单引用"] = var["OrderRef"]
        tmp["交易所代码"] = var["ExchangeID"]
        tmp["成交编号"] = var["TradeID"]
        tmp["买卖方向"] = var["Direction"]
        tmp["报单编号"] = var["OrderSysID"]
        tmp["合约在交易所的代码"] = var["ExchangeInstID"]
        tmp["开平标志"] = var["OffsetFlag"]
        tmp["价格"] = var["Price"]
        tmp["数量"] = var["Volume"]
        tmp["成交日期"] = var["TradeDate"]
        tmp["成交时间"] = var["TradeTime"]
        tmp["本地报单编号"] = var["OrderLocalID"]
        tmp["交易日"] = var["TradingDay"]
        return tmp
委托OrderMonitor

代码: 全选

class OrderMonitor(QTableWidget):
    """用于显示所有报单"""
    signal = pyqtSignal(type(Event()))
    #----------------------------------------------------------------------
    def __init__(self, eventEngine, parent=None):
        """Constructor"""
        super(OrderMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine
        self.dictLabels = ["报单日期","合约代码","状态信息", "买卖开平标志", "价格", "数量","今成交数量",
                           "剩余数量", "前置编号", "会话编号", "报单引用","本地报单编号","报单编号",]
        self.dict = {}	    # 用来保存报单号对应的单元格对象
        self.setWindowTitle('报单')
        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels)
        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QTableWidget.NoEditTriggers) # 设为不可编辑状态
        self.signal.connect(self.updateOrder)
        self.__eventEngine.register(EVENT_ORDER, self.signal.emit)
    #----------------------------------------------------------------------
    def updateOrder(self, event):
        """"""
        var = event.dict_['data']
        index = str(var["OrderLocalID"])+'.'+var["InstrumentID"]
        if index not in self.dict.keys():
            self.insertRow(0)
            self.dict[index] = {}
            self.dict[index]["合约代码"] = QTableWidgetItem (str(var["InstrumentID"]))
            self.dict[index]["报单引用"] = QTableWidgetItem(str(var["OrderRef"]))
            self.dict[index]["价格"] = QTableWidgetItem(str(var["LimitPrice"]))
            self.dict[index]["数量"] = QTableWidgetItem(str(var["VolumeTotalOriginal"]))
            self.dict[index]["本地报单编号"] = QTableWidgetItem(str(var["OrderLocalID"]))
            self.dict[index]["今成交数量"] = QTableWidgetItem(str(var["VolumeTraded"]))
            self.dict[index]["剩余数量"] = QTableWidgetItem(str(var["VolumeTotal"]))
            self.dict[index]["报单编号"] = QTableWidgetItem(str(var["OrderSysID"]))
            self.dict[index]["前置编号"] = QTableWidgetItem(str(var["FrontID"]))
            self.dict[index]["会话编号"] = QTableWidgetItem(str(var["SessionID"]))
            self.dict[index]["状态信息"] = QTableWidgetItem(str(var["OrderStatus"]))
            t =str(var["InsertDate"]) + ' '+str(var["InsertTime"])
            self.dict[index]["报单日期"] = QTableWidgetItem(str(t))
            if var["CombOffsetFlag"] == OFFSET_OPEN:
                if var["Direction"] == DIRECTION_LONG:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('多开')
                    kp = '多开'  
                else:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('空开')
                    kp = '空开'  
            else:
                if var["Direction"] == DIRECTION_SHORT:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('多平')
                    kp = '多平' 
                else:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('空平')
                    kp = '空平'
            col =0
            for i in self.dictLabels :
                self.setItem(0,col,self.dict[index][i])
                col +=1
            self.setColumnWidth(0,150)
            self.setColumnWidth(2, 120)
未成交NonetradeMonitor

代码: 全选

class NonetradeMonitor(QTableWidget):
    """用于未成交报单"""
    signal = pyqtSignal(type(Event()))
    #----------------------------------------------------------------------
    def __init__(self, eventEngine,mainEngine,  parent=None):
        """Constructor"""
        super(NonetradeMonitor, self).__init__(parent)
        self.__eventEngine = eventEngine
        self.__mainEngine = mainEngine
        self.dictLabels = ["报单日期", "交易所代码", "合约代码", "买卖开平标志", "价格", "数量", "今成交数量", "剩余数量", "状态信息", "本地报单编号", "报单编号", "报单引用", "前置编号", "会话编号"]
        self.L = len(self.dictLabels )
        #self.orderref= {}	    # 用来保存报单号
        self.dict = {}	    # 用来保存报单数据
        self.setWindowTitle('未成交')
        self.setColumnCount(len(self.dictLabels))
        self.setHorizontalHeaderLabels(self.dictLabels)
        self.verticalHeader().setVisible(False)                 # 关闭左边的垂直表头
        self.setEditTriggers(QTableWidget.NoEditTriggers) # 设为不可编辑状态
        self.signal.connect(self.updateOrder)
        self.__eventEngine.register(EVENT_ORDER, self.signal.emit)
        self.itemDoubleClicked.connect(self.cancelOrder)
        
    def cancelOrder(self):
        """撤单"""
        req = CtaCancelOrderReq()
        
        req.symbol = self.item(self.currentRow(),2).text()              # 代码
        req.exchange = self.item(self.currentRow(),1).text()            # 交易所
        req.orderID = self.item(self.currentRow(),11).text()            # 报单引用
        req.frontID = self.item(self.currentRow(),12).text()             # 前置机号
        req.sessionID = self.item(self.currentRow(),13).text()           # 会话号
        req.OrderSysID = self.item(self.currentRow(),10).text()        #报单编号
       
        self.__mainEngine.cancelOrder(req)
    #----------------------------------------------------------------------
    def updateOrder(self, event):
        """更新可撤单"""
        var = event.dict_['data']
        index = str(var["OrderSysID"])+'.'+var["InstrumentID"]
        
        if index not in self.dict.keys() and var["StatusMsg"] == '未成交':
            self.insertRow(0)
            self.dict[index] = {}
            self.dict[index]["合约代码"] = QTableWidgetItem (str(var["InstrumentID"]))
            self.dict[index]["报单引用"] = QTableWidgetItem(str(var["OrderRef"]))
            self.dict[index]["价格"] = QTableWidgetItem(str(var["LimitPrice"]))
            self.dict[index]["数量"] = QTableWidgetItem(str(var["VolumeTotalOriginal"]))
            self.dict[index]["本地报单编号"] = QTableWidgetItem(str(var["OrderLocalID"]))
            self.dict[index]["今成交数量"] = QTableWidgetItem(str(var["VolumeTraded"]))
            self.dict[index]["剩余数量"] = QTableWidgetItem(str(var["VolumeTotal"]))
            self.dict[index]["报单编号"] = QTableWidgetItem(str(var["OrderSysID"]))
            self.dict[index]["前置编号"] = QTableWidgetItem(str(var["FrontID"]))
            self.dict[index]["会话编号"] = QTableWidgetItem(str(var["SessionID"]))
            self.dict[index]["状态信息"] = QTableWidgetItem(str(var["StatusMsg"]))
            self.dict[index]["交易所代码"] = QTableWidgetItem(str(var["ExchangeID"]))
            t =str(var["InsertDate"]) + ' '+str(var["InsertTime"])
            self.dict[index]["报单日期"] = QTableWidgetItem(str(t))
            if var["CombOffsetFlag"] == OFFSET_OPEN:
                if var["Direction"] == DIRECTION_LONG:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('多开')
                else:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('空开')
            else:
                if var["Direction"] == DIRECTION_SHORT:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('多平')
                else:
                    self.dict[index]["买卖开平标志"] = QTableWidgetItem('空平')
            col =0
            for i in self.dictLabels :
                self.setItem(0,col,self.dict[index][i])
                col +=1
            self.setColumnWidth(0,120)
            self.setColumnWidth(2, 120)
            
        if index  in self.dict.keys():#撤单
            self.dict[index]["状态信息"].setText(str(var["StatusMsg"]))
            if var["StatusMsg"] == '全部成交':
                r =self.rowCount()
                for i in range(r):
                    j=r-1-i
                    if self.item(j,9).text() != '未成交':
                        self.removeRow(j)
代码在附件
附件
demo_gui.rar
(11.58 KiB) 下载 621 次
上次由 玉米棉花糖 在 2018年 1月 14日 20:27,总共编辑 9 次。

头像
玉米棉花糖
帖子: 92
注册时间: 2018年 1月 11日 17:13

Re: vnpy量化平台学习过程中的经验分享

#18

帖子 玉米棉花糖 » 2018年 1月 13日 19:07

主窗体
主窗体我们只需要把几个窗口组件都装在一起,布局可以自行设计,我直接把窗口叠在一起用标签切换。

代码: 全选

class MainWindow(QMainWindow):
    """主窗口"""
    # signal = pyqtSignal(type(Event()))
    #----------------------------------------------------------------------
    def __init__(self, mainEngine, eventEngine):
        """Constructor"""
        super(MainWindow, self).__init__()

        self.me = mainEngine
        self.ee = eventEngine
        
        self.initUi()
    #----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.setWindowTitle("demo——基于vnpy的ctp接口")

        widgetLogM, dockLogM = self.createDock(LogMonitor, '日志', Qt.BottomDockWidgetArea)
        widgetAccountM, dockAccountM = self.createDock(AccountMonitor, '账户资金', Qt.BottomDockWidgetArea)
        widgetPositionM, dockPositionM = self.createDock(PositionMonitor, '持仓', Qt.BottomDockWidgetArea)
        widgetTradeM, dockTradeM = self.createDock(TradeMonitor, '成交', Qt.BottomDockWidgetArea)
        widgetOrderM, dockOrderM = self.createDock(OrderMonitor, '委托', Qt.BottomDockWidgetArea)
        widgetNonetradeM, dockNonetradeM = self.createDock(NonetradeMonitor, '撤单', Qt.BottomDockWidgetArea, True)

        self.tabifyDockWidget(dockAccountM, dockPositionM)
        self.tabifyDockWidget(dockAccountM, dockTradeM)
        self.tabifyDockWidget(dockAccountM, dockOrderM)
        self.tabifyDockWidget(dockAccountM, dockNonetradeM)
        self.tabifyDockWidget(dockAccountM, dockLogM)     
        dockLogM.raise_()

        dockAccountM.setMinimumWidth(720)
        dockLogM.setMinimumWidth(260)
        

        

            
    def createDock(self, widgetClass, widgetName, widgetArea, mainEngine=False):
        """创建停靠组件"""
        widget = widgetClass(self.ee, self.me) if mainEngine else widgetClass(self.ee) 

        dock = QDockWidget(widgetName)
        dock.setWidget(widget)
        dock.setObjectName(widgetName)
        dock.setFeatures(dock.DockWidgetMovable)
        self.addDockWidget(widgetArea, dock)
        return widget, dock
            

    def closeEvent(self, event):
        """关闭事件"""
        reply = QMessageBox.question(self, '退出',
                                           '确认退出?', QMessageBox.Yes | 
                                           QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes: 
            self.me.exit()
            event.accept()
        else:
            event.ignore()
在mainengine中增加

代码: 全选

        self.mw = MainWindow(self, self.ee)

        self.mw.show()
运行结果如图
demogui.GIF
demogui.GIF (23.37 KiB) 查看 16992 次
代码在附件,覆盖原来的文件
附件
demo_mainwindow.rar
(12.21 KiB) 下载 632 次
上次由 玉米棉花糖 在 2018年 1月 17日 09:13,总共编辑 2 次。

头像
博弈
帖子: 241
注册时间: 2018年 1月 11日 15:35

Re: vnpy量化平台学习过程中的经验分享

#19

帖子 博弈 » 2018年 1月 13日 20:14

我想回帖跟你说6楼demo的事,
结果一看,这论坛居然每一个帖子的下面的楼层号没有显示,折腾了一个小时总算有了...
在每楼增加了一个类似#6的序号,点一下帖子前面的 #6 可以复制到楼层的链接https://www.jyzzj.online/viewtopic.php?p=28#p28,方面你到时候汇总在顶楼
刚想说什么都忘了

头像
玉米棉花糖
帖子: 92
注册时间: 2018年 1月 11日 17:13

Re: vnpy量化平台学习过程中的经验分享

#20

帖子 玉米棉花糖 » 2018年 1月 14日 20:23

一个乱下单的策略

现在我们有基本的界面,行情、查询和报撤单的封装。要写策略,比较规范的方案是参考vnpy做个cta引擎来处理行情推送、报撤单等任务,然后写一个策略模板备好合成k线、指标计算等多数策略都会用到的方法。这样你的策略只要继承策略模板,再专注于策略逻辑就行了。而且由于策略模板标准化,可以直接把同一个策略文件用于回测或实盘,不需要任何修改。要学习的直接看vnpy源码,我就不多说了。

除此之外,既然有行情和基本的指令,你也可以随你的心意来写策略,直接调用这些方法即可。我这把写一个随便下单的策略,只是举个例子,不是规范的做法。

我就这样写吧,当过去1000个tick(相当于500秒)的最新价的99%置信区间的上下限比值大于101%,即波动大于1%时,随机开多单或空单,并设置一个止损和止盈。

代码: 全选

# encoding: UTF-8
"""
随便乱下单的策略
"""
import json
import numpy as np
from random import randint
from datetime import datetime, timedelta
from eventEngine import  *
from eventType import  *

class StrategyRandom:
    """乱下单策略"""
    def __init__(self, mainEngine, eventEngine, symbol):
        """Constructor"""
        self.me = mainEngine
        self.ee = eventEngine
        self.symbol = symbol
        
        self.active = False
        self.inited = False
        self.positionUpdated = False
        self.status = 0
        self.initerest = 0
        self.TodayPosition = 0
        self.TodayPositionDate = 'N/A'
        self.direction = 'N/A'
        self.stopPrice = 0.0
        self.exitPrice = 0.0
        self.size = 1000

        self.arrayTick = np.zeros(self.size)
        self.tickCount = 0

        self.__loadSetting()
        

    def __loadSetting(self):
        """读取设置"""
        try:
            with open('setting.json', 'r', encoding='utf-8') as settingFile:
                index = json.load(settingFile)
            self.__dict__ = dict(self.__dict__, **index)

        except FileNotFoundError:
            pass

    def start(self):
        """开始策略"""
        self.active = True
        self.__subscribe()
        self.putLog('乱下单策略成功启动')
        
    def stop(self):
        """停止策略"""
        self.savesetting()
        self.__unsubscribe()
        self.active = False
        self.putLog('乱下单策略成功停止')

    def savesetting(self):
        """保存参数"""
        toBeSave ={}
        toBeSave = dict(toBeSave, **self.__dict__)
        toBeSave.pop('me')
        toBeSave.pop('ee')
        toBeSave.pop('active')
        toBeSave.pop('inited')
        toBeSave.pop('arrayTick')
        toBeSave.pop('tickCount')

        with open('setting.json', 'w', encoding='utf-8') as settingFile:
            jsonD = json.dumps(toBeSave)
            settingFile.write(jsonD)
        
    def __subscribe(self):
        """订阅合约"""
        self.ee.register(EVENT_TICK+self.symbol, self.onTick)
        self.me.md.subscribe(self.symbol)
        
    def __unsubscribe(self):
        """取消订阅合约"""
        self.ee.unregister(EVENT_TICK+self.symbol, self.onTick)
        self.me.md.unsubscribe(self.symbol)

    def init(self, tick):
        '''初始化'''
        self.tickCount += 1
        if not self.inited and self.tickCount >= self.size:
            self.inited = True
        self.newTick(tick)

    def newTick(self, tick):
        '''更新缓存的tick'''
        self.arrayTick[0:self.size-1] = self.arrayTick[1:self.size]
        self.arrayTick[-1] = tick.lastPrice
        # 计算指标
        self.rangeMax = np.percentile(self.arrayTick, 99.5)
        self.rangeMin = np.percentile(self.arrayTick, 0.5)

    def onTick(self,event):
        """ 处理tick数据,产生交易信号"""
        tick = event.dict_['data']

        # 非交易时间的tick不触发交易
        d = tick.date
        t = tick.time
        t = datetime.strptime(d+t, '%Y%m%d%H:%M:%S')
        if (15 <= t.hour < 21) or (5 <= t.hour < 9):
            return

        # 第一个tick更新今仓数据,交易所
        if not self.positionUpdated:
            tradedate = tick.date
            self.exchage = tick.exchage
            if not tradedate == self.TodayPositionDate: # 更新平今仓数据
                self.TodayPosition = 0
                self.TodayPositionDate == 'N/A'
            self.positionUpdated = True

        # 先初始化
        if not self.inited:
            self.init(tick)
            return
            
        # 根据策略阶段选择方法
        func = self.case0 if self.status == 0 else self.case1
        func(tick)
        
    def case0(self, tick):
        """空仓阶段"""

        r = self.rangeMax / self.rangeMin

        if r > 1.01:
            n = randint(0, 9) % 2
            self.direction = 'long' if n else 'short' # 随机多空
                
            if self.direction == 'long':
                self.me.buy(self.symbol, tick.askPrice1, 1)     
                self.status = 1
                self.initerest = 1
                if self.exchage == "SHFE":
                    self.TodayPosition = 1
                    self.TodayPositionDate = tick.date
                self.openPrice = tick.askPrice1
                self.stopPrice = tick.askPrice1 * 0.998
                self.exitPrice = tick.askPrice1 * 1.006
                self.savesetting()        
                
                log = '策略信号:{symbol},多头开仓,开仓价{price},持仓量:{interest},止损价:{stop},止盈价:{exit}'.format(
                    symbol=self.symbol, price=self.openPrice, interest=self.interest, stop=self.stopPrice, exit=self.exitPrice)
                self.putLog(log)

            elif self.direction == 'short':
                self.me.short(self.symbol, tick.bidPrice1, 1)     
                self.status = 1
                self.initerest = 1
                if self.exchage == "SHFE":
                    self.TodayPosition = 1
                    self.TodayPositionDate = tick.date
                self.openPrice = tick.bidPrice1
                self.openPrice = tick.bidPrice1
                self.stopPrice = tick.bidPrice1 * 1.002
                self.exitPrice = tick.bidPrice1 * 0.994
                self.savesetting()        
                
                log = '策略信号:{symbol},空头开仓,开仓价{price},持仓量:{interest},止损价:{stop},止盈价:{exit}'.format(
                    symbol=self.symbol, price=self.openPrice, interest=self.interest, stop=self.stopPrice, exit=self.exitPrice)
                self.putLog(log)
                

                
    def case1(self, tick):
        """持仓阶段"""
        l = tick.lastPrice
        
        if self.direction == 'long': # 多头退出
            if l > self.exitPrice or l < self.stopPrice:
                func = self.me.selltoday if (self.exchage=='SHFE' and self.TodayPosition > 0) else self.me.sell
                func(self.symbol, tick.bidPrice1, self.interest)
                
                log = '策略信号:%s,多头退出%d手' %(self.symbol,  self.interest)
                self.putLog(log)
                self.restart()
                
        if self.direction == 'short': # 空头退出
                if l < self.exitPrice or l > self.stopPrice:
                    func = self.me.covertoday if (self.exchage=='SHFE' and self.TodayPosition > 0) else self.me.cover
                    func(self.symbol, tick.askPrice1, self.interest)
                    
                    log = '策略信号:%s,多头退出%d手' %(self.symbol,  self.interest)
                    self.putLog(log)
                    self.restart()

    def putLog(self, log):
        """向事件引擎投放日志"""
        event = Event(type_=EVENT_LOG)
        event.dict_['log'] = log
        self.ee.put(event)
        
    def restart(self):
        """回到空仓状态,清空参数"""
        self.status = 0         # 0,空仓
        self.direction = 'N/A'  # 交易方向 long,short,N/A
        self.stopPrice = 0.0    # 止损价
        self.exitPrice = 0.0    # 止盈价
        self.openPrice = 0.0    # 首笔开仓价
        self.interest = 0       # 持仓
        self.TodayPosition = 0  # 上期所今仓
        self.TodayPositionDate = 'N/A'
        
        self.savesetting()
在mainengine增加策略启动的函数

代码: 全选

            # 启动策略
            if not self.strategyActive:
                self.runStrategy()
                self.strategyActive = True
    # ----------------------------------------------------------------------
    def runStrategy(self):
        '''启动策略'''
        strategyRB = StrategyRandom(self, self.ee, 'rb1805')
        strategyRB.start()
运行结果如下
strategy.JPG
strategy.JPG (47.68 KiB) 查看 16987 次
代码见附件
附件
demo_strategy.rar
(9.2 KiB) 下载 605 次
上次由 玉米棉花糖 在 2018年 1月 17日 15:34,总共编辑 4 次。

回复