def _startAccountManager(self, strategyCls): """ 启动策略的账户管理者 """ if strategyCls.broker is None: return True if strategyCls.broker not in self._accountManagers: # 实例化券商账户管理者 accountManager = self.accountManagerMap[strategyCls.broker](self._eventEngine, self._info) # 账户管理的开盘前准备 accountManager.onOpen(datetime.now().strftime("%Y-%m-%d")) self._accountManagers[strategyCls.broker] = accountManager # 登录策略的实盘交易接口 event = DyEvent(DyEventType.stockLogin) event.data['broker'] = strategyCls.broker self._eventEngine.put(event) self._info.print('股票CTA引擎: 账号[{0}]绑定策略[{1}]'.format(self.accountManagerMap[strategyCls.broker].brokerName, strategyCls.chName), DyLogData.ind1) return True
def _newEntrust(self, type, datetime, strategyCls, code, name, price, volume): """ 生成新的委托,并向交易接口发送委托事件 """ # check if there's same code and type of entrust not done for different strategy entrusts = self._curEntrusts.get(code) if entrusts is not None: for entrust in entrusts: if (not entrust.isDone()) and entrust.type == type and entrust.strategyCls != strategyCls: self._info.print('{}: 策略[{}]委托失败({}, {}, {}, 价格={}, 数量={}): 策略[{}]有未完成的委托'.format(self.__class__.__name__, strategyCls.chName, code, name, type, price, volume, entrust.strategyCls.chName), DyLogData.warning) return None # create a new entrust curEntrustCount = self.newCurEntrustCount() entrust = DyStockEntrust(datetime, type, code, name, price, volume) entrust.dyEntrustId = '{0}.{1}_{2}'.format(self.broker, self._curTDay, curEntrustCount) entrust.strategyCls = strategyCls # add into 当日委托 self._curEntrusts.setdefault(code, []) self._curEntrusts[code].append(copy.copy(entrust)) # put buy/sell event eventType = DyEventType.stockBuy if type == DyStockOpType.buy else DyEventType.stockSell event = DyEvent(eventType + self.broker) event.data = copy.copy(entrust) self._eventEngine.put(event) return entrust
def cancel(self, cancelEntrust): """ 取消委托 """ # find corresponding entrust entrusts = self._curEntrusts.get(cancelEntrust.code) if entrusts is None: return False for entrust in entrusts: if entrust.dyEntrustId == cancelEntrust.dyEntrustId: break else: return False if entrust.isDone(): self._info.print('{}: 撤销委托失败: {}'.format(self.__class__.__name__, entrust.__dict__, DyLogData.warning)) return False if entrust.brokerEntrustId is None: self._curWorkingCancelEntrusts.append(entrust) return True # put cancel event event = DyEvent(DyEventType.stockCancel + self.broker) event.data = copy.copy(entrust) self._eventEngine.put(event) return True
def putStockMarketStrengthUpdateEvent(self, strategyCls, time, marketStrengthInfo): event = DyEvent(DyEventType.stockMarketStrengthUpdate) event.data['class'] = strategyCls event.data['time'] = time event.data['data'] = marketStrengthInfo self._eventEngine.put(event)
def _stopAccountManager(self, strategyCls, oneKeyHangUp=False): """ 停止策略的账户管理者 @oneKeyHangUp: 一键挂机导致的 """ if strategyCls.broker is None: return if strategyCls.broker not in self._accountManagers: return self._info.print('股票CTA引擎: 账号[{0}]解除绑定策略[{1}]'.format(self.accountManagerMap[strategyCls.broker].brokerName, strategyCls.chName), DyLogData.ind1) # check if other running strategies use the same account manager for strategy, _ in self._strategies.values(): if strategy.name != strategyCls.name and \ strategyCls.broker == strategy.broker and \ strategy.state.isState(DyStockStrategyState.running): return # 退出策略的实盘交易接口 event = DyEvent(DyEventType.stockLogout) event.data['broker'] = strategyCls.broker event.data['oneKeyHangUp'] = oneKeyHangUp self._eventEngine.put(event) # 销毁券商账户管理者 self._accountManagers[strategyCls.broker].exit() del self._accountManagers[strategyCls.broker]
def print(self, description, type=DyLogData.info): if not self._enabled and type != DyLogData.error and type != DyLogData.warning: return event = DyEvent(DyEventType.subLog_ + '_' + str(self._paramGroupNo) + str(self._period)) event.data = DyLogData(description, type) self._outQueue.put(event)
def _stockMarketTicksHandler(self, event): if self._pushAction is None: return ticks = event.data strategyData = self._latestStrategyData.get('DyST_IndexMeanReversion') if strategyData is None: return strategyCls = strategyData[0] tick = ticks.get(strategyCls.targetCode) if tick is None: return # event event = DyEvent(DyEventType.stockStrategyManualBuy) event.data['class'] = strategyCls event.data['tick'] = tick event.data['volume'] = 100 event.data['price'] = round(tick.preClose * 0.92, 3) self._eventEngine.put(event) self._info.print('通过WX委托买入{0}, {1}股, 价格{2}'.format(tick.name, event.data['volume'], event.data['price']), DyLogData.ind1) # sent to WX self._sendWx('委托买入{0}, {1}股, 价格{2}'.format(tick.name, event.data['volume'], event.data['price'])) # clear self._pushAction = None self._eventEngine.unregister(DyEventType.stockMarketTicks, self._stockMarketTicksHandler, DyStockTradeEventHandType.wxEngine)
def _stockStrategyDataPrepareHandler(self, event): date = event.data['date'] classes = event.data['classes'] if self._strategyClsCount == 0: # new start self._strategyClsCount = len(classes) else: if self._isStopped: # reset self._strategyClsCount = 0 self._isStopped = False # send stopAck self._eventEngine.put(DyEvent(DyEventType.stopAck)) return # only process the first one self._stockStrategyDataPrepare(date, classes[0]) self._stockStrategyPosDataPrepare(date, classes[0]) self._stockStrategySimuTraderPosCloseUpdate(date, classes[0]) self._strategyClsCount -= 1 # check if finish or not if self._strategyClsCount == 0: # finish self._eventEngine.put(DyEvent(DyEventType.finish)) else: # send left event = DyEvent(DyEventType.stockStrategyDataPrepare) event.data['date'] = date event.data['classes'] = classes[1:] self._eventEngine.put(event)
def runStrategy(self, strategyCls, paramters): self._info.print("开始准备运行选股策略: {0}".format(strategyCls.chName), DyLogData.ind) self._info.initProgress() # init self._init() # create strategy instance self._strategy = strategyCls(paramters, self._info) # run if self._run(): # ack event = DyEvent(DyEventType.stockSelectStrategySelectAck) event.data['class'] = strategyCls event.data['result'] = self._result event.data['baseDate'] = self._strategy.baseDate self._eventEngine.put(event) # finish self._eventEngine.put(DyEvent(DyEventType.finish)) ret = True else: # fail self._eventEngine.put(DyEvent(DyEventType.fail)) ret = False return ret
def _updateStockHistDays_Handler(self, event): # unpack codes = event.data # check stop flag firstly if self._isStopped: self._printCount() self._eventEngine.put(DyEvent(DyEventType.stopAck)) return # update one code each time code = sorted(codes)[0] self._updateOneCode(code, codes[code]) # update progress self._progress.update() # delete updated code del codes[code] if not codes: # all codes are are updated self._printCount() self._eventEngine.put(DyEvent(DyEventType.finish)) return # send for next updating event = DyEvent(DyEventType.updateStockHistDays_) event.data = codes self._eventEngine.put(event)
def dyStockSelectRegressionEngineProcess(outQueue, inQueue, tradeDays, strategy, codes, histDaysDataSource): strategyCls = strategy['class'] parameters = strategy['param'] DyStockCommon.defaultHistDaysDataSource = histDaysDataSource dummyEventEngine = DyDummyEventEngine() queueInfo = DyQueueInfo(outQueue) selectEngine = DyStockSelectSelectEngine(dummyEventEngine, queueInfo, False) selectEngine.setTestedStocks(codes) for day in tradeDays: try: event = inQueue.get_nowait() except queue.Empty: pass parameters['基准日期'] = day if selectEngine.runStrategy(strategyCls, parameters): event = DyEvent(DyEventType.stockSelectStrategyRegressionAck) event.data['class'] = strategyCls event.data['period'] = [tradeDays[0], tradeDays[-1]] event.data['day'] = day event.data['result'] = selectEngine.result outQueue.put(event) else: queueInfo.print('回归选股策略失败:{0}, 周期[{1}, {2}], 基准日期{3}'.format(strategyCls.chName, tradeDays[0], tradeDays[-1], day), DyLogData.error)
def _histDaysMannualUpdate(self): if self._histDaysMannualUpdateAction.text() == '停止': self._mainEngine._info.print('停止股票(指数)历史日线数据手动更新...', DyLogData.ind) # change UI self._stopRunningMutexAction() event = DyEvent(DyEventType.stopUpdateStockHistDaysReq) self._mainEngine.eventEngine.put(event) else: # 开始手动更新 data = {} if DyStockDataHistDaysManualUpdateDlg(data, self).exec_(): self._mainEngine._info.print('开始股票(指数,基金)历史日线数据手动更新[{0}, {1}]...'.format(data['startDate'], data['endDate']), DyLogData.ind) # change UI self._startRunningMutexAction(self._histDaysMannualUpdateAction) event = DyEvent(DyEventType.updateStockHistDays) event.data = data event.data['codes'] = DyStockCommon.getDyStockCodes(event.data['codes']) # 是否要更新指数的日线数据 if event.data['index']: if event.data['codes'] is None: event.data['codes'] = [] event.data['codes'].extend(list(DyStockCommon.indexes)) self._mainEngine.eventEngine.put(event)
def handle_msg_all(self, msg): if msg['user']['name'] == 'self': event = DyEvent(DyEventType.wxQueryStockStrategy) event.data = msg['content']['data'] self._eventEngine.put(event)
def _putTickEvent(self, ctaTickDatas): if not ctaTickDatas: return event = DyEvent(DyEventType.stockMarketTicks) event.data = ctaTickDatas self._eventEngine.put(event)
def _updateStrategyDeal(self, deal): """ 向UI推送策略成交事件 """ event = DyEvent(DyEventType.stockStrategyDealsUpdate + deal.strategyCls.name) event.data = [deal] self._eventEngine.put(event)
def progressSingle(self, percent): if self._progressSingle != percent: self._progressSingle = percent event = DyEvent(DyEventType.progressSingle) event.data = percent self._eventEngine.put(event)
def progressTotal(self, percent): if self._progressTotal != percent: self._progressTotal = percent event = DyEvent(DyEventType.progressTotal) event.data = percent self._eventEngine.put(event)
def _stockPosSyncFromBrokerHandler(self, event): """ 收到来自券商接口的持仓同步事件 !!!如果交易不是通过策略,或者持仓同步不是在开盘时,可能会导致信息不一致或者错误。 """ # unpack header = event.data['header'] rows = event.data['rows'] # it's synchronized from broker self._curPosSyncData = {} for data in rows: # unpack from 券商接口持仓数据 code = DyStockCommon.getDyStockCode(data[header.index(self.headerNameMap['position']['code'])]) name = data[header.index(self.headerNameMap['position']['name'])] totalVolume = float(data[header.index(self.headerNameMap['position']['totalVolume'])]) availVolume = float(data[header.index(self.headerNameMap['position']['availVolume'])]) price = float(data[header.index(self.headerNameMap['position']['price'])]) cost = float(data[header.index(self.headerNameMap['position']['cost'])]) # get position pos = self._curPos.get(code) if pos is None: continue # set sync data firstly self._curPosSyncData[code] = {'volumeAdjFactor': totalVolume/pos.totalVolume, 'priceAdjFactor': pos.cost/cost, 'cost': cost } # syn with positions from broker pos.price = price pos.cost = cost pos.totalVolume = totalVolume pos.availVolume = availVolume pos.priceAdjFactor = self._curPosSyncData[code]['priceAdjFactor'] pos.volumeAdjFactor = self._curPosSyncData[code]['volumeAdjFactor'] if pos.priceAdjFactor != 1: pos.xrd = True pos.sync = True # 发送行情监控事件 self._putStockMarketMonitorEvent() # 发送股票持仓同步事件 event = DyEvent(DyEventType.stockPosSyncFromAccountManager) event.data['broker'] = self.broker event.data['data'] = self._curPosSyncData self._eventEngine.put(event)
def _updateStrategyEntrust(self, entrust): """ 向UI推送策略委托事件 """ # because only one entrust, no need OrderedDict event = DyEvent(DyEventType.stockStrategyEntrustsUpdate + entrust.strategyCls.name) event.data = {entrust.dyEntrustId: copy.copy(entrust)} self._eventEngine.put(event)
def _updateStrategyPos(self, strategy): """ 向UI推送策略持仓更新事件 """ # 持仓 event = DyEvent(DyEventType.stockStrategyPosUpdate + strategy.name) event.data = copy.deepcopy(strategy.curPos) self._eventEngine.put(event)
def _startStockCtaStrategyHandler(self, event): """ 启动策略, 创建唯一策略实例 """ strategyCls = event.data['class'] state = event.data['state'] self._info.print('开始启动策略: {0}, 状态: {1},...'.format(strategyCls.chName, state.state), DyLogData.ind) # 是否是唯一策略实例 if strategyCls.name in self._strategies: self._info.print('重复启动策略: {0}'.format(strategyCls.chName), DyLogData.error) return # !!!It's tricky for live trading but not accurate. sleep(1) # sleep so that UI related dynamic windows can be created firstly. # 实例化策略 strategy = strategyCls(self, self._info, state) # 策略开盘前初始化 if not strategy.onOpen(datetime.now().strftime("%Y-%m-%d"), strategy.onOpenCodes()): self._info.print('策略: {0}启动失败'.format(strategyCls.chName), DyLogData.error) return # 启动策略的账户管理 if state.isState(DyStockStrategyState.running): if strategy.broker is not None: # Strategy has configured the broker if not self._startAccountManager(strategyCls): return # 从券商管理类同步策略持仓 self._accountManagers[strategy.broker].syncStrategyPos(strategy) # 获取策略要监控的股票池 monitoredStocks = strategy.onMonitor() # 添加到策略字典 self._strategies[strategyCls.name] = (strategy, DyStockMarketFilter(monitoredStocks)) # 添加到bar聚合字典 if 'bar' in strategyCls.liveMode: if strategyCls.liveMode not in self._barAggs: self._barAggs[strategyCls.liveMode] = DyStockCtaBarAggFast(strategyCls.liveMode, monitoredStocks) else: self._barAggs[strategyCls.liveMode].add(monitoredStocks) # 向股票市场发送监控的股票池 monitoredStocks = monitoredStocks + [DyStockCommon.etf300, DyStockCommon.etf500] # always add ETF300 and ETF500 for 大盘参考。主要原因是历史分笔数据没有指数的,所以只能用ETF替代。 if monitoredStocks: event = DyEvent(DyEventType.stockMarketMonitor) event.data = monitoredStocks self._eventEngine.put(event) # 向UI推送策略的账户相关事件 self._updateStrategyAccount(strategy) self._info.print('策略: {0}启动成功'.format(strategyCls.chName), DyLogData.ind)
def _stockPositionUpdateHandler(self, event): """ 收到来自券商接口的账户持仓更新事件 """ # unpack header = event.data['header'] rows = event.data['rows'] # 先前持仓代码 codes = list(self._curPos) for data in rows: # unpack from 券商接口持仓数据 code = DyStockCommon.getDyStockCode(data[header.index(self.headerNameMap['position']['code'])]) name = data[header.index(self.headerNameMap['position']['name'])] totalVolume = float(data[header.index(self.headerNameMap['position']['totalVolume'])]) availVolume = float(data[header.index(self.headerNameMap['position']['availVolume'])]) price = float(data[header.index(self.headerNameMap['position']['price'])]) cost = float(data[header.index(self.headerNameMap['position']['cost'])]) # get position if code in self._curPos: pos = self._curPos[code] codes.remove(code) else: # new pos, we just take time now without accuracy if totalVolume > 0: pos = DyStockPos(datetime.now(), None, code, name, price, totalVolume, 0) pos.sync = True else: continue # syn with positions from broker pos.price = price pos.cost = cost pos.totalVolume = totalVolume pos.availVolume = availVolume # write back self._curPos[code] = pos # 删除不在券商接口数据里的持仓 for code in codes: del self._curPos[code] # 发送行情监控事件 self._putStockMarketMonitorEvent() # 发送券商账户股票持仓更新事件 event = DyEvent(DyEventType.stockOnPos) event.data['broker'] = self.broker event.data['pos'] = copy.deepcopy(self._curPos) self._eventEngine.put(event)
def _focusAnalysisAct(self): data = {} if not DyDateDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'focusAnalysis' self._mainEngine.eventEngine.put(event)
def putEvent(self, type, data): """ 推送一个事件到事件引擎 @type: 事件类型 @data: 事件数据,一般为dict """ event = DyEvent(type) event.data = data self._eventEngine.put(event)
def uncheckAll(self, strategyCls, eventEngine): if self._state is None: return self._state = None event = DyEvent(DyEventType.stopStockCtaStrategy) event.data['class'] = strategyCls eventEngine.put(event)
def progressTotal(self, percent): if not self._enabled: return if self._progressTotal != percent: self._progressTotal = percent event = DyEvent(DyEventType.subProgressTotal_ + '_' + str(self._paramGroupNo) + str(self._period)) event.data = percent self._outQueue.put(event)
def _limitUpStatsAct(self): data = {} if not DyDateDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'limitUpStats' self._mainEngine.eventEngine.put(event)
def _bBandsStats(self): data = {} if not DyStockSelectBBandsStatsDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'bBandsStats' self._mainEngine.eventEngine.put(event)
def _jaccardIndexAct(self): data = {} if not DyStockSelectJaccardIndexDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'jaccardIndex' self._mainEngine.eventEngine.put(event)
def _highLowDistAct(self): data = {} if not DyDateDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'highLowDist' self._mainEngine.eventEngine.put(event)
class DyStockTradeCapitalWidget(DyTableWidget): """ 股票交易账户资金状况窗口 !!!券商接口推送的原始数据 """ signal = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, eventEngine, broker): super().__init__(None, True, False) self._eventEngine = eventEngine self._broker = broker self._headerSet = False self._registerEvent() def _signalEmitWrapper(self, event): self.signal.emit(event) def _registerEvent(self): self.signal.connect(self._stockCapitalUpdateHandler) self._eventEngine.register(DyEventType.stockCapitalUpdate + self._broker, self._signalEmitWrapper) self._eventEngine.register(DyEventType.stockCapitalTickUpdate + self._broker, self._signalEmitWrapper) def _unregisterEvent(self): self.signal.disconnect(self._stockCapitalUpdateHandler) self._eventEngine.unregister(DyEventType.stockCapitalUpdate + self._broker, self._signalEmitWrapper) self._eventEngine.unregister(DyEventType.stockCapitalTickUpdate + self._broker, self._signalEmitWrapper) def _stockCapitalUpdateHandler(self, event): header = event.data['header'] rows = event.data['rows'] if not self._headerSet: self.setColNames(header) self._headerSet = True # strip for row in rows: if isinstance(row[0], str): row[0] = row[0].strip() self[0] = rows[0] def closeEvent(self, event): self._unregisterEvent() return super().closeEvent(event)
def _histTicksVerifyAct(self): if self._histTicksVerifyAction.text() == '停止': self._mainEngine._info.print('停止股票(基金)历史分笔数据校验...', DyLogData.ind) # change UI self._stopRunningMutexAction() event = DyEvent(DyEventType.stopVerifyStockHistTicksReq) self._mainEngine.eventEngine.put(event) else: # 开始数据校验 data = {} if DyStockDataHistTicksVerifyDlg(data, self).exec_(): self._mainEngine._info.print('开始股票(基金)历史分笔数据校验...', DyLogData.ind) # change UI self._startRunningMutexAction(self._histTicksVerifyAction) event = DyEvent(DyEventType.verifyStockHistTicks) event.data = data self._mainEngine.eventEngine.put(event)
def _strategyDataPrepare(self): if self._strategyDataPrepareAction.text() == '停止': self._mainEngine._info.print('停止实盘策略准备数据和持仓准备数据...', DyLogData.ind) # change UI self._stopRunningMutexAction() event = DyEvent(DyEventType.stopStockStrategyDataPrepareReq) self._mainEngine.eventEngine.put(event) else: # 开始策略准备数据 data = {} if DyStockDataStrategyDataPrepareDlg(data, self).exec_(): self._mainEngine._info.print('开始实盘策略准备数据和持仓准备数据...', DyLogData.ind) # change UI self._startRunningMutexAction(self._strategyDataPrepareAction) event = DyEvent(DyEventType.stockStrategyDataPrepare) event.data = data self._mainEngine.eventEngine.put(event)
def putStockMarketMonitorUiEvent(self, strategyCls, data=None, newData=False, op=None, signalDetails=None, datetime_=None): """ 触发策略行情监控事件(通常用于通知GUI更新) @strategyCls: strategy class @data: 策略显示数据, [[]] @newData: True-策略显示数据是全新的数据,False-只是更新策略显示数据 @op: 策略操作数据, [[]]。 若策略是运行状态(实盘状态)并且绑定券商账户,则op是实盘操作。若策略是监控状态或者没有绑定券商账户,则op就是非实盘操作。 其实非实盘时,可以不向UI推送操作,这么做只是为了非真正实盘时,能看到策略的操作明细。 信号明细和操作明细是不同的,有信号未必就会有操作,因为实盘时的操作牵涉到风控和资金管理。 @signalDetails: 策略信号明细数据, [[]] @datetime_: 行情数据时有效 """ event = DyEvent(DyEventType.stockMarketMonitorUi + strategyCls.name) # data if data: event.data['data'] = {'data': data, 'new': newData, 'datetime': datetime_ } # indication ind = {} if op: ind['op'] = op if signalDetails: ind['signalDetails'] = signalDetails if ind: event.data['ind'] = ind # put event if event.data: event.data['class'] = strategyCls self._eventEngine.put(event)
def _autoUpdate(self): # get latest date from DB latestDate = self._commonEngine.getLatestDateInDb() if latestDate is None: self._info.print("数据库里没有日线数据", DyLogData.error) self._eventEngine.put(DyEvent(DyEventType.fail)) return # 贪婪法获得最大的更新结束日期 endDate = datetime.now().strftime("%Y-%m-%d") # check if now is after trading time ret = self._gateway.isNowAfterTradingTime() if ret is None: # error self._eventEngine.put(DyEvent(DyEventType.fail)) return if ret is False: # now is trade day and before end of trading time self._info.print("今天是交易日, 请18:00后更新今日日线数据", DyLogData.error) self._eventEngine.put(DyEvent(DyEventType.fail)) return startDate = DyTime.getDateStr(latestDate, 1) # next date after latest date in DB # compare dates if endDate < startDate: # update progress UI self._progress.init(0) self._info.print("数据库日线数据已经是最新", DyLogData.ind) self._eventEngine.put(DyEvent(DyEventType.finish)) return self._update(startDate, endDate, DyStockDataCommon.dayIndicators)
class DyStockTradeCurDealsWidget(DyTableWidget): """ 股票交易账户当日成交窗口 """ signal = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, eventEngine, broker): super().__init__(None, True, False, floatRound=3) self._eventEngine = eventEngine self._broker = broker self._headerSet = False self._registerEvent() def _signalEmitWrapper(self, event): """ !!!Note: The value of signal.emit will always be changed each time you getting. """ self.signal.emit(event) def _registerEvent(self): self.signal.connect(self._stockCurDealsUpdateHandler) self._eventEngine.register( DyEventType.stockCurDealsUpdate + self._broker, self._signalEmitWrapper) def _unregisterEvent(self): self.signal.disconnect(self._stockCurDealsUpdateHandler) self._eventEngine.unregister( DyEventType.stockCurDealsUpdate + self._broker, self._signalEmitWrapper) def _stockCurDealsUpdateHandler(self, event): header = event.data['header'] rows = event.data['rows'] if not self._headerSet: self.setColNames(header) self._headerSet = True self.fastAppendRows(rows, new=True) self.setItemsForeground(range(self.rowCount()), (('买入', Qt.red), ('卖出', Qt.darkGreen))) def closeEvent(self, event): self._unregisterEvent() return super().closeEvent(event)
def _histTicksDataSourceAct(self): self._curHistTicksDataSourceAction.setChecked(False) # get triggered action for action in self._histTicksDataSourceMenu.actions(): if action.isChecked(): # 设置历史分笔数据源 dataSource = action.text() # set default DyStockDataCommon.defaultHistTicksDataSource = dataSource event = DyEvent(DyEventType.updateHistTicksDataSource) event.data = dataSource self._mainEngine.eventEngine.put(event) self._curHistTicksDataSourceAction = action self._histTicksDataSourceMenuAction.setText( '历史分笔数据源:{0}'.format(dataSource)) break if not self._curHistTicksDataSourceAction.isChecked(): self._curHistTicksDataSourceAction.setChecked(True)
def _newEntrust(self, type, datetime, strategyCls, code, name, price, volume): """ 生成新的委托,并向交易接口发送委托事件 """ if not self._canNewEntrust(type, datetime, strategyCls, code, name, price, volume): return None # create a new entrust curEntrustCount = self.newCurEntrustCount() entrust = DyStockEntrust(datetime, type, code, name, price, volume) entrust.dyEntrustId = '{0}.{1}_{2}'.format(self.broker, self._curTDay, curEntrustCount) entrust.strategyCls = strategyCls # check price correct or not # 实盘发现股票的委托价超过小数点两位 if entrust.code in DyStockCommon.funds: entrust.price = round(entrust.price, 3) else: entrust.price = round(entrust.price, 2) # add into 当日委托 self._curEntrusts.setdefault(code, []) self._curEntrusts[code].append(copy.copy(entrust)) # put buy/sell event eventType = DyEventType.stockBuy if type == DyStockOpType.buy else DyEventType.stockSell event = DyEvent(eventType + self.broker) event.data = copy.copy(entrust) self._eventEngine.put(event) return entrust
def _updateHistDays(self, startDate, endDate, indicators, isForced=False, codes=None): # get updated codes data info codes = self._getUpdatedCodes(startDate, endDate, indicators, isForced, codes) if codes is None: return # init self._isStopped = False self._updatedCodeCount = 0 self._progress.init(len(codes), 10) self._info.print("开始更新{0}只股票(指数,基金)的历史日线数据...".format(len(codes))) # send for updating event = DyEvent(DyEventType.updateStockHistDays_) event.data = codes self._eventEngine.put(event)
def dyStockBackTestingStrategyEngineProcess(outQueue, inQueue, reqData, config=None): """ 股票回测处理实体。每个回测处理实体由一个参数组合和一个回测周期组成。 每个交易日回测结束后向UI推送持仓和成交信息 """ paramGroupNo = reqData.paramGroupNo period = [reqData.tDays[0], reqData.tDays[-1]] if config is not None: DyStockConfig.setConfigForBackTesting(config) # set DB Cache useDbCache = __setDbCache(reqData) # Engines eventEngine = DyDummyEventEngine() info = DySubInfo(paramGroupNo, period, outQueue) dataEngine = DyStockDataEngine(eventEngine, info, False, dbCache=useDbCache) # create stock back testing CTA engine ctaEngine = DyStockBackTestingCtaEngine(eventEngine, info, dataEngine, reqData, dbCache=useDbCache) for tDay in reqData.tDays: try: event = inQueue.get_nowait() except queue.Empty: pass # 回测当日数据 if not ctaEngine.run(tDay): break # 发送当日回测结果数据事件 event = DyEvent(DyEventType.stockStrategyBackTestingAck) event.data = ctaEngine.getCurAckData() outQueue.put(event) # 发送'股票回测策略引擎处理结束'事件 event = DyEvent(DyEventType.stockBackTestingStrategyEngineProcessEnd) event.data[paramGroupNo] = period outQueue.put(event)
def _updateWindow(self): assert(self._windowSize > 0) # decrease self._windowSize -= 1 # hook stop event if self._isStopped: if self._windowSize == 0: # send stopAck event self._printCounts() self._eventEngine.put(DyEvent(DyEventType.stopAck)) return # Note: To get good Gateway throughput, size can be more than 1. factor * @stockHistTicksHandNbr is a good choice. # Note: Don't forget intial number of requests already sent to Gateway. self._increaseWindowSize(self.shiftWindowSize)
def start(self): assert self._scheduler is None isTradeDay = self._checkDay() if isTradeDay is None: return False # 推送endTradeDay事件 if not isTradeDay or datetime.now().strftime('%H:%M:%S') > '15:45:00': self._eventEngine.put(DyEvent(DyEventType.endStockTradeDay)) if self.testMode: threading.Thread(target=self._testModeRun).start() else: self._scheduler = DyScheduler() self._scheduler.addJob(self._beginDay, {1, 2, 3, 4, 5}, '08:30:00') self._scheduler.addJob(self._endDay, {1, 2, 3, 4, 5}, '15:45:00') self._scheduler.start() return True
def _stockRegression(self): strategyCls, param = self._widgetStrategy.getStrategy() if strategyCls is None: return data = {} if not DyDateDlg(data).exec_(): return # change UI self._startRunningMutexAction(self._stockRegressionAction) event = DyEvent(DyEventType.stockSelectStrategyRegressionReq) event.data['class'] = strategyCls event.data['param'] = param event.data['startDate'] = data['startDate'] event.data['endDate'] = data['endDate'] self._mainEngine.eventEngine.put(event)
def _updateStrategyAccount(self, strategy): """ 向UI推送策略的账户相关事件 只有不改变的对象内容可以在多线程之间传递。策略账户相关的对象可能会改变,所以做deepcopy或者copy """ # 持仓 self._updateStrategyPos(strategy) # 委托 event = DyEvent(DyEventType.stockStrategyEntrustsUpdate + strategy.name) event.data = copy.deepcopy(strategy.curEntrusts) self._eventEngine.put(event) # 成交 event = DyEvent(DyEventType.stockStrategyDealsUpdate + strategy.name) event.data = list(strategy.curDeals.values()) # [DyStockDeal] self._eventEngine.put(event)
def _histDaysForcedUpdate(self): if self._histDaysForcedUpdateAction.text() == '停止': self._mainEngine._info.print('停止股票(指数)历史日线数据强制更新...', DyLogData.ind) # change UI self._stopRunningMutexAction() event = DyEvent(DyEventType.stopUpdateStockHistDaysReq) self._mainEngine.eventEngine.put(event) else: # 开始手动更新 data = {} if DyDateDlg(data, self).exec_(): self._mainEngine._info.print('开始股票(指数,基金)历史日线数据强制更新[{0}, {1}]...'.format(data['startDate'], data['endDate']), DyLogData.ind) # change UI self._startRunningMutexAction(self._histDaysForcedUpdateAction) event = DyEvent(DyEventType.updateStockHistDays) event.data = data event.data['indicators'] = DyStockDataCommon.dayIndicators event.data['forced'] = True self._mainEngine.eventEngine.put(event)
def _ok(self): try: if self._codeLabel.text() != self._tick.code: QMessageBox.warning(self, '错误', '没有指定代码的Tick数据!') return except Exception: QMessageBox.warning(self, '错误', '没有指定代码的Tick数据!') return event = DyEvent(DyEventType.stockStrategyManualBuy) event.data['class'] = self._strategyCls event.data['tick'] = self._tick event.data['volume'] = float(self._buyVolumeLineEdit.text()) * 100 # 不指定价格,则根据tick买入 price = self._buyPriceLineEdit.text() event.data['price'] = float(price) if price else None self._eventEngine.put(event) self._unregisterEvent() self.accept()
def _histTicksMannualUpdate(self): if self._histTicksMannualUpdateAction.text() == '停止': self._mainEngine._info.print('停止股票(基金)历史分笔数据手动更新...', DyLogData.ind) # change UI self._stopRunningMutexAction() event = DyEvent(DyEventType.stopUpdateStockHistTicksReq) self._mainEngine.eventEngine.put(event) else: # 开始手动更新 data = {} codeLabelText = '股票(基金)代码(空代表所有代码), e.g. 600016,510300,002213,...' if DyCodeDateDlg(codeLabelText, data, self).exec_(): self._mainEngine._info.print('开始股票(基金)历史分笔数据手动更新...', DyLogData.ind) # change UI self._startRunningMutexAction(self._histTicksMannualUpdateAction) event = DyEvent(DyEventType.updateStockHistTicks) event.data = data event.data['codes'] = DyStockCommon.getDyStockCodes(event.data['codes']) self._mainEngine.eventEngine.put(event)
class DyStockSelectMainWindow(DyBasicMainWindow): name = 'DyStockSelectMainWindow' signalPlot = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, parent=None): self._mainEngine = DyStockSelectMainEngine() super().__init__(self._mainEngine.eventEngine, self._mainEngine.info, parent) self._initUi() self._registerEvent() def _initUi(self): """ 初始化界面 """ self.setWindowTitle('选股') self._initCentral() self._initMenu() self._initToolBar() self._loadWindowSettings() # at last, raise log dock widget self._dockLog.raise_() def _initCentral(self): """ 初始化中心区域 """ widgetParam, dockParam = self._createDock(DyStockSelectParamWidget, '策略参数', Qt.RightDockWidgetArea) self._widgetStrategy, dockStrategy = self._createDock(DyStockSelectStrategyWidget, '策略', Qt.LeftDockWidgetArea, widgetParam) widgetProgress, dockProgress = self._createDock(DyProgressWidget, '进度', Qt.LeftDockWidgetArea, self._mainEngine.eventEngine) widgetLog, self._dockLog = self._createDock(DyLogWidget, '日志', Qt.RightDockWidgetArea, self._mainEngine.eventEngine) self._widgetSelectResult, dockSelectResult = self._createDock(DyStockSelectSelectResultWidget, '选股结果', Qt.RightDockWidgetArea, self._mainEngine.eventEngine, widgetParam) self._widgetRegressionResult, dockRegressionResult = self._createDock(DyStockSelectRegressionResultWidget, '回归结果', Qt.RightDockWidgetArea, self._mainEngine.eventEngine, widgetParam) self.tabifyDockWidget(self._dockLog, dockSelectResult) self.tabifyDockWidget(self._dockLog, dockRegressionResult) def _initMenu(self): """ 初始化菜单 """ # 创建菜单 menuBar = self.menuBar() # ----- 添加'数据'菜单 ----- menu = menuBar.addMenu('数据') # 测试操作 action = QAction('测试', self) action.triggered.connect(self._test) menu.addAction(action) # 打开策略选股/回归结果 action = QAction('打开策略选股/回归结果...', self) action.triggered.connect(self._openStrategySelectResultAct) menu.addAction(action) # 布林统计操作 action = QAction('布林统计...', self) action.triggered.connect(self._bBandsStats) menu.addAction(action) # 杰拉德指数操作 action = QAction('杰卡德指数...', self) action.triggered.connect(self._jaccardIndexAct) menu.addAction(action) # 指数连续日阴线或者阳线统计 lineStatsMenu = menu.addMenu('指数连续日K线统计') self._greenLineStatsAction = QAction('指数连续日阴线统计...', self) self._greenLineStatsAction.triggered.connect(self._indexConsecutiveDayLineStatsAct) lineStatsMenu.addAction(self._greenLineStatsAction) self._greenLineStatsAction.setCheckable(True) self._redLineStatsAction = QAction('指数连续日阳线统计...', self) self._redLineStatsAction.triggered.connect(self._indexConsecutiveDayLineStatsAct) lineStatsMenu.addAction(self._redLineStatsAction) self._redLineStatsAction.setCheckable(True) # 封板率统计 action = QAction('封板率统计...', self) action.triggered.connect(self._limitUpStatsAct) menu.addAction(action) # 热点分析 action = QAction('热点分析...', self) action.triggered.connect(self._focusAnalysisAct) menu.addAction(action) # 股票日内最高和最低价分布 action = QAction('最高和最低价分布...', self) action.triggered.connect(self._highLowDistAct) menu.addAction(action) # ----- 添加'设置'菜单 ----- menu = menuBar.addMenu('设置') self._setProcessNbrAction = QAction('回归进程数...', self) self._setProcessNbrAction.triggered.connect(self._setProcessNbr) menu.addAction(self._setProcessNbrAction) self._setDayKChartPeriodNbrAction = QAction('日K线前后周期数...', self) self._setDayKChartPeriodNbrAction.triggered.connect(self._setDayKChartPeriodNbr) menu.addAction(self._setDayKChartPeriodNbrAction) self._enableSelectEngineExceptionAction = QAction('选股引擎的异常捕捉', self) self._enableSelectEngineExceptionAction.triggered.connect(self._enableSelectEngineExceptionAct) menu.addAction(self._enableSelectEngineExceptionAction) self._enableSelectEngineExceptionAction.setCheckable(True) # ----- 添加'测试'菜单 ----- menu = menuBar.addMenu('测试') self._testedStocksAction = QAction('调试股票...', self) self._testedStocksAction.triggered.connect(self._testedStocks) menu.addAction(self._testedStocksAction) self._testedStocksAction.setCheckable(True) def _enableSelectEngineExceptionAct(self): DyStockSelectCommon.enableSelectEngineException = self._enableSelectEngineExceptionAction.isChecked() def _test(self): event = DyEvent(DyEventType.plotReq) event.data['type'] = 'test' self._mainEngine.eventEngine.put(event) def _openStrategySelectResultAct(self): defaultDir = DyCommon.createPath('Stock/User/Save/Strategy/选股') fileName, _ = QFileDialog.getOpenFileName(self, "打开策略选股/回归结果", defaultDir, "JSON files (*.json)") # open try: with open(fileName) as f: data = json.load(f) except Exception: return if not data: return # create strategy class strategyClsName = data.get('strategyCls') if not strategyClsName: return strategyCls = eval(strategyClsName) # load if self._widgetSelectResult.load(data, strategyCls): return self._widgetRegressionResult.load(data, strategyCls) def _indexConsecutiveDayLineStatsAct(self): if self._greenLineStatsAction.isChecked(): greenLine = True self._greenLineStatsAction.setChecked(False) else: greenLine = False self._redLineStatsAction.setChecked(False) data = {} if not DyDateDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'indexConsecutiveDayLineStats' event.data['greenLine'] = greenLine self._mainEngine.eventEngine.put(event) def _limitUpStatsAct(self): data = {} if not DyDateDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'limitUpStats' self._mainEngine.eventEngine.put(event) def _highLowDistAct(self): data = {} if not DyDateDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'highLowDist' self._mainEngine.eventEngine.put(event) def _focusAnalysisAct(self): data = {} if not DyDateDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'focusAnalysis' self._mainEngine.eventEngine.put(event) def _jaccardIndexAct(self): data = {} if not DyStockSelectJaccardIndexDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'jaccardIndex' self._mainEngine.eventEngine.put(event) def _bBandsStats(self): data = {} if not DyStockSelectBBandsStatsDlg(data).exec_(): return event = DyEvent(DyEventType.plotReq) event.data = data event.data['type'] = 'bBandsStats' self._mainEngine.eventEngine.put(event) def _setProcessNbr(self): data = {'nbr':DyStockSelectRegressionEngine.periodNbr} if DyProcessNbrDlg(data, self).exec_(): DyStockSelectRegressionEngine.periodNbr = data['nbr'] self._mainEngine._info.print('回归进程数设为{0}'.format(data['nbr']), DyLogData.ind) def _setDayKChartPeriodNbr(self): data = {'periodNbr': DyStockCommon.dayKChartPeriodNbr} if DyStockSelectDayKChartPeriodDlg(data, self).exec_(): DyStockCommon.dayKChartPeriodNbr = data['periodNbr'] self._mainEngine._info.print('股票(指数)日K线前后交易日周期设为{0}'.format(data['periodNbr']), DyLogData.ind) def _testedStocks(self): isTested = self._testedStocksAction.isChecked() codes = None if isTested: data = {} if DyStockSelectTestedStocksDlg(data).exec_(): codes = data['codes'] else: self._testedStocksAction.setChecked(False) # put event event = DyEvent(DyEventType.stockSelectTestedCodes) event.data = codes self._mainEngine.eventEngine.put(event) def _stockSelect(self): strategyCls, param = self._widgetStrategy.getStrategy() if strategyCls is None: return # change UI self._startRunningMutexAction(self._stockSelectAction) event = DyEvent(DyEventType.stockSelectStrategySelectReq) event.data['class'] = strategyCls event.data['param'] = param self._mainEngine.eventEngine.put(event) def _stockSelectForTrade(self): strategyCls, param = self._widgetStrategy.getStrategy() if strategyCls is None: return # change UI self._startRunningMutexAction(self._stockSelectForTradeAction) event = DyEvent(DyEventType.stockSelectStrategySelectReq) event.data['class'] = strategyCls event.data['param'] = param event.data['param']['forTrade'] = None self._mainEngine.eventEngine.put(event) def _stockRegression(self): strategyCls, param = self._widgetStrategy.getStrategy() if strategyCls is None: return data = {} if not DyDateDlg(data).exec_(): return # change UI self._startRunningMutexAction(self._stockRegressionAction) event = DyEvent(DyEventType.stockSelectStrategyRegressionReq) event.data['class'] = strategyCls event.data['param'] = param event.data['startDate'] = data['startDate'] event.data['endDate'] = data['endDate'] self._mainEngine.eventEngine.put(event) def _initToolBar(self): """ 初始化工具栏 """ # 操作工具栏 toolBar = self.addToolBar('选股和回归') toolBar.setObjectName('选股和回归') # 创建操作工具栏的操作 self._stockSelectAction = QAction('选股', self) self._stockSelectAction.setEnabled(False) self._stockSelectAction.triggered.connect(self._stockSelect) self._addMutexAction(self._stockSelectAction) toolBar.addAction(self._stockSelectAction) self._stockRegressionAction = QAction('回归', self) self._stockRegressionAction.setEnabled(False) self._stockRegressionAction.triggered.connect(self._stockRegression) self._addMutexAction(self._stockRegressionAction) toolBar.addAction(self._stockRegressionAction) """ self._stockSelectForTradeAction = QAction('实盘选股', self) self._stockSelectForTradeAction.setEnabled(False) self._stockSelectForTradeAction.triggered.connect(self._stockSelectForTrade) self._addMutexAction(self._stockSelectForTradeAction) toolBar.addAction(self._stockSelectForTradeAction) """ #self._widgetStrategy.setRelatedActions([self._stockSelectAction, self._stockRegressionAction, self._stockSelectForTradeAction]) self._widgetStrategy.setRelatedActions([self._stockSelectAction, self._stockRegressionAction]) # K线工具栏 toolBar = self.addToolBar('K线') toolBar.setObjectName('K线') # K线周期菜单Action self._kPeriodMenuAction = QAction('K线周期', self) toolBar.addAction(self._kPeriodMenuAction) self._kPeriodMenuAction.triggered.connect(self._kPeriodMenuAct) # K线周期菜单 self._kPeriodMenu = QMenu(self) # 创建K线周期菜单的操作 actions = [QAction('{0}'.format(x), self) for x in [60, 90, 120, 180, 250, 500]] for action in actions: action.setCheckable(True) action.triggered.connect(self._kPeriodAct) self._kPeriodMenu.addAction(action) # set default K period if int(action.text()) == DyStockCommon.dayKChartPeriodNbr: action.setChecked(True) self._curKPeriodAction = action self._kPeriodMenuAction.setText('K线周期:{0}'.format(DyStockCommon.dayKChartPeriodNbr)) # 滑动窗口(w)菜单Action self._rollingWindowWMenuAction = QAction('滑动窗口(w)', self) toolBar.addAction(self._rollingWindowWMenuAction) self._rollingWindowWMenuAction.triggered.connect(self._rollingWindowWMenuAct) # 滑动窗口(w)菜单 self._rollingWindowWMenu = QMenu(self) # 创建滑动窗口(w)菜单的操作 actions = [QAction('{0}'.format(x), self) for x in [1, 2, 3, 4, 5, 6, 7, 8, 9]] for action in actions: action.setCheckable(True) action.triggered.connect(self._rollingWindowWAct) self._rollingWindowWMenu.addAction(action) # set default rolling window w if int(action.text()) == DyStockCommon.rollingWindowW: action.setChecked(True) self._curRollingWindowWAction = action self._rollingWindowWMenuAction.setText('滑动窗口(w):{0}'.format(DyStockCommon.rollingWindowW)) # 支撑和阻力菜单Action self._hsarMenuAction = QAction('支撑和阻力', self) toolBar.addAction(self._hsarMenuAction) self._hsarMenuAction.triggered.connect(self._hsarMenuAct) # 支撑和阻力菜单 self._hsarMenu = QMenu(self) actions = [QAction(x, self) for x in ['成本', '极值平均', '极值之极']] for action in actions: action.setCheckable(True) action.triggered.connect(self._hsarsAct) self._hsarMenu.addAction(action) if action.text() == DyStockCommon.hsarMode: action.setChecked(True) self._curHsarAction = action self._hsarMenuAction.setText('支撑和阻力:{0}'.format(DyStockCommon.hsarMode)) # 趋势线周期菜单Action self._trendLinePeriodMenuAction = QAction('趋势线周期', self) toolBar.addAction(self._trendLinePeriodMenuAction) self._trendLinePeriodMenuAction.triggered.connect(self._trendLinePeriodMenuAct) # 趋势线周期菜单 self._trendLinePeriodMenu = QMenu(self) # 清除所有Action action = QAction('清除所有', self) action.triggered.connect(self._trendLinePeriodClearAllAct) self._trendLinePeriodMenu.addAction(action) # 创建趋势线周期菜单的操作 actions = [QAction('{0}'.format(x), self) for x in list(range(10, 121, 5)) + [180, 250, 500]] for action in actions: action.setCheckable(True) action.triggered.connect(self._trendLinePeriodAct) self._trendLinePeriodMenu.addAction(action) # set default trend line periods if int(action.text()) in DyStockCommon.trendLinePeriods: action.setChecked(True) self._trendLinePeriodMenuAction.setText('趋势线周期:{0}'.format(','.join([str(x) for x in DyStockCommon.trendLinePeriods]))) def _kPeriodMenuAct(self): self._kPeriodMenu.popup(QCursor.pos()) def _trendLinePeriodMenuAct(self): self._trendLinePeriodMenu.popup(QCursor.pos()) def _rollingWindowWMenuAct(self): self._rollingWindowWMenu.popup(QCursor.pos()) def _hsarMenuAct(self): self._hsarMenu.popup(QCursor.pos()) def _trendLinePeriodAct(self): periods = [] for action in self._trendLinePeriodMenu.actions(): if action.isChecked(): periods.append(int(action.text())) DyStockCommon.trendLinePeriods = periods self._trendLinePeriodMenuAction.setText('趋势线周期:{0}'.format(','.join([str(x) for x in DyStockCommon.trendLinePeriods]))) def _trendLinePeriodClearAllAct(self): # clear all for action in self._trendLinePeriodMenu.actions(): if action.isChecked(): action.setChecked(False) DyStockCommon.trendLinePeriods = [] self._trendLinePeriodMenuAction.setText('趋势线周期:{0}'.format(','.join([str(x) for x in DyStockCommon.trendLinePeriods]))) def _kPeriodAct(self): self._curKPeriodAction.setChecked(False) # get triggered action for action in self._kPeriodMenu.actions(): if action.isChecked(): # 设置K线周期 DyStockCommon.dayKChartPeriodNbr = int(action.text()) self._curKPeriodAction = action self._kPeriodMenuAction.setText('K线周期:{0}'.format(DyStockCommon.dayKChartPeriodNbr)) break if not self._curKPeriodAction.isChecked(): self._curKPeriodAction.setChecked(True) def _hsarsAct(self): self._curHsarAction.setChecked(False) # get triggered action for action in self._hsarMenu.actions(): if action.isChecked(): # 设置支撑和阻力 DyStockCommon.hsarMode = action.text() self._curHsarAction = action self._hsarMenuAction.setText('支撑和阻力:{0}'.format(DyStockCommon.hsarMode)) break if not self._curHsarAction.isChecked(): self._curHsarAction.setChecked(True) def _rollingWindowWAct(self): self._curRollingWindowWAction.setChecked(False) # get triggered action for action in self._rollingWindowWMenu.actions(): if action.isChecked(): # 设置rolling window w DyStockCommon.rollingWindowW = int(action.text()) self._curRollingWindowWAction = action self._rollingWindowWMenuAction.setText('滑动窗口(w):{0}'.format(DyStockCommon.rollingWindowW)) break if not self._curRollingWindowWAction.isChecked(): self._curRollingWindowWAction.setChecked(True) def closeEvent(self, event): """ 关闭事件 """ self._mainEngine.exit() return super().closeEvent(event) def _plotAckHandler(self, event): # unpack plot = event.data['plot'] # plot plot(event) def _registerEvent(self): """ 注册GUI更新相关的事件监听 """ self.signalPlot.connect(self._plotAckHandler) self._mainEngine.eventEngine.register(DyEventType.plotAck, self.signalPlot.emit)
def print(self, description, type=DyLogData.info): if type == DyLogData.error or type == DyLogData.warning: event = DyEvent(DyEventType.log) event.data = DyLogData(description, type) self._eventEngine.put(event)
class DyStockTradeStrategyMarketMonitorWidget(QWidget): """ 股票策略实时监控窗口,动态创建 """ signal = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, eventEngine, strategyCls, strategyState): super().__init__() self._eventEngine = eventEngine self._strategyCls = strategyCls self._cloneDataWidgets = [] self._registerEvent() self._initUi(strategyState) def _initUi(self, strategyState): self._dataWidget = DyStockTradeStrategyMarketMonitorDataWidget( self._strategyCls, self) self._indWidget = DyStockTradeStrategyMarketMonitorIndWidget( self._eventEngine, self._strategyCls, strategyState) self._dataLabel = QLabel('数据') self._indLabel = QLabel('指示') grid = QGridLayout() grid.setSpacing(0) grid.addWidget(self._dataLabel, 0, 0) grid.addWidget(self._dataWidget, 1, 0) grid.addWidget(self._indLabel, 2, 0) grid.addWidget(self._indWidget, 3, 0) grid.setRowStretch(0, 1) grid.setRowStretch(1, 30) grid.setRowStretch(2, 1) grid.setRowStretch(3, 30) self.setLayout(grid) # set menu for labels self._dataLabel.setContextMenuPolicy(Qt.CustomContextMenu) self._dataLabel.customContextMenuRequested.connect( self._showDataLabelContextMenu) self._indLabel.setContextMenuPolicy(Qt.CustomContextMenu) self._indLabel.customContextMenuRequested.connect( self._showIndLabelContextMenu) self._dataLabelMenu = QMenu(self) action = QAction('叠加', self) action.triggered.connect(self._overlapAct) self._dataLabelMenu.addAction(action) action = QAction('克隆', self) action.triggered.connect(self._cloneDataWidgetAct) self._dataLabelMenu.addAction(action) self._indLabelMenu = QMenu(self) action = QAction('叠加', self) action.triggered.connect(self._overlapAct) self._indLabelMenu.addAction(action) def _stockMarketMonitorUiHandler(self, event): if 'data' in event.data: data = event.data['data']['data'] new = event.data['data']['new'] strategyCls = event.data['class'] if strategyCls.maxUiDataRowNbr is not None: data = data[:strategyCls.maxUiDataRowNbr] self._dataWidget.update(data, new) for w in self._cloneDataWidgets: w.update(data, new) if 'ind' in event.data: self._indWidget.update(event.data['ind']) def _signalEmitWrapper(self, event): """ !!!Note: The value of signal.emit will always be changed each time you getting. """ self.signal.emit(event) def _registerEvent(self): self.signal.connect(self._stockMarketMonitorUiHandler) self._eventEngine.register( DyEventType.stockMarketMonitorUi + self._strategyCls.name, self._signalEmitWrapper) def _unregisterEvent(self): self.signal.disconnect(self._stockMarketMonitorUiHandler) self._eventEngine.unregister( DyEventType.stockMarketMonitorUi + self._strategyCls.name, self._signalEmitWrapper) def closeEvent(self, event): self._dataWidget.close() self._indWidget.close() self._unregisterEvent() return super().closeEvent(event) def _showDataLabelContextMenu(self, position): self._dataLabelMenu.popup(QCursor.pos()) def _showIndLabelContextMenu(self, position): self._indLabelMenu.popup(QCursor.pos()) def _overlapAct(self): grid = self.layout() # remove self._dataLabel.setText('') self._indLabel.setText('') grid.removeWidget(self._dataLabel) grid.removeWidget(self._dataWidget) grid.removeWidget(self._indLabel) grid.removeWidget(self._indWidget) # add self._tabWidget = QTabWidget() self._tabWidget.addTab(self._dataWidget, '数据') self._tabWidget.addTab(self._indWidget, '指示') grid.addWidget(self._tabWidget, 0, 0) grid.setRowStretch(0, 1) grid.setRowStretch(1, 0) grid.setRowStretch(2, 0) grid.setRowStretch(3, 0) # 设置Tab右键菜单事件 tabBar = self._tabWidget.tabBar() tabBar.setContextMenuPolicy(Qt.CustomContextMenu) tabBar.customContextMenuRequested.connect(self._showTabContextMenu) # 创建TabBar菜单 self._tabBarMenu = QMenu(self) action = QAction('平铺', self) action.triggered.connect(self._flatAct) self._tabBarMenu.addAction(action) def _showTabContextMenu(self, position): self._tabBarMenu.popup(QCursor.pos()) def _flatAct(self): grid = self.layout() # remove self._tabWidget.removeTab(0) self._tabWidget.removeTab(0) grid.removeWidget(self._tabWidget) self._tabWidget.hide() # add self._dataLabel.setText('数据') self._indLabel.setText('指示') grid.addWidget(self._dataLabel, 0, 0) grid.addWidget(self._dataWidget, 1, 0) grid.addWidget(self._indLabel, 2, 0) grid.addWidget(self._indWidget, 3, 0) self._dataWidget.show() self._indWidget.show() grid.setRowStretch(0, 1) grid.setRowStretch(1, 30) grid.setRowStretch(2, 1) grid.setRowStretch(3, 30) def removeCloneDataWidget(self, cloneWidget): try: self._cloneDataWidgets.remove(cloneWidget) except: pass def _cloneDataWidgetAct(self): dataWidget = self._dataWidget.clone() self._cloneDataWidgets.append(dataWidget) dataWidget.setWindowTitle('策略[{}]: 数据'.format( self._strategyCls.chName)) dataWidget.showMaximized()
class DyStockTableWidget(DyStatsTableWidget): """ 股票基类窗口 默认每行以‘代码’,‘名称’开始 提供关于股票所有相关的操作和信息展示 决定股票表的两个因子:name和baseDate """ # header signal stockTableAddColumnsActAckSignal = QtCore.pyqtSignal(type(DyEvent())) # item signal stockTableIndustryCompareActAckSignal = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, eventEngine, parent=None, name=None, baseDate=None, readOnly=True, index=False, autoScroll=False, floatRound=2 ): super().__init__(parent=parent, readOnly=readOnly, index=index, autoScroll=autoScroll, floatRound=floatRound ) self._name = name self._baseDate = baseDate self._eventEngine = eventEngine self._windows = [] self._curActionOngoing = False self._initDataViewer() self._registerEevent() self.itemDoubleClicked.connect(self._itemDoubleClicked) def _registerEevent(self): # header self.stockTableAddColumnsActAckSignal.connect(self._stockTableAddColumnsActAckHandler) self._eventEngine.register(DyEventType.stockTableAddColumnsActAck, self._stockTableAddColumnsActAckSignalEmitWrapper) # item self.stockTableIndustryCompareActAckSignal.connect(self._stockTableIndustryCompareActAckHandler) self._eventEngine.register(DyEventType.stockTableIndustryCompareActAck, self._stockTableIndustryCompareActAckSignalEmitWrapper) def _unregisterEevent(self): # header self.stockTableAddColumnsActAckSignal.disconnect(self._stockTableAddColumnsActAckHandler) self._eventEngine.unregister(DyEventType.stockTableAddColumnsActAck, self._stockTableAddColumnsActAckSignalEmitWrapper) # item self.stockTableIndustryCompareActAckSignal.disconnect(self._stockTableIndustryCompareActAckHandler) self._eventEngine.unregister(DyEventType.stockTableIndustryCompareActAck, self._stockTableIndustryCompareActAckSignalEmitWrapper) def closeEvent(self, event): self._unregisterEevent() return super().closeEvent(event) def _initDataViewer(self): # 省去非错误log的输出 errorInfo = DyErrorInfo(self._eventEngine) self._dataEngine = DyStockDataEngine(self._eventEngine, errorInfo, registerEvent=False) self._dataViewer = DyStockDataViewer(self._dataEngine, errorInfo) self._daysEngine = self._dataEngine.daysEngine self._ticksEngine = self._dataEngine.ticksEngine self._errorProgressInfo = DyErrorProgressInfo(self._eventEngine) def _initHeaderMenu(self): """ 初始化表头右键菜单 子类可以改写添加定制菜单 """ super()._initHeaderMenu() self._headerMenu.addSeparator() action = QAction('新窗口', self) action.triggered.connect(self._newWindowAct) self._headerMenu.addAction(action) self._headerMenu.addSeparator() # '添加列' menu = self._headerMenu.addMenu('添加列') action = QAction('个股资料...', self) action.triggered.connect(self._addStockInfoColumnsAct) menu.addAction(action) menu.addSeparator() action = QAction('涨幅...', self) action.triggered.connect(self._addIncreaseColumnsAct) menu.addAction(action) action = QAction('最大最小涨幅...', self) action.triggered.connect(self._addMaxMinIncreaseColumnsAct) menu.addAction(action) action = QAction('最大振幅...', self) action.triggered.connect(self._addMaxAmplitudeColumnsAct) menu.addAction(action) action = QAction('分钟涨幅(ETF)...', self) # 本来应该添加当日大盘开盘开始的分钟涨幅,主要为了T+1的策略。由于没有大盘指数的分笔数据,所以使用对应的ETF。 action.triggered.connect(self._addMinuteIncreaseColumnsAct) menu.addAction(action) action = QAction('开盘缺口...', self) # 添加开盘缺口,0代表是当日 action.triggered.connect(self._addOpenGapColumnsAct) menu.addAction(action) action = QAction('开盘涨幅', self) action.triggered.connect(self._addOpenIncreaseColumnsAct) menu.addAction(action) menu.addSeparator() action = QAction('ER(效率系数)...', self) action.triggered.connect(self._addErColumnsAct) menu.addAction(action) action = QAction('波动率...', self) action.triggered.connect(self._addVolatilityColumnsAct) menu.addAction(action) action = QAction('日收益率大盘相关系数...', self) action.triggered.connect(self._addDayReturnIndexCorrColumnsAct) menu.addAction(action) menu.addSeparator() action = QAction('列运算...', self) action.triggered.connect(self._addColumnOperateColumnsAct) menu.addAction(action) # '列操作' menu = self._headerMenu.addMenu('列操作') self._upDownRatioAction = QAction('涨跌比', self) self._upDownRatioAction.triggered.connect(self._upDownRatioAct) menu.addAction(self._upDownRatioAction) self._limitUpRatioAction = QAction('涨停比', self) self._limitUpRatioAction.triggered.connect(self._limitUpRatioAct) menu.addAction(self._limitUpRatioAction) action = QAction('过滤...', self) action.triggered.connect(self._filterAct) self._headerMenu.addAction(action) self._headerMenu.addSeparator() action = QAction('导出到同花顺...', self) action.triggered.connect(self._export2JqkaAct) self._headerMenu.addAction(action) action = QAction('保存...', self) action.triggered.connect(self._saveAsAct) self._headerMenu.addAction(action) def _initItemMenu(self): """ 初始化Item右键菜单 子类可以改写添加定制菜单 """ super()._initItemMenu() self._itemMenu.addSeparator() # 分时图 self._timeShareChartMenu = self._itemMenu.addMenu('分时图') actions = [QAction('基准日期', self)] + [QAction('向前{0}日'.format(day), self) for day in range(1, 10)] for action in actions: action.triggered.connect(self._timeShareChartAct) action.setCheckable(True) self._timeShareChartMenu.addAction(action) # 成交分布 self._dealsDistMenu = self._itemMenu.addMenu('成交分布') actions = [QAction('基准日期', self)] + [QAction('向前{0}日'.format(day), self) for day in [5, 10, 20, 30, 60, 90, 120]] for action in actions: action.triggered.connect(self._dealsDistAct) action.setCheckable(True) self._dealsDistMenu.addAction(action) # 成交明细 action = QAction('成交明细', self) action.triggered.connect(self._dealDetailsAct) self._itemMenu.addAction(action) # 日内K线图 self._intraDayKLineMenu = self._itemMenu.addMenu('日内K线图') actions = [QAction('{0}秒'.format(s), self) for s in range(5, 60, 5)] + [QAction('{0}分'.format(m), self) for m in range(1, 16)] for action in actions: action.triggered.connect(self._intraDayKLineAct) action.setCheckable(True) self._intraDayKLineMenu.addAction(action) self._itemMenu.addSeparator() # 个股资料 action = QAction('个股资料', self) action.triggered.connect(self._stockInfoAct) self._itemMenu.addAction(action) # 行业对比 action = QAction('行业对比...', self) action.triggered.connect(self._industryCompareAct) self._itemMenu.addAction(action) self._itemMenu.addSeparator() # 水平支撑和阻力 action = QAction('水平支撑和阻力', self) action.triggered.connect(self._hsarsAct) self._itemMenu.addAction(action) # Swing action = QAction('Swing', self) action.triggered.connect(self._swingAct) self._itemMenu.addAction(action) # Trend channel action = QAction('趋势通道', self) action.triggered.connect(self._trendChannelAct) self._itemMenu.addAction(action) # 波动分布 action = QAction('波动分布...', self) action.triggered.connect(self._volatilityDistAct) self._itemMenu.addAction(action) # ATR Extreme通道 action = QAction('ATR Extreme', self) action.triggered.connect(self._atrExtremeAct) self._itemMenu.addAction(action) # 波动率 action = QAction('波动率', self) action.triggered.connect(self._volatilityAct) self._itemMenu.addAction(action) #------------------------------------------- Item Actions ------------------------------------------- def _intraDayKLineAct(self): code, date = self.getRightClickCodeDate() if code is None: return # get triggered action for action in self._intraDayKLineMenu.actions(): if action.isChecked(): action.setChecked(False) text = action.text() barUnit = 'min' if text[-1] == '分' else 'S' bar = text[:-1] + barUnit break self._dataViewer.plotIntraDayCandleStick(code, [0, date, 0], bar) def _dealsDistAct(self): code, date = self.getRightClickCodeDate() if code is None: return # get triggered action for action in self._dealsDistMenu.actions(): if action.isChecked(): action.setChecked(False) text = action.text() try: n = -int(text[2:-1]) + 1 except Exception as ex: n = 0 break self._dataViewer.plotDealsDist(code, date, n) def _timeShareChartAct(self): code, date = self.getRightClickCodeDate() if code is None: return # get triggered action for action in self._timeShareChartMenu.actions(): if action.isChecked(): action.setChecked(False) text = action.text() try: n = -int(text[2:-1]) except Exception as ex: n = 0 break self._dataViewer.plotTimeShareChart(code, date, n) def _dealDetailsAct(self): code, date = self.getRightClickCodeDate() if code is None: return window = DyStockDealDetailsMainWindow(self._dataViewer, self) window.set(code, date) window.show() def _stockInfoAct(self): code, name = self.getRightClickCodeName() if code is None: return browser = DyWebView() url = 'http://basic.10jqka.com.cn/32/{0}/'.format(code[:-3]) browser.load(QUrl(url)) browser.setWindowTitle(name) rect = QApplication.desktop().availableGeometry() taskBarHeight = QApplication.desktop().height() - rect.height() browser.resize(rect.width()//3 * 2, rect.height() - taskBarHeight) browser.move((rect.width() - browser.width())//2, 0) browser.show() self._windows.append(browser) def _newIndustryCompareWindow(self, code, name, baseDate, dfs): window = DyStockIndustryCompareWindow(self._eventEngine, DyStockTableWidget, code, name, baseDate) window.addCategorys(dfs) window.setWindowTitle('行业对比[{0}]-基准日期[{1}]'.format(name, baseDate)) window.showMaximized() self._windows.append(window) def _stockTableIndustryCompareActAckSignalEmitWrapper(self, event): self.stockTableIndustryCompareActAckSignal.emit(event) def _stockTableIndustryCompareActAckHandler(self, event): if self is not event.data['self']: return code, name, baseDate, categoryDfs = event.data['args'] if not categoryDfs: return self._newIndustryCompareWindow(code, name, baseDate, categoryDfs) self._curActionOngoing = False def _industryCompareAct(self): def _func(self, code, name, baseDate, forwardNTDays, industry2, industry3): categoryDfs = self._getIndustryCompare(code, baseDate, forwardNTDays, industry2, industry3) self._actAck(DyEventType.stockTableIndustryCompareActAck, code, name, baseDate, categoryDfs) code, date = self.getRightClickCodeDate() if code is None: return code, name = self.getRightClickCodeName() data = {} if not DyStockIndustryCompareDlg(name, date, data).exec_(): return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, code, name, date, data['forwardNTDays'], data['industry2'], data['industry3'])) t.start() def _hsarsAct(self): code, date = self.getRightClickCodeDate() if code is None: return self._dataViewer.plotHSARs(code, [-DyStockCommon.dayKChartPeriodNbr, date, DyStockCommon.dayKChartPeriodNbr]) def _swingAct(self): code, date = self.getRightClickCodeDate() if code is None: return self._dataViewer.plotSwingChart(code, [-DyStockCommon.dayKChartPeriodNbr, date, DyStockCommon.dayKChartPeriodNbr]) def _trendChannelAct(self): code, date = self.getRightClickCodeDate() if code is None: return self._dataViewer.plotTrendChannelChart(code, [-DyStockCommon.dayKChartPeriodNbr, date, DyStockCommon.dayKChartPeriodNbr]) def _volatilityDistAct(self): code, date = self.getRightClickCodeDate() if code is None: return code, name = self.getRightClickCodeName() data = {} if not DySingleEditDlg(data, '波动分布[{0}]'.format(name), '基准日期[{0}]向前N日(不包含基准日期)'.format(date), 90).exec_(): return self._dataViewer.plotVolatilityDist(code, date, data['data']) def _atrExtremeAct(self): code, date = self.getRightClickCodeDate() if code is None: return self._dataViewer.plotAtrExtreme(code, [-DyStockCommon.dayKChartPeriodNbr, date, DyStockCommon.dayKChartPeriodNbr]) def _volatilityAct(self, item): code, date = self.getRightClickCodeDate() if code is None: return data = {} if DySingleEditDlg(data, '波动率*√量比(%)', '均值周期', default=20).exec_(): volatilityVolumePeriod = int(data['data']) self._dataViewer.plotVolatilityChart(code, [-DyStockCommon.dayKChartPeriodNbr, date, DyStockCommon.dayKChartPeriodNbr], volatilityVolumePeriod) def _itemDoubleClicked(self, item): code, baseDate = self.getCodeDate(item) if code is None or baseDate is None: return self._dataViewer.plotCandleStick(code, [-DyStockCommon.dayKChartPeriodNbr, baseDate, DyStockCommon.dayKChartPeriodNbr]) def _export2Jqka(self, file, stocks): """ @file: 要保存的绝对路径文件名 """ if not stocks: return now = datetime.now().strftime("%Y_%m_%d %H-%M-%S") f = open(file, 'wb') nbr = struct.pack('<H', len(stocks)) f.write(nbr) for stock in stocks: prefix = bytearray.fromhex('0721') f.write(prefix) code = stock[:-3] code = code.encode('ascii') f.write(code) f.close() #---------------------------------------------- Header Actions ---------------------------------------------- def _limitUpRatioAct(self): colData = self.getColumnsData([self._rightClickHeaderItem.text()]) limitUpNbr, nonLimitUpNbr = 0, 0 for row in colData: if row is None: continue value = row[0] try: value = float(value) except Exception: continue if value >= DyStockCommon.limitUpPct: limitUpNbr += 1 else: nonLimitUpNbr += 1 totalNbr = limitUpNbr + nonLimitUpNbr table = DyTableWidget(readOnly=True, index=False) table.setColNames(['涨停', '非涨停', '涨停占比(%)']) table.appendRow([limitUpNbr, nonLimitUpNbr, limitUpNbr/totalNbr*100]) table.setWindowTitle('涨停比') table.resize(QApplication.desktop().size().width()//2, QApplication.desktop().size().height()//3) table.show() self._windows.append(table) def _upDownRatioAct(self): colData = self.getColumnsData([self._rightClickHeaderItem.text()]) upNbr, downNbr, noChangeNbr = 0, 0, 0 for row in colData: if row is None: continue value = row[0] try: value = float(value) except Exception: continue if value > 0: upNbr += 1 elif value < 0: downNbr += 1 else: noChangeNbr += 1 totalNbr = upNbr + downNbr + noChangeNbr table = DyTableWidget(readOnly=True, index=False) table.setColNames(['涨', '跌', '平', '上涨占比(%)', '下跌占比(%)', '平占比(%)']) table.appendRow([upNbr, downNbr, noChangeNbr, upNbr/totalNbr*100, downNbr/totalNbr*100, noChangeNbr/totalNbr*100]) table.setWindowTitle('涨跌比') table.resize(QApplication.desktop().size().width()//2, QApplication.desktop().size().height()//3) table.show() self._windows.append(table) def _actAck(self, eventType, *args): event = DyEvent(eventType) event.data['self'] = self event.data['args'] = args self._eventEngine.put(event) def _stockTableAddColumnsActAckSignalEmitWrapper(self, event): self.stockTableAddColumnsActAckSignal.emit(event) def _addIncreaseColumnsAct(self): def _func(self, dateCodeList, data): dateCodeIncreaseList = DyStockDataAssembler.getStockIndexIncrease(self._daysEngine, dateCodeList, data['days'], data['backward'], DyProgress(self._errorProgressInfo)) if dateCodeIncreaseList is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockIndexIncrease(dateCodeIncreaseList, data['days'], data['backward']) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {} if not DyStockTableAddColumnsDlg(data, '涨幅').exec_(): return dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _addMaxMinIncreaseColumnsAct(self): def _func(self, dateCodeList, data): dateCodeIncreaseList = DyStockDataAssembler.getStockIndexMaxMinIncrease(self._daysEngine, dateCodeList, data['days'], data['backward'], DyProgress(self._errorProgressInfo)) if dateCodeIncreaseList is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockIndexMaxMinIncrease(dateCodeIncreaseList, data['days'], data['backward']) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {} if not DyStockTableAddColumnsDlg(data, '最大最小涨幅').exec_(): return dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _addMaxAmplitudeColumnsAct(self): def _func(self, dateCodeList, data): dateCodeIncreaseList = DyStockDataAssembler.getStockIndexMaxAmplitude(self._daysEngine, dateCodeList, data['days'], data['backward'], DyProgress(self._errorProgressInfo)) if dateCodeIncreaseList is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockIndexMaxAmplitude(dateCodeIncreaseList, data['days'], data['backward']) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {} if not DyStockTableAddColumnsDlg(data, '振幅').exec_(): return dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _addOpenGapColumnsAct(self): """ 添加开盘缺口列,0代表是当日。若T日没有缺口,则T日是None。正值是向上缺口,负值是向下缺口。 """ def _func(self, dateCodeList, data): rows = DyStockDataAssembler.getStockOpenGap(self._daysEngine, dateCodeList, data['days'], data['backward'], DyProgress(self._errorProgressInfo)) if rows is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockOpenGap(rows, data['days'], data['backward']) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {'days': [0, 1]} if not DyStockTableAddColumnsDlg(data, '开盘缺口(0只能出现在向前)', backward=False).exec_(): return dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _stockTableAddColumnsActAckHandler(self, event): if self is not event.data['self']: return colNames, colData = event.data['args'] if colNames is None: return self.fastAppendColumns(colNames, colData) self._curActionOngoing = False def _addErColumnsAct(self): def _func(self, dateCodeList, data): dateCodeErList = DyStockDataAssembler.getStockIndexEr(self._daysEngine, dateCodeList, data['days'], data['backward'], DyProgress(self._errorProgressInfo)) if dateCodeErList is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockIndexEr(dateCodeErList, data['days'], data['backward']) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {'days': [20, 30, 40, 50, 60]} if not DyStockTableAddColumnsDlg(data, 'Efficiency Ratio', backward=False).exec_(): return dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _addVolatilityColumnsAct(self): def _func(self, dateCodeList, data): dateCodeVolatilityList = DyStockDataAssembler.getStockIndexVolatility(self._daysEngine, dateCodeList, data['days'], data['backward'], DyProgress(self._errorProgressInfo)) if dateCodeVolatilityList is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockIndexVolatility(dateCodeVolatilityList, data['days'], data['backward']) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {'days': [20, 30, 40, 50, 60]} if not DyStockTableAddColumnsDlg(data, '波动率', backward=False).exec_(): return dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _addMinuteIncreaseColumnsAct(self): def _func(self, dateCodeList, data): retData = DyStockDataAssembler.getStockEtfMinuteIncrease(self._dataEngine, dateCodeList, data['data'], DyProgress(self._errorProgressInfo)) if retData is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockEtfMinuteIncrease(retData, data['data']) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {'data': '5,10,15'} if not DySingleEditDlg(data, '分钟涨幅(%)', '基准日期开盘几分钟涨幅(%)').exec_(): return if isinstance(data['data'], str): data['data'] = [int(x) for x in data['data'].split(',')] else: data['data'] = [data['data']] dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _addOpenIncreaseColumnsAct(self): def _func(self, dateCodeList): retData = DyStockDataAssembler.getStockIndexOpenIncrease(self._daysEngine, dateCodeList, DyProgress(self._errorProgressInfo)) if retData is None: self._actAck(DyEventType.stockTableAddColumnsActAck, None, None) return colNames, colData = DyStockDataAssembler.flatStockIndexOpenIncrease(retData) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList)) t.start() def _addDayReturnIndexCorrColumnsAct(self): def _func(self, dateCodeList, data): colNames, colData = DyStockDataAssembler.getStockIndexDayReturnCorr(self._daysEngine, dateCodeList, data['days'], data['backward'], DyProgress(self._errorProgressInfo)) self._actAck(DyEventType.stockTableAddColumnsActAck, colNames, colData) data = {'days': [20, 30, 40, 50, 60]} if not DyStockTableAddColumnsDlg(data, '股票大盘日收益率相关系数', backward=False).exec_(): return dateCodeList = self.getDateCodeList() if not dateCodeList: return self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, dateCodeList, data)) t.start() def _newWindowAct(self): self._newWindow() def _newWindow(self, rows=None): """ 子类可改写 """ window = DyStockTableWidget(self._eventEngine, name=self._name, baseDate=self._baseDate, readOnly=True, index=False, autoScroll=False, floatRound=2 ) if rows is None: rows = self.getAll() window.appendStocks(rows, self.getColNames(), self.getAutoForegroundColName()) window.setWindowTitle(window.name) window.showMaximized() self._windows.append(window) def _addStockInfoColumnsAct(self): def _func(self, codePrices, indicators): progress = DyProgress(self._errorProgressInfo) progress.init(len(codePrices)) # get data from Spider names = [] data = [] first = True for code, price in codePrices: rowData = [] # 公司资料 colNames, colData = DyStockDataSpider.getCompanyInfo(code, indicators) if first: names += colNames rowData += colData # 股本 if '实际流通股(亿)' in indicators: freeShares, type = DyStockDataSpider.getLatestRealFreeShares(code) if first: names += ['实际流通股(亿)', '股份类型'] rowData += [freeShares, type] if '实际流通市值(亿元)' in indicators: if '实际流通股(亿)' not in indicators: freeShares, type = DyStockDataSpider.getLatestRealFreeShares(code) if first: names += ['实际流通市值(亿元)'] rowData += [freeShares*price] if '机构占比流通(%)' in indicators: fundPosRatio, fundNbr = DyStockDataSpider.getLatestFundPositionsRatio(code) if first: names += ['机构占比流通(%)'] rowData += [fundPosRatio] # post process if rowData: data.append(rowData) first = False progress.update() self._actAck(DyEventType.stockTableAddColumnsActAck, names, data) codePrices = self.getCodePriceList() if not codePrices: return data = {} if not DyStockInfoDlg(data).exec_(): return indicators = data['indicators'] self._curActionOngoing = True t = threading.Thread(target=_func, args=(self, codePrices, indicators)) t.start() def _addColumnOperateColumnsAct(self): data = {} if DyStockTableColumnOperateDlg(data, self.getColNames()).exec_(): self.addColumnOperateColumns(data['exp']) def _filterAct(self): data = {} if DyStockTableFilterDlg(data, self.getColNames()).exec_(): self._filter(data['filter'], data['newWindow'], data['highlight']) def _filter(self, filter, newWindow, highlight): filterRows = self.filter(filter, highlight) if newWindow: self._newWindow(rows=filterRows) def _export2JqkaAct(self): data = {} if not DyStockTableSelectDlg(data, '{0}导出到同花顺'.format(self.getUniqueName())).exec_(): return defaultFileName = '{0}.sel' if data['all'] else '{0}_高亮.sel' defaultFileName = defaultFileName.format(self.getUniqueName()) defaultDir = DyCommon.createPath('Stock/User/Save/Strategy/同花顺') fileName, _ = QFileDialog.getSaveFileName(self, '导出到同花顺', os.path.join(defaultDir, defaultFileName), "同花顺files (*.sel);;all files(*.*)") if fileName: self.export2Jqka(fileName) def _saveAsAct(self): data = {} if not DyStockTableSelectDlg(data, '{0}保存'.format(self.getUniqueName())).exec_(): return defaultFileName = '{0}.json' if data['all'] else '{0}_高亮.json' defaultFileName = defaultFileName.format(self.getUniqueName()) defaultDir = DyCommon.createPath('Stock/User/Save/Strategy') fileName, _ = QFileDialog.getSaveFileName(self, '保存股票表', os.path.join(defaultDir, defaultFileName), "JSON files (*.json);;all files(*.*)") if fileName: self._saveAs(fileName, data['all']) def _saveAs(self, fileName, all=True): """ 重载@getCustomSaveData定义自己的保存格式 共同数据: { 'autoForegroundColName': autoForegroundColName, 'data': {'colNames': colNames, 'rows': rows} } """ rows = self.getAll() if all else self.getHighlights() colNames, autoForegroundColName = self.getColNames(), self.getAutoForegroundColName() # 子类可以重载@getCustomSaveData customData = self.getCustomSaveData() data = {'name': self._name, 'autoForegroundColName': autoForegroundColName, 'baseDate': self._baseDate, 'data': {'colNames': colNames, 'rows': rows} } data.update(**customData) with open(fileName, 'w') as f: f.write(json.dumps(data, indent=4)) #-------------------------------------- 股票行业比较 -------------------------------------- # !!!原则上应该放到@DyStockDataSpider,但改动比较大,暂时先这么实现 def _toFloat(self, value): try: value = float(value) except: try: value = float(value[:-1]) # e.g value like '15.06%' except: value = 0 return value def _getCompanyFinanceOutline1(self, code, indicators): """ 从财务报表获取指定的指标 @indicators: [] """ mainLink = 'http://basic.10jqka.com.cn/{0}/flash/main.txt'.format(code[:-3]) r = requests.get(mainLink) table = dict(json.loads(r.text)) values = {} for indicator in indicators: # get @indicator position pos = None for i, e in enumerate(table['title']): if isinstance(e, list): if e[0] == indicator: pos = i break # 指标最近的值 value = self._toFloat(table['report'][pos][0]) values[indicator] = value return values def _getCompanyFinanceOutline(self, code): """ 从同花顺'最新动态'网页获取财务概要信息 """ def getIndex(x): for i, name in enumerate(colNames): if x in name: return i return None colNames = ['市盈率(动态)', '市净率', '每股收益', '每股现金流', '每股净资产', '净资产收益率(%)', '营业收入YoY(%)', '净利润YoY(%)', '流通A股(亿股)', '总股本(亿股)'] colData = [None]*len(colNames) # 缺失的数据从财务报表里获取 latest2FinanceMap = {'营业收入YoY(%)': '营业总收入同比增长率', '净利润YoY(%)': '净利润同比增长率'} finance2LatestMap = {value: key for key, value in latest2FinanceMap.items()} try: r = requests.get('http://basic.10jqka.com.cn/16/{0}/'.format(code[:-3])) soup = BeautifulSoup(r.text, 'lxml') table = soup.find('table', class_="m_table m_table_db mt10") tds = table.find_all('td') for td in tds: spans = td.find_all('span') indicator = str(spans[0].string)[:-1] index = getIndex(indicator) if index is None: continue value = None if '%' in colNames[index]: for span in spans[1:]: if '%' in str(span.string): value = str(span.string) break else: value = str(spans[1].string) if value is not None: positive = True if '下降' not in value else False value = re.findall(r"-?\d+\.?\d*", value) if value: value = float(value[0]) if positive else -float(value[0]) colData[index] = value # 处理缺失数据 indicators = [] for key, value in latest2FinanceMap.items(): index = colNames.index(key) if colData[index] is None: indicators.append(value) # 从财务报表获取缺失数据的最新值 if indicators: data = self._getCompanyFinanceOutline1(code, indicators) for key, value in data.items(): index = colNames.index(finance2LatestMap[key]) colData[index] = value except Exception as ex: pass return colNames, colData def _getIndustryCompareTable(self, div, id): colNames = ['销售毛利率'] colNamesForReturn = ['销售毛利率(%)'] colPoses = {} colData = {} try: table = div.find('table', class_='m_table m_hl', id=id) # 获取每个指标的位置 tag = table.find('thead') ths = tag.find_all('th') for i, th in enumerate(ths): indicator = str(th.contents[0]) if indicator in colNames: colPoses[indicator] = i assert(len(colNames) == len(colPoses)) # 获取指定date的指标值 tag = table.find('tbody') trs = tag.find_all('tr') for tr in trs: tds = tr.find_all('td') code = DyStockCommon.getDyStockCode(str(tds[0].string)) name = str(tds[1].string) data = [code, name] for indicator in colNames: data.append(self._toFloat(tds[colPoses[indicator]].string)) colData[code] = data except Exception as ex: pass return ['代码', '名称'] + colNamesForReturn, colData def _getIndustryComparePartly(self, code, industry2, industry3): totalData = {} tableColNames = [] try: r = requests.get('http://basic.10jqka.com.cn/16/{0}/field.html'.format(code[:-3])) soup = BeautifulSoup(r.text, 'lxml') divIds = {"hy3_div": industry3, "hy2_div": industry2} pTexts = {"hy3_div": '三级行业分类:', "hy2_div": '二级行业分类:'} tableIds = {"hy3_div": ["hy3_table_1", "hy3_table_2"], "hy2_div": ["hy2_table_1", "hy2_table_2"]} for divId, bool in divIds.items(): if not bool: continue div = soup.find('div', id=divId) if div is None: continue # 行业分类 category = div.parent.find(text=pTexts[divId]) category = category.parent categoryHead = str(category.contents[0]) span = category.find('span') categoryBody = str(span.contents[0][:-3]) category = categoryHead + categoryBody # table tableColDataTotal = {} for tableId in tableIds[divId]: tableColNames, tableColData = self._getIndustryCompareTable(div, tableId) for code in tableColData: if code not in tableColDataTotal: tableColDataTotal[code] = tableColData[code] totalData[category] = tableColDataTotal except Exception as ex: pass return tableColNames, totalData def _calcIndustryCompareScore(self, categoryDfs): for category, df in categoryDfs.items(): # rank for each indicator, think rank as score that the high score is the better is seriesList = [] series = df['市盈率(动态)'].rank(ascending=False) seriesList.append(series) series = df['市净率'].rank(ascending=False) seriesList.append(series) series = df['净资产收益率(%)'].rank() seriesList.append(series) series = df['每股现金流'].rank() seriesList.append(series) series = df['营业收入YoY(%)'].rank() seriesList.append(series) series = df['净利润YoY(%)'].rank() seriesList.append(series) series = df['销售毛利率(%)'].rank() seriesList.append(series) rankDf = pd.concat(seriesList, axis=1) # total rank series = rankDf.sum(axis=1)*100/(len(seriesList) * rankDf.shape[0]) series.name = '得分' df = pd.concat([df, series], axis=1) columns = list(df.columns) df = df.reindex(columns=columns[:2] + columns[-1:] + columns[2:-1]) categoryDfs[category] = df def _getIndustryCompare(self, code, baseDate, forwardNTDays, industry2, industry3): # 获取同行业的数据 name, data = self._getIndustryComparePartly(code, industry2, industry3) # 合并代码表 codes = set() for _, data_ in data.items(): codes.update(list(data_.keys())) codes = list(codes) # 获取股票的基本财务信息 progress = DyProgress(self._errorProgressInfo) progress.init(len(codes)) financeOutline = {} for code in codes: outlineNames, outlineData = self._getCompanyFinanceOutline(code) financeOutline[code] = outlineData progress.update() financeOutlineDf = pd.DataFrame(financeOutline, index=outlineNames).T # 根据行业分级合并数据 categoryDfs = {} for category, data_ in data.items(): df = pd.DataFrame(data_, index=name).T df = pd.concat([df, financeOutlineDf], axis=1) df = df[df[name[0]].notnull()] df = df.reindex(columns=name[:2] + outlineNames[:-2] + name[2:] + outlineNames[-2:]) categoryDfs[category] = df # 计算得分 self._calcIndustryCompareScore(categoryDfs) # 获取前N日涨幅 daysEngine = self._daysEngine if not daysEngine.load([baseDate, -forwardNTDays], codes=codes): return categoryDfs # 计算前N日涨幅 autoForegroundColName = '前{0}日涨幅(%)'.format(forwardNTDays) pcts = {} for code in codes: df = daysEngine.getDataFrame(code) if df is not None and not df.empty: pct = (df.ix[-1, 'close'] - df.ix[0, 'close'])*100/df.ix[0, 'close'] pcts[code] = [df.ix[-1, 'close'], pct] # 获取指定周期内停牌股票的最新收盘价 for code in codes: if code not in pcts: # 同花顺可能会含有终止上市的股票或者没有上市的股票 if daysEngine.loadCode(code, [baseDate, 0]): df = daysEngine.getDataFrame(code) pcts[code] = [df.ix[-1, 'close'], None] pctDf = pd.DataFrame(pcts, index=['当日价格', autoForegroundColName]).T # 根据行业分级合并数据 for category, df in categoryDfs.items(): df = pd.concat([df, pctDf], axis=1) df = df[df.ix[:,0].notnull()] df.sort_values('得分', axis=0, ascending=False, inplace=True) # 添加市值 df['流通市值(亿元)'] = df['流通A股(亿股)'] * df['当日价格'] df['总市值(亿元)'] = df['总股本(亿股)'] * df['当日价格'] columns = list(df.columns) df = df.reindex(columns=columns[:-4] + columns[-2:] + columns[-4:-2]) categoryDfs[category] = df return categoryDfs #---------------------------------------------- interfaces ---------------------------------------------- def export2Jqka(self, fileName): self._export2Jqka(fileName, self.getCodeList()) def setBaseDate(self, baseDate): self._baseDate = baseDate def appendStocks(self, rows, header=None, autoForegroundColName=None, new=False): if header is not None: self.setColNames(header) self.fastAppendRows(rows, autoForegroundColName=autoForegroundColName, new=new) #---------------------------------------------- 由子类根据自己的Table格式改写 ---------------------------------------------- def getDateCodeList(self): if self._baseDate is None: raise AttributeError codes = self.getColumnsData(['代码']) return [[self._baseDate, code[0]] for code in codes] def getCodeList(self): codes = self.getColumnsData(['代码']) return [code[0] for code in codes] def getCodePriceList(self): return self.getColumnsData(['代码', '当日价格']) def getRightClickCodeDate(self): item = self.itemAt(self._rightClickPoint) if item is None: return None, None code = self[item.row(), '代码'] return code, self._baseDate def getRightClickCodeName(self): item = self.itemAt(self._rightClickPoint) if item is None: return None, None code = self[item.row(), '代码'] name = self[item.row(), '名称'] return code, name def getCodeDate(self, item): row = self.row(item) code = self[row, '代码'] return code, self._baseDate def getUniqueName(self): """ Get unique name of this table. Usually it's combined with name + baseDate """ return '{0}_{1}'.format(self._name, self._baseDate) def getCustomSaveData(self): """ 获取每个类的定制保存数据 @return: dict """ return {} def customizeHeaderContextMenu(self, headerItem): """ 子类改写 """ self._upDownRatioAction.setEnabled('涨幅' in headerItem.text()) self._limitUpRatioAction.setEnabled('涨幅' in headerItem.text()) #---------------------------------------------- 属性 ---------------------------------------------- @property def dataViewer(self): return self._dataViewer @property def name(self): return self._name @property def eventEngine(self): return self._eventEngine @property def baseDate(self): return self._baseDate
def _test(self): event = DyEvent(DyEventType.plotReq) event.data['type'] = 'test' self._mainEngine.eventEngine.put(event)
def _actAck(self, eventType, *args): event = DyEvent(eventType) event.data['self'] = self event.data['args'] = args self._eventEngine.put(event)
def _stockPositionUpdateHandler(self, event): """ 收到来自券商接口的账户持仓更新事件 """ # unpack header = event.data['header'] rows = event.data['rows'] # 先前持仓代码 codes = list(self._curPos) for data in rows: # unpack from 券商接口持仓数据 code = DyStockCommon.getDyStockCode(data[header.index( self.headerNameMap['position']['code'])]) name = data[header.index(self.headerNameMap['position']['name'])] totalVolume = float(data[header.index( self.headerNameMap['position']['totalVolume'])]) availVolume = float(data[header.index( self.headerNameMap['position']['availVolume'])]) price = float(data[header.index( self.headerNameMap['position']['price'])]) cost = float(data[header.index( self.headerNameMap['position']['cost'])]) # get position if code in self._curPos: pos = self._curPos[code] codes.remove(code) else: # new pos, we just take time now without accuracy if totalVolume > 0: pos = DyStockPos(datetime.now(), None, code, name, price, totalVolume, 0) pos.sync = True else: continue # syn with positions from broker pos.price = price pos.cost = cost pos.totalVolume = totalVolume pos.availVolume = availVolume # write back self._curPos[code] = pos # 删除不在券商接口数据里的持仓 for code in codes: del self._curPos[code] # 发送行情监控事件 self._putStockMarketMonitorEvent() # 发送券商账户股票持仓更新事件 event = DyEvent(DyEventType.stockOnPos) event.data['broker'] = self.broker event.data['pos'] = copy.deepcopy(self._curPos) self._eventEngine.put(event)
class DyStockSelectSelectResultWidget(QTabWidget): stockSelectStrategySelectAckSignal = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, eventEngine, paramWidget, registerSelectAckEvent=True): super().__init__() self._eventEngine = eventEngine self._paramWidget = paramWidget self._registerSelectAckEvent = registerSelectAckEvent self._strategyWidgets = {} self._windows = [] # only for show self._registerEvent() # 窗口事件相关 self.setTabsClosable(True) self.tabCloseRequested.connect(self._closeTab) def _addTab(self, tabName, widget): self.addTab(widget, tabName) self._strategyWidgets[tabName] = widget def _refactory(self, tableWidget, params, newWindow): newRows = tableWidget.refactory(params) if newWindow: window = DyStockSelectSelectResultWidget(self._eventEngine, None, False) widget = DyStockSelectStrategySelectResultWidget( self._dataViewer, tableWidget.baseDate, tableWidget.strategyName, self._widgetParam, tableWidget.strategyClsName) widget.append(newRows, tableWidget.getColNames(), tableWidget.getAutoForegroundColName()) window._addTab(tableWidget.strategyName, widget) window.setWindowTitle(tableWidget.strategyName) window.showMaximized() self._newWindows.append(window) else: tableWidget.append(newRows, tableWidget.getColNames()) def _filter(self, tableWidget, filter, newWindow, highlight): filterRows = tableWidget.filter(filter, highlight) if newWindow: window = DyStockSelectSelectResultWidget(self._eventEngine, None, False) widget = DyStockSelectStrategySelectResultWidget( self._dataViewer, tableWidget.baseDate, tableWidget.strategyName, self._widgetParam, tableWidget.strategyClsName) widget.append(filterRows, tableWidget.getColNames(), tableWidget.getAutoForegroundColName()) window._addTab(tableWidget.strategyName, widget) window.setWindowTitle(tableWidget.strategyName) window.showMaximized() self._newWindows.append(window) def _stockSelectStrategySelectAckHandler(self, event): # unpack strategyCls = event.data['class'] result = event.data['result'] baseDate = event.data['baseDate'] if result is None: return # show result if strategyCls.chName in self._strategyWidgets: self._strategyWidgets[strategyCls.chName].setBaseDate(baseDate) else: # create a new widget widget = DyStockSelectStrategySelectResultWidget( self._eventEngine, strategyCls, baseDate, self._paramWidget) self._addTab(strategyCls.chName, widget) self._strategyWidgets[strategyCls.chName].appendStocks( result[1:], result[0]) self.parentWidget().raise_() def _stockSelectStrategySelectAckSignalEmitWrapper(self, event): self.stockSelectStrategySelectAckSignal.emit(event) def _registerEvent(self): if self._registerSelectAckEvent: self.stockSelectStrategySelectAckSignal.connect( self._stockSelectStrategySelectAckHandler) self._eventEngine.register( DyEventType.stockSelectStrategySelectAck, self._stockSelectStrategySelectAckSignalEmitWrapper) def _unregisterEvent(self): if self._registerSelectAckEvent: self.stockSelectStrategySelectAckSignal.disconnect( self._stockSelectStrategySelectAckHandler) self._eventEngine.unregister( DyEventType.stockSelectStrategySelectAck, self._stockSelectStrategySelectAckSignalEmitWrapper) def _closeTab(self, index): tabName = self.tabText(index) self._strategyWidgets[tabName].close() del self._strategyWidgets[tabName] self.removeTab(index) def closeEvent(self, event): self._unregisterEvent() return super().closeEvent(event) def load(self, data, strategyCls): """ @data: JSON data """ className = data.get('class') if not className: return False if className != 'DyStockSelectStrategySelectResultWidget': return False window = DyStockSelectStrategySelectResultWidget( self._eventEngine, strategyCls, data['baseDate']) window.appendStocks( data['data']['rows'], data['data']['colNames'], autoForegroundColName=data['autoForegroundColName']) window.setWindowTitle('{0}[{1}]'.format(strategyCls.chName, data['baseDate'])) window.showMaximized() self._windows.append(window) return True
def _stockPosSyncFromBrokerHandler(self, event): """ 收到来自券商接口的持仓同步事件 !!!如果交易不是通过策略,或者持仓同步不是在开盘时,可能会导致信息不一致或者错误。 """ # unpack header = event.data['header'] rows = event.data['rows'] # it's synchronized from broker self._curPosSyncData = {} for data in rows: # unpack from 券商接口持仓数据 code = DyStockCommon.getDyStockCode(data[header.index( self.headerNameMap['position']['code'])]) name = data[header.index(self.headerNameMap['position']['name'])] totalVolume = float(data[header.index( self.headerNameMap['position']['totalVolume'])]) availVolume = float(data[header.index( self.headerNameMap['position']['availVolume'])]) price = float(data[header.index( self.headerNameMap['position']['price'])]) cost = float(data[header.index( self.headerNameMap['position']['cost'])]) # get position pos = self._curPos.get(code) if pos is None: continue # set sync data firstly self._curPosSyncData[code] = { 'volumeAdjFactor': totalVolume / pos.totalVolume, 'priceAdjFactor': pos.cost / cost, 'cost': cost } # syn with positions from broker pos.price = price pos.cost = cost pos.totalVolume = totalVolume pos.availVolume = availVolume pos.priceAdjFactor = self._curPosSyncData[code]['priceAdjFactor'] pos.volumeAdjFactor = self._curPosSyncData[code]['volumeAdjFactor'] if pos.priceAdjFactor != 1: pos.xrd = True pos.sync = True # 发送行情监控事件 self._putStockMarketMonitorEvent() # 发送股票持仓同步事件 event = DyEvent(DyEventType.stockPosSyncFromAccountManager) event.data['broker'] = self.broker event.data['data'] = self._curPosSyncData self._eventEngine.put(event)
class DyStockTradeStrategySellDlg(QDialog): stockMarketTicksSignal = QtCore.pyqtSignal(type(DyEvent())) stockStrategyPosAckSignal = QtCore.pyqtSignal(type(DyEvent())) stockStrategyPosUpdateSignal = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, eventEngine, strategyCls): super().__init__() self._eventEngine = eventEngine self._strategyCls = strategyCls self._code = None # current stock code self._tick = None # current tick of @self._code self._curPos = None # 策略持仓字典 self._initUi() self._registerEvent() self._init() def _init(self): event = DyEvent(DyEventType.stockStrategyPosReq) event.data = self._strategyCls self._eventEngine.put(event) def _initUi(self): self.setWindowTitle(self._strategyCls.chName) # 策略持仓 posLabel = QLabel('策略持仓') posLabel.setStyleSheet("color:#4169E1") self._posWidget = DyStockTradeStrategyPosSellWidget( self, self._eventEngine, self._strategyCls) # 卖出 sellCodeLabel = QLabel('股票代码') sellCodeLabel.setStyleSheet("color:#4169E1") self._sellCodeLineEdit = QLineEdit() sellVolumeLabel = QLabel('数量(手)') sellVolumeLabel.setStyleSheet("color:#4169E1") self._sellVolumeLineEdit = QLineEdit('1') sellPriceLabel = QLabel('价格(元)') sellPriceLabel.setStyleSheet("color:#4169E1") self._sellPriceLineEdit = QLineEdit() # 行情 self._codeLabel = QLabel('股票代码') self._nameLabel = QLabel('股票名称') self._priceLabel = QLabel('股票现价') self._increaseLabel = QLabel('涨幅(%):') self._bidAskTable = DyTableWidget(readOnly=True, index=False, floatRound=3) self._bidAskTable.setColNames([None, '价格(元)', '数量(手)']) self._bidAskTable.fastAppendRows([['卖5', None, None], ['卖4', None, None], ['卖3', None, None], ['卖2', None, None], ['卖1', None, None], [None, None, None], ['买1', None, None], ['买2', None, None], ['买3', None, None], ['买4', None, None], ['买5', None, None]]) cancelPushButton = QPushButton('Cancel') okPushButton = QPushButton('卖出') cancelPushButton.clicked.connect(self._cancel) okPushButton.clicked.connect(self._ok) # 布局 grid = QGridLayout() grid.setSpacing(10) grid.addWidget(posLabel, 0, 0) grid.addWidget(self._posWidget, 1, 0, 10, 10) start = 12 grid.addWidget(sellCodeLabel, start + 0, 0) grid.addWidget(self._sellCodeLineEdit, start + 1, 0) grid.addWidget(sellVolumeLabel, start + 2, 0) grid.addWidget(self._sellVolumeLineEdit, start + 3, 0) grid.addWidget(sellPriceLabel, start + 4, 0) grid.addWidget(self._sellPriceLineEdit, start + 5, 0) grid.addWidget(self._codeLabel, start + 0, 1, 1, 10) grid.addWidget(self._nameLabel, start + 1, 1) grid.addWidget(self._priceLabel, start + 2, 1) grid.addWidget(self._increaseLabel, start + 3, 1) grid.addWidget(self._bidAskTable, start + 4, 1, 30, 10) grid.addWidget(okPushButton, start + 6, 0) grid.addWidget(cancelPushButton, start + 7, 0) self.setLayout(grid) self.setMinimumWidth(QApplication.desktop().size().width() // 3) self._sellCodeLineEdit.textChanged.connect(self._sellCodeChanged) def _ok(self): try: if self._codeLabel.text() != self._tick.code: QMessageBox.warning(self, '错误', '没有指定代码的Tick数据!') return except Exception: QMessageBox.warning(self, '错误', '没有指定代码的Tick数据!') return event = DyEvent(DyEventType.stockStrategyManualSell) event.data['class'] = self._strategyCls event.data['tick'] = self._tick event.data['volume'] = float(self._sellVolumeLineEdit.text()) * 100 # 不指定价格,则根据tick买入 price = self._sellPriceLineEdit.text() event.data['price'] = float(price) if price else None self._eventEngine.put(event) self._unregisterEvent() self._posWidget.close() self.accept() def _cancel(self): self._unregisterEvent() self._posWidget.close() self.reject() def setSellCodePrice(self, code, price): self._sellCodeLineEdit.setText(code[:6]) self._sellPriceLineEdit.setText(str(price)) def _getInputCode(self): if not self._curPos: return None code = self._sellCodeLineEdit.text() if len(code) != 6: return None code = DyStockCommon.getDyStockCode(code) if code not in self._curPos: return None return code def _sellCodeChanged(self): self._code = self._getInputCode() if self._code is None: self._codeLabel.setText('输入代码在策略持仓里不存在!') return self._codeLabel.setText(self._code) def _stockStrategyPosAckSignalEmitWrapper(self, event): self.stockStrategyPosAckSignal.emit(event) def _stockMarketTicksSignalEmitWrapper(self, event): self.stockMarketTicksSignal.emit(event) def _stockStrategyPosUpdateSignalEmitWrapper(self, event): self.stockStrategyPosUpdateSignal.emit(event) def _registerEvent(self): self.stockMarketTicksSignal.connect(self._stockMarketTicksHandler) self._eventEngine.register(DyEventType.stockMarketTicks, self._stockMarketTicksSignalEmitWrapper) self.stockStrategyPosAckSignal.connect( self._stockStrategyPosAckHandler) self._eventEngine.register(DyEventType.stockStrategyPosAck, self._stockStrategyPosAckSignalEmitWrapper) self.stockStrategyPosUpdateSignal.connect( self._stockStrategyPosUpdateHandler) self._eventEngine.register( DyEventType.stockStrategyPosUpdate + self._strategyCls.name, self._stockStrategyPosUpdateSignalEmitWrapper) def _unregisterEvent(self): self.stockMarketTicksSignal.disconnect(self._stockMarketTicksHandler) self._eventEngine.unregister(DyEventType.stockMarketTicks, self._stockMarketTicksSignalEmitWrapper) self.stockStrategyPosAckSignal.disconnect( self._stockStrategyPosAckHandler) self._eventEngine.unregister( DyEventType.stockStrategyPosAck, self._stockStrategyPosAckSignalEmitWrapper) self.stockStrategyPosUpdateSignal.disconnect( self._stockStrategyPosUpdateHandler) self._eventEngine.unregister( DyEventType.stockStrategyPosUpdate + self._strategyCls.name, self._stockStrategyPosUpdateSignalEmitWrapper) def _stockStrategyPosAckHandler(self, event): self._curPos = event.data self._posWidget.update(event.data) def _stockStrategyPosUpdateHandler(self, event): self._curPos = event.data self._posWidget.update(event.data) def _stockMarketTicksHandler(self, event): ticks = event.data self._tick = ticks.get(self._code) if self._tick is None: return tick = self._tick self._codeLabel.setText(tick.code) self._nameLabel.setText(tick.name) self._priceLabel.setText(str(tick.price)) if tick.price > tick.preClose: self._priceLabel.setStyleSheet("color:red") elif tick.price < tick.preClose: self._priceLabel.setStyleSheet("color:darkgreen") increase = round((tick.price - tick.preClose) / tick.preClose * 100, 2) self._increaseLabel.setText('涨幅(%): {0}%'.format(increase)) if increase > 0: self._increaseLabel.setStyleSheet("color:red") elif increase < 0: self._increaseLabel.setStyleSheet("color:darkgreen") self._bidAskTable.fastAppendRows( [['卖5', tick.askPrices[4], round(tick.askVolumes[4] / 100)], ['卖4', tick.askPrices[3], round(tick.askVolumes[3] / 100)], ['卖3', tick.askPrices[2], round(tick.askVolumes[2] / 100)], ['卖2', tick.askPrices[1], round(tick.askVolumes[1] / 100)], ['卖1', tick.askPrices[0], round(tick.askVolumes[0] / 100)], [None, tick.price, None], ['买1', tick.bidPrices[0], round(tick.bidVolumes[0] / 100)], ['买2', tick.bidPrices[1], round(tick.bidVolumes[1] / 100)], ['买3', tick.bidPrices[2], round(tick.bidVolumes[2] / 100)], ['买4', tick.bidPrices[3], round(tick.bidVolumes[3] / 100)], ['买5', tick.bidPrices[4], round(tick.bidVolumes[4] / 100)]], new=True)
def _startStockCtaStrategyHandler(self, event): """ 启动策略, 创建唯一策略实例 """ strategyCls = event.data['class'] state = event.data['state'] self._info.print( '开始启动策略: {0}, 状态: {1},...'.format(strategyCls.chName, state.state), DyLogData.ind) # 是否是唯一策略实例 if strategyCls.name in self._strategies: self._info.print('重复启动策略: {0}'.format(strategyCls.chName), DyLogData.error) return # !!!It's tricky for live trading but not accurate. sleep( 1 ) # sleep so that UI related dynamic windows can be created firstly. # 实例化策略 strategy = strategyCls(self, self._info, state) # 策略开盘前初始化 if not strategy.onOpen(datetime.now().strftime("%Y-%m-%d"), strategy.onOpenCodes()): self._info.print('策略: {0}启动失败'.format(strategyCls.chName), DyLogData.error) return # 启动策略的账户管理 if state.isState(DyStockStrategyState.running): if strategy.broker is not None: # Strategy has configured the broker if not self._startAccountManager(strategyCls): return # 从券商管理类同步策略持仓 self._accountManagers[strategy.broker].syncStrategyPos( strategy) # 获取策略要监控的股票池 monitoredStocks = strategy.onMonitor() # 添加到策略字典 self._strategies[strategyCls.name] = ( strategy, DyStockMarketFilter(monitoredStocks)) # 添加到bar聚合字典 if 'bar' in strategyCls.liveMode: if strategyCls.liveMode not in self._barAggs: self._barAggs[strategyCls.liveMode] = DyStockCtaBarAggFast( strategyCls.liveMode, monitoredStocks) else: self._barAggs[strategyCls.liveMode].add(monitoredStocks) # 向股票市场发送监控的股票池 monitoredStocks = monitoredStocks + [ DyStockCommon.etf300, DyStockCommon.etf500 ] # always add ETF300 and ETF500 for 大盘参考。主要原因是历史分笔数据没有指数的,所以只能用ETF替代。 if monitoredStocks: event = DyEvent(DyEventType.stockMarketMonitor) event.data = monitoredStocks self._eventEngine.put(event) # 向UI推送策略的账户相关事件 self._updateStrategyAccount(strategy) self._info.print('策略: {0}启动成功'.format(strategyCls.chName), DyLogData.ind)
def _init(self): event = DyEvent(DyEventType.stockStrategyPosReq) event.data = self._strategyCls self._eventEngine.put(event)
def _endDay(self): if self._checkDay() or self.testMode: self._eventEngine.put(DyEvent(DyEventType.endStockTradeDay))