diff --git a/hikyuu/gui/HikyuuTDX.py b/hikyuu/gui/HikyuuTDX.py index ba8736ec..fe21745a 100644 --- a/hikyuu/gui/HikyuuTDX.py +++ b/hikyuu/gui/HikyuuTDX.py @@ -8,8 +8,8 @@ import datetime from configparser import ConfigParser from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox -from PyQt5.QtCore import pyqtSlot -from PyQt5.QtGui import QIcon +from PyQt5.QtCore import pyqtSlot, QObject, pyqtSignal +from PyQt5.QtGui import QIcon, QTextCursor import mysql.connector from mysql.connector import errorcode @@ -19,18 +19,35 @@ from hikyuu.gui.data.MainWindow import * from hikyuu.gui.data.EscapetimeThread import EscapetimeThread from hikyuu.gui.data.UseTdxImportToH5Thread import UseTdxImportToH5Thread from hikyuu.gui.data.UsePytdxImportToH5Thread import UsePytdxImportToH5Thread +from hikyuu.gui.data.CollectThread import CollectThread from hikyuu.data import hku_config_template +class EmittingStream(QObject): + """输出重定向至QT""" + textWritten = pyqtSignal(str) + + def write(self, text): + self.textWritten.emit(str(text)) + + def flush(self): + pass + + class MyMainWindow(QMainWindow, Ui_MainWindow): - def __init__(self, parent=None): + def __init__(self, parent=None, capture_output=False): super(MyMainWindow, self).__init__(parent) + self._capture_output = capture_output #捕获Python stdout 输出 + self.logger = logging.getLogger(self.__class__.__name__) 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() @@ -89,7 +106,39 @@ class MyMainWindow(QMainWindow, Ui_MainWindow): 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) + logger_name_list = [ + self.__class__.__name__, CollectThread.__name__, UsePytdxImportToH5Thread.__name__, + UseTdxImportToH5Thread.__name__ + ] + for name in logger_name_list: + logger = logging.getLogger(name) + logger.addHandler(con) + logger.setLevel(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()) @@ -107,6 +156,8 @@ class MyMainWindow(QMainWindow, Ui_MainWindow): 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() @@ -244,6 +295,8 @@ class MyMainWindow(QMainWindow, Ui_MainWindow): 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 = { @@ -460,6 +513,43 @@ class MyMainWindow(QMainWindow, Ui_MainWindow): 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 + def start(): app = QApplication(sys.argv) @@ -471,16 +561,15 @@ def start(): if __name__ == "__main__": app = QApplication(sys.argv) if (len(sys.argv) > 1 and sys.argv[1] == '0'): - FORMAT = '%(asctime)-15s %(levelname)s: %(message)s [%(name)s::%(funcName)s]' + FORMAT = '%(asctime)-15s [%(levelname)s]: %(message)s [%(name)s::%(funcName)s]' logging.basicConfig( format=FORMAT, level=logging.INFO, handlers=[ logging.StreamHandler(), ] ) - capture_output = False + myWin = MyMainWindow(capture_output=False) else: - capture_output = True + myWin = MyMainWindow(capture_output=True) - myWin = MyMainWindow(None) myWin.show() sys.exit(app.exec()) diff --git a/hikyuu/gui/data/CollectThread.py b/hikyuu/gui/data/CollectThread.py new file mode 100644 index 00000000..4cf98dfc --- /dev/null +++ b/hikyuu/gui/data/CollectThread.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf8 -*- +# cp936 +import logging +import time +from PyQt5.QtCore import QThread + +import mysql.connector +from mysql.connector import errorcode + + +class CollectThread(QThread): + def __init__(self, config, market='SH', interval=60): + super(self.__class__, self).__init__() + self.logger = logging.getLogger(self.__class__.__name__) + self.working = True + self._interval = interval + self._config = config + self.market = market + assert config.getboolean("mysql", "enable", fallback=False) + self._db_config = { + 'user': config['mysql']['usr'], + 'password': config['mysql']['pwd'], + 'host': config['mysql']['host'], + 'port': config['mysql']['port'] + } + self._connect = None + + def __del__(self): + self.working = False + if self._connect is not None: + self._connect.close() + self.wait() + + def run(self): + self.logger.info("{} 数据采集同步线程启动 ({})".format(self.market, self.currentThreadId())) + while self.working == True: + start = time.time() + self.collect() + end = time.time() + x = end - start + if x < self._interval: + delta = int(self._interval - x) + self.logger.info("{} {} 秒后重新采集".format(self.market, delta)) + self.sleep(delta) + self.logger.info("{} 数据采集同步线程终止 ({})!".format(self.market, self.currentThreadId())) + + def collect(self): + self.logger.info("{} collect".format(self.market)) + + def get_connect(self): + if self._connect is None: + try: + self._connect = mysql.connector.connect(**self._db_config) + except mysql.connector.Error as err: + if err.errno == errorcode.ER_ACCESS_DENIED_ERROR: + self.logger.error("MYSQL密码或用户名错误!") + elif err.errno == errorcode.ER_BAD_DB_ERROR: + self.logger.error("MySQL数据库不存在!") + else: + self.logger.error("连接数据库失败,{}".format(err.msg)) + except: + self.logger.error("未知原因导致无法连接数据库!") + return self._connect diff --git a/hikyuu/gui/data/ImportPytdxTimeToH5Task.py b/hikyuu/gui/data/ImportPytdxTimeToH5Task.py index 674a7a80..79251fa7 100644 --- a/hikyuu/gui/data/ImportPytdxTimeToH5Task.py +++ b/hikyuu/gui/data/ImportPytdxTimeToH5Task.py @@ -22,6 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import logging import sqlite3 from pytdx.hq import TdxHq_API from hikyuu.data.pytdx_to_h5 import import_time @@ -39,6 +40,7 @@ class ProgressBar: class ImportPytdxTimeToH5: def __init__(self, queue, sqlitefile, market, quotations, ip, port, dest_dir, max_days): + self.logger = logging.getLogger(self.__class__.__name__) self.task_name = 'IMPORT_TIME' self.queue = queue self.sqlitefile = sqlitefile @@ -66,7 +68,7 @@ class ImportPytdxTimeToH5: progress=progress ) except Exception as e: - print(e) + self.logger.error(e) finally: connect.commit() connect.close() diff --git a/hikyuu/gui/data/ImportPytdxToH5Task.py b/hikyuu/gui/data/ImportPytdxToH5Task.py index 63523d6d..7cb68c62 100644 --- a/hikyuu/gui/data/ImportPytdxToH5Task.py +++ b/hikyuu/gui/data/ImportPytdxToH5Task.py @@ -22,6 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import logging import sqlite3 import mysql.connector from pytdx.hq import TdxHq_API @@ -43,6 +44,7 @@ class ImportPytdxToH5: def __init__( self, queue, config, market, ktype, quotations, ip, port, dest_dir, start_datetime ): + self.logger = logging.getLogger(self.__class__.__name__) self.task_name = 'IMPORT_KDATA' self.queue = queue self.config = config @@ -59,7 +61,7 @@ class ImportPytdxToH5: sqlite_file = "{}/stock.db".format(self.config['hdf5']['dir']) connect = sqlite3.connect(sqlite_file, timeout=1800) import_data = h5_import_data - print('use hdf5 import kdata') + self.logger.debug('use hdf5 import kdata') else: db_config = { 'user': self.config['mysql']['usr'], @@ -69,7 +71,7 @@ class ImportPytdxToH5: } connect = mysql.connector.connect(**db_config) import_data = mysql_import_data - print('use mysql import kdata') + self.logger.debug('use mysql import kdata') count = 0 try: @@ -81,7 +83,7 @@ class ImportPytdxToH5: self.startDatetime, progress ) except Exception as e: - print("ImportPytdxToH5Task failed!", e) + self.logger.error("ImportPytdxToH5Task failed! {}".format(e)) #self.queue.put([self.task_name, self.market, self.ktype, str(e), count]) finally: connect.commit() diff --git a/hikyuu/gui/data/ImportPytdxTransToH5Task.py b/hikyuu/gui/data/ImportPytdxTransToH5Task.py index 95ba1d69..c80d50fe 100644 --- a/hikyuu/gui/data/ImportPytdxTransToH5Task.py +++ b/hikyuu/gui/data/ImportPytdxTransToH5Task.py @@ -22,6 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import logging import sqlite3 from pytdx.hq import TdxHq_API from hikyuu.data.pytdx_to_h5 import import_data, import_trans @@ -39,6 +40,7 @@ class ProgressBar: class ImportPytdxTransToH5: def __init__(self, queue, sqlitefile, market, quotations, ip, port, dest_dir, max_days): + self.logger = logging.getLogger(self.__class__.__name__) self.task_name = 'IMPORT_TRANS' self.queue = queue self.sqlitefile = sqlitefile @@ -67,7 +69,7 @@ class ImportPytdxTransToH5: progress=progress ) except Exception as e: - print(e) + self.logger.error(e) finally: connect.commit() connect.close() diff --git a/hikyuu/gui/data/ImportTdxToH5Task.py b/hikyuu/gui/data/ImportTdxToH5Task.py index 44f49bc9..7a723c2e 100644 --- a/hikyuu/gui/data/ImportTdxToH5Task.py +++ b/hikyuu/gui/data/ImportTdxToH5Task.py @@ -22,6 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import logging import sqlite3 from hikyuu.data.tdx_to_h5 import tdx_import_data @@ -40,6 +41,7 @@ class ProgressBar: class ImportTdxToH5Task: def __init__(self, queue, sqlitefile, market, ktype, quotations, src_dir, dest_dir): super(self.__class__, self).__init__() + self.logger = logging.getLogger(self.__class__.__name__) self.task_name = 'IMPORT_KDATA' self.queue = queue self.sqlitefile = sqlitefile @@ -76,5 +78,5 @@ class ImportTdxToH5Task: progress ) except Exception as e: - print(e) + self.logger.error(e) self.queue.put([self.task_name, self.market, self.ktype, None, count]) diff --git a/hikyuu/gui/data/ImportWeightToSqliteTask.py b/hikyuu/gui/data/ImportWeightToSqliteTask.py index c2834f1c..c533e15e 100644 --- a/hikyuu/gui/data/ImportWeightToSqliteTask.py +++ b/hikyuu/gui/data/ImportWeightToSqliteTask.py @@ -23,6 +23,7 @@ # SOFTWARE. import os +import logging import hashlib import sqlite3 import urllib.request @@ -38,6 +39,7 @@ from hikyuu.data.pytdx_weight_to_mysql import pytdx_import_weight_to_mysql class ImportWeightToSqliteTask: def __init__(self, queue, config, dest_dir): + self.logger = logging.getLogger(self.__class__.__name__) self.queue = queue self.config = config self.dest_dir = dest_dir @@ -50,7 +52,7 @@ class ImportWeightToSqliteTask: sqlite_file = "{}/stock.db".format(self.config['hdf5']['dir']) connect = sqlite3.connect(sqlite_file, timeout=1800) pytdx_import_weight = pytdx_import_weight_to_sqlite - print('use sqlite import weight') + self.logger.debug('use sqlite import weight') else: db_config = { 'user': self.config['mysql']['usr'], @@ -60,7 +62,7 @@ class ImportWeightToSqliteTask: } connect = mysql.connector.connect(**db_config) pytdx_import_weight = pytdx_import_weight_to_mysql - print('use mysql import weight') + self.logger.debug('use mysql import weight') except Exception as e: #self.queue.put([self.msg_name, str(e), -1, 0, total_count]) @@ -127,6 +129,7 @@ class ImportWeightToSqliteTask: api.disconnect() except Exception as e: + self.logger.error(e) #self.queue.put([self.msg_name, str(e), -1, 0, total_count]) self.queue.put([self.msg_name, 'INFO', str(e), 0, 0]) finally: diff --git a/hikyuu/gui/data/MainWindow.py b/hikyuu/gui/data/MainWindow.py index b3a9f23e..9128538d 100644 --- a/hikyuu/gui/data/MainWindow.py +++ b/hikyuu/gui/data/MainWindow.py @@ -312,9 +312,9 @@ class Ui_MainWindow(object): self.tabWidget.addTab(self.tab_2, "") self.tab = QtWidgets.QWidget() self.tab.setObjectName("tab") - self.time_no_week_checkBox = QtWidgets.QCheckBox(self.tab) - self.time_no_week_checkBox.setGeometry(QtCore.QRect(40, 200, 101, 16)) - self.time_no_week_checkBox.setObjectName("time_no_week_checkBox") + self.collect_no_week_checkBox = QtWidgets.QCheckBox(self.tab) + self.collect_no_week_checkBox.setGeometry(QtCore.QRect(40, 200, 101, 16)) + self.collect_no_week_checkBox.setObjectName("collect_no_week_checkBox") self.layoutWidget3 = QtWidgets.QWidget(self.tab) self.layoutWidget3.setGeometry(QtCore.QRect(40, 160, 158, 22)) self.layoutWidget3.setObjectName("layoutWidget3") @@ -324,62 +324,62 @@ class Ui_MainWindow(object): self.label_23 = QtWidgets.QLabel(self.layoutWidget3) self.label_23.setObjectName("label_23") self.horizontalLayout_10.addWidget(self.label_23) - self.time_sample_spinBox = QtWidgets.QSpinBox(self.layoutWidget3) - self.time_sample_spinBox.setMaximum(86400) - self.time_sample_spinBox.setObjectName("time_sample_spinBox") - self.horizontalLayout_10.addWidget(self.time_sample_spinBox) - self.widget = QtWidgets.QWidget(self.tab) - self.widget.setGeometry(QtCore.QRect(40, 30, 201, 25)) - self.widget.setObjectName("widget") - self.horizontalLayout_11 = QtWidgets.QHBoxLayout(self.widget) + self.collect_sample_spinBox = QtWidgets.QSpinBox(self.layoutWidget3) + self.collect_sample_spinBox.setMaximum(86400) + self.collect_sample_spinBox.setObjectName("collect_sample_spinBox") + self.horizontalLayout_10.addWidget(self.collect_sample_spinBox) + self.layoutWidget4 = QtWidgets.QWidget(self.tab) + self.layoutWidget4.setGeometry(QtCore.QRect(40, 30, 201, 25)) + self.layoutWidget4.setObjectName("layoutWidget4") + self.horizontalLayout_11 = QtWidgets.QHBoxLayout(self.layoutWidget4) self.horizontalLayout_11.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_11.setObjectName("horizontalLayout_11") - self.time_start_pushButton = QtWidgets.QPushButton(self.widget) - self.time_start_pushButton.setObjectName("time_start_pushButton") - self.horizontalLayout_11.addWidget(self.time_start_pushButton) - self.time_status = QtWidgets.QLabel(self.widget) - self.time_status.setObjectName("time_status") - self.horizontalLayout_11.addWidget(self.time_status) - self.widget1 = QtWidgets.QWidget(self.tab) - self.widget1.setGeometry(QtCore.QRect(40, 80, 216, 56)) - self.widget1.setObjectName("widget1") - self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1) + self.collect_start_pushButton = QtWidgets.QPushButton(self.layoutWidget4) + self.collect_start_pushButton.setObjectName("collect_start_pushButton") + self.horizontalLayout_11.addWidget(self.collect_start_pushButton) + self.collect_status_Label = QtWidgets.QLabel(self.layoutWidget4) + self.collect_status_Label.setObjectName("collect_status_Label") + self.horizontalLayout_11.addWidget(self.collect_status_Label) + self.layoutWidget5 = QtWidgets.QWidget(self.tab) + self.layoutWidget5.setGeometry(QtCore.QRect(40, 80, 216, 56)) + self.layoutWidget5.setObjectName("layoutWidget5") + self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget5) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout_8 = QtWidgets.QHBoxLayout() self.horizontalLayout_8.setObjectName("horizontalLayout_8") - self.label_21 = QtWidgets.QLabel(self.widget1) + self.label_21 = QtWidgets.QLabel(self.layoutWidget5) self.label_21.setObjectName("label_21") self.horizontalLayout_8.addWidget(self.label_21) self.horizontalLayout_6 = QtWidgets.QHBoxLayout() self.horizontalLayout_6.setObjectName("horizontalLayout_6") - self.time_phase1_start_timeEdit = QtWidgets.QTimeEdit(self.widget1) - self.time_phase1_start_timeEdit.setObjectName("time_phase1_start_timeEdit") - self.horizontalLayout_6.addWidget(self.time_phase1_start_timeEdit) - self.label_22 = QtWidgets.QLabel(self.widget1) + self.collect_phase1_start_timeEdit = QtWidgets.QTimeEdit(self.layoutWidget5) + self.collect_phase1_start_timeEdit.setObjectName("collect_phase1_start_timeEdit") + self.horizontalLayout_6.addWidget(self.collect_phase1_start_timeEdit) + self.label_22 = QtWidgets.QLabel(self.layoutWidget5) self.label_22.setObjectName("label_22") self.horizontalLayout_6.addWidget(self.label_22) - self.time_phase1_last_timeEdit = QtWidgets.QTimeEdit(self.widget1) - self.time_phase1_last_timeEdit.setObjectName("time_phase1_last_timeEdit") - self.horizontalLayout_6.addWidget(self.time_phase1_last_timeEdit) + self.collect_phase1_last_timeEdit = QtWidgets.QTimeEdit(self.layoutWidget5) + self.collect_phase1_last_timeEdit.setObjectName("collect_phase1_last_timeEdit") + self.horizontalLayout_6.addWidget(self.collect_phase1_last_timeEdit) self.horizontalLayout_8.addLayout(self.horizontalLayout_6) self.verticalLayout.addLayout(self.horizontalLayout_8) self.horizontalLayout_9 = QtWidgets.QHBoxLayout() self.horizontalLayout_9.setObjectName("horizontalLayout_9") - self.label_24 = QtWidgets.QLabel(self.widget1) + self.label_24 = QtWidgets.QLabel(self.layoutWidget5) self.label_24.setObjectName("label_24") self.horizontalLayout_9.addWidget(self.label_24) self.horizontalLayout_7 = QtWidgets.QHBoxLayout() self.horizontalLayout_7.setObjectName("horizontalLayout_7") - self.time_phase2_start_timeEdit = QtWidgets.QTimeEdit(self.widget1) - self.time_phase2_start_timeEdit.setObjectName("time_phase2_start_timeEdit") - self.horizontalLayout_7.addWidget(self.time_phase2_start_timeEdit) - self.label_25 = QtWidgets.QLabel(self.widget1) + self.collect_phase2_start_timeEdit = QtWidgets.QTimeEdit(self.layoutWidget5) + self.collect_phase2_start_timeEdit.setObjectName("collect_phase2_start_timeEdit") + self.horizontalLayout_7.addWidget(self.collect_phase2_start_timeEdit) + self.label_25 = QtWidgets.QLabel(self.layoutWidget5) self.label_25.setObjectName("label_25") self.horizontalLayout_7.addWidget(self.label_25) - self.time_phase2_last_timeEdit = QtWidgets.QTimeEdit(self.widget1) - self.time_phase2_last_timeEdit.setObjectName("time_phase2_last_timeEdit") - self.horizontalLayout_7.addWidget(self.time_phase2_last_timeEdit) + self.collect_phase2_last_timeEdit = QtWidgets.QTimeEdit(self.layoutWidget5) + self.collect_phase2_last_timeEdit.setObjectName("collect_phase2_last_timeEdit") + self.horizontalLayout_7.addWidget(self.collect_phase2_last_timeEdit) self.horizontalLayout_9.addLayout(self.horizontalLayout_7) self.verticalLayout.addLayout(self.horizontalLayout_9) self.tabWidget.addTab(self.tab, "") @@ -387,6 +387,8 @@ class Ui_MainWindow(object): self.tab_5.setObjectName("tab_5") self.log_textEdit = QtWidgets.QTextEdit(self.tab_5) self.log_textEdit.setGeometry(QtCore.QRect(10, 10, 571, 511)) + self.log_textEdit.setReadOnly(True) + self.log_textEdit.setAcceptRichText(False) self.log_textEdit.setObjectName("log_textEdit") self.tabWidget.addTab(self.tab_5, "") MainWindow.setCentralWidget(self.centralwidget) @@ -463,10 +465,10 @@ class Ui_MainWindow(object): "
导入完毕!