当前位置: 首页 > news >正文

PyQt6实例_A股日数据维护工具_权息数据增量更新线程

目录

前置:

代码:

1 工作类

2 数据库交互

3 主界面启用子线程

视频:


前置:

1 本系列将以 “PyQt6实例_A股日数据维护工具” 开头放置在“PyQt6实例”专栏
专栏地址 https://blog.csdn.net/m0_37967652/category_12929760.html
2 日数据可在“数据库”专栏,“PostgreSQL_”开头系列博文中获取
3 权息数据可以在“随想”专栏,“A股复权计算_”开头系列博文中获取

代码:

1 工作类

class Worker_ExDiv(QObject):
    signal_finished =pyqtSignal(str)
    signal_error = pyqtSignal(tuple)
    @pyqtSlot(object)
    def do_work(self,task_data:dict):
        try:
            '''
            'splits': splits_csv_path,
            'allotment':allotment_csv_path,
            'equdiv':equdiv_csv_path
            '''
            splits_csv_path = task_data['splits']
            allotment_csv_path = task_data['allotment']
            equdiv_csv_path = task_data['equdiv']

            df_splits = pd.read_csv(splits_csv_path, encoding='utf-8')
            df_allotment = pd.read_csv(allotment_csv_path, encoding='utf-8')
            df_equdiv = pd.read_csv(equdiv_csv_path, encoding='utf-8')

            col_splits = ['ticker', 'reTradeDate', 'splitsRatio']
            col_equdiv = ['ticker', 'exDivDate', 'bonusShareListDate', 'perShareDivRatio', 'perShareTransRatio',
                          'perCashDiv']
            col_allotment = ['ticker', 'exRightsDate', 'allotmentRatio', 'allotmentPrice']
            target_col = ['ticker', 'exDate', 'perShareTransRadio', 'perCashDiv', 'allotmentRatio', 'allotmentPrice']
            df_equdiv['perShareDivRatio'] = df_equdiv['perShareDivRatio'].fillna(0)
            df_equdiv['perShareTransRatio'] = df_equdiv['perShareTransRatio'].fillna(0)
            df_equdiv['perCashDiv'] = df_equdiv['perCashDiv'].fillna(0)
            df_allotment['allotmentRatio'] = df_allotment['allotmentRatio'].fillna(0)
            df_allotment['allotmentPrice'] = df_allotment['allotmentPrice'].fillna(0)

            # 如果 exDivDate 和 bonusShareListDate 都为空,说明该行不需要除权除息
            df_equdiv.dropna(how='all', subset=['exDivDate', 'bonusShareListDate'], inplace=True)
            # 拆股信息,只取拆股率大于等于1的数据
            df_splits = df_splits.loc[df_splits['splitsRatio'] >= 1].copy()

            df_splits['ticker'] = df_splits['secID'].str.slice(0, 6)
            df_equdiv['ticker'] = df_equdiv['secID'].str.slice(0, 6)
            df_allotment['ticker'] = df_allotment['secID'].str.slice(0, 6)

            df_splits = df_splits.loc[:, col_splits].copy()
            df_equdiv = df_equdiv.loc[:, col_equdiv].copy()
            df_allotment = df_allotment.loc[:, col_allotment].copy()

            df_equdiv['exDivDate'] = df_equdiv['exDivDate'].fillna(df_equdiv['bonusShareListDate'])
            df_equdiv['perShareTransRadio'] = df_equdiv['perShareDivRatio'] + df_equdiv['perShareTransRatio']

            df = pd.DataFrame(columns=['ticker', 'exDate', 'perShareTransRadio', 'perCashDiv'])
            df['ticker'] = df_equdiv['ticker'].to_list()
            df['exDate'] = df_equdiv['exDivDate'].to_list()
            df['perShareTransRadio'] = df_equdiv['perShareTransRadio'].to_list()
            df['perCashDiv'] = df_equdiv['perCashDiv'].to_list()

            df_allotment.rename(columns={'exRightsDate': 'exDate'}, inplace=True)
            df_splits.rename(columns={'reTradeDate': 'exDate'}, inplace=True)

            df = pd.merge(df, df_allotment, on=['ticker', 'exDate'], how='outer')
            df = pd.merge(df, df_splits, on=['ticker', 'exDate'], how='outer')
            df['perShareTransRadio'] = df['perShareTransRadio'].fillna(0)
            df.loc[df['splitsRatio'].notnull(), 'perShareTransRadio'] = df['perShareTransRadio'] + df['splitsRatio'] - 1

            df['perCashDiv'] = df['perCashDiv'].fillna(0)
            df['allotmentRatio'] = df['allotmentRatio'].fillna(0)
            df['allotmentPrice'] = df['allotmentPrice'].fillna(0)
            df['date_o'] = pd.to_datetime(df['exDate'])
            df.sort_values(by='date_o', inplace=True)
            df = df.loc[:, target_col]
            postgresql_utils.incrementtal_update_exdiv(df)
            pass
        except Exception:
            traceback.print_exc()
            exctype,value = sys.exc_info()[:2]
            self.signal_error.emit(('exdiv_thread',exctype,value,traceback.format_exc()))
            pass
        finally:
            self.signal_finished.emit('exdiv')
        pass
    pass

