From 20bae552d6c67ff3594139281768ac695d60b9db Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Fri, 1 Dec 2017 13:54:19 +0800 Subject: [PATCH] =?UTF-8?q?[Add]=E6=B7=BB=E5=8A=A0=E6=8C=81=E4=BB=93?= =?UTF-8?q?=E6=83=85=E6=99=AF=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD?= 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 | 4 +- vnpy/trader/app/optionMaster/omBase.py | 3 +- vnpy/trader/app/optionMaster/omEngine.py | 4 +- .../app/optionMaster/uiOmAnalysisManager.py | 208 ++++++++++++++++++ vnpy/trader/app/optionMaster/uiOmWidget.py | 18 +- 6 files changed, 232 insertions(+), 7 deletions(-) create mode 100644 vnpy/trader/app/optionMaster/uiOmAnalysisManager.py diff --git a/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat b/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat index db1d5d87..3faff51b 100644 --- a/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat +++ b/examples/OptionMaster/data/TRADE_MARKET_110100001088sop.dat @@ -1 +1 @@ -1akdYJY+uYtFOYGtWEJkKg== \ No newline at end of file +3PhIldq+vwFT0Ap6unvLxA== \ No newline at end of file diff --git a/vnpy/pricing/black.py b/vnpy/pricing/black.py index 22d59f72..e1a28ce8 100644 --- a/vnpy/pricing/black.py +++ b/vnpy/pricing/black.py @@ -130,7 +130,7 @@ def calculateImpv(price, f, k, r, t, cp): # 如果vega过小接近0,则直接返回 if not vega: - return v + break # 计算误差 dx = (price - p) / vega @@ -143,7 +143,7 @@ def calculateImpv(price, f, k, r, t, cp): v += dx # 检查波动率计算结果非负 - if v < 0: + if v <= 0: return 0 # 保留4位小数 diff --git a/vnpy/trader/app/optionMaster/omBase.py b/vnpy/trader/app/optionMaster/omBase.py index b3c03863..e82e021a 100644 --- a/vnpy/trader/app/optionMaster/omBase.py +++ b/vnpy/trader/app/optionMaster/omBase.py @@ -368,9 +368,10 @@ class OmPortfolio(object): """持仓组合""" #---------------------------------------------------------------------- - def __init__(self, name, underlyingList, chainList): + def __init__(self, name, model, underlyingList, chainList): """Constructor""" self.name = name + self.model = model # 原始容器 self.underlyingDict = OrderedDict() diff --git a/vnpy/trader/app/optionMaster/omEngine.py b/vnpy/trader/app/optionMaster/omEngine.py index 766b35e3..fc9be9bc 100644 --- a/vnpy/trader/app/optionMaster/omEngine.py +++ b/vnpy/trader/app/optionMaster/omEngine.py @@ -156,7 +156,7 @@ class OmEngine(object): underlying.addChain(chain) # 创建持仓组合对象并初始化 - self.portfolio = OmPortfolio(setting['name'], underlyingDict.values(), chainList) + self.portfolio = OmPortfolio(setting['name'], model, underlyingDict.values(), chainList) # 载入波动率配置 self.loadImpvSetting() @@ -198,7 +198,7 @@ class OmEngine(object): f.close() #---------------------------------------------------------------------- - def close(self): + def stop(self): """关闭函数""" self.saveImpvSetting() diff --git a/vnpy/trader/app/optionMaster/uiOmAnalysisManager.py b/vnpy/trader/app/optionMaster/uiOmAnalysisManager.py new file mode 100644 index 00000000..15199f35 --- /dev/null +++ b/vnpy/trader/app/optionMaster/uiOmAnalysisManager.py @@ -0,0 +1,208 @@ +# encoding: UTF-8 + +from __future__ import division + +import numpy as np + +from .uiOmBase import * + + +######################################################################## +class ScenarioValueMonitor(QtWidgets.QTableWidget): + """情景分析监控工具,某一个数值""" + + #---------------------------------------------------------------------- + def __init__(self, key, parent=None): + """Constructor""" + super(ScenarioValueMonitor, self).__init__(parent) + + self.key = key + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setEditTriggers(self.NoEditTriggers) + + self.setMinimumHeight(600) + + #---------------------------------------------------------------------- + def updateData(self, result, priceChangeArray, impvChangeArray): + """更新界面""" + # 清空表格 + self.clearContents() + + # 设置表头 + self.setColumnCount(len(priceChangeArray)) + priceChangeHeaders = [('%s%%' %(priceChange*100)) for priceChange in priceChangeArray] + self.setHorizontalHeaderLabels(priceChangeHeaders) + + self.setRowCount(len(impvChangeArray)) + impvChangeHeaders = [('%s%%' %(impvChange*100)) for impvChange in impvChangeArray] + self.setVerticalHeaderLabels(impvChangeHeaders) + + # 设置数据 + l = [d[self.key] for d in result.values()] + maxValue = max(l) + minValue = min(l) + + # 最大和最小值相等,则说明计算逻辑有问题 + if maxValue == minValue: + return + + midValue = (maxValue + minValue) / 2 + colorRatio = 255*2/(maxValue-minValue) + + for column, priceChange in enumerate(priceChangeArray): + for row, impvChange in enumerate(impvChangeArray): + value = result[(priceChange, impvChange)][self.key] + + # 计算颜色 + red = 255 + green = 255 + colorValue = (value - midValue) * colorRatio + + if colorValue <= 0: + red -= abs(colorValue) + else: + green -= abs(colorValue) + color = QtGui.QColor(red, green, 0) + + # 插入单元格到表格中 + cell = QtWidgets.QTableWidgetItem('%.1f' %value) + cell.setBackground(color) + cell.setForeground(COLOR_BLACK) + self.setItem(row, column, cell) + + self.resizeColumnsToContents() + self.resizeRowsToContents() + + +######################################################################## +class ScenarioAnalysisMonitor(QtWidgets.QTabWidget): + """情景分析监控组件""" + + #---------------------------------------------------------------------- + def __init__(self, parent=None): + """Constructor""" + super(ScenarioAnalysisMonitor, self).__init__(parent) + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """""" + self.valueMonitorList = [] + + for key in ['pnl', 'delta', 'gamma', 'theta', 'vega']: + valueMonitor = ScenarioValueMonitor(key) + self.addTab(valueMonitor, key) + self.valueMonitorList.append(valueMonitor) + + #---------------------------------------------------------------------- + def updateData(self, result, priceChangeArray, impvChangeArray): + """更新数据""" + for valueMonitor in self.valueMonitorList: + valueMonitor.updateData(result, priceChangeArray, impvChangeArray) + + +######################################################################## +class AnalysisManager(QtWidgets.QWidget): + """研究分析管理""" + + #---------------------------------------------------------------------- + def __init__(self, omEngine, parent=None): + """Constructor""" + super(AnalysisManager, self).__init__(parent) + + self.omEngine = omEngine + self.portfolio = omEngine.portfolio + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(u'持仓分析') + + self.scenarioAnalysisMonitor = ScenarioAnalysisMonitor() + + self.buttonScenarioAnalysis = QtWidgets.QPushButton(u'情景分析') + self.buttonScenarioAnalysis.clicked.connect(self.updateData) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(self.buttonScenarioAnalysis) + hbox.addStretch() + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox) + vbox.addWidget(self.scenarioAnalysisMonitor) + self.setLayout(vbox) + + #---------------------------------------------------------------------- + def updateData(self): + """更新数据""" + result, priceChangeArray, impvChangeArray = self.runScenarioAnalysis() + if result: + self.scenarioAnalysisMonitor.updateData(result, priceChangeArray, impvChangeArray) + + #---------------------------------------------------------------------- + def runScenarioAnalysis(self): + """运行情景分析""" + portfolio = self.portfolio + calculateGreeks = portfolio.model.calculateGreeks + + if not portfolio: + return None, None, None + + changeRange = 5 + priceChangeArray = np.arange(-changeRange, changeRange+1) / 100 + impvChangeArray = np.arange(-changeRange, changeRange+1) / 100 + expiryChange = 1/240 # 一个交易日对应的时间变化 + result = {} # 分析结果 + + for priceChange in priceChangeArray: + for impvChange in impvChangeArray: + print priceChange, impvChange + portfolioPnl = 0 + portfolioDelta = 0 + portfolioGamma = 0 + portfolioTheta = 0 + portfolioVega = 0 + + for underlying in portfolio.underlyingDict.values(): + portfolioPnl += underlying.midPrice * underlying.netPos * priceChange + portfolioDelta += underlying.theoDelta * underlying.netPos + + try: + for option in portfolio.optionDict.values(): + if not option.netPos: + continue + + price, delta, gamma, theta, vega = calculateGreeks(option.underlying.midPrice*(1+priceChange), + option.k, + option.r, + max(option.t-expiryChange, 0), + option.pricingImpv*(1+impvChange), + option.cp) + + portfolioPnl += (price - option.theoPrice) * option.netPos * option.size + portfolioDelta += delta * option.netPos * option.size + portfolioGamma += gamma * option.netPos * option.size + portfolioTheta += theta * option.netPos * option.size + portfolioVega += vega * option.netPos * option.size + except ZeroDivisionError: + return None, None, None + + d = { + 'pnl': portfolioPnl, + 'delta': portfolioDelta, + 'gamma': portfolioGamma, + 'theta': portfolioTheta, + 'vega': portfolioVega + } + result[(priceChange, impvChange)] = d + + return result, priceChangeArray, impvChangeArray + \ No newline at end of file diff --git a/vnpy/trader/app/optionMaster/uiOmWidget.py b/vnpy/trader/app/optionMaster/uiOmWidget.py index f60f7e97..f34d0069 100644 --- a/vnpy/trader/app/optionMaster/uiOmWidget.py +++ b/vnpy/trader/app/optionMaster/uiOmWidget.py @@ -12,6 +12,7 @@ from .uiOmBase import QtWidgets, QtCore from .uiOmManualTrader import ManualTrader from .uiOmGreeksMonitor import GreeksMonitor from .uiOmVolatilityManager import VolatilityChart, VolatilityManager +from .uiOmAnalysisManager import AnalysisManager ######################################################################## @@ -70,6 +71,10 @@ class OmManager(QtWidgets.QWidget): self.buttonVolatilityManager.clicked.connect(self.openVolatilityManager) self.buttonVolatilityManager.setDisabled(True) + self.buttonAnalysisManager = QtWidgets.QPushButton(u'持仓分析') + self.buttonAnalysisManager.clicked.connect(self.openAnalysisManager) + self.buttonAnalysisManager.setDisabled(True) + self.logMonitor = QtWidgets.QTextEdit() self.logMonitor.setReadOnly(True) @@ -80,6 +85,7 @@ class OmManager(QtWidgets.QWidget): hbox.addWidget(self.buttonGreeksMonitor) hbox.addWidget(self.buttonVolatilityChart) hbox.addWidget(self.buttonVolatilityManager) + hbox.addWidget(self.buttonAnalysisManager) hbox.addStretch() hbox2 = QtWidgets.QHBoxLayout() @@ -116,6 +122,7 @@ class OmManager(QtWidgets.QWidget): self.buttonGreeksMonitor.setEnabled(True) self.buttonVolatilityChart.setEnabled(True) self.buttonVolatilityManager.setEnabled(True) + self.buttonAnalysisManager.setEnabled(True) #---------------------------------------------------------------------- def writeLog(self, content, time=''): @@ -166,7 +173,16 @@ class OmManager(QtWidgets.QWidget): self.widgetDict['volatilityManager'].show() except KeyError: self.widgetDict['volatilityManager'] = VolatilityManager(self.omEngine) - self.widgetDict['volatilityManager'].show() + self.widgetDict['volatilityManager'].show() + + #---------------------------------------------------------------------- + def openAnalysisManager(self): + """打开持仓分析组件""" + try: + self.widgetDict['analysisManager'].showMaximized() + except KeyError: + self.widgetDict['analysisManager'] = AnalysisManager(self.omEngine) + self.widgetDict['analysisManager'].showMaximized() #---------------------------------------------------------------------- def close(self):