From e096455b446d09641cb31c1ab8a1f0984b365aaf Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 30 Nov 2017 22:32:09 +0800 Subject: [PATCH] =?UTF-8?q?[Add]=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90Option?= =?UTF-8?q?Master=E6=89=8B=E5=8A=A8=E4=BA=A4=E6=98=93=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/TRADE_MARKET_110100001088sop.dat | 2 +- vnpy/pricing/black.py | 10 +- vnpy/trader/app/optionMaster/omBase.py | 4 + vnpy/trader/app/optionMaster/uiOmBase.py | 41 ++ .../app/optionMaster/uiOmManualTrader.py | 411 ++++++++++++++++++ vnpy/trader/app/optionMaster/uiOmWidget.py | 8 +- 6 files changed, 472 insertions(+), 4 deletions(-) create mode 100644 vnpy/trader/app/optionMaster/uiOmBase.py create mode 100644 vnpy/trader/app/optionMaster/uiOmManualTrader.py diff --git a/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat b/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat index 347dba0b..0ca5e675 100644 --- a/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat +++ b/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat @@ -1 +1 @@ -B7/DvEptJ5IBD1LIj9SEFg== \ No newline at end of file +HqLpRXdyn/k8CK6bQvjTlg== \ No newline at end of file diff --git a/vnpy/pricing/black.py b/vnpy/pricing/black.py index 5792da61..0df9c35f 100644 --- a/vnpy/pricing/black.py +++ b/vnpy/pricing/black.py @@ -43,6 +43,10 @@ DX_TARGET = 0.00001 #---------------------------------------------------------------------- def calculatePrice(f, k, r, t, v, cp): """计算期权价格""" + # 如果波动率为0,则直接返回期权空间价值 + if v <= 0: + return max(0, cp * (f - k)) + d1 = (log(f / k) + (0.5 * pow(v, 2) + r) * t) / (v * sqrt(t)) d2 = d1 - v * sqrt(t) price = cp * (f * cdf(cp * d1) - k * cdf(cp * d2) * exp(-r * t)) @@ -121,9 +125,13 @@ def calculateImpv(price, f, k, r, t, cp): for i in range(50): # 计算当前猜测波动率对应的期权价格和vega值 p = calculatePrice(f, k, r, t, v, cp) - #print 'calculating vega', f, k, r, t, v, cp + vega = calculateOriginalVega(f, k, r, t, v, cp) + # 如果vega过小接近0,则直接返回 + if not vega: + return v + # 计算误差 dx = (price - p) / vega diff --git a/vnpy/trader/app/optionMaster/omBase.py b/vnpy/trader/app/optionMaster/omBase.py index 28e6a745..394e5657 100644 --- a/vnpy/trader/app/optionMaster/omBase.py +++ b/vnpy/trader/app/optionMaster/omBase.py @@ -376,6 +376,7 @@ class OmPortfolio(object): self.underlyingDict = OrderedDict() self.chainDict = OrderedDict() self.optionDict = {} + self.instrumentDict = {} for underlying in underlyingList: self.underlyingDict[underlying.symbol] = underlying @@ -385,6 +386,9 @@ class OmPortfolio(object): self.optionDict.update(chain.callDict) self.optionDict.update(chain.putDict) + self.instrumentDict.update(self.underlyingDict) + self.instrumentDict.update(self.optionDict) + # 持仓数据 self.longPos = EMPTY_INT self.shortPos = EMPTY_INT diff --git a/vnpy/trader/app/optionMaster/uiOmBase.py b/vnpy/trader/app/optionMaster/uiOmBase.py new file mode 100644 index 00000000..7dd30825 --- /dev/null +++ b/vnpy/trader/app/optionMaster/uiOmBase.py @@ -0,0 +1,41 @@ +# encoding: UTF-8 + +from vnpy.trader.uiQt import QtGui, QtWidgets, QtCore + +COLOR_BID = QtGui.QColor(255,174,201) +COLOR_ASK = QtGui.QColor(160,255,160) +COLOR_STRIKE = QtGui.QColor(0,0,160) +COLOR_POS = QtGui.QColor(225,255,255) +COLOR_SYMBOL = QtGui.QColor('white') +COLOR_BLACK = QtGui.QColor('black') + +CALL_SUFFIX = '_call' +PUT_SUFFIX = '_put' + +STYLESHEET_START = "background-color: rgb(111,255,244); color: black" +STYLESHEET_STOP = "background-color: rgb(255,201,111); color: black" + + +######################################################################## +class OmCell(QtWidgets.QTableWidgetItem): + """单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, background=None, foreground=None, data=None): + """Constructor""" + super(OmCell, self).__init__() + + self.data = data + self.background = None + + if text: + self.setText(text) + + if foreground: + self.setForeground(foreground) + + if background: + self.setBackground(background) + self.background = background + + self.setTextAlignment(QtCore.Qt.AlignCenter) \ No newline at end of file diff --git a/vnpy/trader/app/optionMaster/uiOmManualTrader.py b/vnpy/trader/app/optionMaster/uiOmManualTrader.py new file mode 100644 index 00000000..2b9c96cb --- /dev/null +++ b/vnpy/trader/app/optionMaster/uiOmManualTrader.py @@ -0,0 +1,411 @@ +# encoding: UTF-8 + +from vnpy.event import Event + +from vnpy.trader.vtConstant import DIRECTION_LONG, DIRECTION_SHORT, OFFSET_OPEN, OFFSET_CLOSE, PRICETYPE_LIMITPRICE +from vnpy.trader.vtObject import VtOrderReq +from vnpy.trader.vtEvent import EVENT_TICK, EVENT_TRADE +from vnpy.trader.uiBasicWidget import WorkingOrderMonitor, PositionMonitor + +from uiOmBase import * + + + +######################################################################## +class ChainMonitor(QtWidgets.QTableWidget): + """期权链监控""" + headers = [ + u'代码', + u'买价', + u'买量', + u'买隐波', + u'卖价', + u'卖量', + u'卖隐波', + u'净仓', + u'行权价', + u'净仓', + u'买价', + u'买量', + u'买隐波', + u'卖价', + u'卖量', + u'卖隐波', + u'代码' + ] + + signalTick = QtCore.pyqtSignal(type(Event())) + signalPos = QtCore.pyqtSignal(type(Event())) + signalTrade = QtCore.pyqtSignal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, omEngine, eventEngine, parent=None): + """Constructor""" + super(ChainMonitor, self).__init__(parent) + + self.omEngine = omEngine + self.eventEngine = eventEngine + + # 保存代码和持仓的字典 + self.bidPriceDict = {} + self.bidVolumeDict = {} + self.bidImpvDict = {} + self.askPriceDict = {} + self.askVolumeDict = {} + self.askImpvDict = {} + self.posDict = {} + + # 保存期权对象的字典 + portfolio = omEngine.portfolio + + self.instrumentDict = {} + self.instrumentDict.update(portfolio.optionDict) + self.instrumentDict.update(portfolio.underlyingDict) + + # 初始化 + self.initUi() + self.registerEvent() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + portfolio = self.omEngine.portfolio + + # 初始化表格 + self.setColumnCount(len(self.headers)) + self.setHorizontalHeaderLabels(self.headers) + + rowCount = 0 + rowCount += len(portfolio.underlyingDict) + rowCount += len(portfolio.chainDict) + for chain in portfolio.chainDict.values(): + rowCount += len(chain.callDict) + self.setRowCount(rowCount) + + self.verticalHeader().setVisible(False) + self.setEditTriggers(self.NoEditTriggers) + + for i in range(self.columnCount()): + self.horizontalHeader().setResizeMode(i, QtWidgets.QHeaderView.Stretch) + self.horizontalHeader().setResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) + self.horizontalHeader().setResizeMode(self.columnCount()-1, QtWidgets.QHeaderView.ResizeToContents) + + # 初始化标的单元格 + row = 0 + + for underlying in portfolio.underlyingDict.values(): + symbol = underlying.symbol + + cellSymbol = OmCell(symbol, COLOR_SYMBOL, COLOR_BLACK, underlying) + cellBidPrice = OmCell(str(underlying.bidPrice1), COLOR_BID, COLOR_BLACK, underlying) + cellBidVolume = OmCell(str(underlying.bidVolume1), COLOR_BID, COLOR_BLACK, underlying) + cellAskPrice = OmCell(str(underlying.askPrice1), COLOR_ASK, COLOR_BLACK, underlying) + cellAskVolume = OmCell(str(underlying.askVolume1), COLOR_ASK, COLOR_BLACK, underlying) + cellPos = OmCell(str(underlying.netPos), COLOR_POS, COLOR_BLACK, underlying) + + self.setItem(row, 0, cellSymbol) + self.setItem(row, 1, cellBidPrice) + self.setItem(row, 2, cellBidVolume) + self.setItem(row, 4, cellAskPrice) + self.setItem(row, 5, cellAskVolume) + self.setItem(row, 7, cellPos) + + self.bidPriceDict[symbol] = cellBidPrice + self.bidVolumeDict[symbol] = cellBidVolume + self.askPriceDict[symbol] = cellAskPrice + self.askVolumeDict[symbol] = cellAskVolume + self.posDict[symbol] = cellPos + + row += 1 + + row += 1 + + # 初始化期权单元格 + for chain in portfolio.chainDict.values(): + + # call + callRow = row + + for option in chain.callDict.values(): + cellSymbol = OmCell(option.symbol, COLOR_SYMBOL, COLOR_BLACK, option) + cellBidPrice = OmCell(str(option.bidPrice1), COLOR_BID, COLOR_BLACK, option) + cellBidVolume = OmCell(str(option.bidVolume1), COLOR_BID, COLOR_BLACK, option) + cellBidImpv = OmCell('%.1f' %(option.bidImpv*100), COLOR_BID, COLOR_BLACK, option) + cellAskPrice = OmCell(str(option.askPrice1), COLOR_ASK, COLOR_BLACK, option) + cellAskVolume = OmCell(str(option.askVolume1), COLOR_ASK, COLOR_BLACK, option) + cellAskImpv = OmCell('%.1f' %(option.askImpv*100), COLOR_ASK, COLOR_BLACK, option) + cellPos = OmCell(str(option.netPos), COLOR_POS, COLOR_BLACK, option) + cellStrike = OmCell(str(option.k), COLOR_STRIKE) + + self.setItem(callRow, 0, cellSymbol) + self.setItem(callRow, 1, cellBidPrice) + self.setItem(callRow, 2, cellBidVolume) + self.setItem(callRow, 3, cellBidImpv) + self.setItem(callRow, 4, cellAskPrice) + self.setItem(callRow, 5, cellAskVolume) + self.setItem(callRow, 6, cellAskImpv) + self.setItem(callRow, 7, cellPos) + self.setItem(callRow, 8, cellStrike) + + self.bidPriceDict[option.symbol] = cellBidPrice + self.bidVolumeDict[option.symbol] = cellBidVolume + self.bidImpvDict[option.symbol] = cellBidImpv + self.askPriceDict[option.symbol] = cellAskPrice + self.askVolumeDict[option.symbol] = cellAskVolume + self.askImpvDict[option.symbol] = cellAskImpv + self.posDict[option.symbol] = cellPos + + callRow += 1 + + # put + putRow = row + + for option in chain.putDict.values(): + cellSymbol = OmCell(option.symbol, COLOR_SYMBOL, COLOR_BLACK, option) + cellBidPrice = OmCell(str(option.bidPrice1), COLOR_BID, COLOR_BLACK, option) + cellBidVolume = OmCell(str(option.bidVolume1), COLOR_BID, COLOR_BLACK, option) + cellBidImpv = OmCell('%.1f' %(option.bidImpv*100), COLOR_BID, COLOR_BLACK, option) + cellAskPrice = OmCell(str(option.askPrice1), COLOR_ASK, COLOR_BLACK, option) + cellAskVolume = OmCell(str(option.askVolume1), COLOR_ASK, COLOR_BLACK, option) + cellAskImpv = OmCell('%.1f' %(option.askImpv*100), COLOR_ASK, COLOR_BLACK, option) + cellPos = OmCell(str(option.netPos), COLOR_POS, COLOR_BLACK, option) + + self.setItem(putRow, 9, cellPos) + self.setItem(putRow, 10, cellBidPrice) + self.setItem(putRow, 11, cellBidVolume) + self.setItem(putRow, 12, cellBidImpv) + self.setItem(putRow, 13, cellAskPrice) + self.setItem(putRow, 14, cellAskVolume) + self.setItem(putRow, 15, cellAskImpv) + self.setItem(putRow, 16, cellSymbol) + + self.bidPriceDict[option.symbol] = cellBidPrice + self.bidVolumeDict[option.symbol] = cellBidVolume + self.bidImpvDict[option.symbol] = cellBidImpv + self.askPriceDict[option.symbol] = cellAskPrice + self.askVolumeDict[option.symbol] = cellAskVolume + self.askImpvDict[option.symbol] = cellAskImpv + self.posDict[option.symbol] = cellPos + + putRow += 1 + + row = putRow + 1 + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.signalTick.connect(self.processTickEvent) + self.signalTrade.connect(self.processTradeEvent) + + portfolio = self.omEngine.portfolio + + for underlying in portfolio.underlyingDict.values(): + self.eventEngine.register(EVENT_TICK + underlying.vtSymbol, self.signalTick.emit) + self.eventEngine.register(EVENT_TRADE + underlying.vtSymbol, self.signalTick.emit) + + for chain in portfolio.chainDict.values(): + for option in chain.optionDict.values(): + self.eventEngine.register(EVENT_TICK + option.vtSymbol, self.signalTick.emit) + self.eventEngine.register(EVENT_TRADE + option.vtSymbol, self.signalTrade.emit) + + #---------------------------------------------------------------------- + def processTickEvent(self, event): + """行情更新""" + tick = event.dict_['data'] + symbol = tick.symbol + + if symbol in self.bidImpvDict: + option = self.instrumentDict[symbol] + self.bidImpvDict[symbol].setText('%.1f' %(option.bidImpv*100)) + self.askImpvDict[symbol].setText('%.1f' %(option.askImpv*100)) + + self.bidPriceDict[symbol].setText(str(tick.bidPrice1)) + self.bidVolumeDict[symbol].setText(str(tick.bidVolume1)) + self.askPriceDict[symbol].setText(str(tick.askPrice1)) + self.askVolumeDict[symbol].setText(str(tick.askVolume1)) + + #---------------------------------------------------------------------- + def processTradeEvent(self, event): + """成交更新""" + trade = event.dict_['data'] + + symbol = trade.symbol + instrument = self.instrumentDict[symbol] + self.posDict[symbol].setText(str(instrument.netPos)) + + +######################################################################## +class TradingWidget(QtWidgets.QWidget): + """交易组件""" + + #---------------------------------------------------------------------- + def __init__(self, omEngine, parent=None): + """Constructor""" + super(TradingWidget, self).__init__(parent) + + self.omEngine = omEngine + self.mainEngine = omEngine.mainEngine + self.portfolio = omEngine.portfolio + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setFixedWidth(200) + + labelTradingWidget = QtWidgets.QLabel(u'期权交易') + labelSymbol = QtWidgets.QLabel(u'代码') + labelDirection = QtWidgets.QLabel(u'方向') + labelPrice = QtWidgets.QLabel(u'价格') + labelVolume = QtWidgets.QLabel(u'数量') + + self.lineSymbol = QtWidgets.QLineEdit() + self.comboDirection = QtWidgets.QComboBox() + self.comboDirection.addItems([DIRECTION_LONG, DIRECTION_SHORT]) + self.linePrice = QtWidgets.QLineEdit() + self.lineVolume = QtWidgets.QLineEdit() + self.buttonSendOrder = QtWidgets.QPushButton(u'发单') + self.buttonSendOrder.clicked.connect(self.sendOrder) + + grid = QtWidgets.QGridLayout() + grid.addWidget(labelTradingWidget, 0, 0, 1, 2) + grid.addWidget(labelSymbol, 1, 0) + grid.addWidget(labelDirection, 2, 0) + grid.addWidget(labelPrice, 3, 0) + grid.addWidget(labelVolume, 4, 0) + grid.addWidget(self.lineSymbol, 1, 1) + grid.addWidget(self.comboDirection, 2, 1) + grid.addWidget(self.linePrice, 3, 1) + grid.addWidget(self.lineVolume, 4, 1) + grid.addWidget(self.buttonSendOrder, 5, 0, 1, 2) + self.setLayout(grid) + + #---------------------------------------------------------------------- + def sendOrder(self): + """发送委托""" + try: + symbol = str(self.lineSymbol.text()) + direction = str(self.comboDirection.currentText()) + price = float(self.linePrice.text()) + volume = int(self.lineVolume.text()) + except: + return + + instrument = self.portfolio.instrumentDict.get(symbol, None) + if not instrument: + return + + # 做多 + if direction == DIRECTION_LONG: + # 如果空头仓位大于等于买入量,则只需平 + if instrument.shortPos >= volume: + self.fastTrade(symbol, DIRECTION_LONG, OFFSET_CLOSE, price, volume) + # 否则先平后开 + else: + openVolume = volume - instrument.shortPos + if instrument.shortPos: + self.fastTrade(symbol, DIRECTION_LONG, OFFSET_CLOSE, price, instrument.shortPos) + self.fastTrade(symbol, DIRECTION_LONG, OFFSET_OPEN, price, openVolume) + # 做空 + else: + if instrument.longPos >= volume: + self.fastTrade(symbol, DIRECTION_SHORT, OFFSET_CLOSE, price, volume) + else: + openVolume = volume - instrument.longPos + if instrument.longPos: + self.fastTrade(symbol, DIRECTION_SHORT, OFFSET_CLOSE, price, instrument.longPos) + self.fastTrade(symbol ,DIRECTION_SHORT, OFFSET_OPEN, price, openVolume) + + #---------------------------------------------------------------------- + def fastTrade(self, symbol, direction, offset, price, volume): + """封装下单函数""" + contract = self.mainEngine.getContract(symbol) + if not contract: + return + + req = VtOrderReq() + req.symbol = symbol + req.exchange = contract.exchange + req.direction = direction + req.offset = offset + req.price = price + req.volume = volume + req.priceType = PRICETYPE_LIMITPRICE + self.mainEngine.sendOrder(req, contract.gatewayName) + + #---------------------------------------------------------------------- + def updateWidget(self, item): + """双击监控组件单元格后自动更新组件""" + instrument = item.data + if not instrument: + return + + self.lineSymbol.setText(instrument.symbol) + + # short + if item.background is COLOR_BID: + self.comboDirection.setCurrentIndex(1) + self.linePrice.setText(str(instrument.bidPrice1)) + self.lineVolume.setText(str(instrument.bidVolume1)) + # long + elif item.background is COLOR_ASK: + self.comboDirection.setCurrentIndex(0) + self.linePrice.setText(str(instrument.askPrice1)) + self.lineVolume.setText(str(instrument.askVolume1)) + + +######################################################################## +class ManualTrader(QtWidgets.QWidget): + """手动交易组件""" + + #---------------------------------------------------------------------- + def __init__(self, omEngine, parent=None): + """Constructor""" + super(ManualTrader, self).__init__(parent) + + self.omEngine = omEngine + self.mainEngine = omEngine.mainEngine + self.eventEngine = omEngine.eventEngine + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(u'手动交易') + + posMonitor = PositionMonitor(self.mainEngine, self.eventEngine) + for i in range(posMonitor.columnCount()): + posMonitor.horizontalHeader().setResizeMode(QtWidgets.QHeaderView.Stretch) + posMonitor.setSorting(False) + + orderMonitor = WorkingOrderMonitor(self.mainEngine, self.eventEngine) + for i in range(orderMonitor.columnCount()): + orderMonitor.horizontalHeader().setResizeMode(QtWidgets.QHeaderView.Stretch) + orderMonitor.setSorting(False) + + tradingWidget = TradingWidget(self.omEngine) + + chainMonitor = ChainMonitor(self.omEngine, self.eventEngine) + chainMonitor.itemDoubleClicked.connect(tradingWidget.updateWidget) + + vbox1 = QtWidgets.QVBoxLayout() + vbox1.addWidget(orderMonitor) + vbox1.addWidget(posMonitor) + + vbox2 = QtWidgets.QVBoxLayout() + vbox2.addWidget(tradingWidget) + vbox2.addStretch() + + hbox = QtWidgets.QHBoxLayout() + hbox.addLayout(vbox1) + hbox.addLayout(vbox2) + + vbox3 = QtWidgets.QVBoxLayout() + vbox3.addWidget(chainMonitor) + vbox3.addLayout(hbox) + + self.setLayout(vbox3) + + \ No newline at end of file diff --git a/vnpy/trader/app/optionMaster/uiOmWidget.py b/vnpy/trader/app/optionMaster/uiOmWidget.py index 5a12721a..74f2b308 100644 --- a/vnpy/trader/app/optionMaster/uiOmWidget.py +++ b/vnpy/trader/app/optionMaster/uiOmWidget.py @@ -9,6 +9,7 @@ from vnpy.event import Event from vnpy.trader.uiQt import QtWidgets, QtCore from .omBase import EVENT_OM_LOG +from .uiOmManualTrader import ManualTrader ######################################################################## @@ -51,12 +52,16 @@ class OmManager(QtWidgets.QWidget): self.buttonInit = QtWidgets.QPushButton(u'初始化') self.buttonInit.clicked.connect(self.initOmEngine) + self.buttonManualTrader = QtWidgets.QPushButton(u'手动交易') + self.buttonManualTrader.clicked.connect(self.openManualTrader) + self.logMonitor = QtWidgets.QTextEdit() self.logMonitor.setReadOnly(True) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.comboSettingFile) hbox.addWidget(self.buttonInit) + hbox.addWidget(self.buttonManualTrader) hbox.addStretch() hbox2 = QtWidgets.QHBoxLayout() @@ -99,7 +104,7 @@ class OmManager(QtWidgets.QWidget): #---------------------------------------------------------------------- def openManualTrader(self): - """打开手动交易""" + """打开手动交易组件""" try: self.widgetDict['manualTrader'].showMaximized() except KeyError: @@ -120,4 +125,3 @@ class OmManager(QtWidgets.QWidget): self.signal.connect(self.processLogEvent) self.eventEngine.register(EVENT_OM_LOG, self.signal.emit) - \ No newline at end of file