def __init__(self, sas_if: sasIF, task_manager): super(AnnouncementDownloaderUi, self).__init__() # ---------------- ext var ---------------- self.__sas_if = sas_if # self.__data_center = self.__data_hub.get_data_center() if self.__data_hub is not None else None # self.__data_utility = self.__data_hub.get_data_utility() if self.__data_hub is not None else None self.__task_manager = task_manager self.__translate = QtCore.QCoreApplication.translate # Timer for update stock list self.__timer = QTimer() self.__timer.setInterval(1000) self.__timer.timeout.connect(self.on_timer) self.__timer.start() # Ui component self.__combo_name = SecuritiesSelector(self.__sas_if, self) self.__radio_annual_report = QRadioButton('年报') self.__radio_customize_filter = QRadioButton('自定义') self.__line_filter_include = QLineEdit() self.__line_filter_exclude = QLineEdit() self.__button_download = QPushButton('确定') self.__datetime_since = QDateTimeEdit( QDateTime.currentDateTime().addYears(-3)) self.__datetime_until = QDateTimeEdit(QDateTime.currentDateTime()) self.init_ui()
def __init__(self, memo_context: dict): self.__memo_context = memo_context # self.__memo_context.add_observer(self) self.__sas_if: sasIF = self.__memo_context.get('sas_if') super(StockChartUi, self).__init__() # self.__sas = memo_context.get_sas() # self.__memo_record: StockMemoRecord = memo_context.get_memo_record() self.__in_edit_mode = True self.__paint_securities = '' self.__paint_trade_data = None # # Record # user_path = os.path.expanduser('~') # project_path = sas.get_project_path() if sas is not None else os.getcwd() # # self.__root_path = \ # memo_path_from_project_path(project_path) if user_path == '' else \ # memo_path_from_user_path(user_path) # self.__memo_record = Record(os.path.join(self.__root_path, 'stock_memo.csv'), STOCK_MEMO_COLUMNS) # vnpy chart self.__vnpy_chart = ChartWidget() # Memo editor self.__memo_editor: StockMemoEditor = self.__memo_context.get('editor') # Timer for workaround signal fired twice self.__accepted = False self.__timer = QTimer() self.__timer.setInterval(1000) self.__timer.timeout.connect(self.on_timer) self.__timer.start() # Ui component # data_utility = self.__sas.get_data_hub_entry().get_data_utility() if self.__sas is not None else None self.__combo_name = SecuritiesSelector( self.__sas_if) if self.__sas_if is not None else QComboBox() self.__button_ensure = QPushButton('确定') self.__check_abs = QCheckBox('固定价格范围') self.__check_memo = QCheckBox('笔记') self.__check_volume = QCheckBox('成交量') self.__radio_adj_tail = QRadioButton('后复权') self.__radio_adj_head = QRadioButton('前复权') self.__radio_adj_none = QRadioButton('不复权') self.__group_adj = QButtonGroup(self) self.__radio_log_return = QRadioButton('对数收益') self.__radio_simple_return = QRadioButton('算术收益') self.__group_return = QButtonGroup(self) self.__init_ui() self.__config_ui()
def __init__(self, memo_context: dict): self.__memo_context = memo_context self.__sas_if: sasIF = self.__memo_context.get( 'sas_if') if self.__memo_context is not None else None super(StockMemoDeck, self).__init__() if self.__memo_context is not None: # self.__memo_context.add_observer(self) self.__sas_if: sasIF = self.__memo_context.get('sas_if') self.__memo_editor: StockMemoEditor = self.__memo_context.get( 'editor') else: # For layout debug self.__sas_if: sasIF = None self.__memo_editor = None self.__memo_extras = [] self.__list_securities = [] self.__show_securities = [] # ---------------- Page ----------------- self.__page = 1 self.__item_per_page = 20 self.__button_first = QPushButton('|<') self.__button_prev = QPushButton('<') self.__spin_page = QSpinBox() self.__label_total_page = QLabel('/ 1') self.__button_jump = QPushButton('GO') self.__button_next = QPushButton('>') self.__button_last = QPushButton('>|') self.__button_reload = QPushButton('Reload') self.__check_show_black_list = QCheckBox('Black List') # --------------- Widgets --------------- self.__memo_table = TableViewEx() self.__stock_selector = \ SecuritiesSelector(self.__sas_if) if self.__sas_if is not None else QComboBox() # TODO: Path from server self.__line_path = QLineEdit('') self.__info_panel = QLabel(NOTE) self.__button_new = QPushButton('New') self.__button_filter = QPushButton('Filter') self.__button_browse = QPushButton('Browse') # self.__button_black_list = QPushButton('Black List') self.__layout_extra = QHBoxLayout() self.init_ui() self.config_ui() self.show_securities(self.__sas_if.stock_memo_get_all_security( ) if self.__sas_if is not None else [])
def __init__(self, sas_if: sasIF): self.__sas_if = sas_if super(BlackListUi.BlackListEditor, self).__init__() self.__stock_selector = SecuritiesSelector( sas_if) if sas_if is not None else QComboBox() self.__editor_reason = QTextEdit() self.init_ui() self.config_ui()
class BlackListEditor(QWidget): def __init__(self, sas_if: sasIF): self.__sas_if = sas_if super(BlackListUi.BlackListEditor, self).__init__() self.__stock_selector = SecuritiesSelector( sas_if) if sas_if is not None else QComboBox() self.__editor_reason = QTextEdit() self.init_ui() self.config_ui() def init_ui(self): main_layout = QVBoxLayout() self.setLayout(main_layout) main_layout.addWidget(QLabel('Security: ')) main_layout.addWidget(self.__stock_selector) main_layout.addWidget(QLabel('Reason: ')) main_layout.addWidget(self.__editor_reason) def config_ui(self): self.setMinimumSize(QSize(600, 400)) def get_input(self) -> (str, str): return self.__stock_selector.get_select_securities(), \ self.__editor_reason.toPlainText()
def __init__(self, sas_if: sasIF): super(ChartLab, self).__init__() # ---------------- ext var ---------------- self.__sas_if = sas_if self.__inited = False self.__plot_table = {} self.__paint_data = None # ------------- plot resource ------------- self.__figure = plt.figure() self.__canvas = FigureCanvas(self.__figure) # -------------- ui resource -------------- self.__data_frame_widget = None self.__combo_factor = QComboBox() self.__label_comments = QLabel('') # Parallel comparison self.__radio_parallel_comparison = QRadioButton('横向比较') self.__combo_year = QComboBox() self.__combo_quarter = QComboBox() self.__combo_industry = QComboBox() # Longitudinal comparison self.__radio_longitudinal_comparison = QRadioButton('纵向比较') self.__combo_stock = SecuritiesSelector(self.__sas_if) # Limitation self.__line_lower = QLineEdit('') self.__line_upper = QLineEdit('') self.__button_draw = QPushButton('绘图') self.__button_show = QPushButton('查看绘图数据') self.init_ui()
def __init__(self, memo_context: dict, parent: QWidget = None): self.__memo_context = memo_context self.__sas_if: sasIF = self.__memo_context.get('sas_if') super(StockMemoEditor, self).__init__(parent) # The filter of left list # self.__filter_identity: str = '' # self.__filter_datetime: datetime.datetime = None # The stock that selected by combobox or outer setting self.__current_stock = None # The current index of editing memo # Not None: Update exists # None: Create new self.__current_index = None # The memo list that displaying in the left list self.__current_memos: pd.DataFrame = None self.__observers = [] # self.__sas = self.__memo_context.get_sas() if self.__memo_context is not None else None # self.__memo_record = self.__memo_context.get_memo_record() if self.__memo_context is not None else None # data_utility = self.__sas.get_data_hub_entry().get_data_utility() if self.__sas is not None else None self.__combo_stock = SecuritiesSelector(self.__sas_if) if self.__sas_if is not None else QComboBox() self.__table_memo_index = EasyQTableWidget() self.__datetime_time = QDateTimeEdit(QDateTime().currentDateTime()) self.__line_brief = QLineEdit() self.__text_record = QTextEdit() self.__button_new = QPushButton('New') self.__button_apply = QPushButton('Save') self.__button_delete = QPushButton('Delete') self.init_ui() self.config_ui()
class AnnouncementDownloaderUi(QWidget): def __init__(self, sas_if: sasIF, task_manager): super(AnnouncementDownloaderUi, self).__init__() # ---------------- ext var ---------------- self.__sas_if = sas_if # self.__data_center = self.__data_hub.get_data_center() if self.__data_hub is not None else None # self.__data_utility = self.__data_hub.get_data_utility() if self.__data_hub is not None else None self.__task_manager = task_manager self.__translate = QtCore.QCoreApplication.translate # Timer for update stock list self.__timer = QTimer() self.__timer.setInterval(1000) self.__timer.timeout.connect(self.on_timer) self.__timer.start() # Ui component self.__combo_name = SecuritiesSelector(self.__sas_if, self) self.__radio_annual_report = QRadioButton('年报') self.__radio_customize_filter = QRadioButton('自定义') self.__line_filter_include = QLineEdit() self.__line_filter_exclude = QLineEdit() self.__button_download = QPushButton('确定') self.__datetime_since = QDateTimeEdit( QDateTime.currentDateTime().addYears(-3)) self.__datetime_until = QDateTimeEdit(QDateTime.currentDateTime()) self.init_ui() # ---------------------------------------------------- UI Init ----------------------------------------------------- def init_ui(self): self.__layout_control() self.__config_control() def __layout_control(self): main_layout = QVBoxLayout() self.setLayout(main_layout) main_layout.addLayout( horizon_layout([QLabel('股票代码'), self.__combo_name], [1, 10])) main_layout.addLayout( horizon_layout([QLabel('报告起始'), self.__datetime_since], [1, 10])) main_layout.addLayout( horizon_layout([QLabel('报告截止'), self.__datetime_until], [1, 10])) main_layout.addLayout( horizon_layout([ QLabel('报告类型'), self.__radio_annual_report, self.__radio_customize_filter ], [1, 5, 5])) main_layout.addLayout( horizon_layout([QLabel('包含词条(以,分隔)'), self.__line_filter_include], [1, 10])) main_layout.addLayout( horizon_layout([QLabel('排除词条(以,分隔)'), self.__line_filter_exclude], [1, 10])) main_layout.addWidget(QLabel(DEFAULT_INFO)) main_layout.addWidget(self.__button_download) def __config_control(self): # self.__combo_name.setEditable(True) # self.__combo_name.addItem('所有') # self.__combo_name.addItem('股票列表载入中') self.__radio_annual_report.setChecked(True) self.__line_filter_include.setEnabled(False) self.__line_filter_exclude.setEnabled(False) self.__radio_customize_filter.setEnabled(False) self.__radio_annual_report.clicked.connect(self.on_radio_report_type) self.__radio_customize_filter.clicked.connect( self.on_radio_report_type) self.__button_download.clicked.connect(self.on_button_download) def on_timer(self): if self.__combo_name.count() > 1: self.__combo_name.insertItem(0, ALL_STOCK_TEXT) self.__combo_name.setCurrentIndex(0) self.__timer.stop() # # Check stock list ready and update combobox # if self.__data_utility is not None: # if self.__data_utility.stock_cache_ready(): # self.__combo_name.clear() # self.__combo_name.addItem(ALL_STOCK_TEXT) # stock_list = self.__data_utility.get_stock_list() # for stock_identity, stock_name in stock_list: # self.__combo_name.addItem(stock_identity + ' | ' + stock_name, stock_identity) def on_radio_report_type(self): if self.__radio_annual_report.isChecked(): self.__line_filter_include.setEnabled(False) self.__line_filter_exclude.setEnabled(False) else: self.__line_filter_include.setEnabled(True) self.__line_filter_exclude.setEnabled(True) def on_button_download(self): # input_securities = self.__combo_name.currentText() # if '|' in input_securities: # input_securities = input_securities.split('|')[0].strip() input_securities = self.__combo_name.get_input_securities() if input_securities == ALL_STOCK_TEXT: if self.__sas_if is None: QMessageBox.information( self, QtCore.QCoreApplication.translate('main', '提示'), QtCore.QCoreApplication.translate('main', '无法获取股票列表'), QMessageBox.Yes, QMessageBox.No) return reply = QMessageBox.question( self, QtCore.QCoreApplication.translate('main', '操作确认'), QtCore.QCoreApplication.translate('main', DOWNLOAD_ALL_TIPS), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply != QMessageBox.Yes: return self.__build_download_task(input_securities) def __build_download_task(self, securities: str): task = AnnouncementDownloadTask() task.securities = securities task.period_since = self.__datetime_since.dateTime().toPyDateTime() task.period_until = self.__datetime_until.dateTime().toPyDateTime() task.filter_include = self.__line_filter_include.text().split(',') task.filter_exclude = self.__line_filter_exclude.text().split(',') task.report_type = \ AnnouncementDownloadTask.REPORT_TYPE_ANNUAL \ if self.__radio_annual_report.isChecked() else \ AnnouncementDownloadTask.REPORT_TYPE_NONE task.task_manager = self.__task_manager task.sas_if = self.__sas_if # task.data_utility = self.__data_utility if self.__task_manager is not None: self.__task_manager.append_task(task) else: task.run()
class StockChartUi(QWidget): ADJUST_TAIL = 0 ADJUST_HEAD = 1 ADJUST_NONE = 2 RETURN_LOG = 3 RETURN_SIMPLE = 4 def __init__(self, memo_context: dict): self.__memo_context = memo_context # self.__memo_context.add_observer(self) self.__sas_if: sasIF = self.__memo_context.get('sas_if') super(StockChartUi, self).__init__() # self.__sas = memo_context.get_sas() # self.__memo_record: StockMemoRecord = memo_context.get_memo_record() self.__in_edit_mode = True self.__paint_securities = '' self.__paint_trade_data = None # # Record # user_path = os.path.expanduser('~') # project_path = sas.get_project_path() if sas is not None else os.getcwd() # # self.__root_path = \ # memo_path_from_project_path(project_path) if user_path == '' else \ # memo_path_from_user_path(user_path) # self.__memo_record = Record(os.path.join(self.__root_path, 'stock_memo.csv'), STOCK_MEMO_COLUMNS) # vnpy chart self.__vnpy_chart = ChartWidget() # Memo editor self.__memo_editor: StockMemoEditor = self.__memo_context.get('editor') # Timer for workaround signal fired twice self.__accepted = False self.__timer = QTimer() self.__timer.setInterval(1000) self.__timer.timeout.connect(self.on_timer) self.__timer.start() # Ui component # data_utility = self.__sas.get_data_hub_entry().get_data_utility() if self.__sas is not None else None self.__combo_name = SecuritiesSelector( self.__sas_if) if self.__sas_if is not None else QComboBox() self.__button_ensure = QPushButton('确定') self.__check_abs = QCheckBox('固定价格范围') self.__check_memo = QCheckBox('笔记') self.__check_volume = QCheckBox('成交量') self.__radio_adj_tail = QRadioButton('后复权') self.__radio_adj_head = QRadioButton('前复权') self.__radio_adj_none = QRadioButton('不复权') self.__group_adj = QButtonGroup(self) self.__radio_log_return = QRadioButton('对数收益') self.__radio_simple_return = QRadioButton('算术收益') self.__group_return = QButtonGroup(self) self.__init_ui() self.__config_ui() def __init_ui(self): main_layout = QVBoxLayout() self.setLayout(main_layout) self.__group_adj.addButton(self.__radio_adj_tail) self.__group_adj.addButton(self.__radio_adj_head) self.__group_adj.addButton(self.__radio_adj_none) group_box_adj, group_layout = create_h_group_box('') group_layout.addWidget(self.__radio_adj_tail) group_layout.addWidget(self.__radio_adj_head) group_layout.addWidget(self.__radio_adj_none) self.__group_return.addButton(self.__radio_log_return) self.__group_return.addButton(self.__radio_simple_return) group_box_return, group_layout = create_h_group_box('') group_layout.addWidget(self.__radio_log_return) group_layout.addWidget(self.__radio_simple_return) group_box, group_layout = create_v_group_box('Securities') main_layout.addWidget(self.__vnpy_chart, 99) main_layout.addWidget(group_box) group_layout.addLayout( horizon_layout([ self.__combo_name, group_box_adj, group_box_return, self.__button_ensure, self.__check_abs, self.__check_volume, self.__check_memo ])) def __config_ui(self): # data_utility = self.__sas.get_data_hub_entry().get_data_utility() index_dict = self.__sas_if.sas_get_support_index() self.__combo_name.setEditable(True) for key in index_dict: self.__combo_name.addItem(key + ' | ' + index_dict.get(key), key) self.__radio_adj_none.setChecked(True) self.__radio_simple_return.setChecked(True) self.__check_abs.clicked.connect(self.on_button_abs) self.__check_memo.clicked.connect(self.on_button_memo) self.__check_volume.clicked.connect(self.on_button_volume) self.__button_ensure.clicked.connect(self.on_button_ensure) self.setMinimumWidth(1280) self.setMinimumHeight(800) # --------------------- Editor ---------------------- # self.__memo_editor.closeEvent = self.on_editor_closed # ---------------------- Chart ---------------------- self.__vnpy_chart.add_plot("candle", hide_x_axis=True) self.__vnpy_chart.add_plot("volume", maximum_height=200) self.__vnpy_chart.add_plot("memo", maximum_height=50) self.__vnpy_chart.add_item(CandleItem, "candle", "candle") self.__vnpy_chart.add_item(VolumeItem, "volume", "volume") self.__vnpy_chart.add_item(MemoItem, "memo", "memo") self.__vnpy_chart.add_cursor() self.__vnpy_chart.scene().sigMouseClicked.connect( self.on_chart_clicked) @pyqtSlot(MouseClickEvent) def on_chart_clicked(self, event: MouseClickEvent): if not event.double or self.__accepted: return self.__accepted = True scene_pt = event.scenePos() items = self.__vnpy_chart.scene().items(scene_pt) for i in items: if isinstance(i, pg.PlotItem): view = i.getViewBox() view_pt = view.mapSceneToView(scene_pt) self.popup_memo_editor(view_pt.x()) break # print("Plots:" + str([x for x in items if isinstance(x, pg.PlotItem)])) def on_timer(self): # Workaround for click event double fire self.__accepted = False def on_button_abs(self): enable = self.__check_abs.isChecked() self.__vnpy_chart.get_item('candle').set_y_range_dynamic(not enable) self.__vnpy_chart.refresh_history() self.__vnpy_chart.update() def on_button_memo(self): # enable = self.__check_memo.isChecked() # self.__vnpy_chart.enable_item('memo', enable) pass def on_button_volume(self): # enable = self.__check_volume.isChecked() # self.__vnpy_chart.enable_item('volume', enable) pass def on_button_ensure(self): input_securities = self.__combo_name.currentText() if '|' in input_securities: input_securities = input_securities.split('|')[0].strip() self.show_security(input_securities) # ------------------- Interface of StockMemoData.Observer -------------------- # def on_memo_updated(self): # self.load_security_memo() # self.__vnpy_chart.refresh_history() def on_data_updated(self, name: str, data: any): nop(data) if self.__in_edit_mode and name == 'memo_record': self.load_security_memo() self.__vnpy_chart.refresh_history() self.__in_edit_mode = False # ---------------------------------------------------------------------------- def show_security(self, security: str, check_update: bool = False): if self.__radio_adj_tail.isChecked(): adjust_method = StockChartUi.ADJUST_TAIL elif self.__radio_adj_head.isChecked(): adjust_method = StockChartUi.ADJUST_HEAD elif self.__radio_adj_none.isChecked(): adjust_method = StockChartUi.ADJUST_NONE else: adjust_method = StockChartUi.ADJUST_NONE if self.__radio_log_return.isChecked(): return_style = StockChartUi.RETURN_LOG elif self.__radio_simple_return.isChecked(): return_style = StockChartUi.RETURN_SIMPLE else: return_style = StockChartUi.RETURN_SIMPLE if not self.load_security_data(security, adjust_method, return_style, check_update): QMessageBox.information( self, QtCore.QCoreApplication.translate('History', '没有数据'), QtCore.QCoreApplication.translate('History', '没有交易数据,请检查证券编码或更新本地数据'), QMessageBox.Ok, QMessageBox.Ok) else: self.load_security_memo() self.__vnpy_chart.refresh_history() def popup_memo_editor(self, ix: float): bar_data = self.__vnpy_chart.get_bar_manager().get_bar(ix) if bar_data is not None: _time = bar_data.datetime.to_pydatetime() self.popup_memo_editor_by_time(_time) else: self.popup_memo_editor_by_time(now()) self.__in_edit_mode = True def popup_memo_editor_by_time(self, _time: datetime): self.__memo_editor.select_security(self.__paint_securities) self.__memo_editor.select_memo_by_day(_time) # self.__memo_editor.show() self.__memo_editor.exec() def load_security_data(self, securities: str, adjust_method: int, return_style: int, check_update: bool = False) -> bool: # data_utility = self.__sas.get_data_hub_entry().get_data_utility() index_dict = self.__sas_if.sas_get_support_index() # if securities != self.__paint_securities or self.__paint_trade_data is None: if securities in index_dict.keys(): uri = 'TradeData.Index.Daily' else: uri = 'TradeData.Stock.Daily' with futures.ThreadPoolExecutor(max_workers=2) as executor: # Code is beautiful. # But the un-canceled network (update task) will still block the main thread. # What the hell... # if check_update: # future_update: futures.Future = executor.submit(self.__update_security_data, uri, securities) # WaitingWindow.wait_future('检查更新数据中...\n' # '取消则使用离线数据\n' # '注意:取消更新后在网络连接超时前界面仍可能卡住,这或许是Python的机制导致\n' # '如果想禁用自动更新功能请删除StockChartUi.py中check_update相关的代码', # future_update, None) future_load_calc: futures.Future = executor.submit( self.__load_calc_security_data, uri, securities, adjust_method, return_style) if not WaitingWindow.wait_future('载入数据中...', future_load_calc, None): return False trade_data = future_load_calc.result(timeout=0) if trade_data is None: return False bars = self.df_to_bar_data(trade_data, securities) self.__vnpy_chart.get_bar_manager().clear_all() self.__vnpy_chart.get_bar_manager().update_history(bars) return True def load_security_memo(self) -> bool: self.__combo_name.select_security(self.__paint_securities, True) # memo_record = self.__memo_record.get_stock_memos(self.__paint_securities) memo_record = self.__sas_if.stock_memo_get_record( self.__paint_securities) bar_manager = self.__vnpy_chart.get_bar_manager() if memo_record is None or bar_manager is None: return False try: memo_df = memo_record.copy() memo_df['normalised_time'] = memo_df['time'].dt.normalize() memo_df_grouped = memo_df.groupby('normalised_time') except Exception as e: return False finally: pass append_items = [] max_memo_count = 0 for group_time, group_df in memo_df_grouped: memo_count = len(group_df) max_memo_count = max(memo_count, max_memo_count) if not bar_manager.set_item_data(group_time, 'memo', group_df): bar = BarData(datetime=group_time, exchange=Exchange.SSE, symbol=self.__paint_securities) bar.extra = {'memo': group_df} append_items.append(bar) bar_manager.set_item_data_range('memo', 0.0, max_memo_count) bar_manager.update_history(append_items) # memo_item = self.__vnpy_chart.get_item('memo') # if memo_item is not None: # memo_item.refresh_history() # self.__vnpy_chart.update_history() return True def __update_security_data(self, uri: str, securities: str): pass # self.__sas.get_data_hub_entry().get_data_utility().check_update(uri, securities) def __load_calc_security_data(self, uri: str, securities: str, adjust_method: int, return_style: int): # trade_data = self.__sas.get_data_hub_entry().get_data_center().query(uri, securities) trade_data = self.__sas_if.sas_query(uri, securities) self.__paint_trade_data = trade_data self.__paint_securities = securities if self.__paint_trade_data is None or len( self.__paint_trade_data) == 0: return None trade_data = pd.DataFrame() if adjust_method == StockChartUi.ADJUST_TAIL and 'adj_factor' in self.__paint_trade_data.columns: trade_data['open'] = self.__paint_trade_data[ 'open'] * self.__paint_trade_data['adj_factor'] trade_data['close'] = self.__paint_trade_data[ 'close'] * self.__paint_trade_data['adj_factor'] trade_data['high'] = self.__paint_trade_data[ 'high'] * self.__paint_trade_data['adj_factor'] trade_data['low'] = self.__paint_trade_data[ 'low'] * self.__paint_trade_data['adj_factor'] elif adjust_method == StockChartUi.ADJUST_HEAD and 'adj_factor' in self.__paint_trade_data.columns: trade_data['open'] = self.__paint_trade_data[ 'open'] / self.__paint_trade_data['adj_factor'] trade_data['close'] = self.__paint_trade_data[ 'close'] / self.__paint_trade_data['adj_factor'] trade_data['high'] = self.__paint_trade_data[ 'high'] / self.__paint_trade_data['adj_factor'] trade_data['low'] = self.__paint_trade_data[ 'low'] / self.__paint_trade_data['adj_factor'] else: trade_data['open'] = self.__paint_trade_data['open'] trade_data['close'] = self.__paint_trade_data['close'] trade_data['high'] = self.__paint_trade_data['high'] trade_data['low'] = self.__paint_trade_data['low'] trade_data['amount'] = self.__paint_trade_data['amount'] trade_data['trade_date'] = pd.to_datetime( self.__paint_trade_data['trade_date']) if return_style == StockChartUi.RETURN_LOG: trade_data['open'] = np.log(trade_data['open']) trade_data['close'] = np.log(trade_data['close']) trade_data['high'] = np.log(trade_data['high']) trade_data['low'] = np.log(trade_data['low']) return trade_data # TODO: Move it to common place @staticmethod def df_to_bar_data(df: pd.DataFrame, securities: str, exchange: Exchange = Exchange.SSE) -> [BarData]: # 98 ms bars = [] for trade_date, amount, open, close, high, low in \ zip(df['trade_date'], df['amount'], df['open'], df['close'], df['high'], df['low']): bar = BarData(datetime=trade_date, exchange=exchange, symbol=securities) bar.interval = Interval.DAILY bar.volume = amount * 10000 bar.open_interest = 0 bar.open_price = open bar.high_price = high bar.low_price = low bar.close_price = close bars.append(bar) return bars
class StockMemoEditor(QDialog): LIST_HEADER = ['Time', 'Preview'] # class Observer: # def __init__(self): # pass # # def on_memo_updated(self): # pass def __init__(self, memo_context: dict, parent: QWidget = None): self.__memo_context = memo_context self.__sas_if: sasIF = self.__memo_context.get('sas_if') super(StockMemoEditor, self).__init__(parent) # The filter of left list # self.__filter_identity: str = '' # self.__filter_datetime: datetime.datetime = None # The stock that selected by combobox or outer setting self.__current_stock = None # The current index of editing memo # Not None: Update exists # None: Create new self.__current_index = None # The memo list that displaying in the left list self.__current_memos: pd.DataFrame = None self.__observers = [] # self.__sas = self.__memo_context.get_sas() if self.__memo_context is not None else None # self.__memo_record = self.__memo_context.get_memo_record() if self.__memo_context is not None else None # data_utility = self.__sas.get_data_hub_entry().get_data_utility() if self.__sas is not None else None self.__combo_stock = SecuritiesSelector(self.__sas_if) if self.__sas_if is not None else QComboBox() self.__table_memo_index = EasyQTableWidget() self.__datetime_time = QDateTimeEdit(QDateTime().currentDateTime()) self.__line_brief = QLineEdit() self.__text_record = QTextEdit() self.__button_new = QPushButton('New') self.__button_apply = QPushButton('Save') self.__button_delete = QPushButton('Delete') self.init_ui() self.config_ui() def init_ui(self): root_layout = QHBoxLayout() self.setLayout(root_layout) group_box, group_layout = create_v_group_box('') group_layout.addWidget(self.__combo_stock, 1) group_layout.addWidget(self.__table_memo_index, 99) group_layout.addWidget(self.__button_delete, 1) root_layout.addWidget(group_box, 4) group_box, group_layout = create_v_group_box('') group_layout.addLayout(horizon_layout([QLabel('Time:'), self.__datetime_time, self.__button_new], [1, 99, 1])) group_layout.addLayout(horizon_layout([QLabel('Brief:'), self.__line_brief], [1, 99])) group_layout.addLayout(horizon_layout([QLabel('Content:'), QLabel('')], [1, 99])) group_layout.addWidget(self.__text_record) group_layout.addLayout(horizon_layout([QLabel(''), self.__button_apply], [99, 1])) root_layout.addWidget(group_box, 6) self.setMinimumSize(500, 600) def config_ui(self): self.setWindowTitle('Memo Editor') self.__datetime_time.setCalendarPopup(True) self.__combo_stock.setEditable(False) self.__combo_stock.currentIndexChanged.connect(self.on_combo_select_changed) self.__table_memo_index.insertColumn(0) self.__table_memo_index.insertColumn(0) self.__table_memo_index.setHorizontalHeaderLabels(StockMemoEditor.LIST_HEADER) self.__table_memo_index.setSelectionMode(QAbstractItemView.SingleSelection) self.__table_memo_index.setSelectionBehavior(QAbstractItemView.SelectRows) self.__table_memo_index.itemSelectionChanged.connect(self.on_table_selection_changed) self.__table_memo_index.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.__button_new.clicked.connect(self.on_button_new) self.__button_apply.clicked.connect(self.on_button_apply) self.__button_delete.clicked.connect(self.on_button_delete) # self.setWindowFlags( # QtCore.Qt.Window | # QtCore.Qt.CustomizeWindowHint | # QtCore.Qt.WindowTitleHint | # QtCore.Qt.WindowCloseButtonHint | # QtCore.Qt.WindowStaysOnTopHint # ) def on_button_new(self): if not str_available(self.__current_stock): QMessageBox.information(self, '错误', '请选择需要做笔记的股票', QMessageBox.Ok, QMessageBox.Ok) self.create_new_memo(None) # self.__trigger_memo_updated() # ['time', 'security', 'brief', 'content', 'classify'] def on_button_apply(self): _time = self.__datetime_time.dateTime().toPyDateTime() brief = self.__line_brief.text() content = self.__text_record.toPlainText() if not str_available(brief): QMessageBox.information(self, '错误', '请至少填写笔记摘要', QMessageBox.Ok, QMessageBox.Ok) return if self.__current_index is not None: ret = self.__sas_if.stock_memo_update_record( self.__current_index, self.__current_stock, _time, brief, content, True) else: if str_available(self.__current_stock): ret, index = self.__sas_if.stock_memo_add_record( self.__current_stock, _time, brief, content, True) self.__current_index = index else: ret = False QMessageBox.information(self, '错误', '没有选择做笔记的股票', QMessageBox.Ok, QMessageBox.Ok) if ret: self.load_security_memo(self.__current_stock) if self.__current_index is not None: self.select_memo_by_memo_index(self.__current_index) else: self.select_memo_by_list_index(0) # TODO: Broadcast update # self.__trigger_memo_updated() # self.__memo_context.broadcast_data_updated('memo_record') def on_button_delete(self): if self.__current_index is not None: # self.__memo_record.del_records(self.__current_index) # self.__memo_record.save() self.__sas_if.stock_memo_delete_record(self.__current_index, None) self.load_security_memo(self.__current_stock) self.update_memo_list() self.__memo_context.broadcast_data_updated('memo_record') def on_combo_select_changed(self): input_securities = self.__combo_stock.get_input_securities() if input_securities != self.__current_stock: self.load_security_memo(input_securities) def on_table_selection_changed(self): sel_index = self.__table_memo_index.GetCurrentIndex() if sel_index < 0: return sel_item = self.__table_memo_index.item(sel_index, 0) if sel_item is None: return df_index = sel_item.data(Qt.UserRole) self.load_edit_memo(df_index) # -------------------------------------------------------------------------------------------- # def add_observer(self, ob: Observer): # self.__observers.append(ob) # # def __trigger_memo_updated(self): # for ob in self.__observers: # ob.on_memo_updated() # ------------------------------------- Select Functions ------------------------------------- # Will update UI control, which will trigger the linkage def select_security(self, security: str): self.__combo_stock.select_security(security, True) def select_memo_by_day(self, _time: datetime.datetime): if self.__current_memos is None or self.__current_memos.empty: self.create_new_memo(_time) return df = self.__current_memos time_serial = df['time'].dt.normalize() select_df = df[time_serial == _time.replace(hour=0, minute=0, second=0, microsecond=0)] select_index = None for index, row in select_df.iterrows(): select_index = index break if select_index is not None: self.select_memo_by_memo_index(select_index) else: self.create_new_memo(_time) def select_memo_by_memo_index(self, memo_index: int): for row in range(0, self.__table_memo_index.rowCount()): row_memo_index = self.__table_memo_index.item(row, 0).data(Qt.UserRole) if row_memo_index == memo_index: self.__table_memo_index.selectRow(row) break def select_memo_by_list_index(self, list_index: int): if list_index >= 0: self.__table_memo_index.selectRow(list_index) self.__table_memo_index.scrollToItem(self.__table_memo_index.item(list_index, 0)) else: self.__table_memo_index.clearSelection() self.create_new_memo(now()) # ----------------------------------- Select and Load Logic ----------------------------------- def load_security_memo(self, security: str): condition = {'classify': 'memo'} if str_available(security): condition['security'] = security # df = self.__memo_record.get_records(condition) df = self.__sas_if.stock_memo_filter_record(condition) self.__current_memos = df self.__current_stock = security self.update_memo_list() def update_memo_list(self): if self.__current_memos is None: return self.__table_memo_index.clear() self.__table_memo_index.setRowCount(0) self.__table_memo_index.setHorizontalHeaderLabels(StockMemoEditor.LIST_HEADER) for index, row in self.__current_memos.iterrows(): row_index = self.__table_memo_index.rowCount() brief = row['brief'] content = row['content'] text = brief if str_available(brief) else content # https://stackoverflow.com/a/2873416/12929244 self.__table_memo_index.AppendRow([datetime2text(row['time']), text[:30] + (text[30:] and '...')], index) self.__table_memo_index.item(row_index, 0).setData(Qt.UserRole, index) self.__current_index = None self.__enter_new_mode() def create_new_memo(self, _time: datetime.datetime or None): self.__table_memo_index.clearSelection() if _time is not None: self.__datetime_time.setDateTime(_time) self.__line_brief.setText('') self.__text_record.setText('') self.__current_index = None self.__enter_new_mode() def load_edit_memo(self, index: int): """ The index should exist in left memo list :param index: :return: """ df = self.__current_memos if df is None or df.empty: self.create_new_memo() return s = df.loc[index] if len(s) == 0: self.create_new_memo() return self.__current_index = index _time = s['time'] brief = s['brief'] content = s['content'] self.__datetime_time.setDateTime(to_py_datetime(_time)) self.__line_brief.setText(str(brief)) self.__text_record.setText(str(content)) self.__enter_edit_mode() def __enter_new_mode(self): self.__button_new.setText('New *') def __enter_edit_mode(self): self.__button_new.setText('New')
class StockMemoDeck(QWidget): STATIC_HEADER = ['Code', 'Name'] def __init__(self, memo_context: dict): self.__memo_context = memo_context self.__sas_if: sasIF = self.__memo_context.get( 'sas_if') if self.__memo_context is not None else None super(StockMemoDeck, self).__init__() if self.__memo_context is not None: # self.__memo_context.add_observer(self) self.__sas_if: sasIF = self.__memo_context.get('sas_if') self.__memo_editor: StockMemoEditor = self.__memo_context.get( 'editor') else: # For layout debug self.__sas_if: sasIF = None self.__memo_editor = None self.__memo_extras = [] self.__list_securities = [] self.__show_securities = [] # ---------------- Page ----------------- self.__page = 1 self.__item_per_page = 20 self.__button_first = QPushButton('|<') self.__button_prev = QPushButton('<') self.__spin_page = QSpinBox() self.__label_total_page = QLabel('/ 1') self.__button_jump = QPushButton('GO') self.__button_next = QPushButton('>') self.__button_last = QPushButton('>|') self.__button_reload = QPushButton('Reload') self.__check_show_black_list = QCheckBox('Black List') # --------------- Widgets --------------- self.__memo_table = TableViewEx() self.__stock_selector = \ SecuritiesSelector(self.__sas_if) if self.__sas_if is not None else QComboBox() # TODO: Path from server self.__line_path = QLineEdit('') self.__info_panel = QLabel(NOTE) self.__button_new = QPushButton('New') self.__button_filter = QPushButton('Filter') self.__button_browse = QPushButton('Browse') # self.__button_black_list = QPushButton('Black List') self.__layout_extra = QHBoxLayout() self.init_ui() self.config_ui() self.show_securities(self.__sas_if.stock_memo_get_all_security( ) if self.__sas_if is not None else []) def init_ui(self): main_layout = QVBoxLayout() self.setLayout(main_layout) # Page control page_control_line = QHBoxLayout() page_control_line.addWidget(self.__button_reload, 1) page_control_line.addWidget(self.__check_show_black_list, 1) page_control_line.addWidget(QLabel(''), 99) page_control_line.addWidget(self.__button_first, 1) page_control_line.addWidget(self.__button_prev, 1) page_control_line.addWidget(self.__spin_page, 1) page_control_line.addWidget(self.__label_total_page, 1) page_control_line.addWidget(self.__button_jump, 1) page_control_line.addWidget(self.__button_next, 1) page_control_line.addWidget(self.__button_last, 1) group_box, group_layout = create_v_group_box('Stock Memo') group_layout.addWidget(self.__memo_table) group_layout.addLayout(page_control_line) main_layout.addWidget(group_box, 10) group_box, group_layout = create_h_group_box('Edit') right_area = QVBoxLayout() group_layout.addWidget(self.__info_panel, 4) group_layout.addLayout(right_area, 6) line = horizon_layout([ QLabel('股票选择:'), self.__stock_selector, self.__button_filter, self.__button_new ], [1, 10, 1, 1]) right_area.addLayout(line) line = horizon_layout( [QLabel('保存路径:'), self.__line_path, self.__button_browse], [1, 10, 1]) right_area.addLayout(line) # line = horizon_layout([QLabel('其它功能:'), self.__button_black_list, QLabel('')], # [1, 1, 10]) self.__layout_extra.addWidget(QLabel('其它功能:')) self.__layout_extra.addStretch() right_area.addLayout(self.__layout_extra) main_layout.addWidget(group_box, 1) def config_ui(self): self.setMinimumSize(800, 600) self.__info_panel.setWordWrap(True) # -------------- Page Control -------------- self.__button_first.clicked.connect( partial(self.__on_page_control, '|<')) self.__button_prev.clicked.connect(partial(self.__on_page_control, '<')) self.__button_jump.clicked.connect(partial(self.__on_page_control, 'g')) self.__button_next.clicked.connect(partial(self.__on_page_control, '>')) self.__button_last.clicked.connect( partial(self.__on_page_control, '>|')) self.__button_reload.clicked.connect(self.__on_button_reload) self.__check_show_black_list.setChecked(False) self.__check_show_black_list.clicked.connect( self.__on_check_show_black_list) self.__memo_table.SetColumn(self.__memo_table_columns()) self.__memo_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.__memo_table.setSelectionMode(QAbstractItemView.SingleSelection) self.__memo_table.setEditTriggers(QAbstractItemView.NoEditTriggers) self.__memo_table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.__memo_table.doubleClicked.connect( self.__on_memo_item_double_clicked) self.__button_new.clicked.connect(self.__on_button_new_clicked) self.__button_browse.clicked.connect(self.__on_button_browse) self.__button_filter.clicked.connect(self.__on_button_filter_clicked) self.__button_filter.setDefault(True) line_editor = self.__stock_selector.lineEdit() if line_editor is not None: line_editor.returnPressed.connect(self.__on_button_filter_clicked) def add_memo_extra(self, extra: MemoExtra): extra.set_memo_ui(self) self.__memo_extras.append(extra) global_entry_text = extra.global_entry_text() if str_available(global_entry_text): button = QPushButton(global_entry_text) button.clicked.connect( partial(self.__on_button_global_entry, extra)) self.__layout_extra.insertWidget(1, button) def update_list(self): self.__update_memo_securities_list() def show_securities(self, securities: [str]): self.__update_securities(securities) self.__update_memo_securities_list() # # ------------------- Interface of StockMemoData.Observer -------------------- # # # def on_memo_updated(self): # # self.update_list() # # def on_data_updated(self, name: str, data: any): # nop(data) # if name in ['memo_record', 'tags']: # self.update_list() # ---------------------------------------------------------------------------- def __on_page_control(self, button_mark: str): new_page = self.__page if button_mark == '|<': new_page = 0 elif button_mark == '<': new_page -= 1 elif button_mark == 'g': new_page = self.__spin_page.value() elif button_mark == '>': new_page += 1 elif button_mark == '>|': new_page = self.__max_page() new_page = max(1, new_page) new_page = min(self.__max_page(), new_page) if self.__page != new_page: self.__page = new_page self.__spin_page.setValue(self.__page) self.update_list() def __on_button_browse(self): folder = str( QFileDialog.getExistingDirectory( self, "Select Private Folder", directory=self.__line_path.text())) if folder == '': return self.__line_path.setText(folder) # Save to system config config = self.__sas_if.sas_get_service_config() config['memo_path'] = folder self.__sas_if.sas_set_service_config(config) # Now those setting are on server side. # TODO: How to handle setting update on server side # Update new path to memo extras # self.__memo_context.set_root_path(folder) # # stock_tags: Tags = self.__memo_context.get('tags') # if stock_tags is not None: # stock_tags.load(os.path.join(folder, 'tags.json')) # self.__memo_context.get_memo_record().load(os.path.join(folder, 'stock_memo.csv')) def __on_button_new_clicked(self): security = self.__stock_selector.get_input_securities() self.__memo_editor.select_security(security) self.__memo_editor.create_new_memo(now()) self.__memo_editor.exec() def __on_button_filter_clicked(self): input_security = self.__stock_selector.get_input_securities() list_securities = self.__sas_if.sas_guess_stock_identities( input_security) self.show_securities(list_securities) def __on_button_reload(self): self.show_securities(self.__sas_if.stock_memo_get_all_security( ) if self.__sas_if is not None else []) def __on_check_show_black_list(self): self.__update_securities() self.__update_memo_securities_list() def __on_memo_item_double_clicked(self, index: QModelIndex): item_data = index.data(Qt.UserRole) if item_data is not None and isinstance(item_data, tuple): memo_extra, security = item_data memo_extra.security_entry(security) # print('Double Click on memo item: %s - %s' % (memo_extra.title_text(), security)) def __on_button_global_entry(self, extra: MemoExtra): extra.global_entry() def __update_memo_securities_list(self, securities: [str] or None = None): if securities is None: securities = self.__show_securities columns = self.__memo_table_columns() self.__memo_table.Clear() self.__memo_table.SetColumn(columns) index = [] offset = (self.__page - 1) * self.__item_per_page for i in range(offset, offset + self.__item_per_page): if i < 0 or i >= len(securities): continue security = securities[i] index.append(str(i + 1)) self.__memo_table.AppendRow([''] * len(columns)) row_count = self.__memo_table.RowCount() row = row_count - 1 col = 0 self.__memo_table.SetItemText(row, col, security) self.__memo_table.SetItemData(row, col, security) col = 1 self.__memo_table.SetItemText( row, col, self.__sas_if.sas_stock_identity_to_name(security)) for memo_extra in self.__memo_extras: if not str_available(memo_extra.title_text()): continue col += 1 text = memo_extra.security_entry_text(security) self.__memo_table.SetItemText(row, col, text) self.__memo_table.SetItemData(row, col, (memo_extra, security)) # _item = self.__memo_table.GetItem(row, col) # _item.clicked.connect(partial(self.__on_memo_item_clicked, _item, security, memo_extra)) model = self.__memo_table.model() model.setVerticalHeaderLabels(index) def __update_securities(self, list_securities: [str] or None = None): if list_securities is None: # Just refresh the show securities pass else: self.__list_securities = list_securities \ if isinstance(list_securities, (list, tuple)) else [list_securities] self.__update_show_securities() self.__update_page_control() def __update_show_securities(self): self.__show_securities = self.__list_securities show_black_list = self.__check_show_black_list.isChecked() if not show_black_list: # black_list: BlackList = self.__memo_context.get_data('black_list') # if black_list is not None: # black_list_securities = black_list.all_black_list() if self.__sas_if is not None: black_list_securities = self.__sas_if.all_black_list() if black_list_securities is not None: self.__show_securities = list( set(self.__show_securities).difference( set(black_list_securities))) else: pass def __memo_table_columns(self) -> [str]: return StockMemoDeck.STATIC_HEADER + [ memo_extra.title_text() for memo_extra in self.__memo_extras if str_available(memo_extra.title_text()) ] def __max_page(self) -> int: return (len(self.__show_securities) + self.__item_per_page - 1) // self.__item_per_page def __update_page_control(self): self.__page = 1 max_page = self.__max_page() self.__spin_page.setValue(1) self.__spin_page.setMinimum(1) self.__spin_page.setMaximum(max_page) self.__label_total_page.setText('/ %s' % max_page)
class ChartLab(QWidget): def __init__(self, sas_if: sasIF): super(ChartLab, self).__init__() # ---------------- ext var ---------------- self.__sas_if = sas_if self.__inited = False self.__plot_table = {} self.__paint_data = None # ------------- plot resource ------------- self.__figure = plt.figure() self.__canvas = FigureCanvas(self.__figure) # -------------- ui resource -------------- self.__data_frame_widget = None self.__combo_factor = QComboBox() self.__label_comments = QLabel('') # Parallel comparison self.__radio_parallel_comparison = QRadioButton('横向比较') self.__combo_year = QComboBox() self.__combo_quarter = QComboBox() self.__combo_industry = QComboBox() # Longitudinal comparison self.__radio_longitudinal_comparison = QRadioButton('纵向比较') self.__combo_stock = SecuritiesSelector(self.__sas_if) # Limitation self.__line_lower = QLineEdit('') self.__line_upper = QLineEdit('') self.__button_draw = QPushButton('绘图') self.__button_show = QPushButton('查看绘图数据') self.init_ui() # ---------------------------------------------------- UI Init ----------------------------------------------------- def init_ui(self): self.__layout_control() self.__config_control() def __layout_control(self): main_layout = QVBoxLayout() self.setLayout(main_layout) self.setMinimumSize(1280, 800) bottom_layout = QHBoxLayout() main_layout.addWidget(self.__canvas, 99) main_layout.addLayout(bottom_layout, 1) group_box, group_layout = create_v_group_box('因子') bottom_layout.addWidget(group_box, 2) group_layout.addWidget(self.__combo_factor) group_layout.addWidget(self.__label_comments) group_box, group_layout = create_v_group_box('比较方式') bottom_layout.addWidget(group_box, 2) line = QHBoxLayout() line.addWidget(self.__radio_parallel_comparison, 1) line.addWidget(self.__combo_industry, 5) line.addWidget(self.__combo_year, 5) line.addWidget(self.__combo_quarter, 5) group_layout.addLayout(line) line = QHBoxLayout() line.addWidget(self.__radio_longitudinal_comparison, 1) line.addWidget(self.__combo_stock, 10) group_layout.addLayout(line) group_box, group_layout = create_v_group_box('范围限制') bottom_layout.addWidget(group_box, 1) line = QHBoxLayout() line.addWidget(QLabel('下限')) line.addWidget(self.__line_lower) group_layout.addLayout(line) line = QHBoxLayout() line.addWidget(QLabel('上限')) line.addWidget(self.__line_upper) group_layout.addLayout(line) col = QVBoxLayout() col.addWidget(self.__button_draw) col.addWidget(self.__button_show) bottom_layout.addLayout(col, 1) def __config_control(self): for year in range(now().year, 1989, -1): self.__combo_year.addItem(str(year), str(year)) self.__combo_year.setCurrentIndex(1) self.__combo_quarter.addItem('一季报', '03-31') self.__combo_quarter.addItem('中报', '06-30') self.__combo_quarter.addItem('三季报', '09-30') self.__combo_quarter.addItem('年报', '12-31') self.__combo_quarter.setCurrentIndex(3) self.__combo_industry.addItem('全部', '全部') identities = self.__sas_if.sas_get_all_industries() for identity in identities: self.__combo_industry.addItem(identity, identity) if self.__sas_if is not None: factors = self.__sas_if.sas_get_all_factors() for fct in factors: self.__combo_factor.addItem(fct, fct) self.on_factor_updated(0) self.__combo_stock.setEnabled(False) self.__radio_parallel_comparison.setChecked(True) self.__radio_parallel_comparison.setToolTip(TIP_PARALLEL_COMPARISON) self.__radio_longitudinal_comparison.setToolTip( TIP_LONGITUDINAL_COMPARISON) self.__line_lower.setToolTip(TIP_LIMIT_UPPER_LOWER) self.__line_upper.setToolTip(TIP_LIMIT_UPPER_LOWER) self.__button_show.setToolTip(TIP_BUTTON_SHOW) self.__button_draw.clicked.connect(self.on_button_draw) self.__button_show.clicked.connect(self.on_button_show) self.__combo_factor.currentIndexChanged.connect(self.on_factor_updated) self.__radio_parallel_comparison.clicked.connect( self.on_radio_comparison) self.__radio_longitudinal_comparison.clicked.connect( self.on_radio_comparison) mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei'] mpl.rcParams['axes.unicode_minus'] = False def on_factor_updated(self, value): self.__line_lower.setText('') self.__line_upper.setText('') factor = self.__combo_factor.itemData(value) comments = self.__sas_if.sas_get_factor_comments(factor) self.__label_comments.setText(comments) def on_button_draw(self): factor = self.__combo_factor.currentData() lower = str2float_safe(self.__line_lower.text(), None) upper = str2float_safe(self.__line_upper.text(), None) if self.__radio_parallel_comparison.isChecked(): year = self.__combo_year.currentData() month_day = self.__combo_quarter.currentData() period = year + '-' + month_day industry = self.__combo_industry.currentData() self.plot_factor_parallel_comparison(factor, industry, text_auto_time(period), lower, upper) else: securities = self.__combo_stock.get_input_securities() self.plot_factor_longitudinal_comparison(factor, securities) def on_button_show(self): if self.__data_frame_widget is not None and \ self.__data_frame_widget.isVisible(): self.__data_frame_widget.close() if self.__paint_data is not None: self.__data_frame_widget = DataFrameWidget(self.__paint_data) self.__data_frame_widget.show() def on_radio_comparison(self): if self.__radio_parallel_comparison.isChecked(): self.__combo_year.setEnabled(True) self.__combo_quarter.setEnabled(True) self.__line_lower.setEnabled(True) self.__line_upper.setEnabled(True) self.__combo_stock.setEnabled(False) else: self.__combo_year.setEnabled(False) self.__combo_quarter.setEnabled(False) self.__line_lower.setEnabled(False) self.__line_upper.setEnabled(False) self.__combo_stock.setEnabled(True) # --------------------------------------------------------------------------------------- def plot_factor_parallel_comparison(self, factor: str, industry: str, period: datetime.datetime, lower: float, upper: float): identities = '' if industry != '全部': identities = self.__sas_if.sas_get_industry_stocks(industry) df = self.__sas_if.sas_factor_query(identities, [factor], (period, period), {}, readable=True) if df is None or df.empty: return s1 = df[factor] if lower is not None and upper is not None: s1 = s1.apply(lambda x: (x if x < upper else upper) if x > lower else lower) elif lower is not None: s1 = s1.apply(lambda x: x if x > lower else lower) elif upper is not None: s1 = s1.apply(lambda x: x if x < upper else upper) plt.clf() plt.subplot(1, 1, 1) s1.hist(bins=100) plt.title(factor) self.__canvas.draw() self.__canvas.flush_events() self.__paint_data = df self.__paint_data.sort_values(factor, inplace=True) def plot_factor_longitudinal_comparison(self, factor: str, securities: str): df = self.__sas_if.sas_factor_query(securities, [factor], None, {}, readable=True) # Only for annual report df = df[df['period'].dt.month == 12] df['报告期'] = df['period'] df.set_index('报告期', inplace=True) s1 = df[factor] plt.clf() plt.subplot(1, 1, 1) s1.plot.line() plt.title(factor) self.__canvas.draw() self.__canvas.flush_events() self.__paint_data = df self.__paint_data.sort_values('period', ascending=False, inplace=True) # --------------------------------------------------------------------------------------- def plot(self): self.plot_histogram_statistics() def plot_histogram_statistics(self): # --------------------------- The Data and Period We Want to Check --------------------------- stock = '' period = (text_auto_time('2018-12-01'), text_auto_time('2018-12-31')) # --------------------------------------- Query Pattern -------------------------------------- # fields_balance_sheet = ['货币资金', '资产总计', '负债合计', # '短期借款', '一年内到期的非流动负债', '其他流动负债', # '长期借款', '应付债券', '其他非流动负债', '流动负债合计', # '应收票据', '应收账款', '其他应收款', '预付款项', # '交易性金融资产', '可供出售金融资产', # '在建工程', '商誉', '固定资产'] # fields_income_statement = ['营业收入', '营业总收入', '减:营业成本', '息税前利润'] # # df, result = batch_query_readable_annual_report_pattern( # self.__data_hub, stock, period, fields_balance_sheet, fields_income_statement) # if result is not None: # return result # df_balance_sheet, result = query_readable_annual_report_pattern( # self.__data_hub, 'Finance.BalanceSheet', stock, period, fields_balance_sheet) # if result is not None: # print('Data Error') # # df_income_statement, result = query_readable_annual_report_pattern( # self.__data_hub, 'Finance.IncomeStatement', stock, period, fields_income_statement) # if result is not None: # print('Data Error') # -------------------------------- Merge and Pre-processing -------------------------------- # df = pd.merge(df_balance_sheet, # df_income_statement, # how='left', on=['stock_identity', 'period']) # df = df.sort_values('period') # df = df.reset_index() # df = df.fillna(0) # df = df.replace(0, 1) # ------------------------------------- Calc and Plot ------------------------------------- mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei'] mpl.rcParams['axes.unicode_minus'] = False # font = matplotlib.font_manager.FontProperties(fname='C:/Windows/Fonts/msyh.ttf') # mpl.rcParams['axes.unicode_minus'] = False # df['应收款'] = df['应收账款'] + df['应收票据'] # df['净资产'] = df['资产总计'] - df['负债合计'] # df['短期负债'] = df['短期借款'] + df['一年内到期的非流动负债'] + df['其他流动负债'] # df['有息负债'] = df['短期负债'] + df['长期借款'] + df['应付债券'] + df['其他非流动负债'] # df['金融资产'] = df['交易性金融资产'] + df['可供出售金融资产'] # # df['财务费用正'] = df['减:财务费用'].apply(lambda x: x if x > 0 else 0) # df['三费'] = df['减:销售费用'] + df['减:管理费用'] + df['财务费用正'] df = self.__sas_if.sas_auto_query( '', period, ['减:财务费用', '减:销售费用', '减:管理费用', '营业总收入', '营业收入', '减:营业成本'], ['stock_identity', 'period']) df['毛利润'] = df['营业收入'] - df['减:营业成本'] df['财务费用正'] = df['减:财务费用'].apply(lambda x: x if x > 0 else 0) df['三费'] = df['减:销售费用'] + df['减:管理费用'] + df['财务费用正'] s1 = df['三费'] / df['营业总收入'] s1 = s1.apply(lambda x: (x if x < 1 else 1) if x > -0.1 else -0.1) plt.subplot(2, 1, 1) s1.hist(bins=100) plt.title('三费/营业总收入') s2 = df['三费'] / df['毛利润'] s2 = s2.apply(lambda x: (x if x < 1 else 1) if x > -0.1 else -0.1) plt.subplot(2, 1, 2) s2.hist(bins=100) plt.title('三费/毛利润') # s1 = df['货币资金'] / df['有息负债'] # s1 = s1.apply(lambda x: x if x < 10 else 10) # plt.subplot(2, 1, 1) # s1.hist(bins=100) # plt.title('货币资金/有息负债') # # s2 = df['有息负债'] / df['资产总计'] # plt.subplot(2, 1, 2) # s2.hist(bins=100) # plt.title('有息负债/资产总计') # s1 = df['应收款'] / df['营业收入'] # s1 = s1.apply(lambda x: x if x < 2 else 2) # plt.subplot(4, 1, 1) # s1.hist(bins=100) # plt.title('应收款/营业收入') # # s2 = df['其他应收款'] / df['营业收入'] # s2 = s2.apply(lambda x: x if x < 1 else 1) # plt.subplot(4, 1, 2) # s2.hist(bins=100) # plt.title('其他应收款/营业收入') # # s3 = df['预付款项'] / df['营业收入'] # s3 = s3.apply(lambda x: x if x < 1 else 1) # plt.subplot(4, 1, 3) # s3.hist(bins=100) # plt.title('预付款项/营业收入') # # s4 = df['预付款项'] / df['减:营业成本'] # s4 = s4.apply(lambda x: x if x < 1 else 1) # plt.subplot(4, 1, 4) # s4.hist(bins=100) # plt.title('预付款项/营业成本') # s1 = df['商誉'] / df['净资产'] # s1 = s1.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(3, 1, 1) # s1.hist(bins=100) # plt.title('商誉/净资产') # # s2 = df['在建工程'] / df['净资产'] # s2 = s2.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(3, 1, 2) # s2.hist(bins=100) # plt.title('在建工程/净资产') # # s2 = df['在建工程'] / df['资产总计'] # s2 = s2.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(3, 1, 3) # s2.hist(bins=100) # plt.title('在建工程/资产总计') # s1 = df['固定资产'] / df['资产总计'] # s1 = s1.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(2, 1, 1) # s1.hist(bins=100) # plt.title('固定资产/资产总计') # # s2 = df['息税前利润'] / df['固定资产'] # s2 = s2.apply(lambda x: (x if x < 10 else 10) if x > -10 else -10) # plt.subplot(2, 1, 2) # s2.hist(bins=100) # plt.title('息税前利润/固定资产') # self.plot_proportion([ # ChartLab.PlotItem('固定资产', '资产总计', 0, 1), # ChartLab.PlotItem('息税前利润', '固定资产', -10, 10), # ], text_auto_time('2018-12-31')) self.repaint() class PlotItem: def __init__(self, num: str, den: str, lower: float or None = None, upper: float or None = None, bins: int = 100): self.numerator = num self.denominator = den self.limit_lower = lower self.limit_upper = upper self.plot_bins = bins def plot_proportion(self, plot_set: [PlotItem], period: datetime.datetime): df = self.prepare_plot_data(plot_set, period) plot_count = len(plot_set) for plot_index in range(plot_count): plot_item = plot_set[plot_index] s = df[plot_item.numerator] / df[plot_item.denominator] if plot_item.limit_lower is not None: s = s.apply(lambda x: max(x, plot_item.limit_lower)) if plot_item.limit_upper is not None: s = s.apply(lambda x: min(x, plot_item.limit_upper)) plt.subplot(plot_count, 1, plot_index + 1) s.hist(bins=plot_item.plot_bins) plt.title(plot_item.numerator + '/' + plot_item.denominator) def prepare_plot_data(self, plot_set: [PlotItem], period: datetime.datetime) -> pd.DataFrame: fields = [] for plot_item in plot_set: fields.append(plot_item.numerator) fields.append(plot_item.denominator) fields = list(set(fields)) return self.__sas_if.sas_auto_query( '', (period - datetime.timedelta(days=1), period), fields, ['stock_identity', 'period'])