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/