vn.py量化社区
By Traders, For Traders.
Member
avatar
加入于:
帖子: 9
声望: 0

目的是添加一个手动开仓的UI,按钮链接到策略的sendOrder,然后策略自动平仓(譬如止损),但失败了,启动半个小时左右,UI就 无响应了
版本 vnpy1.8.1
具体实施的步骤如下:
1、在主窗口增加了一个 “测试” 菜单
description
2、‘’测试‘下拉菜单里,添加按钮,连接到弹出窗口
测试按钮代码:

        # 帮助
        helpMenu = menubar.addMenu(vtText.HELP)
        helpMenu.addAction(self.createAction(vtText.CONTRACT_SEARCH, self.openContract, loadIconPath('contract.ico')))
        helpMenu.addAction(self.createAction(vtText.EDIT_SETTING, self.openSettingEditor, loadIconPath('editor.ico')))
        helpMenu.addSeparator()
        helpMenu.addAction(self.createAction(vtText.RESTORE, self.restoreWindow, loadIconPath('restore.ico')))
        helpMenu.addAction(self.createAction(vtText.ABOUT, self.openAbout, loadIconPath('about.ico')))
        helpMenu.addSeparator()
        helpMenu.addAction(self.createAction(vtText.TEST, self.test, loadIconPath('test.ico'))) 
       # 测试
        testMenu = menubar.addMenu(vtText.TEST)
        testMenu.addAction(self.createAction(u'参数配置', self.openSettingParameter, loadIconPath('editor.ico')))
        testMenu.addAction(self.createAction(u'sniper', self.sniperTrade, loadIconPath('editor.ico')))**

弹出窗口函数

    def sniperTrade(self):
        """打开手动交易"""
        try:
            self.widgetDict['sniperTrade'].show()
        except KeyError:
            self.widgetDict['sniperTrade'] = SniperTradingWidget(self.mainEngine, self.eventEngine, self.ctaEngine)    # 无问西东 新建窗体 新增传入ctaEngine
            self.widgetDict['sniperTrade'].show()

3、弹出窗口是参照“简单交易组件”照搬过来,增加了几个下单的按钮(可以连接到策略中的sendOrder)

class TradingWidget(QtWidgets.QFrame):

class SniperTradingWidget(QtWidgets.QFrame):

description

这个UI界面中没有耗时程序,基本updateTick函数,更新UI时间在10ms内

description

一般能正常启动和正常运行,半个小时到一个小时后,UI就无响应了(包括MainWindow),UI虽然无响应,但是后台的策略程序在正常运行。

description

请问这样加载一个新的弹出窗口的程序步骤是正确的么?无响应的问题会出在哪个环节?谢谢

Administrator
avatar
加入于:
帖子: 1690
声望: 80

如果你的updateTick函数中涉及到GUI操作,不能直接将其注册到事件引擎里,而要通过pyqtSignal连接后,将signal.emit注册到事件引擎里。

具体请搜索Qt Signal/Slot机制。

Member
avatar
加入于:
帖子: 9
声望: 0

非常感谢群主百忙之中的解答
signal.emit注册是有的,因为这个UI窗口是照搬“简单交易组件”来的,所以,注册机制跟MainWindow里的您写的那个手动下单一致
参考的源代码:

class TradingWidget(QtWidgets.QFrame):
    """简单交易组件"""
    signal = QtCore.Signal(type(Event()))
    ......
    #----------------------------------------------------------------------
    def registerEvent(self):
        """注册事件监听"""
        self.signal.connect(self.updateTick)
        self.eventEngine.register(EVENT_TICK, self.signal.emit)

实际代码如下,这两个class的注册是一模一样的

class SniperTradingWidget(QtWidgets.QFrame):
    """狙击交易组件"""
    signal = QtCore.Signal(type(Event()))
    ......
    #----------------------------------------------------------------------
    def registerEvent(self):
        """注册事件监听"""
        self.signal.connect(self.updateTick)
        self.eventEngine.register(EVENT_TICK, self.signal.emit)