2 数据库交互

def incrementtal_update_exdiv(df:pd.DataFrame)->None:
    ticker_list = df['ticker'].to_list()
    ticker_list_str = '\',\''.join(ticker_list)
    ticker_list_str = '\'' + ticker_list_str + '\''
    sql_str = f"select ticker,exDate,perShareTransRadio,perCashDiv,allotmentRatio,allotmentPrice from t_exdividend where ticker in ({ticker_list_str});"

    conn = connect_db()
    cur = conn.cursor()
    cur.execute(sql_str)
    res = cur.fetchall()
    df00 = None
    for one_node in res:
        ticker = one_node[0]
        df_node = pd.DataFrame({
            'exDate':one_node[1],
            'perShareTransRadio':one_node[2],
            'perCashDiv':one_node[3],
            'allotmentRatio':one_node[4],
            'allotmentPrice':one_node[5]
        })
        df_node['ticker'] = ticker
        if df00 is None:
            df00 = df_node.copy()
        else:
            df00 = pd.concat([df00,df_node])
        pass

    df = pd.concat([df,df00])
    df.drop_duplicates(inplace=True)

    df_group = df.groupby(by='ticker')
    data_list = []
    for ticker, group in df_group:
        one_node = (
            ticker,
            group['exDate'].to_list(),
            group['perShareTransRadio'].to_list(),
            group['perCashDiv'].to_list(),
            group['allotmentRatio'].to_list(),
            group['allotmentPrice'].to_list()
        )
        data_list.append(one_node)
        pass

    sql_insert_str = '''
                insert into t_exdividend (ticker,exDate,perShareTransRadio,perCashDiv,allotmentRatio,allotmentPrice) values (%s,%s,%s,%s,%s,%s);
                '''
    sql_delete_str = f"delete from t_exdividend where ticker in ({ticker_list_str});"

    try:
        cur.execute(sql_delete_str)
        cur.executemany(sql_insert_str,data_list)
        conn.commit()
        pass
    except Exception as e:
        print(f'error: {e}')
        conn.rollback()
        pass
    finally:
        cur.close()
        conn.close()
        pass
    pass

3 主界面启用子线程

