def start(self): #gui communication self.msg = MessageQueue() self.msg_event = THD.Event() self.msg_thread = THD.Thread(target=self.__handleMessage) self.msg_thread.start() #data engine self.engine = Engine(self) self.__startAutoRefresh(True) self.engine.qryEtfQuoteFeed() self.engine.qryTableDataFeed()
def __init__(self, gui): self.gui = gui #original position table self.ori_positions = None #etf quote self.etf = TableHandler() self.etf.reset(1, Engine.ETF_QUOTE_HEADERS, -1) #marketdata service self.md = MarketdataAdaptor() #database service self.dp = DADAPTOR.DataProxy() self.__reloadPositions() #flow control self.last_sync_time = DT.datetime.now() #gui communication self.msg = MessageQueue() self.msg_event = THD.Event() self.msg_thread = THD.Thread(target=self.__handleMessage) self.msg_thread.start() return
class Engine: etf_code = '510050.SH' ETF_QUOTE_HEADERS = ('last_price', 'open_price', 'high_price', 'low_price', 'update_time') STATISTICS_HEADERS = ('implied_vol', 'delta', 'gamma', 'vega', 'theta', 'intrnic', 'time_value') #------------------------------------------------------------- def __init__(self, gui): self.gui = gui #original position table self.ori_positions = None #etf quote self.etf = TableHandler() self.etf.reset(1, Engine.ETF_QUOTE_HEADERS, -1) #marketdata service self.md = MarketdataAdaptor() #database service self.dp = DADAPTOR.DataProxy() self.__reloadPositions() #flow control self.last_sync_time = DT.datetime.now() #gui communication self.msg = MessageQueue() self.msg_event = THD.Event() self.msg_thread = THD.Thread(target=self.__handleMessage) self.msg_thread.start() return def quit(self): self.__pushMsg(MessageTypes.QUIT) self.msg_thread.join() #------------------------------------------------------------- def qryUpdateData(self): self.__pushMsg(MessageTypes.UPDATE_QUOTE_DATA) def qryEtfQuoteFeed(self): self.__pushMsg(MessageTypes.GUI_QUERY_ETF_QUOTE_FEED) def qryTableDataFeed(self): self.__pushMsg(MessageTypes.GUI_QUERY_TABLE_FEED) def qryPositionBasedata(self): self.__pushMsg(MessageTypes.GUI_QUERY_POSITION_BASEDATA_FEED) def qryCalGreeksSensibilityByGroup(self, option_group_id, stock_group_id, x_axis_type): self.__pushMsg(MessageTypes.GUI_QUERY_CAL_SENSI, (option_group_id, stock_group_id, PassedIndexType.GROUP, x_axis_type)) def qryCalGreeksSensibilityByPosition(self, option_rows, stock_rows, x_axis_type): self.__pushMsg( MessageTypes.GUI_QUERY_CAL_SENSI, (option_rows, stock_rows, PassedIndexType.ROW, x_axis_type)) def qryExerciseCurveByGroup(self, option_group_id, stock_group_id): self.__pushMsg( MessageTypes.GUI_QUERY_EXERCISE_CURVE, (option_group_id, stock_group_id, PassedIndexType.GROUP)) def qryExerciseCurveByPosition(self, option_rows, stock_rows): self.__pushMsg(MessageTypes.GUI_QUERY_EXERCISE_CURVE, (option_rows, stock_rows, PassedIndexType.ROW)) def qryReloadPositions(self, positions_data=None): self.__pushMsg(MessageTypes.GUI_QUERY_RELOAD_POSITIONS, positions_data) def qrySavePositionCsv(self): self.__pushMsg(MessageTypes.SAVE_POSITION_CSV) def __pushMsg(self, msg_type, content=None): self.msg.pushMsg(msg_type, content) self.msg_event.set() def __handleMessage(self): try: while True: msg = self.msg.getMsg() if msg is None: self.msg_event.wait() self.msg_event.clear() #update marketdata order by user elif msg.type is MessageTypes.UPDATE_QUOTE_DATA: self.__updateData() #qry engine provide table data elif msg.type is MessageTypes.GUI_QUERY_TABLE_FEED: self.__feedDataTable() #qry etf data elif msg.type is MessageTypes.GUI_QUERY_ETF_QUOTE_FEED: self.__feedEtfQuote() #qry position base data for editor elif msg.type is MessageTypes.GUI_QUERY_POSITION_BASEDATA_FEED: self.__feedPositionBaseData() #cal greeks sensibility elif msg.type is MessageTypes.GUI_QUERY_CAL_SENSI: self.__calGreekSensibility(msg.content[0], msg.content[1], msg.content[2], msg.content[3]) elif msg.type is MessageTypes.GUI_QUERY_EXERCISE_CURVE: self.__calOptionExerciseProfitCurve( msg.content[0], msg.content[1], msg.content[2]) elif msg.type is MessageTypes.GUI_QUERY_RELOAD_POSITIONS: self.__reloadPositions(msg.content) elif msg.type is MessageTypes.SAVE_POSITION_CSV: self.__savePosition2Csv() elif msg.type is MessageTypes.QUIT: break except Exception as err: self.gui.onEngineError(err) #thread terminate return #----------------------------------------------------------- #positions should be a instance of TableHandler def __reloadPositions(self, positions=None): if type(positions) is TableHandler: pos = positions.toDataFrame() else: pos, err = DADAPTOR.loadPositionCsv() if not err is None: raise Exception('load position csv failed ...') #save pos self.ori_positions = pos #separate data option_rows = list() stock_rows = list() for r in range(0, pos.shape[0]): code = pos['code'].iat[r] contract_type = self.md.getContractType(code) if contract_type in ['call', 'put']: option_rows.append(r) else: stock_rows.append(r) option_df = pos.iloc[option_rows, :] stock_df = pos.iloc[stock_rows, :] self.dp.initialize(option_df, stock_df) self.__updateData(True) return def __savePosition2Csv(self): DADAPTOR.savePositionCsv(self.ori_positions) return def __updateData(self, update_baseinfo=False): self.last_sync_time = DT.datetime.now() #stock self.__updateEtfData() stk = self.dp.getStockData() for r in range(0, stk.rows()): self.__updateStockRow(r) #option opt = self.dp.getOptionData() for r in range(0, opt.rows()): if update_baseinfo: self.__updateRowBaseInfos(r) self.__updateOptionRow(r) #update database self.dp.updateData() return #update etf price data def __updateEtfData(self): etf_last_price = self.md.getLastprice(Engine.etf_code) self.etf.setByHeader(0, 'last_price', etf_last_price) self.etf.setByHeader(0, 'update_time', self.md.getLastUpdateTime(Engine.etf_code)) if not self.etf.getByHeader(0, 'open_price') < 0: self.etf.setByHeader( 0, 'high_price', max(etf_last_price, self.etf.getByHeader(0, 'high_price'))) self.etf.setByHeader( 0, 'low_price', min(etf_last_price, self.etf.getByHeader(0, 'low_price'))) else: O = self.md.getDailyOpen(Engine.etf_code) H = self.md.getDailyHigh(Engine.etf_code) L = self.md.getDailyLow(Engine.etf_code) if O and H and L: self.etf.setByHeader(0, 'open_price', O) self.etf.setByHeader(0, 'high_price', H) self.etf.setByHeader(0, 'low_price', L) return def __updateStockRow(self, irow): pos = self.dp.getStockData() last_price = self.etf.getByHeader(0, 'last_price') float_profit = ANALYSER.getFloatProfit( pos.getByHeader(irow, 'dir'), pos.getByHeader(irow, 'lots'), pos.getByHeader(irow, 'open_price'), last_price, self.md.getStockMultiplier()) pos.setByHeader(irow, 'last_price', last_price) pos.setByHeader(irow, 'float_profit', float_profit) return #update basic_infos like expiry, strike_price etc. def __updateRowBaseInfos(self, irow): pos = self.dp.getOptionData() code = pos.getByHeader(irow, 'code') pos.setByHeader(irow, 'type', self.md.getContractType(code)) pos.setByHeader(irow, 'strike', self.md.getStrikePrice(code)) pos.setByHeader(irow, 'expiry', self.md.getExerciseDate(code)) pos.setByHeader(irow, 'left_days', self.md.getDaysBeforeExercise(code)) return #update def __updateOptionRow(self, irow): pos = self.dp.getOptionData() code = pos.getByHeader(irow, 'code') last_price = self.md.getLastprice(code) pos.setByHeader(irow, 'last_price', last_price) ################################### S = self.etf.getByHeader(0, 'last_price') K = pos.getByHeader(irow, 'strike') T = pos.getByHeader(irow, 'left_days') opt_type = pos.getByHeader(irow, 'type') #greeks stat = None if opt_type.lower() == 'call': stat = ANALYSER.getStatistics(S, K, T, last_price, True) elif opt_type.lower() == 'put': stat = ANALYSER.getStatistics(S, K, T, last_price, False) if stat: for header in Engine.STATISTICS_HEADERS: pos.setByHeader(irow, header, stat[header]) #trade state float_profit = ANALYSER.getFloatProfit( pos.getByHeader(irow, 'dir'), pos.getByHeader(irow, 'lots'), pos.getByHeader(irow, 'open_price'), last_price, self.md.getOptionMultiplier()) pos.setByHeader(irow, 'float_profit', float_profit) return def __feedDataTable(self): opt_data = TableHandler() opt_data.copyDataframe(self.dp.getOptionData().getDataFrame()) stk_data = TableHandler() stk_data.copyDataframe(self.dp.getStockData().getDataFrame()) ptf_data = TableHandler() ptf_data.copyDataframe(self.dp.getPortfolioData().getDataFrame()) self.gui.onRepTableFeed(opt_data, stk_data, ptf_data) return def __feedEtfQuote(self): snap_etf = TableHandler() snap_etf.copy(self.etf) self.gui.onRepEtfQuoteFeed(snap_etf) return def __feedPositionBaseData(self): tdata = TableHandler() tdata.copyDataframe(self.ori_positions) self.gui.onRepPositionBasedataFeed(tdata) return def __calGreekSensibility(self, option_idx, stock_idx, idx_type, x_axis_type): opt = self.dp.getOptionData() stk = self.dp.getStockData() if idx_type is PassedIndexType.GROUP: opt_data = opt.getPositionDataByGroupId(option_idx) stk_data = stk.getPositionDataByGroupId(stock_idx) elif idx_type is PassedIndexType.ROW: opt_data = opt.getPositionDataByRowIdx(option_idx) stk_data = stk.getPositionDataByRowIdx(stock_idx) else: return if x_axis_type is XAxisType.PRICE: rtn = ANALYSER.getGreeksSensibilityByPrice( opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) elif x_axis_type is XAxisType.VOLATILITY: rtn = ANALYSER.getGreeksSensibilityByVolatility( opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) elif x_axis_type is XAxisType.TIME: rtn = ANALYSER.getGreeksSensibilityByTime( opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) else: return self.gui.onRepCalGreeksSensibility(rtn, x_axis_type) return def __calOptionExerciseProfitCurve(self, option_idx, stock_idx, idx_type): opt = self.dp.getOptionData() stk = self.dp.getStockData() if idx_type is PassedIndexType.GROUP: opt_data = opt.getPositionDataByGroupId(option_idx) stk_data = stk.getPositionDataByGroupId(stock_idx) elif idx_type is PassedIndexType.ROW: opt_data = opt.getPositionDataByRowIdx(option_idx) stk_data = stk.getPositionDataByRowIdx(stock_idx) else: return rtn = ANALYSER.getExerciseProfitCurve( opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) self.gui.onRepCalExerciseCurve(rtn) return
class OptionCalculator(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(OptionCalculator, self).__init__(parent) self.setupUi(self) #show self.show() #define signal&slot self.connect(self.fresh_quotes_button, SIGNAL('clicked()'), self.__onRefreshQuoteBtClicked) self.connect(self.edit_position_button, SIGNAL('clicked()'), self.__onEditPosBtClicked) self.connect(self.plot_button, SIGNAL('clicked()'), self.__onPlotBtClicked) self.connect(self, SIGNAL('PLOT_SENSIBILITY'), self.__plotGreeksSensibility) self.connect(self, SIGNAL('PLOT_EXERCISE_CURVE'), self.__plotExerciseCurve) self.connect(self, SIGNAL('SET_ETF_DISPLAY'), self.__setEtfDataDisplay) self.connect(self, SIGNAL('SET_CENTRAL_DISPLAY'), self.__setCentralTableDisplay) self.connect(self, SIGNAL('ENGINE_ERROR'), self.__notifyErrorOccur) #init position vtable self.option_data = MatrixModel(self) self.pos_deleg = AutoFormDelegate(self) self.position_vtable.setItemDelegate(self.pos_deleg) self.position_vtable.setModel(self.option_data) self.option_data.setSize(0, OPTION_DF_HEADERS) #init stock_position vtable self.stock_data = MatrixModel(self) self.stpos_deleg = AutoFormDelegate(self) self.stock_position_vtable.setItemDelegate(self.stpos_deleg) self.stock_position_vtable.setModel(self.stock_data) self.stock_data.setSize(0, STOCK_DF_HEADERS) #init portfolio vtable self.portfolio_data = MatrixModel(self) self.ptf_deleg = AutoFormDelegate(self) self.portfolio_vtable.setItemDelegate(self.ptf_deleg) self.portfolio_vtable.setModel(self.portfolio_data) self.portfolio_data.setSize(0, PORTFOLIO_DF_HEADERS) #gui communication self.msg = None self.msg_event = None self.msg_thread = None #data engine self.engine = None #flow control self.is_updating = False self.auto_refresh_timer = None #qt child self.edit_dialog = PosEditor(self) self.edit_dialog.setControler(self) return def start(self): #gui communication self.msg = MessageQueue() self.msg_event = THD.Event() self.msg_thread = THD.Thread(target=self.__handleMessage) self.msg_thread.start() #data engine self.engine = Engine(self) self.__startAutoRefresh(True) self.engine.qryEtfQuoteFeed() self.engine.qryTableDataFeed() def quit(self): if not self.auto_refresh_timer is None: self.auto_refresh_timer.cancel() if not self.engine is None: self.engine.quit() if not self.msg_thread is None: self.__pushMsg(MessageTypes.QUIT) self.msg_thread.join() return def closeEvent(self, event): rtn = QMessageBox.question(self, 'Save & exit', 'Save positions to csv?', QMessageBox.Save, QMessageBox.No, QMessageBox.Cancel) if rtn == QMessageBox.Cancel: event.ignore() return if rtn == QMessageBox.Save: self.onSavePosition2Csv() self.quit() #close super(OptionCalculator, self).closeEvent(event) return #------------------------------------------------------------------------- def onEngineError(self, err): with open(r'./logs.txt', 'a') as fid: err_info = '\n>>%s\n%s' % (str(DT.datetime.now()), str(err)) fid.write(err_info) self.emit(SIGNAL('ENGINE_ERROR')) return def onRepTableFeed(self, option_data, stock_data, ptf_data): self.__pushMsg(MessageTypes.REPLY_TABLE_FEED, (option_data, stock_data, ptf_data)) def onRepEtfQuoteFeed(self, etf_data): self.__pushMsg(MessageTypes.REPLY_ETF_QUOTE_FEED, etf_data) def onRepCalGreeksSensibility(self, plot_data, x_axis_type): self.__pushMsg(MessageTypes.REPLY_CAL_SENSI, (plot_data, x_axis_type)) def onRepCalExerciseCurve(self, plot_data): self.__pushMsg(MessageTypes.REPLY_EXERCISE_CURVE, plot_data) def onRepPositionBasedataFeed(self, positions): self.__pushMsg(MessageTypes.REPLY_POSITION_BASEDATA_FEED, positions) def __handleMessage(self): while True: msg = self.msg.getMsg() if msg is None: self.msg_event.wait() self.msg_event.clear() #received data for display elif msg.type is MessageTypes.REPLY_TABLE_FEED: self.emit(SIGNAL('SET_CENTRAL_DISPLAY'), msg.content[0], msg.content[1], msg.content[2]) elif msg.type is MessageTypes.REPLY_ETF_QUOTE_FEED: self.emit(SIGNAL('SET_ETF_DISPLAY'), msg.content) elif msg.type is MessageTypes.REPLY_CAL_SENSI: self.emit(SIGNAL('PLOT_SENSIBILITY'), msg.content[0], msg.content[1]) elif msg.type is MessageTypes.REPLY_EXERCISE_CURVE: self.emit(SIGNAL('PLOT_EXERCISE_CURVE'), msg.content) elif msg.type is MessageTypes.REPLY_POSITION_BASEDATA_FEED: self.__updatePosEditorData(msg.content) elif msg.type is MessageTypes.QUIT: break return def __pushMsg(self, msg_type, content=None): self.msg.pushMsg(msg_type, content) self.msg_event.set() #---------------------------------------------------------------------- def onEditorClickBtSaveAll(self, position_data): self.engine.qryReloadPositions(position_data) self.__queryUpdateData() def onEditorClickBtReloadPosition(self): self.engine.qryReloadPositions() self.engine.qryPositionBasedata() self.__queryUpdateData() def onSavePosition2Csv(self): self.engine.qrySavePositionCsv() #---------------------------------------------------------------------- def __onRefreshQuoteBtClicked(self): self.__queryUpdateData() def __onEditPosBtClicked(self): self.edit_dialog.wakeupEditor() self.engine.qryPositionBasedata() def __onPlotBtClicked(self): x_axis_type = self.greeks_x_axis_combobox.currentIndex() #pass group id list if self.portfolio_checkBox.isChecked(): #collect group id group_ids = list() for r in getSelectedRows(self.portfolio_vtable): item = self.portfolio_data.getValueByHeader(r, 'group') try: group_ids.append(int(item)) except: pass if group_ids: if x_axis_type == 0: self.engine.qryCalGreeksSensibilityByGroup(group_ids, group_ids, XAxisType.PRICE) elif x_axis_type == 1: self.engine.qryCalGreeksSensibilityByGroup(group_ids, group_ids, XAxisType.VOLATILITY) elif x_axis_type == 2: self.engine.qryCalGreeksSensibilityByGroup(group_ids, group_ids, XAxisType.TIME) elif x_axis_type == 3: self.engine.qryExerciseCurveByGroup(group_ids, group_ids) #pass row numbers list else: option_idx = getSelectedRows(self.position_vtable) stock_idx = getSelectedRows(self.stock_position_vtable) if option_idx or stock_idx: if x_axis_type == 0: self.engine.qryCalGreeksSensibilityByPosition(option_idx, stock_idx, XAxisType.PRICE) elif x_axis_type == 1: self.engine.qryCalGreeksSensibilityByPosition(option_idx, stock_idx, XAxisType.VOLATILITY) elif x_axis_type == 2: self.engine.qryCalGreeksSensibilityByPosition(option_idx, stock_idx, XAxisType.TIME) elif x_axis_type == 3: self.engine.qryExerciseCurveByPosition(option_idx, stock_idx) return #------------------------------------------------------------------- def __startAutoRefresh(self, onInit=False): self.auto_refresh_timer = THD.Timer(300, self.__startAutoRefresh) self.auto_refresh_timer.start() if not onInit: self.__queryUpdateData() return def __queryUpdateData(self): if not self.is_updating: self.is_updating = True self.engine.qryUpdateData() self.engine.qryEtfQuoteFeed() self.engine.qryTableDataFeed() def __setEtfDataDisplay(self, etf_data): self.update_time_label.setText('%s' % etf_data.getByHeader(0, 'update_time').strftime(r'%H:%M:%S')) self.etf_openprice_label.setText('open: %.3f' % etf_data.getByHeader(0, 'open_price')) self.etf_highprice_label.setText('high: %.3f' % etf_data.getByHeader(0, 'high_price')) self.etf_lowprice_label.setText('low: %.3f' % etf_data.getByHeader(0, 'low_price')) self.etf_lastprice_label.setText('last: %.3f' % etf_data.getByHeader(0, 'last_price')) def __setCentralTableDisplay(self, option_data, stock_data, portfolio_data): self.option_data.setTableContent(option_data) self.stock_data.setTableContent(stock_data) self.portfolio_data.setTableContent(portfolio_data) #notify_updating_completed self.is_updating = False return def __updatePosEditorData(self, pos_table_handler): self.edit_dialog.setEditTableContent(pos_table_handler) def __plotGreeksSensibility(self, p_data, x_axis_type): if x_axis_type == XAxisType.PRICE: figure_name = 'by price' elif x_axis_type == XAxisType.VOLATILITY: figure_name = 'by volatility' elif x_axis_type == XAxisType.TIME: figure_name = 'by time' else: figure_name = '' fig = PLT.figure() fig.suptitle(figure_name) sp = fig.add_subplot(2, 2, 1) decorateAndPlot(sp, p_data['ax_x'], p_data['delta'], title='delta', central_x=p_data['central_x']) sp = fig.add_subplot(2, 2, 2) decorateAndPlot(sp, p_data['ax_x'], p_data['gamma'], title='gamma', central_x=p_data['central_x']) sp = fig.add_subplot(2, 2, 3) decorateAndPlot(sp, p_data['ax_x'], p_data['vega'], title='vega', central_x=p_data['central_x']) sp = fig.add_subplot(2, 2, 4) decorateAndPlot(sp, p_data['ax_x'], p_data['theta'], title='theta', central_x=p_data['central_x']) PLT.show() return def __plotExerciseCurve(self, plot_data): fig = PLT.figure() fig.suptitle('Theoretical earnings curve') sp = fig.add_subplot(1, 1, 1) decorateAndPlot(sp, plot_data['ax_x'], plot_data['exercise_profit'], central_x=plot_data['central_x']) plotZeroLine(sp) PLT.show() return def __notifyErrorOccur(self): QMessageBox.question(self, 'Error', 'engine error occurs, restart manually ...', QMessageBox.Yes)
class OptionCalculator(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(OptionCalculator, self).__init__(parent) self.setupUi(self) #show self.show() #define signal&slot self.connect(self.fresh_quotes_button, SIGNAL('clicked()'), self.__onRefreshQuoteBtClicked) self.connect(self.edit_position_button, SIGNAL('clicked()'), self.__onEditPosBtClicked) self.connect(self.plot_button, SIGNAL('clicked()'), self.__onPlotBtClicked) self.connect(self, SIGNAL('PLOT_SENSIBILITY'), self.__plotGreeksSensibility) self.connect(self, SIGNAL('PLOT_EXERCISE_CURVE'), self.__plotExerciseCurve) self.connect(self, SIGNAL('SET_ETF_DISPLAY'), self.__setEtfDataDisplay) self.connect(self, SIGNAL('SET_CENTRAL_DISPLAY'), self.__setCentralTableDisplay) self.connect(self, SIGNAL('ENGINE_ERROR'), self.__notifyErrorOccur) #init position vtable self.option_data = MatrixModel(self) self.pos_deleg = AutoFormDelegate(self) self.position_vtable.setItemDelegate(self.pos_deleg) self.position_vtable.setModel(self.option_data) self.option_data.setSize(0, OPTION_DF_HEADERS) #init stock_position vtable self.stock_data = MatrixModel(self) self.stpos_deleg = AutoFormDelegate(self) self.stock_position_vtable.setItemDelegate(self.stpos_deleg) self.stock_position_vtable.setModel(self.stock_data) self.stock_data.setSize(0, STOCK_DF_HEADERS) #init portfolio vtable self.portfolio_data = MatrixModel(self) self.ptf_deleg = AutoFormDelegate(self) self.portfolio_vtable.setItemDelegate(self.ptf_deleg) self.portfolio_vtable.setModel(self.portfolio_data) self.portfolio_data.setSize(0, PORTFOLIO_DF_HEADERS) #gui communication self.msg = None self.msg_event = None self.msg_thread = None #data engine self.engine = None #flow control self.is_updating = False self.auto_refresh_timer = None #qt child self.edit_dialog = PosEditor(self) self.edit_dialog.setControler(self) return def start(self): #gui communication self.msg = MessageQueue() self.msg_event = THD.Event() self.msg_thread = THD.Thread(target=self.__handleMessage) self.msg_thread.start() #data engine self.engine = Engine(self) self.__startAutoRefresh(True) self.engine.qryEtfQuoteFeed() self.engine.qryTableDataFeed() def quit(self): if not self.auto_refresh_timer is None: self.auto_refresh_timer.cancel() if not self.engine is None: self.engine.quit() if not self.msg_thread is None: self.__pushMsg(MessageTypes.QUIT) self.msg_thread.join() return def closeEvent(self, event): rtn = QMessageBox.question(self, 'Save & exit', 'Save positions to csv?', QMessageBox.Save, QMessageBox.No, QMessageBox.Cancel) if rtn == QMessageBox.Cancel: event.ignore() return if rtn == QMessageBox.Save: self.onSavePosition2Csv() self.quit() #close super(OptionCalculator, self).closeEvent(event) return #------------------------------------------------------------------------- def onEngineError(self, err): with open(r'./logs.txt', 'a') as fid: err_info = '\n>>%s\n%s' % (str(DT.datetime.now()), str(err)) fid.write(err_info) self.emit(SIGNAL('ENGINE_ERROR')) return def onRepTableFeed(self, option_data, stock_data, ptf_data): self.__pushMsg(MessageTypes.REPLY_TABLE_FEED, (option_data, stock_data, ptf_data)) def onRepEtfQuoteFeed(self, etf_data): self.__pushMsg(MessageTypes.REPLY_ETF_QUOTE_FEED, etf_data) def onRepCalGreeksSensibility(self, plot_data, x_axis_type): self.__pushMsg(MessageTypes.REPLY_CAL_SENSI, (plot_data, x_axis_type)) def onRepCalExerciseCurve(self, plot_data): self.__pushMsg(MessageTypes.REPLY_EXERCISE_CURVE, plot_data) def onRepPositionBasedataFeed(self, positions): self.__pushMsg(MessageTypes.REPLY_POSITION_BASEDATA_FEED, positions) def __handleMessage(self): while True: msg = self.msg.getMsg() if msg is None: self.msg_event.wait() self.msg_event.clear() #received data for display elif msg.type is MessageTypes.REPLY_TABLE_FEED: self.emit(SIGNAL('SET_CENTRAL_DISPLAY'), msg.content[0], msg.content[1], msg.content[2]) elif msg.type is MessageTypes.REPLY_ETF_QUOTE_FEED: self.emit(SIGNAL('SET_ETF_DISPLAY'), msg.content) elif msg.type is MessageTypes.REPLY_CAL_SENSI: self.emit(SIGNAL('PLOT_SENSIBILITY'), msg.content[0], msg.content[1]) elif msg.type is MessageTypes.REPLY_EXERCISE_CURVE: self.emit(SIGNAL('PLOT_EXERCISE_CURVE'), msg.content) elif msg.type is MessageTypes.REPLY_POSITION_BASEDATA_FEED: self.__updatePosEditorData(msg.content) elif msg.type is MessageTypes.QUIT: break return def __pushMsg(self, msg_type, content=None): self.msg.pushMsg(msg_type, content) self.msg_event.set() #---------------------------------------------------------------------- def onEditorClickBtSaveAll(self, position_data): self.engine.qryReloadPositions(position_data) self.__queryUpdateData() def onEditorClickBtReloadPosition(self): self.engine.qryReloadPositions() self.engine.qryPositionBasedata() self.__queryUpdateData() def onSavePosition2Csv(self): self.engine.qrySavePositionCsv() #---------------------------------------------------------------------- def __onRefreshQuoteBtClicked(self): self.__queryUpdateData() def __onEditPosBtClicked(self): self.edit_dialog.wakeupEditor() self.engine.qryPositionBasedata() def __onPlotBtClicked(self): x_axis_type = self.greeks_x_axis_combobox.currentIndex() #pass group id list if self.portfolio_checkBox.isChecked(): #collect group id group_ids = list() for r in getSelectedRows(self.portfolio_vtable): item = self.portfolio_data.getValueByHeader(r, 'group') try: group_ids.append(int(item)) except: pass if group_ids: if x_axis_type == 0: self.engine.qryCalGreeksSensibilityByGroup( group_ids, group_ids, XAxisType.PRICE) elif x_axis_type == 1: self.engine.qryCalGreeksSensibilityByGroup( group_ids, group_ids, XAxisType.VOLATILITY) elif x_axis_type == 2: self.engine.qryCalGreeksSensibilityByGroup( group_ids, group_ids, XAxisType.TIME) elif x_axis_type == 3: self.engine.qryExerciseCurveByGroup(group_ids, group_ids) #pass row numbers list else: option_idx = getSelectedRows(self.position_vtable) stock_idx = getSelectedRows(self.stock_position_vtable) if option_idx or stock_idx: if x_axis_type == 0: self.engine.qryCalGreeksSensibilityByPosition( option_idx, stock_idx, XAxisType.PRICE) elif x_axis_type == 1: self.engine.qryCalGreeksSensibilityByPosition( option_idx, stock_idx, XAxisType.VOLATILITY) elif x_axis_type == 2: self.engine.qryCalGreeksSensibilityByPosition( option_idx, stock_idx, XAxisType.TIME) elif x_axis_type == 3: self.engine.qryExerciseCurveByPosition( option_idx, stock_idx) return #------------------------------------------------------------------- def __startAutoRefresh(self, onInit=False): self.auto_refresh_timer = THD.Timer(300, self.__startAutoRefresh) self.auto_refresh_timer.start() if not onInit: self.__queryUpdateData() return def __queryUpdateData(self): if not self.is_updating: self.is_updating = True self.engine.qryUpdateData() self.engine.qryEtfQuoteFeed() self.engine.qryTableDataFeed() def __setEtfDataDisplay(self, etf_data): self.update_time_label.setText( '%s' % etf_data.getByHeader(0, 'update_time').strftime(r'%H:%M:%S')) self.etf_openprice_label.setText('open: %.3f' % etf_data.getByHeader(0, 'open_price')) self.etf_highprice_label.setText('high: %.3f' % etf_data.getByHeader(0, 'high_price')) self.etf_lowprice_label.setText('low: %.3f' % etf_data.getByHeader(0, 'low_price')) self.etf_lastprice_label.setText('last: %.3f' % etf_data.getByHeader(0, 'last_price')) def __setCentralTableDisplay(self, option_data, stock_data, portfolio_data): self.option_data.setTableContent(option_data) self.stock_data.setTableContent(stock_data) self.portfolio_data.setTableContent(portfolio_data) #notify_updating_completed self.is_updating = False return def __updatePosEditorData(self, pos_table_handler): self.edit_dialog.setEditTableContent(pos_table_handler) def __plotGreeksSensibility(self, p_data, x_axis_type): if x_axis_type == XAxisType.PRICE: figure_name = 'by price' elif x_axis_type == XAxisType.VOLATILITY: figure_name = 'by volatility' elif x_axis_type == XAxisType.TIME: figure_name = 'by time' else: figure_name = '' fig = PLT.figure() fig.suptitle(figure_name) sp = fig.add_subplot(2, 2, 1) decorateAndPlot(sp, p_data['ax_x'], p_data['delta'], title='delta', central_x=p_data['central_x']) sp = fig.add_subplot(2, 2, 2) decorateAndPlot(sp, p_data['ax_x'], p_data['gamma'], title='gamma', central_x=p_data['central_x']) sp = fig.add_subplot(2, 2, 3) decorateAndPlot(sp, p_data['ax_x'], p_data['vega'], title='vega', central_x=p_data['central_x']) sp = fig.add_subplot(2, 2, 4) decorateAndPlot(sp, p_data['ax_x'], p_data['theta'], title='theta', central_x=p_data['central_x']) PLT.show() return def __plotExerciseCurve(self, plot_data): fig = PLT.figure() fig.suptitle('Theoretical earnings curve') sp = fig.add_subplot(1, 1, 1) decorateAndPlot(sp, plot_data['ax_x'], plot_data['exercise_profit'], central_x=plot_data['central_x']) plotZeroLine(sp) PLT.show() return def __notifyErrorOccur(self): QMessageBox.question(self, 'Error', 'engine error occurs, restart manually ...', QMessageBox.Yes)
class Engine: etf_code = '510050.SH' ETF_QUOTE_HEADERS = ('last_price', 'open_price', 'high_price', 'low_price', 'update_time') STATISTICS_HEADERS = ('implied_vol', 'delta', 'gamma', 'vega', 'theta', 'intrnic', 'time_value') #------------------------------------------------------------- def __init__(self, gui): self.gui = gui #original position table self.ori_positions = None #etf quote self.etf = TableHandler() self.etf.reset(1, Engine.ETF_QUOTE_HEADERS, -1) #marketdata service self.md = MarketdataAdaptor() #database service self.dp = DADAPTOR.DataProxy() self.__reloadPositions() #flow control self.last_sync_time = DT.datetime.now() #gui communication self.msg = MessageQueue() self.msg_event = THD.Event() self.msg_thread = THD.Thread(target=self.__handleMessage) self.msg_thread.start() return def quit(self): self.__pushMsg(MessageTypes.QUIT) self.msg_thread.join() #------------------------------------------------------------- def qryUpdateData(self): self.__pushMsg(MessageTypes.UPDATE_QUOTE_DATA) def qryEtfQuoteFeed(self): self.__pushMsg(MessageTypes.GUI_QUERY_ETF_QUOTE_FEED) def qryTableDataFeed(self): self.__pushMsg(MessageTypes.GUI_QUERY_TABLE_FEED) def qryPositionBasedata(self): self.__pushMsg(MessageTypes.GUI_QUERY_POSITION_BASEDATA_FEED) def qryCalGreeksSensibilityByGroup(self, option_group_id, stock_group_id, x_axis_type): self.__pushMsg(MessageTypes.GUI_QUERY_CAL_SENSI, (option_group_id, stock_group_id, PassedIndexType.GROUP, x_axis_type)) def qryCalGreeksSensibilityByPosition(self, option_rows, stock_rows, x_axis_type): self.__pushMsg(MessageTypes.GUI_QUERY_CAL_SENSI, (option_rows, stock_rows, PassedIndexType.ROW, x_axis_type)) def qryExerciseCurveByGroup(self, option_group_id, stock_group_id): self.__pushMsg(MessageTypes.GUI_QUERY_EXERCISE_CURVE, (option_group_id, stock_group_id, PassedIndexType.GROUP)) def qryExerciseCurveByPosition(self, option_rows, stock_rows): self.__pushMsg(MessageTypes.GUI_QUERY_EXERCISE_CURVE, (option_rows, stock_rows, PassedIndexType.ROW)) def qryReloadPositions(self, positions_data=None): self.__pushMsg(MessageTypes.GUI_QUERY_RELOAD_POSITIONS, positions_data) def qrySavePositionCsv(self): self.__pushMsg(MessageTypes.SAVE_POSITION_CSV) def __pushMsg(self, msg_type, content=None): self.msg.pushMsg(msg_type, content) self.msg_event.set() def __handleMessage(self): try: while True: msg = self.msg.getMsg() if msg is None: self.msg_event.wait() self.msg_event.clear() #update marketdata order by user elif msg.type is MessageTypes.UPDATE_QUOTE_DATA: self.__updateData() #qry engine provide table data elif msg.type is MessageTypes.GUI_QUERY_TABLE_FEED: self.__feedDataTable() #qry etf data elif msg.type is MessageTypes.GUI_QUERY_ETF_QUOTE_FEED: self.__feedEtfQuote() #qry position base data for editor elif msg.type is MessageTypes.GUI_QUERY_POSITION_BASEDATA_FEED: self.__feedPositionBaseData() #cal greeks sensibility elif msg.type is MessageTypes.GUI_QUERY_CAL_SENSI: self.__calGreekSensibility(msg.content[0], msg.content[1], msg.content[2], msg.content[3]) elif msg.type is MessageTypes.GUI_QUERY_EXERCISE_CURVE: self.__calOptionExerciseProfitCurve(msg.content[0], msg.content[1], msg.content[2]) elif msg.type is MessageTypes.GUI_QUERY_RELOAD_POSITIONS: self.__reloadPositions(msg.content) elif msg.type is MessageTypes.SAVE_POSITION_CSV: self.__savePosition2Csv() elif msg.type is MessageTypes.QUIT: break except Exception as err: self.gui.onEngineError(err) #thread terminate return #----------------------------------------------------------- #positions should be a instance of TableHandler def __reloadPositions(self, positions=None): if type(positions) is TableHandler: pos = positions.toDataFrame() else: pos, err = DADAPTOR.loadPositionCsv() if not err is None: raise Exception('load position csv failed ...') #save pos self.ori_positions = pos #separate data option_rows = list() stock_rows = list() for r in range(0, pos.shape[0]): code = pos['code'].iat[r] contract_type = self.md.getContractType(code) if contract_type in ['call', 'put']: option_rows.append(r) else: stock_rows.append(r) option_df = pos.iloc[option_rows, :] stock_df = pos.iloc[stock_rows, :] self.dp.initialize(option_df, stock_df) self.__updateData(True) return def __savePosition2Csv(self): DADAPTOR.savePositionCsv(self.ori_positions) return def __updateData(self, update_baseinfo=False): self.last_sync_time = DT.datetime.now() #stock self.__updateEtfData() stk = self.dp.getStockData() for r in range(0, stk.rows()): self.__updateStockRow(r) #option opt = self.dp.getOptionData() for r in range(0, opt.rows()): if update_baseinfo: self.__updateRowBaseInfos(r) self.__updateOptionRow(r) #update database self.dp.updateData() return #update etf price data def __updateEtfData(self): etf_last_price = self.md.getLastprice(Engine.etf_code) self.etf.setByHeader(0, 'last_price', etf_last_price) self.etf.setByHeader(0, 'update_time', self.md.getLastUpdateTime(Engine.etf_code)) if not self.etf.getByHeader(0, 'open_price') < 0: self.etf.setByHeader(0, 'high_price', max(etf_last_price, self.etf.getByHeader(0, 'high_price'))) self.etf.setByHeader(0, 'low_price', min(etf_last_price, self.etf.getByHeader(0, 'low_price'))) else: O = self.md.getDailyOpen(Engine.etf_code) H = self.md.getDailyHigh(Engine.etf_code) L = self.md.getDailyLow(Engine.etf_code) if O and H and L: self.etf.setByHeader(0, 'open_price', O) self.etf.setByHeader(0, 'high_price', H) self.etf.setByHeader(0, 'low_price', L) return def __updateStockRow(self, irow): pos = self.dp.getStockData() last_price = self.etf.getByHeader(0, 'last_price') float_profit = ANALYSER.getFloatProfit(pos.getByHeader(irow, 'dir'), pos.getByHeader(irow, 'lots'), pos.getByHeader(irow, 'open_price'), last_price, self.md.getStockMultiplier()) pos.setByHeader(irow, 'last_price', last_price) pos.setByHeader(irow, 'float_profit', float_profit) return #update basic_infos like expiry, strike_price etc. def __updateRowBaseInfos(self, irow): pos = self.dp.getOptionData() code = pos.getByHeader(irow, 'code') pos.setByHeader(irow, 'type', self.md.getContractType(code)) pos.setByHeader(irow, 'strike', self.md.getStrikePrice(code)) pos.setByHeader(irow, 'expiry', self.md.getExerciseDate(code)) pos.setByHeader(irow, 'left_days', self.md.getDaysBeforeExercise(code)) return #update def __updateOptionRow(self, irow): pos = self.dp.getOptionData() code = pos.getByHeader(irow, 'code') last_price = self.md.getLastprice(code) pos.setByHeader(irow, 'last_price', last_price) ################################### S = self.etf.getByHeader(0, 'last_price') K = pos.getByHeader(irow, 'strike') T = pos.getByHeader(irow, 'left_days') opt_type = pos.getByHeader(irow, 'type') #greeks stat = None if opt_type.lower() == 'call': stat = ANALYSER.getStatistics(S, K, T, last_price, True) elif opt_type.lower() == 'put': stat = ANALYSER.getStatistics(S, K, T, last_price, False) if stat: for header in Engine.STATISTICS_HEADERS: pos.setByHeader(irow, header, stat[header]) #trade state float_profit = ANALYSER.getFloatProfit(pos.getByHeader(irow, 'dir'), pos.getByHeader(irow, 'lots'), pos.getByHeader(irow, 'open_price'), last_price, self.md.getOptionMultiplier()) pos.setByHeader(irow, 'float_profit', float_profit) return def __feedDataTable(self): opt_data = TableHandler() opt_data.copyDataframe(self.dp.getOptionData().getDataFrame()) stk_data = TableHandler() stk_data.copyDataframe(self.dp.getStockData().getDataFrame()) ptf_data = TableHandler() ptf_data.copyDataframe(self.dp.getPortfolioData().getDataFrame()) self.gui.onRepTableFeed(opt_data, stk_data, ptf_data) return def __feedEtfQuote(self): snap_etf = TableHandler() snap_etf.copy(self.etf) self.gui.onRepEtfQuoteFeed(snap_etf) return def __feedPositionBaseData(self): tdata = TableHandler() tdata.copyDataframe(self.ori_positions) self.gui.onRepPositionBasedataFeed(tdata) return def __calGreekSensibility(self, option_idx, stock_idx, idx_type, x_axis_type): opt = self.dp.getOptionData() stk = self.dp.getStockData() if idx_type is PassedIndexType.GROUP: opt_data = opt.getPositionDataByGroupId(option_idx) stk_data = stk.getPositionDataByGroupId(stock_idx) elif idx_type is PassedIndexType.ROW: opt_data = opt.getPositionDataByRowIdx(option_idx) stk_data = stk.getPositionDataByRowIdx(stock_idx) else: return if x_axis_type is XAxisType.PRICE: rtn = ANALYSER.getGreeksSensibilityByPrice(opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) elif x_axis_type is XAxisType.VOLATILITY: rtn = ANALYSER.getGreeksSensibilityByVolatility(opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) elif x_axis_type is XAxisType.TIME: rtn = ANALYSER.getGreeksSensibilityByTime(opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) else: return self.gui.onRepCalGreeksSensibility(rtn, x_axis_type) return def __calOptionExerciseProfitCurve(self, option_idx, stock_idx, idx_type): opt = self.dp.getOptionData() stk = self.dp.getStockData() if idx_type is PassedIndexType.GROUP: opt_data = opt.getPositionDataByGroupId(option_idx) stk_data = stk.getPositionDataByGroupId(stock_idx) elif idx_type is PassedIndexType.ROW: opt_data = opt.getPositionDataByRowIdx(option_idx) stk_data = stk.getPositionDataByRowIdx(stock_idx) else: return rtn = ANALYSER.getExerciseProfitCurve(opt_data, stk_data, self.etf.getByHeader(0, 'last_price')) self.gui.onRepCalExerciseCurve(rtn) return