From 64bd2eb6d2c9002831d780ca227ce38a89e5ebb5 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Thu, 30 Nov 2017 15:08:35 +0800 Subject: [PATCH] =?UTF-8?q?[Add]=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90Option?= =?UTF-8?q?Master=E6=A0=B8=E5=BF=83=E5=AE=9A=E4=BB=B7=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=9A=84=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/TRADE_MARKET_110100001088sop.dat | 2 +- examples/OptionMaster/run.py | 3 +- vnpy/pricing/__init__.py | 0 vnpy/pricing/black.py | 14 +- vnpy/trader/app/optionMaster/__init__.py | 8 +- .../app/optionMaster/etf_portfolio.json | 15 +-- vnpy/trader/app/optionMaster/omBase.py | 47 ++++--- vnpy/trader/app/optionMaster/omEngine.py | 126 +++++++++++------- vnpy/trader/app/optionMaster/uiOmWidget.py | 123 +++++++++++++++++ vnpy/trader/gateway/secGateway/secGateway.py | 13 +- vnpy/trader/vtEngine.py | 5 + 11 files changed, 269 insertions(+), 87 deletions(-) create mode 100644 vnpy/pricing/__init__.py create mode 100644 vnpy/trader/app/optionMaster/uiOmWidget.py diff --git a/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat b/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat index 78625f8b..347dba0b 100644 --- a/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat +++ b/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat @@ -1 +1 @@ -PurwJxAEvTeQ9X8HMnmMRw== \ No newline at end of file +B7/DvEptJ5IBD1LIj9SEFg== \ No newline at end of file diff --git a/examples/OptionMaster/run.py b/examples/OptionMaster/run.py index 663d722e..bc6948b0 100644 --- a/examples/OptionMaster/run.py +++ b/examples/OptionMaster/run.py @@ -19,7 +19,7 @@ from vnpy.trader.uiMainWindow import MainWindow from vnpy.trader.gateway import (secGateway) # 加载上层应用 -from vnpy.trader.app import (riskManager) +from vnpy.trader.app import (riskManager, optionMaster) #---------------------------------------------------------------------- @@ -39,6 +39,7 @@ def main(): # 添加上层应用 me.addApp(riskManager) + me.addApp(optionMaster) # 创建主窗口 mw = MainWindow(me, ee) diff --git a/vnpy/pricing/__init__.py b/vnpy/pricing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vnpy/pricing/black.py b/vnpy/pricing/black.py index ecc5ae3e..5792da61 100644 --- a/vnpy/pricing/black.py +++ b/vnpy/pricing/black.py @@ -23,6 +23,7 @@ theta:当t变动1天时,price的变动(国内交易日每年240天) vega:当v涨跌1个点时,price的变动(如从16%涨到17%) ''' +from __future__ import division from scipy import stats from math import (log, pow, sqrt, exp) @@ -74,9 +75,15 @@ def calculateTheta(f, k, r, t, v, cp): #---------------------------------------------------------------------- def calculateVega(f, k, r, t, v, cp): """计算Vega值""" + vega = calculateVega(f, k, r, t, v, cp) / 100 + return vega + +#---------------------------------------------------------------------- +def calculateOriginalVega(f, k, r, t, v, cp): + """计算原始vega值""" price1 = calculatePrice(f, k, r, t, v*STEP_UP, cp) price2 = calculatePrice(f, k, r, t, v*STEP_DOWN, cp) - vega = (price1 - price2) / (v * STEP_DIFF * 100) + vega = (price1 - price2) / (v * STEP_DIFF) return vega #---------------------------------------------------------------------- @@ -109,12 +116,13 @@ def calculateImpv(price, f, k, r, t, cp): return 0 # 采用Newton Raphson方法计算隐含波动率 - v = 0.2 # 初始波动率猜测 + v = 0.3 # 初始波动率猜测 for i in range(50): # 计算当前猜测波动率对应的期权价格和vega值 p = calculatePrice(f, k, r, t, v, cp) - vega = calculateVega(f, k, r, t, v, cp) + #print 'calculating vega', f, k, r, t, v, cp + vega = calculateOriginalVega(f, k, r, t, v, cp) # 计算误差 dx = (price - p) / vega diff --git a/vnpy/trader/app/optionMaster/__init__.py b/vnpy/trader/app/optionMaster/__init__.py index 05ff3d30..e2f06d8f 100644 --- a/vnpy/trader/app/optionMaster/__init__.py +++ b/vnpy/trader/app/optionMaster/__init__.py @@ -1,10 +1,10 @@ # encoding: UTF-8 -#from rmEngine import RmEngine -#from uiRmWidget import RmEngineManager +from .omEngine import OmEngine +from .uiOmWidget import OmManager appName = 'OptionMaster' appDisplayName = u'OptionMaster' -appEngine = None -appWidget = None +appEngine = OmEngine +appWidget = OmManager appIco = 'om.ico' \ No newline at end of file diff --git a/vnpy/trader/app/optionMaster/etf_portfolio.json b/vnpy/trader/app/optionMaster/etf_portfolio.json index 46a685e7..029bdf0f 100644 --- a/vnpy/trader/app/optionMaster/etf_portfolio.json +++ b/vnpy/trader/app/optionMaster/etf_portfolio.json @@ -2,19 +2,18 @@ "name": "etf_portfolio", "model": "black", "underlying": [ - "IH1711", - "IH1712" + "510050" ], "chain": [ { - "underlyingSymbol": "IH1711", - "chainSymbol": "m1709", - "interestRate": 0.03, + "underlyingSymbol": "510050", + "chainSymbol": "510050-1712", + "r": 0.03 }, { - "underlyingSymbol": "IH1711", - "chainSymbol": "m1801", - "interestRate": 0.03, + "underlyingSymbol": "510050", + "chainSymbol": "510050-1801", + "r": 0.03 } ] } \ No newline at end of file diff --git a/vnpy/trader/app/optionMaster/omBase.py b/vnpy/trader/app/optionMaster/omBase.py index 98780ebe..28e6a745 100644 --- a/vnpy/trader/app/optionMaster/omBase.py +++ b/vnpy/trader/app/optionMaster/omBase.py @@ -1,5 +1,7 @@ # encoding: UTF-8 +from __future__ import division + from copy import copy from collections import OrderedDict @@ -26,7 +28,10 @@ class OmInstrument(VtTickData): """Constructor""" super(OmInstrument, self).__init__() + self.tickInited = False + # 初始化合约信息 + self.symbol = contract.symbol self.exchange = contract.exchange self.vtSymbol = contract.vtSymbol @@ -38,10 +43,15 @@ class OmInstrument(VtTickData): self.midPrice = EMPTY_FLOAT # 持仓数据 - self.longPos = detail.longPos - self.shortPos = detail.shortPos - self.netPos = self.longPos - self.shortPos + self.longPos = 0 + self.shortPos = 0 + self.netPos = 0 + if detail: + self.longPos = detail.longPos + self.shortPos = detail.shortPos + self.netPos = self.longPos - self.shortPos + #---------------------------------------------------------------------- def newTick(self, tick): """行情更新""" @@ -61,6 +71,7 @@ class OmInstrument(VtTickData): self.askPrice1 = tick.askPrice1 self.bidVolume1 = tick.bidVolume1 self.askVolume1 = tick.askVolume1 + self.midPrice = (self.bidPrice1 + self.askPrice1) / 2 #---------------------------------------------------------------------- def newTrade(self, trade): @@ -98,17 +109,22 @@ class OmUnderlying(OmInstrument): """标的物""" #---------------------------------------------------------------------- - def __init__(self, contract, chainList): + def __init__(self, contract, detail, chainList=None): """Constructor""" - super(OmUnderlying, self).__init__(contract) + super(OmUnderlying, self).__init__(contract, detail) # 以该合约为标的物的期权链字典 - self.chainDict = OrderedDict((chain.symbol, chain) for chain in chainList) + self.chainDict = OrderedDict() # 希腊值 self.theoDelta = EMPTY_FLOAT # 理论delta值 self.posDelta = EMPTY_FLOAT # 持仓delta值 + #---------------------------------------------------------------------- + def addChain(self, chain): + """添加以该合约为标的的期权链""" + self.chainDict[chain.symbol] = chain + #---------------------------------------------------------------------- def newTick(self, tick): """行情更新""" @@ -117,7 +133,7 @@ class OmUnderlying(OmInstrument): self.theoDelta = self.size * self.midPrice / 100 # 遍历推送自己的行情到期权链中 - for chain in self.chainList: + for chain in self.chainDict.values(): chain.newUnderlyingTick() #---------------------------------------------------------------------- @@ -137,12 +153,12 @@ class OmOption(OmInstrument): """期权""" #---------------------------------------------------------------------- - def __init__(self, contract, underlying, model, r): + def __init__(self, contract, detail, underlying, model, r): """Constructor""" - super(OmOption, self).__init__(contract) + super(OmOption, self).__init__(contract, detail) # 期权属性 - self.underlying = None # 标的物对象 + self.underlying = underlying # 标的物对象 self.k = contract.strikePrice # 行权价 self.r = r # 利率 @@ -183,7 +199,7 @@ class OmOption(OmInstrument): self.chain = None #---------------------------------------------------------------------- - def calculateImpv(self): + def calculateOptionImpv(self): """计算隐含波动率""" underlyingPrice = self.underlying.midPrice if not underlyingPrice: @@ -222,12 +238,12 @@ class OmOption(OmInstrument): def newTick(self, tick): """行情更新""" super(OmOption, self).newTick(tick) - self.calculateImpv() + self.calculateOptionImpv() #---------------------------------------------------------------------- def newUnderlyingTick(self): """标的行情更新""" - self.calculateImpv() + self.calculateOptionImpv() self.calculateTheoGreeks() self.calculatePosGreeks() @@ -243,7 +259,6 @@ class OmOption(OmInstrument): self.underlying = underlying - ######################################################################## class OmChain(object): """期权链""" @@ -394,10 +409,10 @@ class OmPortfolio(object): self.posTheta = 0 self.posVega = 0 - for underlying in self.underlyingList: + for underlying in self.underlyingDict.values(): self.posDelta += underlying.posDelta - for chain in self.chainList: + for chain in self.chainDict.values(): self.longPos += chain.longPos self.shortPos += chain.shortPos diff --git a/vnpy/trader/app/optionMaster/omEngine.py b/vnpy/trader/app/optionMaster/omEngine.py index ef2414a1..766b35e3 100644 --- a/vnpy/trader/app/optionMaster/omEngine.py +++ b/vnpy/trader/app/optionMaster/omEngine.py @@ -5,16 +5,26 @@ import json import shelve import os import traceback +from collections import OrderedDict from vnpy.event import Event from vnpy.trader.vtEvent import EVENT_TICK, EVENT_TRADE, EVENT_CONTRACT from vnpy.trader.vtFunction import getTempPath, getJsonPath -from vnpy.trader.vtObject import VtLogData +from vnpy.trader.vtObject import VtLogData, VtSubscribeReq +from vnpy.trader.vtConstant import PRODUCT_OPTION, OPTION_CALL, OPTION_PUT +from vnpy.pricing import black from .omBase import (OmOption, OmUnderlying, OmChain, OmPortfolio, EVENT_OM_LOG) + +# 定价模型字典 +MODEL_DICT = {} +MODEL_DICT['black'] = black + + + ######################################################################## class OmEngine(object): """期权主引擎""" @@ -28,7 +38,7 @@ class OmEngine(object): self.eventEngine = eventEngine self.portfolio = None - self.contractDict = {} # symbol:contract + self.optionContractDict = {} # symbol:contract self.registerEvent() @@ -53,18 +63,22 @@ class OmEngine(object): def processContractEvent(self, event): """合约事件""" contract = event.dict_['data'] - if contract.symbol: - self.contractDict[contract.symbol] = contract + if contract.symbol and contract.productClass == PRODUCT_OPTION: + self.optionContractDict[contract.symbol] = contract #---------------------------------------------------------------------- def subscribeEvent(self, symbol): """订阅对应合约的事件""" - contract = self.contractDict[symbol] + contract = self.mainEngine.getContract(symbol) + if not contract: + self.writeLog(u'行情订阅失败,找不到合约:%s' %symbol) + return + vtSymbol = contract.vtSymbol # 订阅行情 req = VtSubscribeReq() - req.symbol = symbol + req.symbol = contract.symbol req.exchange = contract.exchange self.mainEngine.subscribe(req, contract.gatewayName) @@ -80,67 +94,81 @@ class OmEngine(object): f = file(fileName) setting = json.load(f) + + # 读取定价模型 + model = MODEL_DICT.get(setting['model'], None) + if not model: + self.writeLog(u'找不到定价模型%s' %setting['model']) + return - # 创建期货和股票标的对象 - equityDict = OrderedDict([(symbol, OmEquity(symbol)) for symbol in setting['equity']]) - futuresDict = OrderedDict([(symbol, OmFutures(symbol)) for symbol in setting['futures']]) + # 创建标的对象 + underlyingDict = OrderedDict() + + for underlyingSymbol in setting['underlying']: + contract = self.mainEngine.getContract(underlyingSymbol) + if not contract: + self.writeLog(u'找不到标的物合约%s' %underlyingSymbol) + continue + + detail = self.mainEngine.getPositionDetail(contract.vtSymbol) + + underlying = OmUnderlying(contract, detail) + underlyingDict[underlyingSymbol] = underlying # 创建期权链对象并初始化 - chainDict = OrderedDict() + chainList = [] for d in setting['chain']: - interestRate = d['interestRate'] + chainSymbol = d['chainSymbol'] + r = d['r'] - # 锁定标的对象,若无则创建 - if d['underlyingType'] == 'futures': - if d['underlyingSymbol'] not in futuresDict: - underlying = OmFutures(d['underlyingSymbol']) - futuresDict[underlying.symbol] = underlying - else: - underlying = futuresDict[d['underlyingSymbol']] - elif d['underlyingType'] == 'equity': - if d['underlyingSymbol'] not in equityDict: - underlying = OmEquity(d['underlyingSymbol']) - equityDict[underlying.symbol] = underlying - else: - underlying = equityDict[d['underlyingSymbol']] + # 锁定标的对象 + underlying = underlyingDict.get(d['underlyingSymbol'], None) + if not underlying: + self.writeLog(u'%s期权链的标的合约%s尚未创建,请检查配置文件' %(chainSymbol, underlyingSymbol)) + continue # 创建期权对象并初始化 - callList = [] - putList = [] + callDict = {} + putDict = {} - for symbol, contract in self.contractDict.items(): - if contract.optionType and contract.underlyingSymbol == d['chainSymbol']: - option = OmOption(symbol) - option.init(contract, underlying, interestRate) - self.subscribeEvent(option.symbol) # 订阅事件 + for symbol, contract in self.optionContractDict.items(): + if contract.underlyingSymbol == d['chainSymbol']: + detail = self.mainEngine.getPositionDetail(contract.vtSymbol) + option = OmOption(contract, detail, underlying, model, r) if contract.optionType is OPTION_CALL: - callList.append(option) + callDict[option.k] = option else: - putList.append(option) + putDict[option.k] = option + + # 期权排序 + strikeList = callDict.keys() + strikeList.sort() + callList = [callDict[k] for k in strikeList] + putList = [putDict[k] for k in strikeList] + + # 创建期权链 + chain = OmChain(chainSymbol, callList, putList) + chainList.append(chain) + + # 添加标的映射关系 + underlying.addChain(chain) - chain = OmChain(d['chainSymbol']) - chain.init(underlying, callList, putList) - chainDict[chain.symbol] = chain - - # 初始化标的对象 - for underlying in (equityDict.values() + futuresDict.values()): - l = [] - for chain in chainDict.values(): - if chain.underlying is underlying: - l.append(chain) - contract = self.contractDict[underlying.symbol] - underlying.init(contract, l) - self.subscribeEvent(underlying.symbol) # 订阅事件 - # 创建持仓组合对象并初始化 - self.portfolio = OmPortfolio(setting['name']) - self.portfolio.init(futuresDict, equityDict, chainDict) + self.portfolio = OmPortfolio(setting['name'], underlyingDict.values(), chainList) # 载入波动率配置 self.loadImpvSetting() + # 订阅行情和事件 + for underlying in underlyingDict.values(): + self.subscribeEvent(underlying.vtSymbol) + + for chain in chainList: + for option in chain.optionDict.values(): + self.subscribeEvent(option.vtSymbol) + # 载入成功返回 return True diff --git a/vnpy/trader/app/optionMaster/uiOmWidget.py b/vnpy/trader/app/optionMaster/uiOmWidget.py new file mode 100644 index 00000000..5a12721a --- /dev/null +++ b/vnpy/trader/app/optionMaster/uiOmWidget.py @@ -0,0 +1,123 @@ +# encoding: UTF-8 + +from __future__ import division + +import os +from datetime import datetime + +from vnpy.event import Event +from vnpy.trader.uiQt import QtWidgets, QtCore + +from .omBase import EVENT_OM_LOG + + +######################################################################## +class OmManager(QtWidgets.QWidget): + """管理组件""" + signal = QtCore.pyqtSignal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, omEngine, eventEngine, parent=None): + """Constructor""" + super(OmManager, self).__init__(parent) + + self.omEngine = omEngine + self.eventEngine = eventEngine + + self.widgetDict = {} + + self.initUi() + self.registerEvent() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(u'OptionMaster管理') + + # 读取配置文件 + settingFileList = [] + + path = os.path.abspath(os.path.dirname(__file__)) + for root, subdirs, files in os.walk(path): + for name in files: + if '_portfolio.json' in name: + settingFileList.append(name) + + # 设置界面 + self.comboSettingFile = QtWidgets.QComboBox() + self.comboSettingFile.addItems(settingFileList) + self.comboSettingFile.setCurrentIndex(0) + + self.buttonInit = QtWidgets.QPushButton(u'初始化') + self.buttonInit.clicked.connect(self.initOmEngine) + + self.logMonitor = QtWidgets.QTextEdit() + self.logMonitor.setReadOnly(True) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(self.comboSettingFile) + hbox.addWidget(self.buttonInit) + hbox.addStretch() + + hbox2 = QtWidgets.QHBoxLayout() + hbox2.addStretch() + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox) + vbox.addLayout(hbox2) + vbox.addWidget(self.logMonitor) + + self.setLayout(vbox) + + #---------------------------------------------------------------------- + def initOmEngine(self): + """初始化引擎""" + path = os.path.abspath(os.path.dirname(__file__)) + fileName = unicode(self.comboSettingFile.currentText()) + fileName = os.path.join(path, fileName) + result = self.omEngine.initEngine(fileName) + + if result: + self.writeLog(u'引擎初始化成功') + else: + self.writeLog(u'请勿重复初始化引擎') + + #---------------------------------------------------------------------- + def writeLog(self, content, time=''): + """记录日志""" + if not time: + time = datetime.now().strftime('%H:%M:%S') + content = time + '\t' + content + self.logMonitor.append(content) + + #---------------------------------------------------------------------- + def processLogEvent(self, event): + """处理日志事件""" + log = event.dict_['data'] + self.writeLog(log.logContent, log.logTime) + self.raise_() + + #---------------------------------------------------------------------- + def openManualTrader(self): + """打开手动交易""" + try: + self.widgetDict['manualTrader'].showMaximized() + except KeyError: + self.widgetDict['manualTrader'] = ManualTrader(self.omEngine) + self.widgetDict['manualTrader'].showMaximized() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + for widget in self.widgetDict.values(): + widget.close() + + super(OmManagerWidget, self).close() + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.signal.connect(self.processLogEvent) + + self.eventEngine.register(EVENT_OM_LOG, self.signal.emit) + \ No newline at end of file diff --git a/vnpy/trader/gateway/secGateway/secGateway.py b/vnpy/trader/gateway/secGateway/secGateway.py index deae0611..86aa7e33 100644 --- a/vnpy/trader/gateway/secGateway/secGateway.py +++ b/vnpy/trader/gateway/secGateway/secGateway.py @@ -50,8 +50,11 @@ exchangeMapReverse = {v:k for k,v in exchangeMap.items()} #---------------------------------------------------------------------- def print_dict(d): """""" - for k, v in d.items(): - print '%s:%s' %(k, v) + print '-' * 30 + l = d.keys() + l.sort() + for k in l: + print '%s:%s' %(k, d[k]) ######################################################################## @@ -893,7 +896,7 @@ class SecTdApi(TdApi): #---------------------------------------------------------------------- def onRspStockQryStockStaticInfo(self, data, error, flag): - """股票合约查询回报""" + """股票合约查询回报""" if not data: return @@ -1226,8 +1229,8 @@ class SecTdApi(TdApi): contract.strikePrice = data['execPrice'] - contract.underlyingSymbol = data['securityID'] - contract.expiryDate = data['endTradingDay'] + contract.underlyingSymbol = '-'.join([data['securityID'], str(data['endTradingDay'])[2:-2]]) + contract.expiryDate = str(data['endTradingDay']) # 合约类型 contract.productClass = PRODUCT_OPTION diff --git a/vnpy/trader/vtEngine.py b/vnpy/trader/vtEngine.py index f0122ace..d6844375 100644 --- a/vnpy/trader/vtEngine.py +++ b/vnpy/trader/vtEngine.py @@ -277,6 +277,11 @@ class MainEngine(object): """查询委托""" return self.dataEngine.getOrder(vtOrderID) + #---------------------------------------------------------------------- + def getPositionDetail(self, vtSymbol): + """查询持仓细节""" + return self.dataEngine.getPositionDetail(vtSymbol) + #---------------------------------------------------------------------- def getAllWorkingOrders(self): """查询所有的活跃的委托(返回列表)"""