class MainWindow(QMainWindow):
    signal_daily = pyqtSignal(object)
    signal_exdiv = pyqtSignal(object)
    def __init__(self):
        super().__init__()
        self.setWindowTitle('股票日数据维护')
        self.setMinimumSize(QSize(800,600))

        self.btn_download = QPushButton('下载某个股票未复权数据',clicked=self.btn_download_clicked)
        self.btn_download_adj = QPushButton('下载某个股票前复权数据',clicked=self.btn_download_adj_clicked)
        self.btn_download_ex = QPushButton('下载某个股票的权息数据',clicked=self.btn_download_ex_clicked)

        groupbox1 = QGroupBox('日数据更新')
        groupbox2 = QGroupBox('权息数据更新')

        label11 = QLabel('增量日数据csv文件所在目录')
        label21 = QLabel('拆股数据csv文件位置:')
        label22 = QLabel('配股数据csv文件位置:')
        label23 = QLabel('分红数据csv文件位置:')

        self.lineedit_daily_dir = QLineEdit()
        self.lineedit_sqlits_file = QLineEdit()
        self.lineedit_allotment_file = QLineEdit()
        self.lineedit_equdiv_file = QLineEdit()

        self.btn_daily_dir = QPushButton('打开文件夹',clicked=self.btn_daily_dir_clicked)
        self.btn_splits_file = QPushButton('打开文件',clicked=self.btn_splits_file_clicked)
        self.btn_allotment_file = QPushButton('打开文件',clicked=self.btn_allotment_file_clicked)
        self.btn_equdiv_file = QPushButton('打开文件',clicked=self.btn_equdiv_file_clicked)

        self.btn_execute_daily = QPushButton('执行',clicked=self.btn_execute_daily_clicked)
        self.btn_execute_ex = QPushButton('执行',clicked=self.btn_execute_ex_clicked)

        self.btn_current_data = QPushButton('查看数据表中最新数据',clicked=self.btn_current_data_clicked)

        label31 = QLabel('运行日志:')
        label32 = QLabel('备忘信息:')

        self.btn_add_notes = QPushButton('添加',clicked=self.btn_add_notes_clicked)

        self.textedit_log = QTextEdit()

        self.table_widget = QTableWidget()
        self.table_widget.setColumnCount(3)
        self.table_widget.setHorizontalHeaderLabels(['时间','项','备忘内容'])

        layout00 = QHBoxLayout()
        layout00.addWidget(self.btn_download)
        layout00.addWidget(self.btn_download_adj)
        layout00.addWidget(self.btn_download_ex)

        layout11 = QHBoxLayout()
        layout11.addWidget(label11)
        layout11.addWidget(self.lineedit_daily_dir)
        layout11.addWidget(self.btn_daily_dir)

        layout12 = QHBoxLayout()
        layout12.addStretch(1)
        layout12.addWidget(self.btn_current_data)
        layout12.addWidget(self.btn_execute_daily)

        layout13 = QVBoxLayout()
        layout13.addLayout(layout11)
        layout13.addLayout(layout12)

        groupbox1.setLayout(layout13)

        layout21 = QHBoxLayout()
        layout21.addWidget(label21)
        layout21.addWidget(self.lineedit_sqlits_file)
        layout21.addWidget(self.btn_splits_file)
        layout22 = QHBoxLayout()
        layout22.addWidget(label22)
        layout22.addWidget(self.lineedit_allotment_file)
        layout22.addWidget(self.btn_allotment_file)
        layout23 = QHBoxLayout()
        layout23.addWidget(label23)
        layout23.addWidget(self.lineedit_equdiv_file)
        layout23.addWidget(self.btn_equdiv_file)

        layout24 = QHBoxLayout()
        layout24.addStretch(1)
        layout24.addWidget(self.btn_execute_ex)

        layout25 = QVBoxLayout()
        layout25.addLayout(layout21)
        layout25.addLayout(layout22)
        layout25.addLayout(layout23)
        layout25.addLayout(layout24)
        groupbox2.setLayout(layout25)

        layout31 = QVBoxLayout()
        layout31.addWidget(label31)
        layout31.addWidget(self.textedit_log)

        layout32 = QHBoxLayout()
        layout32.addWidget(label32)
        layout32.addWidget(self.btn_add_notes)
        layout33 = QVBoxLayout()
        layout33.addLayout(layout32)
        layout33.addWidget(self.table_widget)

        layout34 = QHBoxLayout()
        layout34.addLayout(layout31)
        layout34.addLayout(layout33)

        layout = QVBoxLayout()
        layout.addLayout(layout00)
        layout.addWidget(groupbox1)
        layout.addWidget(groupbox2)
        layout.addLayout(layout34)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        self.open_init()
        pass
    def open_init(self):
        self.daily_worker = None
        self.daily_worker_thread = None
        self.exdiv_worker = None
        self.exdiv_worker_thread = None
        pass
    def btn_download_clicked(self):
        pass
    def btn_download_adj_clicked(self):
        pass
    def btn_download_ex_clicked(self):
        pass
    def btn_daily_dir_clicked(self):
        pass
    def btn_splits_file_clicked(self):
        pass
    def btn_allotment_file_clicked(self):
        pass
    def btn_equdiv_file_clicked(self):
        pass
    def btn_execute_daily_clicked(self):
        pre_dir = self.lineedit_daily_dir.text()
        if not pre_dir or len(pre_dir.strip())<=0:
            self.message_dialog('请选择日数据csv文件所在文件夹')
            return
        file_list = os.listdir(pre_dir)
        if not file_list or len(file_list)<=0:
            self.message_dialog('所选文件夹为空')
            return

        self.daily_worker = Worker_Daily()
        self.daily_worker_thread = QThread()
        self.daily_worker.signal_finished.connect(self.worker_signal_finished)
        self.daily_worker.signal_error.connect(self.thread_signal_error)
        self.signal_daily.connect(self.daily_worker.do_work)
        self.daily_worker.moveToThread(self.daily_worker_thread)
        self.daily_worker_thread.start()

        self.btn_execute_daily.setEnabled(False)
        self.lineedit_daily_dir.setEnabled(False)
        self.btn_daily_dir.setEnabled(False)
        task_data = {
            'pre_dir':pre_dir
        }
        self.signal_daily.emit(task_data)
        self.insert_log_msg('日数据开始更新.')
        pass
    def worker_signal_finished(self,thread_name:str):
        if thread_name == 'daily':
            self.btn_execute_daily.setEnabled(True)
            self.lineedit_daily_dir.setEnabled(True)
            self.btn_daily_dir.setEnabled(True)

            self.insert_log_msg('日数据更新完毕。')
            self.daily_worker_thread.exit()
            pass
        elif thread_name == 'exdiv':
            self.btn_execute_ex.setEnabled(True)
            self.lineedit_sqlits_file.setEnabled(True)
            self.lineedit_allotment_file.setEnabled(True)
            self.lineedit_equdiv_file.setEnabled(True)
            self.btn_splits_file.setEnabled(True)
            self.btn_allotment_file.setEnabled(True)
            self.btn_equdiv_file.setEnabled(True)

            self.insert_log_msg('权息数据更新完毕')
            self.exdiv_worker_thread.exit()
        else:
            pass
        pass
    def thread_signal_error(self,error:tuple):
        self.insert_log_msg(f"{error[0]} {error[-1]}")
        pass
    def btn_execute_ex_clicked(self):
        splits_csv_path = self.lineedit_sqlits_file.text()
        allotment_csv_path = self.lineedit_allotment_file.text()
        equdiv_csv_path = self.lineedit_equdiv_file.text()
        if not splits_csv_path or len(splits_csv_path.strip()) <= 0:
            self.message_dialog('请选择拆股csv文件')
            return
        if not allotment_csv_path or len(allotment_csv_path.strip()) <= 0:
            self.message_dialog('请选择配股csv文件')
            return
        if not equdiv_csv_path or len(equdiv_csv_path.strip()) <= 0:
            self.message_dialog('请选择分红csv文件')
            return

        self.exdiv_worker = Worker_ExDiv()
        self.exdiv_worker_thread = QThread()
        self.exdiv_worker.signal_finished.connect(self.worker_signal_finished)
        self.exdiv_worker.signal_error.connect(self.thread_signal_error)
        self.signal_exdiv.connect(self.exdiv_worker.do_work)
        self.exdiv_worker.moveToThread(self.exdiv_worker_thread)
        self.exdiv_worker_thread.start()

        self.btn_execute_ex.setEnabled(False)
        self.lineedit_sqlits_file.setEnabled(False)
        self.lineedit_allotment_file.setEnabled(False)
        self.lineedit_equdiv_file.setEnabled(False)
        self.btn_splits_file.setEnabled(False)
        self.btn_allotment_file.setEnabled(False)
        self.btn_equdiv_file.setEnabled(False)
        task_data = {
            'splits': splits_csv_path,
            'allotment':allotment_csv_path,
            'equdiv':equdiv_csv_path
        }
        self.signal_exdiv.emit(task_data)
        self.insert_log_msg('权息数据开始更新.')
        pass
    def btn_add_notes_clicked(self):
        pass
    def btn_current_data_clicked(self):
        pass
    def message_dialog(self,msg:str):
        QMessageBox.information(
            self,
            '提示',
            msg,
            QMessageBox.StandardButton.Ok
        )
    def insert_log_msg(self,msg:str):
        pre_str = f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {msg}"
        self.textedit_log.append(pre_str)
        pass