至于调用这个class的上层函数,我也是参考’功能应用‘菜单的模板
’功能应用‘菜单源码,参考’打开配置编辑‘代码:

    def createOpenAppFunction(self, appDetail):
        """创建打开应用UI的函数"""
        def openAppFunction():
            appName = appDetail['appName']
            try:
                self.widgetDict[appName].show()
            except KeyError:
                appEngine = self.mainEngine.getApp(appName)
                # self.widgetDict[appName] = appDetail['appWidget'](appEngine, self.eventEngine)

                # 无问西东,这部分细节实现还不太清楚,传递的参数不是(self.mainEngine, self.eventEngine, self.ctaEngine)
                # 譬如CtaEngineManager(ctaEngine, eventEngine, parent=None)
                self.widgetDict[appName] = appDetail['appWidget'](appEngine, self.eventEngine)
                self.widgetDict[appName].show()

        return openAppFunction

    def openSettingEditor(self):
        """打开配置编辑"""
        try:
            self.widgetDict['settingEditor'].show()
        except KeyError:
            self.widgetDict['settingEditor'] = SettingEditor(self.mainEngine)
            self.widgetDict['settingEditor'].show()

调用自编UI的代码(多传了一个ctaEngine,在初始化时的_init_里面就传入了):

    def sniperTrade(self):
        """打开手动交易"""
        try:
            self.widgetDict['sniperTrade'].show()
        except KeyError:
            self.widgetDict['sniperTrade'] = SniperTradingWidget(self.mainEngine, self.eventEngine, self.ctaEngine)    # 无问西东 新建窗体 新增传入ctaEngine
            self.widgetDict['sniperTrade'].show()

自己写的updateTick函数内容也挺简单,也就是参考“简单交易组件”更新一下数据

    def updateTick(self, event):
        """更新行情"""
        tick = event.dict_['data']

        if tick.vtSymbol != self.symbol:
            return
        self.tick = tick

        updateStart = datetime.datetime.now()
        self.ctaEngine.writeCtaLog(u'updateStart时间: %s' % (updateStart))

        # 新增
        self.lastTick = tick
        self.getVolume()    # 成交量相关计算函数

        if not self.checkFixed.isChecked():
            self.spinPrice.setValue(tick.lastPrice)

        # priceTick = self.mainEngine.getContract(self.vtSymbol).priceTick
        self.labelBidVolume1.setText(str(tick.bidVolume1))
        self.labelAskVolume1.setText(str(tick.askVolume1))

        self.labelBidPrice1.setText(str(tick.bidPrice1))
        self.labelBidPrice2.setText(str(tick.bidPrice1 - 1 * self.priceTick))
        self.labelBidPrice3.setText(str(tick.bidPrice1 - 2 * self.priceTick))
        self.labelBidPrice4.setText(str(tick.bidPrice1 - 3 * self.priceTick))
        self.labelBidPrice5.setText(str(tick.bidPrice1 - 4 * self.priceTick))

        self.labelAskPrice1.setText(str(tick.askPrice1))
        self.labelAskPrice2.setText(str(tick.askPrice1 + 1 * self.priceTick))
        self.labelAskPrice3.setText(str(tick.askPrice1 + 2 * self.priceTick))
        self.labelAskPrice4.setText(str(tick.askPrice1 + 3 * self.priceTick))
        self.labelAskPrice5.setText(str(tick.askPrice1 + 4 * self.priceTick))

        # 新增
        self.labelBidPrice1Middle.setText(str(tick.bidPrice1))
        self.labelAskPrice1Middle.setText(str(tick.askPrice1))
        self.labelBidVolume1Middle.setText(str(tick.bidVolume1))
        self.labelAskVolume1Middle.setText(str(tick.askVolume1))

        self.labelAskVolumeLast.setText(str(self.askVolumeLast))
        self.labelBidVolumeLast.setText(str(self.bidVolumeLast))
        self.labelAskVolumeSum.setText(str(self.askVolumeSum))
        self.labelBidVolumeSum.setText(str(self.bidVolumeSum))

        # 偏移与点差计算挂单价格
        self.labelAskpriceDiff.setText(str(tick.askPrice1 + self.spinDiff.value() * self.priceTick))
        self.labelBidpriceDiff.setText(str(tick.bidPrice1 - self.spinDiff.value() * self.priceTick))

        if tick.bidPrice2:
            self.labelBidPrice2.setText(str(tick.bidPrice2))
            self.labelBidPrice3.setText(str(tick.bidPrice3))
            self.labelBidPrice4.setText(str(tick.bidPrice4))
            self.labelBidPrice5.setText(str(tick.bidPrice5))

            self.labelAskPrice2.setText(str(tick.askPrice2))
            self.labelAskPrice3.setText(str(tick.askPrice3))
            self.labelAskPrice4.setText(str(tick.askPrice4))
            self.labelAskPrice5.setText(str(tick.askPrice5))

            self.labelBidVolume2.setText(str(tick.bidVolume2))
            self.labelBidVolume3.setText(str(tick.bidVolume3))
            self.labelBidVolume4.setText(str(tick.bidVolume4))
            self.labelBidVolume5.setText(str(tick.bidVolume5))

            self.labelAskVolume2.setText(str(tick.askVolume2))
            self.labelAskVolume3.setText(str(tick.askVolume3))
            self.labelAskVolume4.setText(str(tick.askVolume4))
            self.labelAskVolume5.setText(str(tick.askVolume5))

        self.labelLastPrice.setText(str(tick.lastPrice))

        if tick.preClosePrice:
            rt = (tick.lastPrice/tick.preClosePrice)-1
            self.labelReturn.setText(('%.2f' %(rt*100))+'%')
        else:
            self.labelReturn.setText('')

        # 新增
        self.askPricePrevious = self.lastTick.askPrice1
        self.bidPricePrevious = self.lastTick.bidPrice1
        self.volumePrevious = self.lastTick.volume

        updateEnd= datetime.datetime.now()
        self.ctaEngine.writeCtaLog(u'updateEnd时间: %s' % (updateEnd))
        self.ctaEngine.writeCtaLog(u'update运行时间: %s' % (updateEnd - updateStart))

