Example #1
0
    def on_start_import_pushButton_clicked(self):
        config = self.getCurrentConfig()
        if config.getboolean('hdf5', 'enable') \
            and (not os.path.lexists(config['hdf5']['dir']) or not os.path.isdir(config['hdf5']['dir'])):
            QMessageBox.about(self, "错误", '指定的目标数据存放目录不存在!')
            return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.lexists(config['tdx']['dir'])
                 or not os.path.isdir(config['tdx']['dir'])):
            QMessageBox.about(self, "错误", "请确认通达信安装目录是否正确!")
            return

        self.import_running = True
        self.start_import_pushButton.setEnabled(False)
        self.reset_progress_bar()

        self.import_status_label.setText("正在启动任务....")
        QApplication.processEvents()

        if self.tdx_radioButton.isChecked():
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #2
0
    def start_import_data(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            print("错误:", '指定的目标数据存放目录不存在!')
            sys.exit(-1)
            #return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            print("错误:", "请确认通达信安装目录是否正确!")
            sys.exit(-1)
            #return

        self.import_running = True

        print("正在启动任务....")
        QCoreApplication.processEvents()

        if config.getboolean('tdx', 'enable'):
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #3
0
    def on_start_import_pushButton_clicked(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            QMessageBox.about(self, "错误", '指定的目标数据存放目录不存在!')
            return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            QMessageBox.about(self, "错误", "请确认通达信安装目录是否正确!")
            return

        self.import_running = True
        self.start_import_pushButton.setEnabled(False)
        self.reset_progress_bar()

        self.import_status_label.setText("正在启动任务....")
        QApplication.processEvents()

        if self.tdx_radioButton.isChecked():
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #4
0
    def start_import_data(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            print("错误:", '指定的目标数据存放目录不存在!')
            sys.exit(-1)
            #return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            print("错误:", "请确认通达信安装目录是否正确!")
            sys.exit(-1)
            #return

        self.import_running = True

        print("正在启动任务....")
        QCoreApplication.processEvents()

        if config.getboolean('tdx', 'enable'):
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #5
0
class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None, capture_output=False, use_dark_style=False):
        super(MyMainWindow, self).__init__(parent)
        self._capture_output = capture_output  # 捕获Python stdout 输出
        self._use_dark_style = use_dark_style  # 使用暗黑主题
        self._text_color = '#FFFFFF' if use_dark_style else '#000000'
        self.setupUi(self)
        self.initUI()
        self.initLogger()
        self.initThreads()

    def closeEvent(self, event):
        if self.import_running:
            QMessageBox.about(self, '提示', '正在执行导入任务,请耐心等候!')
            event.ignore()
            return

        if self.sched_import_thread is not None and self.sched_import_thread.isRunning():
            self.sched_import_thread.terminate()

        if self.collect_spot_thread is not None and self.collect_spot_thread.isRunning():
            self.collect_spot_thread.terminate()

        self.saveConfig()

        if self.hdf5_import_thread:
            self.hdf5_import_thread.stop()
        if self.escape_time_thread:
            self.escape_time_thread.stop()
        event.accept()

    def getUserConfigDir(self):
        return os.path.expanduser('~') + '/.hikyuu'

    def getHikyuuConfigFileName(self):
        return self.getUserConfigDir() + '/hikyuu.ini'

    def saveConfig(self):
        if not os.path.lexists(self.getUserConfigDir()):
            os.mkdir(self.getUserConfigDir())

        current_config = self.getCurrentConfig()
        filename = self.getUserConfigDir() + '/importdata-gui.ini'
        with open(filename, 'w', encoding='utf-8') as f:
            current_config.write(f)

        filename = self.getHikyuuConfigFileName()
        if current_config.getboolean('hdf5', 'enable', fallback=True):
            data_dir = current_config['hdf5']['dir']
            if not os.path.lexists(data_dir + '/tmp'):
                os.mkdir(data_dir + '/tmp')

            # 此处不能使用 utf-8 参数,否则导致Windows下getBlock无法找到板块分类
            # with open(filename, 'w', encoding='utf-8') as f:
            with open(filename, 'w') as f:
                f.write(
                    hku_config_template.hdf5_template.format(
                        dir=data_dir,
                        day=current_config.getboolean('preload', 'day', fallback=True),
                        week=current_config.getboolean('preload', 'week', fallback=False),
                        month=current_config.getboolean('preload', 'month', fallback=False),
                        quarter=current_config.getboolean('preload', 'quarter', fallback=False),
                        halfyear=current_config.getboolean('preload', 'halfyear', fallback=False),
                        year=current_config.getboolean('preload', 'year', fallback=False),
                        min1=current_config.getboolean('preload', 'min', fallback=False),
                        min5=current_config.getboolean('preload', 'min5', fallback=False),
                        min15=current_config.getboolean('preload', 'min15', fallback=False),
                        min30=current_config.getboolean('preload', 'min30', fallback=False),
                        min60=current_config.getboolean('preload', 'min60', fallback=False),
                        day_max=current_config.getint('preload', 'day_max', fallback=100000),
                        week_max=current_config.getint('preload', 'week_max', fallback=100000),
                        month_max=current_config.getint('preload', 'month_max', fallback=100000),
                        quarter_max=current_config.getint(
                            'preload', 'quarter_max', fallback=100000
                        ),
                        halfyear_max=current_config.getint(
                            'preload', 'halfyear_max', fallback=100000
                        ),
                        year_max=current_config.getint('preload', 'year_max', fallback=100000),
                        min1_max=current_config.getint('preload', 'min_max', fallback=4096),
                        min5_max=current_config.getint('preload', 'min5_max', fallback=4096),
                        min15_max=current_config.getint('preload', 'min15_max', fallback=4096),
                        min30_max=current_config.getint('preload', 'min30_max', fallback=4096),
                        min60_max=current_config.getint('preload', 'min60_max', fallback=4096),
                    )
                )
        else:
            data_dir = current_config['mysql']['tmpdir']
            with open(filename, 'w') as f:
                f.write(
                    hku_config_template.mysql_template.format(
                        dir=data_dir,
                        host=current_config['mysql']['host'],
                        port=current_config['mysql']['port'],
                        usr=current_config['mysql']['usr'],
                        pwd=current_config['mysql']['pwd'],
                        day=current_config.getboolean('preload', 'day', fallback=True),
                        week=current_config.getboolean('preload', 'week', fallback=False),
                        month=current_config.getboolean('preload', 'month', fallback=False),
                        quarter=current_config.getboolean('preload', 'quarter', fallback=False),
                        halfyear=current_config.getboolean('preload', 'halfyear', fallback=False),
                        year=current_config.getboolean('preload', 'year', fallback=False),
                        min1=current_config.getboolean('preload', 'min', fallback=False),
                        min5=current_config.getboolean('preload', 'min5', fallback=False),
                        min15=current_config.getboolean('preload', 'min15', fallback=False),
                        min30=current_config.getboolean('preload', 'min30', fallback=False),
                        min60=current_config.getboolean('preload', 'min60', fallback=False),
                        day_max=current_config.getint('preload', 'day_max', fallback=100000),
                        week_max=current_config.getint('preload', 'week_max', fallback=100000),
                        month_max=current_config.getint('preload', 'month_max', fallback=100000),
                        quarter_max=current_config.getint(
                            'preload', 'quarter_max', fallback=100000
                        ),
                        halfyear_max=current_config.getint(
                            'preload', 'halfyear_max', fallback=100000
                        ),
                        year_max=current_config.getint('preload', 'year_max', fallback=100000),
                        min1_max=current_config.getint('preload', 'min_max', fallback=4096),
                        min5_max=current_config.getint('preload', 'min5_max', fallback=4096),
                        min15_max=current_config.getint('preload', 'min15_max', fallback=4096),
                        min30_max=current_config.getint('preload', 'min30_max', fallback=4096),
                        min60_max=current_config.getint('preload', 'min60_max', fallback=4096),
                    )
                )

        if not os.path.lexists(data_dir):
            os.makedirs(data_dir)

        if not os.path.lexists(data_dir + '/block'):
            current_dir = os.path.dirname(os.path.abspath(__file__))
            dirname, _ = os.path.split(current_dir)
            dirname = os.path.join(dirname, 'config/block')
            shutil.copytree(dirname, data_dir + '/block')
            os.remove(data_dir + '/block/__init__.py')

    @pyqtSlot()
    def on_save_pushButton_clicked(self):
        try:
            self.saveConfig()
            QMessageBox.about(self, '', '保存成功')
        except Exception as e:
            QMessageBox.about(self, "错误", str(e))

    def normalOutputWritten(self, text):
        """普通打印信息重定向"""
        if text.find('[DEBUG]') >= 0:
            text = '<font color="#66CC99">{}</font>'.format(text)
        elif text.find('[WARNING]') >= 0:
            text = '<font color="#0000FF">{}</font>'.format(text)
        elif text.find('[ERROR]') >= 0:
            text = '<font color="#FF0000">{}</font>'.format(text)
        elif text.find('[CRITICAL]') >= 0:
            text = '<span style="background-color: #ff0000;">{}</span>'.format(text)
        else:
            # 主动加入<font>标签,避免 append 时多加入空行
            text = '<font color="{}">{}</font>'.format(self._text_color, text)
        cursor = self.log_textEdit.textCursor()
        cursor.movePosition(QTextCursor.End)
        self.log_textEdit.append(text)

    def initLogger(self):
        if not self._capture_output:
            return

        #普通日志输出控制台
        if self._stream is None:
            self._stream = EmittingStream(textWritten=self.normalOutputWritten)
        con = logging.StreamHandler(self._stream)
        FORMAT = logging.Formatter(
            '%(asctime)-15s [%(levelname)s] - %(message)s [%(name)s::%(funcName)s]'
        )
        con.setFormatter(FORMAT)
        add_class_logger_handler(
            con,
            [
                MyMainWindow,
                CollectSpotThread,  #CollectToMySQLThread, CollectToMemThread, 
                UsePytdxImportToH5Thread,
                UseTdxImportToH5Thread,
                ImportTdxToH5Task,
                SchedImportThread
            ],
            logging.INFO
        )
        hku_logger.addHandler(con)

    def initUI(self):
        self._is_sched_import_running = False
        self._is_collect_running = False
        self._stream = None
        if self._capture_output:
            self._stream = EmittingStream(textWritten=self.normalOutputWritten)
            if self._stream is not None:
                sys.stdout = self._stream
                # python构建时丢失stderr通道,导致安装后的hikyuutdx执行时,
                # logging总是报 'NoneType' object has no attribute 'write'
                #sys.stderr = self._stream
        self.log_textEdit.document().setMaximumBlockCount(1000)

        current_dir = os.path.dirname(__file__)
        self.setWindowIcon(QIcon("{}/hikyuu.ico".format(current_dir)))
        #self.setFixedSize(self.width(), self.height())
        self.import_status_label.setText('')
        self.import_detail_textEdit.clear()
        self.reset_progress_bar()
        self.day_start_dateEdit.setMinimumDate(datetime.date(1990, 12, 19))
        self.day_start_dateEdit.setDate(datetime.date(1990, 12, 19))
        today = datetime.date.today()
        self.min_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min5_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min_start_dateEdit.setMinimumDate(datetime.date(1990, 12, 19))
        self.min5_start_dateEdit.setMinimumDate(datetime.date(1990, 12, 19))
        self.trans_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.time_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.trans_start_dateEdit.setMinimumDate(today - datetime.timedelta(90))
        self.time_start_dateEdit.setMinimumDate(today - datetime.timedelta(300))
        self.collect_status_label.setText("已停止")

        #读取保存的配置文件信息,如果不存在,则使用默认配置
        this_dir = self.getUserConfigDir()
        import_config = ConfigParser()
        if os.path.exists(this_dir + '/importdata-gui.ini'):
            import_config.read(this_dir + '/importdata-gui.ini', encoding='utf-8')

        #初始化导入行情数据类型配置
        self.import_stock_checkBox.setChecked(
            import_config.getboolean('quotation', 'stock', fallback=True)
        )
        self.import_fund_checkBox.setChecked(
            import_config.getboolean('quotation', 'fund', fallback=True)
        )
        self.import_future_checkBox.setChecked(
            import_config.getboolean('quotation', 'future', fallback=False)
        )

        #初始化导入K线类型配置
        self.import_day_checkBox.setChecked(import_config.getboolean('ktype', 'day', fallback=True))
        self.import_min_checkBox.setChecked(import_config.getboolean('ktype', 'min', fallback=True))
        self.import_min5_checkBox.setChecked(
            import_config.getboolean('ktype', 'min5', fallback=True)
        )
        self.import_trans_checkBox.setChecked(
            import_config.getboolean('ktype', 'trans', fallback=False)
        )
        self.import_time_checkBox.setChecked(
            import_config.getboolean('ktype', 'time', fallback=False)
        )
        #self.trans_max_days_spinBox.setValue(import_config.getint('ktype', 'trans_max_days', fallback=70))
        #self.time_max_days_spinBox.setValue(import_config.getint('ktype', 'time_max_days', fallback=70))

        #初始化权息与财务数据设置
        self.import_weight_checkBox.setChecked(
            import_config.getboolean('weight', 'enable', fallback=True)
        )

        #初始化通道信目录配置
        tdx_enable = import_config.getboolean('tdx', 'enable', fallback=False)
        tdx_dir = import_config.get('tdx', 'dir', fallback='d:\TdxW_HuaTai')
        self.tdx_radioButton.setChecked(tdx_enable)
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.tdx_dir_lineEdit.setText(tdx_dir)

        #初始化pytdx配置及显示
        self.pytdx_radioButton.setChecked(
            import_config.getboolean('pytdx', 'enable', fallback=True)
        )
        self.use_tdx_number_spinBox.setValue(
            import_config.getint('pytdx', 'use_tdx_number', fallback=10)
        )

        self.on_tdx_or_pytdx_toggled()

        #初始化hdf5设置
        hdf5_enable = import_config.getboolean('hdf5', 'enable', fallback=True)
        self.enable_hdf55_radioButton.setChecked(hdf5_enable)
        hdf5_dir = import_config.get(
            'hdf5',
            'dir',
            fallback="c:\stock" if sys.platform == "win32" else os.path.expanduser('~') + "/stock"
        )
        self.hdf5_dir_lineEdit.setText(hdf5_dir)
        self.hdf5_dir_lineEdit.setEnabled(hdf5_enable)

        #初始化MYSQL设置
        mysql_enable = import_config.getboolean('mysql', 'enable', fallback=False)
        if hdf5_enable:
            mysql_enable = False
        self.enable_mysql_radioButton.setChecked(mysql_enable)
        self.mysql_tmpdir_lineEdit.setText(
            import_config.get('mysql', 'tmpdir', fallback='c:\stock')
        )
        mysql_ip = import_config.get('mysql', 'host', fallback='127.0.0.1')
        self.mysql_ip_lineEdit.setText(mysql_ip)
        self.mysql_ip_lineEdit.setEnabled(mysql_enable)
        mysql_port = import_config.get('mysql', 'port', fallback='3306')
        self.mysql_port_lineEdit.setText(mysql_port)
        self.mysql_port_lineEdit.setEnabled(mysql_enable)
        mysql_usr = import_config.get('mysql', 'usr', fallback='root')
        self.mysql_usr_lineEdit.setText(mysql_usr)
        self.mysql_usr_lineEdit.setEnabled(mysql_enable)
        mysql_pwd = import_config.get('mysql', 'pwd', fallback='')
        self.mysql_pwd_lineEdit.setText(mysql_pwd)
        self.mysql_pwd_lineEdit.setEnabled(mysql_enable)
        self.mysql_test_pushButton.setEnabled(mysql_enable)

        self.sched_import_timeEdit.setTime(
            datetime.time.fromisoformat(import_config.get('schec', 'time', fallback='18:00'))
        )

        # 初始化定时采集设置
        interval_time = import_config.getint('collect', 'interval', fallback=60 * 60)
        self.collect_sample_spinBox.setValue(interval_time)
        use_zhima_proxy = import_config.getboolean('collect', 'use_zhima_proxy', fallback=False)
        self.collect_use_zhima_checkBox.setChecked(use_zhima_proxy)
        data_source = import_config.get('collect', 'source', fallback='sina')
        self.collect_source_comboBox.setCurrentIndex(0 if data_source == 'sina' else 1)
        self.collect_phase1_start_timeEdit.setTime(
            datetime.time.fromisoformat(
                import_config.get('collect', 'phase1_start', fallback='09:00')
            )
        )
        self.collect_phase1_last_timeEdit.setTime(
            datetime.time.fromisoformat(
                import_config.get('collect', 'phase1_end', fallback='12:05')
            )
        )
        self.collect_phase2_start_timeEdit.setTime(
            datetime.time.fromisoformat(
                import_config.get('collect', 'phase2_start', fallback='13:00')
            )
        )
        self.collect_phase2_last_timeEdit.setTime(
            datetime.time.fromisoformat(
                import_config.get('collect', 'phase2_end', fallback='15:05')
            )
        )

        # 预加载设置
        self.preload_day_checkBox.setChecked(
            import_config.getboolean('preload', 'day', fallback=True)
        )
        self.preload_week_checkBox.setChecked(
            import_config.getboolean('preload', 'week', fallback=False)
        )
        self.preload_month_checkBox.setChecked(
            import_config.getboolean('preload', 'month', fallback=False)
        )
        self.preload_quarter_checkBox.setChecked(
            import_config.getboolean('preload', 'quarter', fallback=False)
        )
        self.preload_halfyear_checkBox.setChecked(
            import_config.getboolean('preload', 'halfyear', fallback=False)
        )
        self.preload_year_checkBox.setChecked(
            import_config.getboolean('preload', 'year', fallback=False)
        )
        self.preload_min1_checkBox.setChecked(
            import_config.getboolean('preload', 'min', fallback=False)
        )
        self.preload_min5_checkBox.setChecked(
            import_config.getboolean('preload', 'min5', fallback=False)
        )
        self.preload_min15_checkBox.setChecked(
            import_config.getboolean('preload', 'min15', fallback=False)
        )
        self.preload_min30_checkBox.setChecked(
            import_config.getboolean('preload', 'min30', fallback=False)
        )
        self.preload_min60_checkBox.setChecked(
            import_config.getboolean('preload', 'min60', fallback=False)
        )
        self.preload_day_spinBox.setValue(
            import_config.getint('preload', 'day_max', fallback=100000)
        )
        self.preload_week_spinBox.setValue(
            import_config.getint('preload', 'week_max', fallback=100000)
        )
        self.preload_month_spinBox.setValue(
            import_config.getint('preload', 'month_max', fallback=100000)
        )
        self.preload_quarter_spinBox.setValue(
            import_config.getint('preload', 'quarter_max', fallback=100000)
        )
        self.preload_halfyear_spinBox.setValue(
            import_config.getint('preload', 'halfyear_max', fallback=100000)
        )
        self.preload_year_spinBox.setValue(
            import_config.getint('preload', 'year_max', fallback=100000)
        )
        self.preload_min1_spinBox.setValue(
            import_config.getint('preload', 'min_max', fallback=5120)
        )
        self.preload_min5_spinBox.setValue(
            import_config.getint('preload', 'min5_max', fallback=5120)
        )
        self.preload_min15_spinBox.setValue(
            import_config.getint('preload', 'min15_max', fallback=5120)
        )
        self.preload_min30_spinBox.setValue(
            import_config.getint('preload', 'min30_max', fallback=5120)
        )
        self.preload_min60_spinBox.setValue(
            import_config.getint('preload', 'min60_max', fallback=5120)
        )

    def getCurrentConfig(self):
        import_config = ConfigParser()
        import_config['quotation'] = {
            'stock': self.import_stock_checkBox.isChecked(),
            'fund': self.import_fund_checkBox.isChecked(),
            'future': self.import_future_checkBox.isChecked()
        }
        import_config['ktype'] = {
            'day': self.import_day_checkBox.isChecked(),
            'min': self.import_min_checkBox.isChecked(),
            'min5': self.import_min5_checkBox.isChecked(),
            'trans': self.import_trans_checkBox.isChecked(),
            'time': self.import_time_checkBox.isChecked(),
            'day_start_date': self.day_start_dateEdit.date().toString('yyyy-MM-dd'),
            'min_start_date': self.min_start_dateEdit.date().toString('yyyy-MM-dd'),
            'min5_start_date': self.min5_start_dateEdit.date().toString('yyyy-MM-dd'),
            'trans_start_date': self.trans_start_dateEdit.date().toString('yyyy-MM-dd'),
            'time_start_date': self.time_start_dateEdit.date().toString('yyyy-MM-dd')
        }
        import_config['weight'] = {
            'enable': self.import_weight_checkBox.isChecked(),
        }
        import_config['tdx'] = {
            'enable': self.tdx_radioButton.isChecked(),
            'dir': self.tdx_dir_lineEdit.text()
        }
        import_config['pytdx'] = {
            'enable': self.pytdx_radioButton.isChecked(),
            'use_tdx_number': self.use_tdx_number_spinBox.value()
        }
        import_config['hdf5'] = {
            'enable': self.enable_hdf55_radioButton.isChecked(),
            'dir': self.hdf5_dir_lineEdit.text()
        }
        import_config['mysql'] = {
            'enable': self.enable_mysql_radioButton.isChecked(),
            'tmpdir': self.mysql_tmpdir_lineEdit.text(),
            'host': self.mysql_ip_lineEdit.text(),
            'port': self.mysql_port_lineEdit.text(),
            'usr': self.mysql_usr_lineEdit.text(),
            'pwd': self.mysql_pwd_lineEdit.text()
        }
        import_config['sched'] = {
            'time': self.sched_import_timeEdit.time().toString(),
        }
        import_config['collect'] = {
            'interval': self.collect_sample_spinBox.value(),
            'source': self.collect_source_comboBox.currentText(),
            'use_zhima_proxy': self.collect_use_zhima_checkBox.isChecked(),
            'phase1_start': self.collect_phase1_start_timeEdit.time().toString(),
            'phase1_end': self.collect_phase1_last_timeEdit.time().toString(),
            'phase2_start': self.collect_phase2_start_timeEdit.time().toString(),
            'phase2_end': self.collect_phase2_last_timeEdit.time().toString(),
        }
        import_config['preload'] = {
            'day': self.preload_day_checkBox.isChecked(),
            'week': self.preload_week_checkBox.isChecked(),
            'month': self.preload_month_checkBox.isChecked(),
            'quarter': self.preload_quarter_checkBox.isChecked(),
            'halfyear': self.preload_halfyear_checkBox.isChecked(),
            'year': self.preload_year_checkBox.isChecked(),
            'min': self.preload_min1_checkBox.isChecked(),
            'min5': self.preload_min5_checkBox.isChecked(),
            'min15': self.preload_min15_checkBox.isChecked(),
            'min30': self.preload_min30_checkBox.isChecked(),
            'min60': self.preload_min60_checkBox.isChecked(),
            'day_max': self.preload_day_spinBox.value(),
            'week_max': self.preload_week_spinBox.value(),
            'month_max': self.preload_month_spinBox.value(),
            'quarter_max': self.preload_quarter_spinBox.value(),
            'halfyear_max': self.preload_halfyear_spinBox.value(),
            'year_max': self.preload_year_spinBox.value(),
            'min_max': self.preload_min1_spinBox.value(),
            'min5_max': self.preload_min5_spinBox.value(),
            'min15_max': self.preload_min15_spinBox.value(),
            'min30_max': self.preload_min30_spinBox.value(),
            'min60_max': self.preload_min60_spinBox.value(),
        }
        return import_config

    def initThreads(self):
        self.escape_time_thread = None
        self.hdf5_import_thread = None
        self.mysql_import_thread = None
        self.collect_spot_thread = None
        self.sched_import_thread = None

        self.import_running = False
        self.hdf5_import_progress_bar = {
            'DAY': self.hdf5_day_progressBar,
            '1MIN': self.hdf5_min_progressBar,
            '5MIN': self.hdf5_5min_progressBar
        }

    @pyqtSlot()
    def on_pytdx_radioButton_clicked(self):
        if self.pytdx_radioButton.isChecked():
            self.tdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    @pyqtSlot()
    def on_tdx_radioButton_clicked(self):
        if self.tdx_radioButton.isChecked():
            self.pytdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    def on_tdx_or_pytdx_toggled(self):
        tdx_enable = self.tdx_radioButton.isChecked()
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.import_trans_checkBox.setEnabled(not tdx_enable)
        self.import_time_checkBox.setEnabled(not tdx_enable)
        self.trans_start_dateEdit.setEnabled(not tdx_enable)
        self.time_start_dateEdit.setEnabled(not tdx_enable)
        self.use_tdx_number_spinBox.setEnabled(not tdx_enable)

    @pyqtSlot()
    def on_select_tdx_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['tdx']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.tdx_dir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_hdf5_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['hdf5']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.hdf5_dir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_enable_hdf55_radioButton_clicked(self):
        if self.enable_hdf55_radioButton.isChecked():
            self.enable_mysql_radioButton.setChecked(False)
        self.on_enable_hdf5_or_mysql_toggled()

    @pyqtSlot()
    def on_enable_mysql_radioButton_clicked(self):
        if self.enable_mysql_radioButton.isChecked():
            self.enable_hdf55_radioButton.setChecked(False)
        self.on_enable_hdf5_or_mysql_toggled()

    def on_enable_hdf5_or_mysql_toggled(self):
        hdf5_enable = self.enable_hdf55_radioButton.isChecked()
        mysql_enable = not hdf5_enable
        self.hdf5_dir_lineEdit.setEnabled(hdf5_enable)
        self.mysql_ip_lineEdit.setEnabled(mysql_enable)
        self.mysql_port_lineEdit.setEnabled(mysql_enable)
        self.mysql_usr_lineEdit.setEnabled(mysql_enable)
        self.mysql_pwd_lineEdit.setEnabled(mysql_enable)
        self.mysql_test_pushButton.setEnabled(mysql_enable)

    @pyqtSlot()
    def on_mysql_tmpdir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['mysql']['tmpdir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.mysql_tmpdir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_mysql_test_pushButton_clicked(self):
        """测试数据库连接"""
        db_config = {
            'user': self.mysql_usr_lineEdit.text(),
            'password': self.mysql_pwd_lineEdit.text(),
            'host': self.mysql_ip_lineEdit.text(),
            'port': self.mysql_port_lineEdit.text()
        }

        try:
            cnx = mysql.connector.connect(**db_config)
            cnx.close()
        except mysql.connector.Error as err:
            if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
                QMessageBox.critical(self, "测试数据库连接", "MYSQL密码或用户名错误!")
            elif err.errno == errorcode.ER_BAD_DB_ERROR:
                QMessageBox.critical(self, "测试数据库连接", "MySQL数据库不存在!")
            else:
                QMessageBox.critical(self, "测试数据库连接", err.msg)
            return

        QMessageBox.about(self, "测试数据库连接", " 连接成功!")

    def reset_progress_bar(self):
        self.hdf5_weight_label.setText('')
        self.hdf5_day_progressBar.setValue(0)
        self.hdf5_min_progressBar.setValue(0)
        self.hdf5_5min_progressBar.setValue(0)
        self.hdf5_trans_progressBar.setValue(0)
        self.hdf5_time_progressBar.setValue(0)
        #self.finance_progressBar.setValue(0)
        self.import_detail_textEdit.clear()

    def on_escapte_time(self, escape):
        self.import_status_label.setText("耗时:{:>.2f} 秒".format(escape))

    def on_message_from_thread(self, msg):
        if not msg or len(msg) < 2:
            print("msg is empty!")
            return

        msg_name, msg_task_name = msg[:2]
        if msg_name == 'ESCAPE_TIME':
            self.escape_time = msg_task_name
            self.import_status_label.setText(
                "耗时:{:>.2f} 秒 ({:>.2f}分钟) {}".format(
                    self.escape_time, self.escape_time / 60, datetime.datetime.now()
                )
            )

        elif msg_name == 'HDF5_IMPORT':
            if msg_task_name == 'INFO':
                self.import_detail_textEdit.append(msg[2])

            elif msg_task_name == 'THREAD':
                status = msg[2]
                if status == 'FAILURE':
                    self.import_status_label.setText("耗时:{:>.2f} 秒 导入异常!".format(self.escape_time))
                    self.import_detail_textEdit.append(msg[3])
                self.hdf5_import_thread.terminate()
                self.hdf5_import_thread = None
                self.escape_time_thread.stop()
                self.escape_time_thread = None
                self.start_import_pushButton.setEnabled(True)
                self.import_detail_textEdit.append("导入完毕!")
                self.import_running = False

            elif msg_task_name == 'IMPORT_KDATA':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_import_progress_bar[ktype].setValue(progress)
                else:
                    self.import_detail_textEdit.append(
                        '导入 {} {} 记录数:{}'.format(msg[3], msg[4], msg[5])
                    )

            elif msg_task_name == 'IMPORT_TRANS':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_trans_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分笔记录数:{}'.format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_TIME':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_time_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分时记录数:{}'.format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_WEIGHT':
                self.hdf5_weight_label.setText(msg[2])
                if msg[2] == '导入权息数据完毕!':
                    self.import_detail_textEdit.append('导入权息记录数:{}'.format(msg[3]))
                #elif msg[2] == '导入通达信财务信息完毕!':
                #    self.import_detail_textEdit.append('导入通达信财务记录数:{}'.format(msg[3]))

            #elif msg_task_name == 'IMPORT_FINANCE':
            #    if msg[2] != 'FINISHED':
            #        self.finance_progressBar.setValue(msg[2])

    @pyqtSlot()
    def on_start_import_pushButton_clicked(self):
        config = self.getCurrentConfig()
        if config.getboolean('hdf5', 'enable') \
            and (not os.path.lexists(config['hdf5']['dir']) or not os.path.isdir(config['hdf5']['dir'])):
            QMessageBox.about(self, "错误", '指定的目标数据存放目录不存在!')
            return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.lexists(config['tdx']['dir'])
                 or not os.path.isdir(config['tdx']['dir'])):
            QMessageBox.about(self, "错误", "请确认通达信安装目录是否正确!")
            return

        self.import_running = True
        self.start_import_pushButton.setEnabled(False)
        self.reset_progress_bar()

        self.import_status_label.setText("正在启动任务....")
        QApplication.processEvents()

        if self.tdx_radioButton.isChecked():
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()

    @pyqtSlot()
    def on_sched_import_pushButton_clicked(self):
        self.sched_import_pushButton.setEnabled(False)
        if self._is_sched_import_running:
            self._is_sched_import_running = False
            self.sched_import_pushButton.setText("启动定时导入")
            self.sched_import_thread.terminate()
            self.sched_import_thread.wait()
            self.logger.info("已停止定时导入")
            self.sched_import_thread = None
            self.sched_import_pushButton.setEnabled(True)
            self.start_import_pushButton.setEnabled(True)
            return

        self.start_import_pushButton.setEnabled(False)
        self._is_sched_import_running = True
        self.sched_import_thread = SchedImportThread(self.getCurrentConfig())
        self.sched_import_thread.message.connect(self.on_start_import_pushButton_clicked)
        self.sched_import_thread.start()
        self.sched_import_pushButton.setText("停止定时导入")
        self.sched_import_pushButton.setEnabled(True)

    @pyqtSlot()
    def on_collect_start_pushButton_clicked(self):
        if self._is_collect_running:
            if self.collect_spot_thread is not None and self.collect_spot_thread.isRunning():
                self.collect_spot_thread.terminate()
                self.collect_spot_thread.wait()
                self.collect_spot_thread = None
            self._is_collect_running = False
            self.logger.info("停止采集")
            self.collect_status_label.setText("已停止")
            self.collect_start_pushButton.setText("启动采集")
            QMessageBox.about(self, '', '已停止')
        else:
            if self.collect_spot_thread is None or self.collect_spot_thread.isFinished():
                self.collect_spot_thread = CollectSpotThread(
                    self.getCurrentConfig(),
                    self.getHikyuuConfigFileName(),
                )
                self.collect_spot_thread.start()
            self._is_collect_running = True
            self.collect_status_label.setText("运行中")
            self.collect_start_pushButton.setText("停止采集")
            QMessageBox.about(self, '', '已启动,请在控制台日志查看是否正常运行')
Example #6
0
class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
        self.initUI()
        self.initThreads()

    def closeEvent(self, event):
        if self.import_running:
            QMessageBox.about(self, '提示', '正在执行导入任务,请耐心等候!')
            event.ignore()
            return

        self.saveConfig()

        if self.hdf5_import_thread:
            self.hdf5_import_thread.stop()
        if self.escape_time_thread:
            self.escape_time_thread.stop()
        event.accept()

    def getUserConfigDir(self):
        return os.path.expanduser('~') + '/.hikyuu'

    def saveConfig(self):
        current_config = self.getCurrentConfig()
        filename = self.getUserConfigDir() + '/importdata-gui.ini'
        with open(filename, 'w', encoding='utf-8') as f:
            current_config.write(f)

        filename = self.getUserConfigDir() + '/hikyuu.ini'
        data_dir = current_config['hdf5']['dir']
        
        # 此处不能使用 utf-8 参数,否则导致Windows下getBlock无法找到板块分类
        # with open(filename, 'w', encoding='utf-8') as f:
        with open(filename, 'w') as f:
            f.write(hku_config_template.hdf5_template.format(dir=data_dir))

        if not os.path.lexists(data_dir + '/block'):
            shutil.copytree('../../config/block', data_dir + '/block')
            os.remove(data_dir + '/block/__init__.py')

    def initUI(self):
        current_dir = os.path.dirname(__file__)
        self.setWindowIcon(QIcon("{}/hikyuu.ico".format(current_dir)))
        self.setFixedSize(self.width(), self.height())
        self.import_status_label.setText('')
        self.import_detail_textEdit.clear()
        self.reset_progress_bar()
        self.day_start_dateEdit.setMinimumDate(datetime.date(1990,12,19))
        self.day_start_dateEdit.setDate(datetime.date(1990,12,19))
        today = datetime.date.today()
        self.min_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min5_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min_start_dateEdit.setMinimumDate(datetime.date(1990,12,19))
        self.min5_start_dateEdit.setMinimumDate(datetime.date(1990,12,19))
        self.trans_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.time_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.trans_start_dateEdit.setMinimumDate(today - datetime.timedelta(90))
        self.time_start_dateEdit.setMinimumDate(today - datetime.timedelta(300))

        #读取保存的配置文件信息,如果不存在,则使用默认配置
        this_dir = self.getUserConfigDir()
        import_config = ConfigParser()
        if os.path.exists(this_dir + '/importdata-gui.ini'):
            import_config.read(this_dir + '/importdata-gui.ini', encoding = 'utf-8')

        #初始化导入行情数据类型配置
        self.import_stock_checkBox.setChecked(import_config.getboolean('quotation', 'stock', fallback=True))
        self.import_fund_checkBox.setChecked(import_config.getboolean('quotation', 'fund', fallback=True))
        self.import_future_checkBox.setChecked(import_config.getboolean('quotation', 'future', fallback=False))

        #初始化导入K线类型配置
        self.import_day_checkBox.setChecked(import_config.getboolean('ktype', 'day', fallback=True))
        self.import_min_checkBox.setChecked(import_config.getboolean('ktype', 'min', fallback=True))
        self.import_min5_checkBox.setChecked(import_config.getboolean('ktype', 'min5', fallback=True))
        self.import_trans_checkBox.setChecked(import_config.getboolean('ktype', 'trans', fallback=False))
        self.import_time_checkBox.setChecked(import_config.getboolean('ktype', 'time', fallback=False))
        #self.trans_max_days_spinBox.setValue(import_config.getint('ktype', 'trans_max_days', fallback=70))
        #self.time_max_days_spinBox.setValue(import_config.getint('ktype', 'time_max_days', fallback=70))

        #初始化权息数据设置
        self.import_weight_checkBox.setChecked(import_config.getboolean('weight', 'enable', fallback=True))

        #初始化通道信目录配置
        tdx_enable = import_config.getboolean('tdx', 'enable', fallback=False)
        tdx_dir = import_config.get('tdx', 'dir', fallback='d:\TdxW_HuaTai')
        self.tdx_radioButton.setChecked(tdx_enable)
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.tdx_dir_lineEdit.setText(tdx_dir)

        #初始化pytdx配置及显示
        self.pytdx_radioButton.setChecked(import_config.getboolean('pytdx', 'enable', fallback=True))
        self.use_tdx_number_spinBox.setValue(import_config.getint('pytdx','use_tdx_number', fallback=10))

        #初始化hdf5设置
        hdf5_enable = import_config.getboolean('hdf5', 'enable', fallback=True)
        hdf5_dir = import_config.get('hdf5', 'dir', fallback="c:\stock" if sys.platform == "win32" else os.path.expanduser('~') + "/stock")
        self.hdf5_dir_lineEdit.setText(hdf5_dir)

        self.on_tdx_or_pytdx_toggled()

    def getCurrentConfig(self):
        import_config = ConfigParser()
        import_config['quotation'] = {'stock': self.import_stock_checkBox.isChecked(),
                                      'fund': self.import_fund_checkBox.isChecked(),
                                      'future': self.import_future_checkBox.isChecked()}
        import_config['ktype'] = {'day': self.import_day_checkBox.isChecked(),
                                  'min': self.import_min_checkBox.isChecked(),
                                  'min5': self.import_min5_checkBox.isChecked(),
                                  'trans': self.import_trans_checkBox.isChecked(),
                                  'time': self.import_time_checkBox.isChecked(),
                                  'day_start_date': self.day_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'min_start_date': self.min_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'min5_start_date': self.min5_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'trans_start_date': self.trans_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'time_start_date': self.time_start_dateEdit.date().toString('yyyy-MM-dd')}
        import_config['weight'] = {'enable': self.import_weight_checkBox.isChecked(),}
        import_config['tdx'] = {'enable': self.tdx_radioButton.isChecked(),
                                'dir': self.tdx_dir_lineEdit.text()}
        import_config['pytdx'] = {'enable': self.pytdx_radioButton.isChecked(),
                                  'use_tdx_number': self.use_tdx_number_spinBox.value()}
        import_config['hdf5'] = {'enable': True,
                                 'dir': self.hdf5_dir_lineEdit.text()}
        return import_config

    def initThreads(self):
        self.escape_time_thread = None
        self.hdf5_import_thread = None
        self.mysql_import_thread = None

        self.import_running = False
        self.hdf5_import_progress_bar = {'DAY': self.hdf5_day_progressBar,
                                         '1MIN': self.hdf5_min_progressBar,
                                         '5MIN': self.hdf5_5min_progressBar}

    @pyqtSlot()
    def on_pytdx_radioButton_clicked(self):
        if self.pytdx_radioButton.isChecked():
            self.tdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    @pyqtSlot()
    def on_tdx_radioButton_clicked(self):
        if self.tdx_radioButton.isChecked():
            self.pytdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    def on_tdx_or_pytdx_toggled(self):
        tdx_enable = self.tdx_radioButton.isChecked()
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.import_trans_checkBox.setEnabled(not tdx_enable)
        self.import_time_checkBox.setEnabled(not tdx_enable)
        self.trans_start_dateEdit.setEnabled(not tdx_enable)
        self.time_start_dateEdit.setEnabled(not tdx_enable)
        self.use_tdx_number_spinBox.setEnabled(not tdx_enable)

    @pyqtSlot()
    def on_select_tdx_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['tdx']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.tdx_dir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_hdf5_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['hdf5']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.hdf5_dir_lineEdit.setText(dirname[0])

    def reset_progress_bar(self):
        self.hdf5_weight_label.setText('')
        self.hdf5_day_progressBar.setValue(0)
        self.hdf5_min_progressBar.setValue(0)
        self.hdf5_5min_progressBar.setValue(0)
        self.hdf5_trans_progressBar.setValue(0)
        self.hdf5_time_progressBar.setValue(0)
        self.import_detail_textEdit.clear()

    def on_escapte_time(self, escape):
        self.import_status_label.setText("耗时:{:>.2f} 秒".format(escape))

    def on_message_from_thread(self, msg):
        if not msg or len(msg) < 2:
            print("msg is empty!")
            return

        msg_name, msg_task_name = msg[:2]
        if msg_name == 'ESCAPE_TIME':
            self.escape_time = msg_task_name
            self.import_status_label.setText("耗时:{:>.2f} 秒 ({:>.2f}分钟)".format(self.escape_time, self.escape_time/60))

        elif msg_name == 'HDF5_IMPORT':
            if msg_task_name == 'INFO':
                self.import_detail_textEdit.append(msg[2])

            elif msg_task_name == 'THREAD':
                status = msg[2]
                if status == 'FAILURE':
                    self.import_status_label.setText("耗时:{:>.2f} 秒 导入异常!".format(self.escape_time))
                    self.import_detail_textEdit.append(msg[3])
                self.hdf5_import_thread.terminate()
                self.hdf5_import_thread = None
                self.escape_time_thread.stop()
                self.escape_time_thread = None
                self.start_import_pushButton.setEnabled(True)
                self.import_detail_textEdit.append("导入完毕!")
                self.import_running = False

            elif msg_task_name == 'IMPORT_KDATA':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_import_progress_bar[ktype].setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} {} 记录数:{}'
                                                       .format(msg[3], msg[4], msg[5]))

            elif msg_task_name == 'IMPORT_TRANS':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_trans_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分笔记录数:{}'
                                                       .format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_TIME':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_time_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分时记录数:{}'
                                                       .format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_WEIGHT':
                self.hdf5_weight_label.setText(msg[2])
                if msg[2] == '导入完成!':
                    self.import_detail_textEdit.append('导入权息记录数:{}'.format(msg[3]))


    @pyqtSlot()
    def on_start_import_pushButton_clicked(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            QMessageBox.about(self, "错误", '指定的目标数据存放目录不存在!')
            return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            QMessageBox.about(self, "错误", "请确认通达信安装目录是否正确!")
            return

        self.import_running = True
        self.start_import_pushButton.setEnabled(False)
        self.reset_progress_bar()

        self.import_status_label.setText("正在启动任务....")
        QApplication.processEvents()

        if self.tdx_radioButton.isChecked():
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #7
0
class HKUImportDataCMD:
    def __init__(self):
        self.initThreads()

    def getUserConfigDir(self):
        return os.path.expanduser('~') + '/.hikyuu'

    def getCurrentConfig(self):
        #读取保存的配置文件信息,如果不存在,则使用默认配置
        this_dir = self.getUserConfigDir()
        import_config = ConfigParser()
        import_config.read(this_dir + '/importdata-gui.ini', encoding='utf-8')
        return import_config

    def initThreads(self):
        self.escape_time_thread = None
        self.hdf5_import_thread = None
        self.mysql_import_thread = None
        self.import_running = False
        self.progress = {'DAY': 0, '1MIN': 0, '5MIN': 0, 'TRANS': 0, 'TIME': 0}
        self.info_type = {
            'DAY': '日线数据',
            '1MIN': '一分钟线',
            '5MIN': '五分钟线',
            'TRANS': '历史分笔',
            'TIME': '分时数据'
        }
        self.escape_time = 0.0
        self.details = []

    def print_progress(self, ktype, progress):
        if progress != self.progress[ktype]:
            print('import progress: {}%  - {} - 已耗时 {:>.2f} 分钟'.format(
                progress, self.info_type[ktype], self.escape_time))
            self.progress[ktype] = progress

    def on_message_from_thread(self, msg):
        if not msg or len(msg) < 2:
            print("msg is empty!")
            return

        msg_name, msg_task_name = msg[:2]
        if msg_name == 'ESCAPE_TIME':
            self.escape_time = msg_task_name / 60

        elif msg_name == 'HDF5_IMPORT':
            if msg_task_name == 'INFO':
                print(msg[2])

            elif msg_task_name == 'THREAD':
                status = msg[2]
                if status == 'FAILURE':
                    self.details.append(msg[3])
                self.hdf5_import_thread.terminate()
                self.hdf5_import_thread = None
                self.escape_time_thread.stop()
                self.escape_time_thread = None
                print("\n导入完毕, 共耗时 {:>.2f} 分钟".format(self.escape_time))
                print(
                    '\n========================================================='
                )
                print("导入详情:")
                for info in self.details:
                    print(info)
                print(
                    '========================================================='
                )
                self.import_running = False
                QCoreApplication.quit()

            elif msg_task_name == 'IMPORT_KDATA':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.print_progress(ktype, progress)
                else:
                    self.details.append('导入 {} {} 记录数:{}'.format(
                        msg[3], msg[4], msg[5]))

            elif msg_task_name == 'IMPORT_TRANS':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.print_progress('TRANS', progress)
                else:
                    self.details.append('导入 {} 分笔记录数:{}'.format(
                        msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_TIME':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.print_progress('TIME', progress)
                else:
                    self.details.append('导入 {} 分时记录数:{}'.format(
                        msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_WEIGHT':
                if msg[2] == 'INFO':
                    pass
                elif msg[2] == 'FINISHED':
                    print('导入权息数据完毕!')
                elif msg[2] == '导入完成!':
                    self.details.append('导入权息记录数:{}'.format(msg[3]))
                elif msg[2] == '权息数据无变化':
                    self.details.append(msg[3])
                else:
                    print('权息{}'.format(msg[2]))

    def start_import_data(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            print("错误:", '指定的目标数据存放目录不存在!')
            sys.exit(-1)
            #return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            print("错误:", "请确认通达信安装目录是否正确!")
            sys.exit(-1)
            #return

        self.import_running = True

        print("正在启动任务....")
        QCoreApplication.processEvents()

        if config.getboolean('tdx', 'enable'):
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #8
0
class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.setupUi(self)
        self.initUI()
        self.initThreads()

    def closeEvent(self, event):
        if self.import_running:
            QMessageBox.about(self, '提示', '正在执行导入任务,请耐心等候!')
            event.ignore()
            return

        self.saveConfig()

        if self.hdf5_import_thread:
            self.hdf5_import_thread.stop()
        if self.escape_time_thread:
            self.escape_time_thread.stop()
        event.accept()

    def getUserConfigDir(self):
        return os.path.expanduser('~') + '/.hikyuu'

    def saveConfig(self):
        if not os.path.lexists(self.getUserConfigDir()):
            os.mkdir(self.getUserConfigDir())

        current_config = self.getCurrentConfig()
        filename = self.getUserConfigDir() + '/importdata-gui.ini'
        with open(filename, 'w', encoding='utf-8') as f:
            current_config.write(f)
            
        filename = self.getUserConfigDir() + '/hikyuu.ini'
        data_dir = current_config['hdf5']['dir']
        
        # 此处不能使用 utf-8 参数,否则导致Windows下getBlock无法找到板块分类
        # with open(filename, 'w', encoding='utf-8') as f:
        with open(filename, 'w') as f:
            f.write(hku_config_template.hdf5_template.format(dir=data_dir))

        if not os.path.lexists(data_dir + '/block'):
            shutil.copytree('../../config/block', data_dir + '/block')
            os.remove(data_dir + '/block/__init__.py')
            
        if not os.path.lexists(data_dir + '/tmp'):
            os.mkdir(data_dir + '/tmp')

    def initUI(self):
        current_dir = os.path.dirname(__file__)
        self.setWindowIcon(QIcon("{}/hikyuu.ico".format(current_dir)))
        self.setFixedSize(self.width(), self.height())
        self.import_status_label.setText('')
        self.import_detail_textEdit.clear()
        self.reset_progress_bar()
        self.day_start_dateEdit.setMinimumDate(datetime.date(1990,12,19))
        self.day_start_dateEdit.setDate(datetime.date(1990,12,19))
        today = datetime.date.today()
        self.min_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min5_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min_start_dateEdit.setMinimumDate(datetime.date(1990,12,19))
        self.min5_start_dateEdit.setMinimumDate(datetime.date(1990,12,19))
        self.trans_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.time_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.trans_start_dateEdit.setMinimumDate(today - datetime.timedelta(90))
        self.time_start_dateEdit.setMinimumDate(today - datetime.timedelta(300))

        #读取保存的配置文件信息,如果不存在,则使用默认配置
        this_dir = self.getUserConfigDir()
        import_config = ConfigParser()
        if os.path.exists(this_dir + '/importdata-gui.ini'):
            import_config.read(this_dir + '/importdata-gui.ini', encoding = 'utf-8')

        #初始化导入行情数据类型配置
        self.import_stock_checkBox.setChecked(import_config.getboolean('quotation', 'stock', fallback=True))
        self.import_fund_checkBox.setChecked(import_config.getboolean('quotation', 'fund', fallback=True))
        self.import_future_checkBox.setChecked(import_config.getboolean('quotation', 'future', fallback=False))

        #初始化导入K线类型配置
        self.import_day_checkBox.setChecked(import_config.getboolean('ktype', 'day', fallback=True))
        self.import_min_checkBox.setChecked(import_config.getboolean('ktype', 'min', fallback=True))
        self.import_min5_checkBox.setChecked(import_config.getboolean('ktype', 'min5', fallback=True))
        self.import_trans_checkBox.setChecked(import_config.getboolean('ktype', 'trans', fallback=False))
        self.import_time_checkBox.setChecked(import_config.getboolean('ktype', 'time', fallback=False))
        #self.trans_max_days_spinBox.setValue(import_config.getint('ktype', 'trans_max_days', fallback=70))
        #self.time_max_days_spinBox.setValue(import_config.getint('ktype', 'time_max_days', fallback=70))

        #初始化权息与财务数据设置
        self.import_weight_checkBox.setChecked(import_config.getboolean('weight', 'enable', fallback=True))
        self.import_finance_checkBox.setChecked(import_config.getboolean('finance', 'enable', fallback=True))

        #初始化通道信目录配置
        tdx_enable = import_config.getboolean('tdx', 'enable', fallback=False)
        tdx_dir = import_config.get('tdx', 'dir', fallback='d:\TdxW_HuaTai')
        self.tdx_radioButton.setChecked(tdx_enable)
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.tdx_dir_lineEdit.setText(tdx_dir)

        #初始化pytdx配置及显示
        self.pytdx_radioButton.setChecked(import_config.getboolean('pytdx', 'enable', fallback=True))
        self.use_tdx_number_spinBox.setValue(import_config.getint('pytdx','use_tdx_number', fallback=10))

        #初始化hdf5设置
        hdf5_enable = import_config.getboolean('hdf5', 'enable', fallback=True)
        hdf5_dir = import_config.get('hdf5', 'dir', fallback="c:\stock" if sys.platform == "win32" else os.path.expanduser('~') + "/stock")
        self.hdf5_dir_lineEdit.setText(hdf5_dir)

        self.on_tdx_or_pytdx_toggled()

    def getCurrentConfig(self):
        import_config = ConfigParser()
        import_config['quotation'] = {'stock': self.import_stock_checkBox.isChecked(),
                                      'fund': self.import_fund_checkBox.isChecked(),
                                      'future': self.import_future_checkBox.isChecked()}
        import_config['ktype'] = {'day': self.import_day_checkBox.isChecked(),
                                  'min': self.import_min_checkBox.isChecked(),
                                  'min5': self.import_min5_checkBox.isChecked(),
                                  'trans': self.import_trans_checkBox.isChecked(),
                                  'time': self.import_time_checkBox.isChecked(),
                                  'day_start_date': self.day_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'min_start_date': self.min_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'min5_start_date': self.min5_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'trans_start_date': self.trans_start_dateEdit.date().toString('yyyy-MM-dd'),
                                  'time_start_date': self.time_start_dateEdit.date().toString('yyyy-MM-dd')}
        import_config['weight'] = {'enable': self.import_weight_checkBox.isChecked(),}
        import_config['finance'] = {'enable': self.import_finance_checkBox.isChecked(),}
        import_config['tdx'] = {'enable': self.tdx_radioButton.isChecked(),
                                'dir': self.tdx_dir_lineEdit.text()}
        import_config['pytdx'] = {'enable': self.pytdx_radioButton.isChecked(),
                                  'use_tdx_number': self.use_tdx_number_spinBox.value()}
        import_config['hdf5'] = {'enable': True,
                                 'dir': self.hdf5_dir_lineEdit.text()}
        return import_config

    def initThreads(self):
        self.escape_time_thread = None
        self.hdf5_import_thread = None
        self.mysql_import_thread = None

        self.import_running = False
        self.hdf5_import_progress_bar = {'DAY': self.hdf5_day_progressBar,
                                         '1MIN': self.hdf5_min_progressBar,
                                         '5MIN': self.hdf5_5min_progressBar}

    @pyqtSlot()
    def on_pytdx_radioButton_clicked(self):
        if self.pytdx_radioButton.isChecked():
            self.tdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    @pyqtSlot()
    def on_tdx_radioButton_clicked(self):
        if self.tdx_radioButton.isChecked():
            self.pytdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    def on_tdx_or_pytdx_toggled(self):
        tdx_enable = self.tdx_radioButton.isChecked()
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.import_trans_checkBox.setEnabled(not tdx_enable)
        self.import_time_checkBox.setEnabled(not tdx_enable)
        self.trans_start_dateEdit.setEnabled(not tdx_enable)
        self.time_start_dateEdit.setEnabled(not tdx_enable)
        self.use_tdx_number_spinBox.setEnabled(not tdx_enable)

    @pyqtSlot()
    def on_select_tdx_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['tdx']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.tdx_dir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_hdf5_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['hdf5']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.hdf5_dir_lineEdit.setText(dirname[0])

    def reset_progress_bar(self):
        self.hdf5_weight_label.setText('')
        self.hdf5_day_progressBar.setValue(0)
        self.hdf5_min_progressBar.setValue(0)
        self.hdf5_5min_progressBar.setValue(0)
        self.hdf5_trans_progressBar.setValue(0)
        self.hdf5_time_progressBar.setValue(0)
        self.finance_progressBar.setValue(0)
        self.import_detail_textEdit.clear()

    def on_escapte_time(self, escape):
        self.import_status_label.setText("耗时:{:>.2f} 秒".format(escape))

    def on_message_from_thread(self, msg):
        if not msg or len(msg) < 2:
            print("msg is empty!")
            return

        msg_name, msg_task_name = msg[:2]
        if msg_name == 'ESCAPE_TIME':
            self.escape_time = msg_task_name
            self.import_status_label.setText("耗时:{:>.2f} 秒 ({:>.2f}分钟)".format(self.escape_time, self.escape_time/60))

        elif msg_name == 'HDF5_IMPORT':
            if msg_task_name == 'INFO':
                self.import_detail_textEdit.append(msg[2])

            elif msg_task_name == 'THREAD':
                status = msg[2]
                if status == 'FAILURE':
                    self.import_status_label.setText("耗时:{:>.2f} 秒 导入异常!".format(self.escape_time))
                    self.import_detail_textEdit.append(msg[3])
                self.hdf5_import_thread.terminate()
                self.hdf5_import_thread = None
                self.escape_time_thread.stop()
                self.escape_time_thread = None
                self.start_import_pushButton.setEnabled(True)
                self.import_detail_textEdit.append("导入完毕!")
                self.import_running = False

            elif msg_task_name == 'IMPORT_KDATA':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_import_progress_bar[ktype].setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} {} 记录数:{}'
                                                       .format(msg[3], msg[4], msg[5]))

            elif msg_task_name == 'IMPORT_TRANS':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_trans_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分笔记录数:{}'
                                                       .format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_TIME':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_time_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分时记录数:{}'
                                                       .format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_WEIGHT':
                self.hdf5_weight_label.setText(msg[2])
                if msg[2] == '导入钱龙权息数据完毕!':
                    self.import_detail_textEdit.append('导入钱龙权息记录数:{}'.format(msg[3]))
                elif msg[2] == '导入通达信权息信息完毕!':
                    self.import_detail_textEdit.append('导入通达信权息记录数:{}'.format(msg[3]))
            
            elif msg_task_name == 'IMPORT_FINANCE':
                if msg[2] != 'FINISHED':
                    self.finance_progressBar.setValue(msg[2])


    @pyqtSlot()
    def on_start_import_pushButton_clicked(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            QMessageBox.about(self, "错误", '指定的目标数据存放目录不存在!')
            return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            QMessageBox.about(self, "错误", "请确认通达信安装目录是否正确!")
            return

        self.import_running = True
        self.start_import_pushButton.setEnabled(False)
        self.reset_progress_bar()

        self.import_status_label.setText("正在启动任务....")
        QApplication.processEvents()

        if self.tdx_radioButton.isChecked():
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #9
0
class HKUImportDataCMD:
    def __init__(self):
        self.initThreads()

    def getUserConfigDir(self):
        return os.path.expanduser('~') + '/.hikyuu'

    def getCurrentConfig(self):
        #读取保存的配置文件信息,如果不存在,则使用默认配置
        this_dir = self.getUserConfigDir()
        import_config = ConfigParser()
        import_config.read(this_dir + '/importdata-gui.ini', encoding = 'utf-8')
        return import_config

    def initThreads(self):
        self.escape_time_thread = None
        self.hdf5_import_thread = None
        self.mysql_import_thread = None
        self.import_running = False
        self.progress = {'DAY': 0, '1MIN': 0, '5MIN': 0, 'TRANS':0, 'TIME':0}
        self.info_type = {'DAY': '日线数据', '1MIN': '一分钟线', '5MIN': '五分钟线', 'TRANS':'历史分笔', 'TIME':'分时数据'}
        self.escape_time = 0.0
        self.details = []

    def print_progress(self, ktype, progress):
        if progress != self.progress[ktype]:
            print('import progress: {}%  - {} - 已耗时 {:>.2f} 分钟'.format(progress, self.info_type[ktype], self.escape_time))
            self.progress[ktype] = progress

    def on_message_from_thread(self, msg):
        if not msg or len(msg) < 2:
            print("msg is empty!")
            return

        msg_name, msg_task_name = msg[:2]
        if msg_name == 'ESCAPE_TIME':
            self.escape_time = msg_task_name / 60
        
        elif msg_name == 'HDF5_IMPORT':
            if msg_task_name == 'INFO':
                print(msg[2])

            elif msg_task_name == 'THREAD':
                status = msg[2]
                if status == 'FAILURE':
                    self.details.append(msg[3])
                self.hdf5_import_thread.terminate()
                self.hdf5_import_thread = None
                self.escape_time_thread.stop()
                self.escape_time_thread = None
                print("\n导入完毕, 共耗时 {:>.2f} 分钟".format(self.escape_time))
                print('\n=========================================================')
                print("导入详情:")
                for info in self.details:
                    print(info)
                print('=========================================================')
                self.import_running = False
                QCoreApplication.quit()

            elif msg_task_name == 'IMPORT_KDATA':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.print_progress(ktype, progress)
                else:
                    self.details.append('导入 {} {} 记录数:{}'.format(msg[3], msg[4], msg[5]))

            elif msg_task_name == 'IMPORT_TRANS':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.print_progress('TRANS', progress)
                else:
                    self.details.append('导入 {} 分笔记录数:{}'.format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_TIME':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.print_progress('TIME', progress)
                else:
                    self.details.append('导入 {} 分时记录数:{}'.format(msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_WEIGHT':
                if msg[2] == 'INFO':
                    pass
                elif msg[2] == 'FINISHED':
                    print('导入权息数据完毕!')
                elif msg[2] == '导入完成!':
                    self.details.append('导入权息记录数:{}'.format(msg[3]))
                elif msg[2] == '权息数据无变化':
                    self.details.append(msg[3])
                else:
                    print('权息{}'.format(msg[2]))


    def start_import_data(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            print("错误:", '指定的目标数据存放目录不存在!')
            sys.exit(-1)
            #return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            print("错误:", "请确认通达信安装目录是否正确!")
            sys.exit(-1)
            #return

        self.import_running = True

        print("正在启动任务....")
        QCoreApplication.processEvents()

        if config.getboolean('tdx', 'enable'):
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()
Example #10
0
class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None, capture_output=False):
        super(MyMainWindow, self).__init__(parent)
        self._capture_output = capture_output  #捕获Python stdout 输出
        self.setupUi(self)
        self.initUI()
        self.initLogger()
        self.initThreads()
        self.logger.info("Init...")

    def closeEvent(self, event):
        self.stop_collect()
        if self.import_running:
            QMessageBox.about(self, '提示', '正在执行导入任务,请耐心等候!')
            event.ignore()
            return

        self.saveConfig()

        if self.hdf5_import_thread:
            self.hdf5_import_thread.stop()
        if self.escape_time_thread:
            self.escape_time_thread.stop()
        event.accept()

    def getUserConfigDir(self):
        return os.path.expanduser('~') + '/.hikyuu'

    def saveConfig(self):
        if not os.path.lexists(self.getUserConfigDir()):
            os.mkdir(self.getUserConfigDir())

        current_config = self.getCurrentConfig()
        filename = self.getUserConfigDir() + '/importdata-gui.ini'
        with open(filename, 'w', encoding='utf-8') as f:
            current_config.write(f)

        filename = self.getUserConfigDir() + '/hikyuu.ini'
        if current_config.getboolean('hdf5', 'enable', fallback=True):
            data_dir = current_config['hdf5']['dir']
            if not os.path.lexists(data_dir + '/tmp'):
                os.mkdir(data_dir + '/tmp')

            # 此处不能使用 utf-8 参数,否则导致Windows下getBlock无法找到板块分类
            # with open(filename, 'w', encoding='utf-8') as f:
            with open(filename, 'w') as f:
                f.write(hku_config_template.hdf5_template.format(dir=data_dir))
        else:
            data_dir = current_config['mysql']['tmpdir']
            with open(filename, 'w') as f:
                f.write(
                    hku_config_template.mysql_template.format(
                        dir=data_dir,
                        host=current_config['mysql']['host'],
                        port=current_config['mysql']['port'],
                        usr=current_config['mysql']['usr'],
                        pwd=current_config['mysql']['pwd']))

        if not os.path.lexists(data_dir):
            os.makedirs(data_dir)

        if not os.path.lexists(data_dir + '/block'):
            current_dir = os.path.dirname(os.path.abspath(__file__))
            dirname, _ = os.path.split(current_dir)
            dirname = os.path.join(dirname, 'config/block')
            shutil.copytree(dirname, data_dir + '/block')
            os.remove(data_dir + '/block/__init__.py')

    def normalOutputWritten(self, text):
        """普通打印信息重定向"""
        cursor = self.log_textEdit.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)
        self.log_textEdit.setTextCursor(cursor)
        self.log_textEdit.ensureCursorVisible()

    def initLogger(self):
        if not self._capture_output:
            return

        #普通日志输出控制台
        con = logging.StreamHandler(
            EmittingStream(textWritten=self.normalOutputWritten))
        FORMAT = logging.Formatter(
            '%(asctime)-15s [%(levelname)s] - %(message)s [%(name)s::%(funcName)s]'
        )
        con.setFormatter(FORMAT)
        add_class_logger_handler(con, [
            MyMainWindow, CollectThread, UsePytdxImportToH5Thread,
            UseTdxImportToH5Thread
        ], logging.INFO)

    def initUI(self):
        if self._capture_output:
            stream = EmittingStream(textWritten=self.normalOutputWritten)
            sys.stdout = stream
            sys.stderr = stream

        current_dir = os.path.dirname(__file__)
        self.setWindowIcon(QIcon("{}/hikyuu.ico".format(current_dir)))
        self.setFixedSize(self.width(), self.height())
        self.import_status_label.setText('')
        self.import_detail_textEdit.clear()
        self.reset_progress_bar()
        self.day_start_dateEdit.setMinimumDate(datetime.date(1990, 12, 19))
        self.day_start_dateEdit.setDate(datetime.date(1990, 12, 19))
        today = datetime.date.today()
        self.min_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min5_start_dateEdit.setDate(today - datetime.timedelta(90))
        self.min_start_dateEdit.setMinimumDate(datetime.date(1990, 12, 19))
        self.min5_start_dateEdit.setMinimumDate(datetime.date(1990, 12, 19))
        self.trans_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.time_start_dateEdit.setDate(today - datetime.timedelta(7))
        self.trans_start_dateEdit.setMinimumDate(today -
                                                 datetime.timedelta(90))
        self.time_start_dateEdit.setMinimumDate(today -
                                                datetime.timedelta(300))
        self.collect_running = False
        self.collect_status_Label.setText("未启动")

        #读取保存的配置文件信息,如果不存在,则使用默认配置
        this_dir = self.getUserConfigDir()
        import_config = ConfigParser()
        if os.path.exists(this_dir + '/importdata-gui.ini'):
            import_config.read(this_dir + '/importdata-gui.ini',
                               encoding='utf-8')

        #初始化导入行情数据类型配置
        self.import_stock_checkBox.setChecked(
            import_config.getboolean('quotation', 'stock', fallback=True))
        self.import_fund_checkBox.setChecked(
            import_config.getboolean('quotation', 'fund', fallback=True))
        self.import_future_checkBox.setChecked(
            import_config.getboolean('quotation', 'future', fallback=False))

        #初始化导入K线类型配置
        self.import_day_checkBox.setChecked(
            import_config.getboolean('ktype', 'day', fallback=True))
        self.import_min_checkBox.setChecked(
            import_config.getboolean('ktype', 'min', fallback=True))
        self.import_min5_checkBox.setChecked(
            import_config.getboolean('ktype', 'min5', fallback=True))
        self.import_trans_checkBox.setChecked(
            import_config.getboolean('ktype', 'trans', fallback=False))
        self.import_time_checkBox.setChecked(
            import_config.getboolean('ktype', 'time', fallback=False))
        #self.trans_max_days_spinBox.setValue(import_config.getint('ktype', 'trans_max_days', fallback=70))
        #self.time_max_days_spinBox.setValue(import_config.getint('ktype', 'time_max_days', fallback=70))

        #初始化权息与财务数据设置
        self.import_weight_checkBox.setChecked(
            import_config.getboolean('weight', 'enable', fallback=True))

        #初始化通道信目录配置
        tdx_enable = import_config.getboolean('tdx', 'enable', fallback=False)
        tdx_dir = import_config.get('tdx', 'dir', fallback='d:\TdxW_HuaTai')
        self.tdx_radioButton.setChecked(tdx_enable)
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.tdx_dir_lineEdit.setText(tdx_dir)

        #初始化pytdx配置及显示
        self.pytdx_radioButton.setChecked(
            import_config.getboolean('pytdx', 'enable', fallback=True))
        self.use_tdx_number_spinBox.setValue(
            import_config.getint('pytdx', 'use_tdx_number', fallback=10))

        #初始化hdf5设置
        hdf5_enable = import_config.getboolean('hdf5', 'enable', fallback=True)
        self.enable_hdf55_radioButton.setChecked(hdf5_enable)
        hdf5_dir = import_config.get(
            'hdf5',
            'dir',
            fallback="c:\stock"
            if sys.platform == "win32" else os.path.expanduser('~') + "/stock")
        self.hdf5_dir_lineEdit.setText(hdf5_dir)
        self.hdf5_dir_lineEdit.setEnabled(hdf5_enable)

        #初始化MYSQL设置
        mysql_enable = import_config.getboolean('mysql',
                                                'enable',
                                                fallback=False)
        if hdf5_enable:
            mysql_enable = False
        self.enable_mysql_radioButton.setChecked(mysql_enable)
        self.mysql_tmpdir_lineEdit.setText(
            import_config.get('mysql', 'tmpdir', fallback='c:\stock'))
        mysql_ip = import_config.get('mysql', 'host', fallback='127.0.0.1')
        self.mysql_ip_lineEdit.setText(mysql_ip)
        self.mysql_ip_lineEdit.setEnabled(mysql_enable)
        mysql_port = import_config.get('mysql', 'port', fallback='3306')
        self.mysql_port_lineEdit.setText(mysql_port)
        self.mysql_port_lineEdit.setEnabled(mysql_enable)
        mysql_usr = import_config.get('mysql', 'usr', fallback='root')
        self.mysql_usr_lineEdit.setText(mysql_usr)
        self.mysql_usr_lineEdit.setEnabled(mysql_enable)
        mysql_pwd = import_config.get('mysql', 'pwd', fallback='')
        self.mysql_pwd_lineEdit.setText(mysql_pwd)
        self.mysql_pwd_lineEdit.setEnabled(mysql_enable)
        self.mysql_test_pushButton.setEnabled(mysql_enable)

        self.on_tdx_or_pytdx_toggled()

    def getCurrentConfig(self):
        import_config = ConfigParser()
        import_config['quotation'] = {
            'stock': self.import_stock_checkBox.isChecked(),
            'fund': self.import_fund_checkBox.isChecked(),
            'future': self.import_future_checkBox.isChecked()
        }
        import_config['ktype'] = {
            'day':
            self.import_day_checkBox.isChecked(),
            'min':
            self.import_min_checkBox.isChecked(),
            'min5':
            self.import_min5_checkBox.isChecked(),
            'trans':
            self.import_trans_checkBox.isChecked(),
            'time':
            self.import_time_checkBox.isChecked(),
            'day_start_date':
            self.day_start_dateEdit.date().toString('yyyy-MM-dd'),
            'min_start_date':
            self.min_start_dateEdit.date().toString('yyyy-MM-dd'),
            'min5_start_date':
            self.min5_start_dateEdit.date().toString('yyyy-MM-dd'),
            'trans_start_date':
            self.trans_start_dateEdit.date().toString('yyyy-MM-dd'),
            'time_start_date':
            self.time_start_dateEdit.date().toString('yyyy-MM-dd')
        }
        import_config['weight'] = {
            'enable': self.import_weight_checkBox.isChecked(),
        }
        import_config['tdx'] = {
            'enable': self.tdx_radioButton.isChecked(),
            'dir': self.tdx_dir_lineEdit.text()
        }
        import_config['pytdx'] = {
            'enable': self.pytdx_radioButton.isChecked(),
            'use_tdx_number': self.use_tdx_number_spinBox.value()
        }
        import_config['hdf5'] = {
            'enable': self.enable_hdf55_radioButton.isChecked(),
            'dir': self.hdf5_dir_lineEdit.text()
        }
        import_config['mysql'] = {
            'enable': self.enable_mysql_radioButton.isChecked(),
            'tmpdir': self.mysql_tmpdir_lineEdit.text(),
            'host': self.mysql_ip_lineEdit.text(),
            'port': self.mysql_port_lineEdit.text(),
            'usr': self.mysql_usr_lineEdit.text(),
            'pwd': self.mysql_pwd_lineEdit.text()
        }
        return import_config

    def initThreads(self):
        self.escape_time_thread = None
        self.hdf5_import_thread = None
        self.mysql_import_thread = None
        self.collect_sh_thread = None
        self.collect_sz_thread = None

        self.import_running = False
        self.hdf5_import_progress_bar = {
            'DAY': self.hdf5_day_progressBar,
            '1MIN': self.hdf5_min_progressBar,
            '5MIN': self.hdf5_5min_progressBar
        }

    @pyqtSlot()
    def on_pytdx_radioButton_clicked(self):
        if self.pytdx_radioButton.isChecked():
            self.tdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    @pyqtSlot()
    def on_tdx_radioButton_clicked(self):
        if self.tdx_radioButton.isChecked():
            self.pytdx_radioButton.setChecked(False)
        self.on_tdx_or_pytdx_toggled()

    def on_tdx_or_pytdx_toggled(self):
        tdx_enable = self.tdx_radioButton.isChecked()
        self.tdx_dir_lineEdit.setEnabled(tdx_enable)
        self.select_tdx_dir_pushButton.setEnabled(tdx_enable)
        self.import_trans_checkBox.setEnabled(not tdx_enable)
        self.import_time_checkBox.setEnabled(not tdx_enable)
        self.trans_start_dateEdit.setEnabled(not tdx_enable)
        self.time_start_dateEdit.setEnabled(not tdx_enable)
        self.use_tdx_number_spinBox.setEnabled(not tdx_enable)

    @pyqtSlot()
    def on_select_tdx_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['tdx']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.tdx_dir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_hdf5_dir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['hdf5']['dir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.hdf5_dir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_enable_hdf55_radioButton_clicked(self):
        if self.enable_hdf55_radioButton.isChecked():
            self.enable_mysql_radioButton.setChecked(False)
        self.on_enable_hdf5_or_mysql_toggled()

    @pyqtSlot()
    def on_enable_mysql_radioButton_clicked(self):
        if self.enable_mysql_radioButton.isChecked():
            self.enable_hdf55_radioButton.setChecked(False)
        self.on_enable_hdf5_or_mysql_toggled()

    def on_enable_hdf5_or_mysql_toggled(self):
        hdf5_enable = self.enable_hdf55_radioButton.isChecked()
        mysql_enable = not hdf5_enable
        self.hdf5_dir_lineEdit.setEnabled(hdf5_enable)
        self.mysql_ip_lineEdit.setEnabled(mysql_enable)
        self.mysql_port_lineEdit.setEnabled(mysql_enable)
        self.mysql_usr_lineEdit.setEnabled(mysql_enable)
        self.mysql_pwd_lineEdit.setEnabled(mysql_enable)
        self.mysql_test_pushButton.setEnabled(mysql_enable)

    @pyqtSlot()
    def on_mysql_tmpdir_pushButton_clicked(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        config = self.getCurrentConfig()
        dlg.setDirectory(config['mysql']['tmpdir'])
        if dlg.exec_():
            dirname = dlg.selectedFiles()
            self.mysql_tmpdir_lineEdit.setText(dirname[0])

    @pyqtSlot()
    def on_mysql_test_pushButton_clicked(self):
        """测试数据库连接"""
        db_config = {
            'user': self.mysql_usr_lineEdit.text(),
            'password': self.mysql_pwd_lineEdit.text(),
            'host': self.mysql_ip_lineEdit.text(),
            'port': self.mysql_port_lineEdit.text()
        }

        try:
            cnx = mysql.connector.connect(**db_config)
            cnx.close()
        except mysql.connector.Error as err:
            if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
                QMessageBox.critical(self, "测试数据库连接", "MYSQL密码或用户名错误!")
            elif err.errno == errorcode.ER_BAD_DB_ERROR:
                QMessageBox.critical(self, "测试数据库连接", "MySQL数据库不存在!")
            else:
                QMessageBox.critical(self, "测试数据库连接", err.msg)
            return

        QMessageBox.about(self, "测试数据库连接", " 连接成功!")

    def reset_progress_bar(self):
        self.hdf5_weight_label.setText('')
        self.hdf5_day_progressBar.setValue(0)
        self.hdf5_min_progressBar.setValue(0)
        self.hdf5_5min_progressBar.setValue(0)
        self.hdf5_trans_progressBar.setValue(0)
        self.hdf5_time_progressBar.setValue(0)
        #self.finance_progressBar.setValue(0)
        self.import_detail_textEdit.clear()

    def on_escapte_time(self, escape):
        self.import_status_label.setText("耗时:{:>.2f} 秒".format(escape))

    def on_message_from_thread(self, msg):
        if not msg or len(msg) < 2:
            print("msg is empty!")
            return

        msg_name, msg_task_name = msg[:2]
        if msg_name == 'ESCAPE_TIME':
            self.escape_time = msg_task_name
            self.import_status_label.setText("耗时:{:>.2f} 秒 ({:>.2f}分钟)".format(
                self.escape_time, self.escape_time / 60))

        elif msg_name == 'HDF5_IMPORT':
            if msg_task_name == 'INFO':
                self.import_detail_textEdit.append(msg[2])

            elif msg_task_name == 'THREAD':
                status = msg[2]
                if status == 'FAILURE':
                    self.import_status_label.setText(
                        "耗时:{:>.2f} 秒 导入异常!".format(self.escape_time))
                    self.import_detail_textEdit.append(msg[3])
                self.hdf5_import_thread.terminate()
                self.hdf5_import_thread = None
                self.escape_time_thread.stop()
                self.escape_time_thread = None
                self.start_import_pushButton.setEnabled(True)
                self.import_detail_textEdit.append("导入完毕!")
                self.import_running = False

            elif msg_task_name == 'IMPORT_KDATA':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_import_progress_bar[ktype].setValue(progress)
                else:
                    self.import_detail_textEdit.append(
                        '导入 {} {} 记录数:{}'.format(msg[3], msg[4], msg[5]))

            elif msg_task_name == 'IMPORT_TRANS':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_trans_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分笔记录数:{}'.format(
                        msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_TIME':
                ktype, progress = msg[2:4]
                if ktype != 'FINISHED':
                    self.hdf5_time_progressBar.setValue(progress)
                else:
                    self.import_detail_textEdit.append('导入 {} 分时记录数:{}'.format(
                        msg[3], msg[5]))

            elif msg_task_name == 'IMPORT_WEIGHT':
                self.hdf5_weight_label.setText(msg[2])
                if msg[2] == '导入权息数据完毕!':
                    self.import_detail_textEdit.append('导入权息记录数:{}'.format(
                        msg[3]))
                #elif msg[2] == '导入通达信财务信息完毕!':
                #    self.import_detail_textEdit.append('导入通达信财务记录数:{}'.format(msg[3]))

            #elif msg_task_name == 'IMPORT_FINANCE':
            #    if msg[2] != 'FINISHED':
            #        self.finance_progressBar.setValue(msg[2])

    @pyqtSlot()
    def on_start_import_pushButton_clicked(self):
        config = self.getCurrentConfig()
        dest_dir = config.get('hdf5', 'dir')
        if not os.path.exists(dest_dir) or not os.path.isdir(dest_dir):
            QMessageBox.about(self, "错误", '指定的目标数据存放目录不存在!')
            return

        if config.getboolean('tdx', 'enable') \
            and (not os.path.exists(config['tdx']['dir']
                 or os.path.isdir(config['tdx']['dir']))):
            QMessageBox.about(self, "错误", "请确认通达信安装目录是否正确!")
            return

        self.import_running = True
        self.start_import_pushButton.setEnabled(False)
        self.reset_progress_bar()

        self.import_status_label.setText("正在启动任务....")
        QApplication.processEvents()

        if self.tdx_radioButton.isChecked():
            self.hdf5_import_thread = UseTdxImportToH5Thread(config)
        else:
            self.hdf5_import_thread = UsePytdxImportToH5Thread(config)

        self.hdf5_import_thread.message.connect(self.on_message_from_thread)
        self.hdf5_import_thread.start()

        self.escape_time = 0.0
        self.escape_time_thread = EscapetimeThread()
        self.escape_time_thread.message.connect(self.on_message_from_thread)
        self.escape_time_thread.start()

    def start_collect(self):
        self.collect_sh_thread = CollectThread(self.getCurrentConfig(), 'SH',
                                               2)
        self.collect_sh_thread.start()
        self.collect_sz_thread = CollectThread(self.getCurrentConfig(), 'SZ',
                                               2)
        self.collect_sz_thread.start()

    def stop_collect(self):
        self.logger.info("终止采集!")
        if self.collect_sh_thread is not None:
            self.collect_sh_thread.working = False
            self.collect_sh_thread.terminate()
            del self.collect_sh_thread
            self.collect_sh_thread = None

        if self.collect_sz_thread is not None:
            self.collect_sz_thread.working = False
            self.collect_sz_thread.terminate()
            del self.collect_sz_thread
            self.collect_sz_thread = None

    @pyqtSlot()
    def on_collect_start_pushButton_clicked(self):
        if self.collect_running:
            self.stop_collect()
            self.collect_status_Label.setText("已停止")
            self.collect_start_pushButton.setText("启动定时采集")
            self.collect_running = False
        else:
            config = self.getCurrentConfig()
            if not config.getboolean("mysql", "enable", fallback=False):
                QMessageBox.critical(self, "定时采集", "仅在存储设置为 MySQL 时支持定时采集!")
                return
            self.collect_status_Label.setText("运行中...")
            self.start_collect()
            self.collect_start_pushButton.setText("停止采集")
            self.collect_running = True