视频:

https://www.bilibili.com/video/BV1X9ZUYVEZt/
https://www.bilibili.com/video/BV1X9ZUYVEL7/

http://www.dtcms.com/a/109219.html

相关文章:

  • 禹神:三小时快速上手TypeScript,TS速通教程(上篇、中篇、下篇,装饰器),根据视频整理
  • Windows查重工具,强烈推荐大家收藏!
  • 前端接收客户端返回的token值使用pinia持久化保存token
  • 元素定位-xpath
  • verl单机多卡与多机多卡使用经验总结
  • MCP的基本组成部分有哪些?MCP Servers服务器起到什么作用?
  • Jetpack Compose 状态管理指南:从基础到高级实践
  • 机器学习算法分类全景解析:从理论到工业实践(2025新版)
  • Electron读取本地Json文件(Win、Mac)
  • JSVMP逆向实战:原理分析与破解思路详解
  • day21 学习笔记
  • 【SPP】蓝牙链路控制(LC)在SPP中互操作性深度解析
  • Cron表达式
  • 什么是混杂模式?为什么 macvlan 依赖它
  • B2B2C商城系统开发:从规划到上线的全流程指南
  • 函数柯里化(Currying)介绍(一种将接受多个参数的函数转换为一系列接受单一参数的函数的技术)
  • 数字孪生在智慧城市中的前端呈现与 UI 设计思路
  • CentOS 7 镜像源失效解决方案(2025年)
  • 【Mysql】之索引详解
  • 游戏无法启动?XINPUT1_3.dll 丢失的终极解决方案
  • 国产替代新选择:CCLink IE与EtherCAT网关在制药行业的应用,配置详解
  • python之 “__init__.py” 文件
  • DeepSeek-R1 面试题汇总
  • SAP-ABAP:SAP ABAP UPDATE 语句用法详解
  • 如何像母语一样学习英语
  • VMware ESXi:企业级虚拟化平台详解
  • MySQL-- 函数(单行函数): 日期和时间函数
  • Linux内核TCP/IP协议栈中的设计模式:从面向对象到系统级软件的跨界实践
  • 数据结构——顺序表
  • 思维链(Chain-of-Thought, CoT)与强化学习区别