感觉定义class 和调用class 都跟原版很接近的呀?好像不是注册事件的原因,还有什么其他的可能性么?或者我的调用class有问题?它的现象是可以正常启动,也可以正常运行,只是运行一个小时左右,就出现UI(未响应)。
感谢群主的指导

补充:
因为在updateTick函数的开始 和结尾都打印了时间戳
查看log发现,最后的时间戳是成对出现的,也就是最后一次是顺利的运行到了updateTick的结尾,不是死在了这个函数的内部计算过程中

2019-07-30 10:33:51,516  INFO: CTA_STRATEGY updateStart时间: 2019-07-30 10:33:51.400000
2019-07-30 10:33:51,571  INFO: CTA_STRATEGY updateEnd时间: 2019-07-30 10:33:51.409000
2019-07-30 10:33:51,572  INFO: CTA_STRATEGY update运行时间: 0:00:00.009000
Administrator
avatar
加入于:
帖子: 1690
声望: 80

从代码上看不出来明显问题,要一步步debug检查了,整体思路就是寻找在事件驱动线程里,对GUI做了什么直接操作(没有通过Signal/Slot机制),运行一段时间后莫名卡住崩溃,99%是因为这个问题。

另外可以在cmd中运行你的run.py,如果是Python层的问题会有异常输出

Member
avatar
加入于:
帖子: 9
声望: 0

基础代码我再认真核对一下,找了好几圈了,目前还没找出哪里直接改了UI而没通过Singal/Slot

另外,从结构上,我看到一些有数据交互功能的UI,譬如CTA策略、价差交易,都被独立封装成了上层应用

    # 添加上层应用
    me.addApp(riskManager)
    me.addApp(ctaStrategy)
    me.addApp(spreadTrading)

description

交互功能如下,有按键修改策略状态初始化,有log输出到UI

description

我自己设计的UI也有类似的功能,就是按钮直接修改策略的参数和链接到策略的sendOrder发单

description

请问,也必须像价差交易、CTA策略等一样做成“上层应用”吗?
我参考的是没有实时交互功能的代码模式
如“打开配置编辑”模板,直接从菜单栏弹出:

    def openSettingEditor(self):
        """打开配置编辑"""
        try:
            self.widgetDict['settingEditor'].show()
        except KeyError:
            self.widgetDict['settingEditor'] = SettingEditor(self.mainEngine)
            self.widgetDict['settingEditor'].show()

自己设计的弹出窗口如下:

    def sniperTrade(self):
        """打开手动交易"""
        try:
            self.widgetDict['sniperTrade'].show()
        except KeyError:
            self.widgetDict['sniperTrade'] = SniperTradingWidget(self.mainEngine, self.eventEngine, self.ctaEngine)    # 无问西东 新建窗体 新增传入ctaEngine
            self.widgetDict['sniperTrade'].show()

交互功能UI,结构上必须做成上层应用么?还是可以做成简单的弹出窗口就行了?
这可能是最后一个疑问了,非常感谢群主的悉心指导

Administrator
avatar
加入于:
帖子: 1690
声望: 80

可以直接打开窗口,本来做成App也就是把重复的打开窗口代码封装了一下

© 2015-2019 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号-3