• PyQt5_股票最近均线状态工具


    批量股票,计算出每个股票最近的均线状态,标记为多头排列、空头排列、黏合三种状态,并计算出最近该状态持续的天数,并统计出该批次股票均线状态的分布情况。基于该工具可以在整体上了解自己关注的股票池均线状态的总体情况,也可以侧重查看更关注的均线状态对应的股票。

    目录

    效果

    ​编辑甄别最近均线状态策略代码

    工具代码

    工具使用

    数据

    效果

    甄别最近均线状态策略代码

     前置说明:

    1. 必须以“excute_strategy”为方法名

    2. excute_strategy 方法的参数1,是股票代码与股票名键值对,该数据在下面数据下载解压缩后的“secID_name.csv”文件中,将“secID_name.csv”文件放置到入口代码对应目录的results_output文件夹(这个文件夹要先创建好)下,工具就能自动读取;参数2是股票日数据对应的文件夹目录,本文使用的股票日数据在下面数据解压缩后的“000”目录中,把“000”目录中的日数据文件放置到一个目录(自定义)下,在运行工具后,选择到这个文件夹,程序就能读取到日数据

    3. 本文中给出的策略代码使用的是ma5 ma20 ma60,想使用其他均线的,可以自行更改,策略代码是动态加载,想另加一个策略py文件,或修改已有的策略文件都可以

    4. 策略代码写完后,保存为.py文件,将该py文件保存到一个目录(自定义)下,运行工具后,选择到这个文件夹,程序会加载该目录下所有的py文件

    1. def excute_strategy(base_data,data_dir):
    2. '''
    3. 股票均线状态
    4. 1. 多头排列
    5. 2. 空头排列
    6. 3. 黏合
    7. 本策略选用均线:5日、20日、60日
    8. 只计算最近两年的数据
    9. :param base_data:
    10. :param data_dir:
    11. :return:
    12. '''
    13. import pandas as pd
    14. import talib,os
    15. from datetime import datetime
    16. from dateutil.relativedelta import relativedelta
    17. def res_pre_two_year_first_day():
    18. pre_year_day = (datetime.now() - relativedelta(years=2)).strftime('%Y-%m-%d')
    19. return pre_year_day
    20. caculate_start_date_str = res_pre_two_year_first_day()
    21. common_json = {
    22. '1':'多头排列',
    23. '-1':'空头排列',
    24. '2':'粘合'
    25. }
    26. dailydata_file_list = os.listdir(data_dir)
    27. res_list = []
    28. for item in dailydata_file_list:
    29. item_arr = item.split('.')
    30. ticker = item_arr[0]
    31. secName = base_data[ticker]
    32. file_path = data_dir + item
    33. df = pd.read_csv(file_path,encoding='utf-8')
    34. # 删除停牌的数据
    35. df = df.loc[df['openPrice'] > 0].copy()
    36. df['o_date'] = df['tradeDate']
    37. df['o_date'] = pd.to_datetime(df['o_date'])
    38. df = df.loc[df['o_date'] >= caculate_start_date_str].copy()
    39. # 保存未复权收盘价数据
    40. df['close'] = df['closePrice']
    41. # 计算前复权数据
    42. df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    43. df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    44. df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    45. df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
    46. if len(df)<=0:
    47. continue
    48. df['ma5'] = talib.MA(df['closePrice'],timeperiod=5)
    49. df['ma20'] = talib.MA(df['closePrice'],timeperiod=20)
    50. df['ma60'] = talib.MA(df['closePrice'],timeperiod=60)
    51. # 多头排列 1;空头排列 -1;纠缠 2
    52. df.reset_index(inplace=True)
    53. status_val = None
    54. status_num = 0
    55. for i in range(len(df)-1,0,-1):
    56. if df.iloc[i]['ma5']>df.iloc[i]['ma20'] and df.iloc[i]['ma20']>df.iloc[i]['ma60']:
    57. if status_val:
    58. if status_val == 1:
    59. status_num +=1
    60. else:
    61. break
    62. else:
    63. status_val = 1
    64. elif df.iloc[i]['ma5']'ma20'] and df.iloc[i]['ma20']'ma60']:
    65. if status_val:
    66. if status_val == -1:
    67. status_num += 1
    68. else:
    69. break
    70. else:
    71. status_val = -1
    72. else:
    73. if status_val:
    74. if status_val == 2:
    75. status_num += 1
    76. else:
    77. break
    78. else:
    79. status_val = 2
    80. pass
    81. if status_val == 0:
    82. print(ticker,status_num)
    83. res_list.append({
    84. 'ticker':ticker,
    85. 'secName':secName,
    86. 'status_val':status_val,
    87. 'status_name':common_json[str(status_val)],
    88. 'status_num':status_num
    89. })
    90. pass
    91. df00 = pd.DataFrame(res_list)
    92. df_status = df00.groupby(by='status_name').count()['ticker']
    93. bar_status_y = df_status.values.tolist()
    94. status_xname_list = df_status.index.tolist()
    95. bar_status_x = []
    96. bar_status_xTick = []
    97. for i,item in enumerate(status_xname_list):
    98. bar_status_x.append(i)
    99. bar_status_xTick.append((i,item))
    100. status_bar_data = {
    101. 'title_str':'均线排列状态分布',
    102. 'x':bar_status_x,
    103. 'y':bar_status_y,
    104. 'xTick':bar_status_xTick
    105. }
    106. results_data = {
    107. 'df':df00,
    108. 'ma':[5,20,60],
    109. 'ma_color':[0,2,4],
    110. 'start_date_str':caculate_start_date_str,
    111. 'status_bar_data':status_bar_data
    112. }
    113. return results_data

    工具代码

    导入需要的包 

    1. import sys,json,os,math,time,talib
    2. from threading import Thread
    3. import numpy as np
    4. import pandas as pd
    5. from datetime import datetime
    6. from dateutil.relativedelta import relativedelta
    7. from typing import Dict,Any,List
    8. from PyQt5 import QtCore,QtGui,QtWidgets
    9. from PyQt5.QtCore import Qt
    10. import pyqtgraph as pg
    11. import pyqtgraph.examples
    12. pg.setConfigOption('background','k')
    13. pg.setConfigOption('foreground','w')

     pyqtgraph横坐标显示日期控件

    1. class RotateAxisItem(pg.AxisItem):
    2. def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
    3. p.setRenderHint(p.Antialiasing,False)
    4. p.setRenderHint(p.TextAntialiasing,True)
    5. ## draw long line along axis
    6. pen,p1,p2 = axisSpec
    7. p.setPen(pen)
    8. p.drawLine(p1,p2)
    9. p.translate(0.5,0) ## resolves some damn pixel ambiguity
    10. ## draw ticks
    11. for pen,p1,p2 in tickSpecs:
    12. p.setPen(pen)
    13. p.drawLine(p1,p2)
    14. ## draw all text
    15. # if self.tickFont is not None:
    16. # p.setFont(self.tickFont)
    17. p.setPen(self.pen())
    18. for rect,flags,text in textSpecs:
    19. # this is the important part
    20. p.save()
    21. p.translate(rect.x(),rect.y())
    22. p.rotate(-30)
    23. p.drawText(-rect.width(),rect.height(),rect.width(),rect.height(),flags,text)
    24. # restoring the painter is *required*!!!
    25. p.restore()

     pyqtgraph 蜡烛图控件

    1. class CandlestickItem(pg.GraphicsObject):
    2. def __init__(self, data):
    3. pg.GraphicsObject.__init__(self)
    4. self.data = data ## data must have fields: time, open, close, min, max
    5. self.generatePicture()
    6. def generatePicture(self):
    7. ## pre-computing a QPicture object allows paint() to run much more quickly,
    8. ## rather than re-drawing the shapes every time.
    9. self.picture = QtGui.QPicture()
    10. p = QtGui.QPainter(self.picture)
    11. p.setPen(pg.mkPen('d'))
    12. w = (self.data[1][0] - self.data[0][0]) / 3.
    13. for (t, open, close, min, max) in self.data:
    14. p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
    15. if open < close:
    16. p.setBrush(pg.mkBrush('r'))
    17. else:
    18. p.setBrush(pg.mkBrush('g'))
    19. p.drawRect(QtCore.QRectF(t-w, open, w * 2, close - open))
    20. p.end()
    21. def paint(self, p, *args):
    22. p.drawPicture(0, 0, self.picture)
    23. def boundingRect(self):
    24. ## boundingRect _must_ indicate the entire area that will be drawn on
    25. ## or else we will get artifacts and possibly crashing.
    26. ## (in this case, QPicture does all the work of computing the bouning rect for us)
    27. return QtCore.QRectF(self.picture.boundingRect())

    pyqtgraph方形图控件

    1. class PyQtGraphHistWidget(QtWidgets.QWidget):
    2. def __init__(self):
    3. super().__init__()
    4. self.init_data()
    5. self.init_ui()
    6. def init_data(self):
    7. self.color_bar = ()
    8. pass
    9. def init_ui(self):
    10. self.title_label = QtWidgets.QLabel('均线排列状态')
    11. self.title_label.setAlignment(Qt.AlignCenter)
    12. xax = RotateAxisItem(orientation='bottom')
    13. xax.setHeight(h=50)
    14. self.pw = pg.PlotWidget(axisItems={'bottom': xax})
    15. self.pw.setMouseEnabled(x=True, y=False)
    16. # self.pw.enableAutoRange(x=False,y=True)
    17. self.pw.setAutoVisible(x=False, y=True)
    18. layout = QtWidgets.QVBoxLayout()
    19. layout.addWidget(self.title_label)
    20. layout.addWidget(self.pw)
    21. self.setLayout(layout)
    22. pass
    23. def set_data(self,data:Dict[str,Any]):
    24. title_str = data['title_str']
    25. x = data['x']
    26. y = data['y']
    27. xTick = [data['xTick']]
    28. self.y_datas = y
    29. self.x_data = xTick
    30. self.x_Tick = data['xTick']
    31. self.title_label.setText(title_str)
    32. xax = self.pw.getAxis('bottom')
    33. xax.setTicks(xTick)
    34. barItem = pg.BarGraphItem(x=x,height=y,width=0.8,brush=(107,200,224))
    35. self.pw.addItem(barItem)
    36. self.vLine = pg.InfiniteLine(angle=90, movable=False)
    37. self.hLine = pg.InfiniteLine(angle=0, movable=False)
    38. self.label = pg.TextItem()
    39. self.pw.addItem(self.vLine, ignoreBounds=True)
    40. self.pw.addItem(self.hLine, ignoreBounds=True)
    41. self.pw.addItem(self.label, ignoreBounds=True)
    42. self.vb = self.pw.getViewBox()
    43. self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
    44. # 显示整条折线图
    45. self.pw.enableAutoRange()
    46. pass
    47. def mouseMoved(self,evt):
    48. pos = evt[0]
    49. if self.pw.sceneBoundingRect().contains(pos):
    50. mousePoint = self.vb.mapSceneToView(pos)
    51. index = int(mousePoint.x())
    52. if index >= 0 and index < len(self.y_datas):
    53. x_str = str(self.x_Tick[index][1])
    54. y_str_html = ''
    55. y_str = str(self.y_datas[index])
    56. y_str_html += ' ' + y_str
    57. html_str = '

       ' + x_str +'
       '
      +y_str_html+ '

      '
    58. self.label.setHtml(html_str)
    59. self.label.setPos(mousePoint.x(), mousePoint.y())
    60. self.vLine.setPos(mousePoint.x())
    61. self.hLine.setPos(mousePoint.y())
    62. pass
    63. pass

     分页表格控件

    1. class PageTableWidget(QtWidgets.QWidget):
    2. output_signal = QtCore.pyqtSignal(object)
    3. def __init__(self):
    4. super().__init__()
    5. self.init_data()
    6. self.init_ui()
    7. pass
    8. def init_data(self):
    9. # 表格全数据 二维数组
    10. self.table_full_data: List[Any] = []
    11. # 表格右键菜单 {菜单名:索引指向}
    12. self.table_right_menus: Dict[str, str] = {}
    13. self.total_page_count: int = 0
    14. self.total_rows_count: int = 0
    15. self.current_page: int = 1
    16. self.single_page_rows: int = 50
    17. pass
    18. def init_ui(self):
    19. pre_page_btn = QtWidgets.QPushButton('上一页')
    20. pre_page_btn.clicked.connect(self.pre_page_btn_clicked)
    21. next_page_btn = QtWidgets.QPushButton('下一页')
    22. next_page_btn.clicked.connect(self.next_page_btn_clicked)
    23. tip_label_0 = QtWidgets.QLabel('第')
    24. self.witch_page_lineedit = QtWidgets.QLineEdit()
    25. self.int_validator = QtGui.QIntValidator()
    26. self.witch_page_lineedit.setValidator(self.int_validator)
    27. self.witch_page_lineedit.setMaximumWidth(20)
    28. tip_label_1 = QtWidgets.QLabel('页')
    29. go_page_btn = QtWidgets.QPushButton('前往')
    30. go_page_btn.clicked.connect(self.go_page_btn_clicked)
    31. layout_witch_page = QtWidgets.QHBoxLayout()
    32. layout_witch_page.addWidget(tip_label_0)
    33. layout_witch_page.addWidget(self.witch_page_lineedit)
    34. layout_witch_page.addWidget(tip_label_1)
    35. layout_witch_page.addWidget(go_page_btn)
    36. layout_witch_page.addStretch(1)
    37. layout_pagechange = QtWidgets.QHBoxLayout()
    38. layout_pagechange.addWidget(pre_page_btn)
    39. layout_pagechange.addWidget(next_page_btn)
    40. layout_pagechange.addLayout(layout_witch_page)
    41. self.total_page_count_label = QtWidgets.QLabel(f"共0页")
    42. self.total_rows_count_label = QtWidgets.QLabel(f"共0行")
    43. self.current_page_label = QtWidgets.QLabel(f"当前第0页")
    44. layout_pagestatus = QtWidgets.QHBoxLayout()
    45. layout_pagestatus.addWidget(self.total_page_count_label)
    46. layout_pagestatus.addWidget(self.total_rows_count_label)
    47. layout_pagestatus.addWidget(self.current_page_label)
    48. layout_pagestatus.addStretch(1)
    49. layout_top = QtWidgets.QVBoxLayout()
    50. layout_top.addLayout(layout_pagechange)
    51. layout_top.addLayout(layout_pagestatus)
    52. self.target_table = QtWidgets.QTableWidget()
    53. self.target_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
    54. self.target_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
    55. self.target_table.clicked.connect(self.target_table_clicked)
    56. layout = QtWidgets.QVBoxLayout()
    57. layout.addLayout(layout_top)
    58. layout.addWidget(self.target_table)
    59. self.setLayout(layout)
    60. pass
    61. def set_table_init_data(self, data: Dict[str, Any]):
    62. '''设置表头、右键菜单等初始化内容'''
    63. headers: List[str] = data['headers']
    64. self.target_table.setColumnCount(len(headers))
    65. self.target_table.setHorizontalHeaderLabels(headers)
    66. pass
    67. def set_table_full_data(self, data: List[Any]):
    68. '''表格全数据'''
    69. self.table_full_data = data
    70. self.total_rows_count = len(data)
    71. self.total_page_count = math.ceil(self.total_rows_count / self.single_page_rows)
    72. self.current_page = 1
    73. self.int_validator.setRange(1, self.total_page_count)
    74. self.total_page_count_label.setText(f"共{self.total_page_count}页")
    75. self.total_rows_count_label.setText(f"共{self.total_rows_count}行")
    76. self.caculate_current_show_data()
    77. pass
    78. def setting_current_pagestatus_label(self):
    79. self.current_page_label.setText(f"当前第{self.current_page}页")
    80. pass
    81. def caculate_current_show_data(self):
    82. '''计算当前要显示的数据'''
    83. start_dot = (self.current_page - 1) * self.single_page_rows
    84. end_dot = start_dot + self.single_page_rows
    85. current_data = self.table_full_data[start_dot:end_dot]
    86. self.fill_table_content(current_data)
    87. self.setting_current_pagestatus_label()
    88. pass
    89. def fill_table_content(self, data: List[Any]):
    90. self.target_table.clearContents()
    91. self.target_table.setRowCount(len(data))
    92. for r_i, r_v in enumerate(data):
    93. for c_i, c_v in enumerate(r_v):
    94. cell = QtWidgets.QTableWidgetItem(str(c_v))
    95. self.target_table.setItem(r_i, c_i, cell)
    96. pass
    97. pass
    98. self.target_table.resizeColumnsToContents()
    99. pass
    100. def go_page_btn_clicked(self):
    101. '''前往按钮点击'''
    102. input_page = self.witch_page_lineedit.text()
    103. input_page = input_page.strip()
    104. if not input_page:
    105. QtWidgets.QMessageBox.information(
    106. self,
    107. '提示',
    108. '请输入要跳转的页码',
    109. QtWidgets.QMessageBox.Yes
    110. )
    111. return
    112. input_page = int(input_page)
    113. if input_page < 0 or input_page > self.total_page_count:
    114. QtWidgets.QMessageBox.information(
    115. self,
    116. '提示',
    117. '输入的页码超出范围',
    118. QtWidgets.QMessageBox.Yes
    119. )
    120. return
    121. self.current_page = input_page
    122. self.caculate_current_show_data()
    123. pass
    124. def pre_page_btn_clicked(self):
    125. '''上一页按钮点击'''
    126. if self.current_page <= 1:
    127. QtWidgets.QMessageBox.information(
    128. self,
    129. '提示',
    130. '已经是首页',
    131. QtWidgets.QMessageBox.Yes
    132. )
    133. return
    134. self.current_page -= 1
    135. self.caculate_current_show_data()
    136. pass
    137. def next_page_btn_clicked(self):
    138. '''下一页按钮点击'''
    139. if self.current_page >= self.total_page_count:
    140. QtWidgets.QMessageBox.information(
    141. self,
    142. '提示',
    143. '已经是最后一页',
    144. QtWidgets.QMessageBox.Yes
    145. )
    146. return
    147. self.current_page += 1
    148. self.caculate_current_show_data()
    149. pass
    150. def target_table_clicked(self):
    151. selectedItems = self.target_table.selectedItems()
    152. if len(selectedItems)<=0:
    153. return
    154. res_list = []
    155. for item in selectedItems:
    156. res_list.append(item.text())
    157. self.output_signal.emit(res_list)
    158. pass
    159. pass

    策略运行结果图表显示控件

     

    1. class PyQtGraphRunningWidget(QtWidgets.QWidget):
    2. def __init__(self):
    3. super().__init__()
    4. self.init_data()
    5. self.init_ui()
    6. pass
    7. def init_data(self):
    8. self.pre_output_dir = './'
    9. self.results_output_dir = './results_output/'
    10. self.json_file_name_list = []
    11. self.dailydata_path: str = ''
    12. self.table_header = ['股票编码','股票简称','均线状态','持续天数']
    13. self.please_select_str = '---请选择---'
    14. self.status_color_map = {
    15. '多头排列':(220, 20, 60),
    16. '空头排列':(0, 128, 0),
    17. '粘合':(30, 144, 255)
    18. }
    19. self.num_sort_map = {
    20. '升序':True,
    21. '降序':False
    22. }
    23. self.table_pd_header: List[str] = ['ticker','secName','status_name','status_num']
    24. # https://www.sioe.cn/yingyong/yanse-rgb-16/
    25. self.color_line = (30, 144, 255)
    26. # 0 幽灵的白色; 1 纯黄; 2 紫红色; 3 纯绿; 4 道奇蓝
    27. self.color_list = [(248,248,255),(255,255,0),(255,0,255),(0,128,0),(30,144,255)]
    28. self.duo_color = (220, 20, 60) # 猩红
    29. self.luan_color = (30, 144, 255) # 蓝
    30. self.kong_color = (0, 128, 0) # 纯绿
    31. self.main_fixed_target_list = [] # 主体固定曲线,不能被删除
    32. self.whole_df = None
    33. self.whole_header = None
    34. self.whole_pd_header = None
    35. self.current_whole_data = None
    36. self.current_whole_df = None
    37. self.duration_list = []
    38. self.duration_color = None
    39. self.line_pd_header = []
    40. self.table_df = None
    41. self.current_table_df = None
    42. pass
    43. def init_ui(self):
    44. tip_0 = QtWidgets.QLabel('股票日数据文件夹:')
    45. self.dailydata_dir_lineedit = QtWidgets.QLineEdit()
    46. self.dailydata_dir_lineedit.setReadOnly(True)
    47. dailydata_choice_btn = QtWidgets.QPushButton('选择文件夹')
    48. dailydata_choice_btn.clicked.connect(self.dailydata_choice_btn_clicked)
    49. tip_1 = QtWidgets.QLabel('Json结果文件选择:')
    50. self.json_combox = QtWidgets.QComboBox()
    51. self.json_combox.addItem(self.please_select_str)
    52. self.json_combox.currentIndexChanged.connect(self.json_combox_currentIndexChanged)
    53. refresh_json_btn = QtWidgets.QPushButton('刷新结果下拉表')
    54. refresh_json_btn.clicked.connect(self.refresh_json_btn_clicked)
    55. layout_top = QtWidgets.QGridLayout()
    56. layout_top.addWidget(tip_0,0,0,1,1)
    57. layout_top.addWidget(self.dailydata_dir_lineedit,0,1,1,3)
    58. layout_top.addWidget(dailydata_choice_btn,0,4,1,1)
    59. layout_top.addWidget(tip_1,1,0,1,1)
    60. layout_top.addWidget(self.json_combox,1,1,1,3)
    61. layout_top.addWidget(refresh_json_btn,1,4,1,1)
    62. tip_3 = QtWidgets.QLabel('股票名模糊查询')
    63. self.query_lineedit = QtWidgets.QLineEdit()
    64. query_btn = QtWidgets.QPushButton('查询')
    65. query_btn.clicked.connect(self.query_btn_clicked)
    66. reset_btn = QtWidgets.QPushButton('重置')
    67. reset_btn.clicked.connect(self.reset_btn_clicked)
    68. tip_2 = QtWidgets.QLabel('均线状态:')
    69. self.status_combox = QtWidgets.QComboBox()
    70. self.status_combox.addItem(self.please_select_str)
    71. self.status_combox.addItems(list(self.status_color_map.keys()))
    72. self.status_combox.currentIndexChanged.connect(self.status_combox_currentIndexChanged)
    73. tip_4 = QtWidgets.QLabel('持续时间排序:')
    74. self.num_combox = QtWidgets.QComboBox()
    75. self.num_combox.addItem(self.please_select_str)
    76. self.num_combox.addItems(list(self.num_sort_map.keys()))
    77. self.num_combox.currentIndexChanged.connect(self.num_combox_currentIndexChanged)
    78. layout_query = QtWidgets.QGridLayout()
    79. layout_query.addWidget(tip_3,0,0,1,1)
    80. layout_query.addWidget(self.query_lineedit,0,1,1,3)
    81. layout_query.addWidget(query_btn,0,4,1,1)
    82. layout_query.addWidget(reset_btn,0,5,1,1)
    83. layout_query.addWidget(tip_2,1,0,1,1)
    84. layout_query.addWidget(self.status_combox,1,1,1,2)
    85. layout_query.addWidget(tip_4,1,3,1,1)
    86. layout_query.addWidget(self.num_combox,1,4,1,2)
    87. self.table = PageTableWidget()
    88. self.table.set_table_init_data({'headers': self.table_header})
    89. self.table.output_signal.connect(self.table_output_signal_emit)
    90. self.bar_widget = PyQtGraphHistWidget()
    91. layout_left_up = QtWidgets.QVBoxLayout()
    92. layout_left_up.addLayout(layout_query)
    93. layout_left_up.addWidget(self.table)
    94. layout_left = QtWidgets.QVBoxLayout()
    95. layout_left.addLayout(layout_left_up,3)
    96. layout_left.addWidget(self.bar_widget,1)
    97. self.whole_duration_label = QtWidgets.QLabel('左边界~右边界')
    98. self.title_label = QtWidgets.QLabel('执行过程查看')
    99. self.title_label.setAlignment(Qt.AlignCenter)
    100. self.title_label.setStyleSheet('QLabel{font-size:18px;font-weight:bold}')
    101. xax = RotateAxisItem(orientation='bottom')
    102. xax.setHeight(h=80)
    103. self.pw = pg.PlotWidget(axisItems={'bottom': xax})
    104. self.pw.setMouseEnabled(x=True, y=True)
    105. # self.pw.enableAutoRange(x=False,y=True)
    106. self.pw.setAutoVisible(x=False, y=True)
    107. layout_right = QtWidgets.QVBoxLayout()
    108. layout_right.addWidget(self.title_label)
    109. layout_right.addWidget(self.whole_duration_label)
    110. layout_right.addWidget(self.pw)
    111. layout_down = QtWidgets.QHBoxLayout()
    112. layout_down.addLayout(layout_left,1)
    113. layout_down.addSpacing(30)
    114. layout_down.addLayout(layout_right,2)
    115. layout = QtWidgets.QVBoxLayout()
    116. layout.addLayout(layout_top)
    117. layout.addLayout(layout_down)
    118. self.setLayout(layout)
    119. pass
    120. def set_json_data(self,data:Dict[str,Any]):
    121. self.table_df = data['df']
    122. self.ma = data['ma']
    123. self.ma_color = data['ma_color']
    124. self.start_date_str = data['start_date_str']
    125. self.current_table_df = self.table_df.copy()
    126. self.fill_table_data()
    127. status_bar_data = data['status_bar_data']
    128. self.bar_widget.set_data(status_bar_data)
    129. pass
    130. def fill_table_data(self):
    131. table_data = self.current_table_df.loc[:, self.table_pd_header].values.tolist()
    132. self.table.set_table_full_data(table_data)
    133. pass
    134. def set_data(self,data:Dict[str,Any]):
    135. title_str = data['title_str']
    136. whole_header = data['whole_header']
    137. whole_df = data['whole_df']
    138. whole_pd_header = data['whole_pd_header']
    139. duration_list = data['duration_list']
    140. duration_color = data['duration_color']
    141. line_pd_header = data['line_pd_header']
    142. self.whole_header = whole_header
    143. self.whole_df = whole_df
    144. self.whole_pd_header = whole_pd_header
    145. self.duration_list = duration_list
    146. self.duration_color = duration_color
    147. self.line_pd_header = line_pd_header
    148. self.title_label.setText(title_str)
    149. self.whole_duration_label.setText(f"{self.whole_df.iloc[0]['tradeDate']}~{self.whole_df.iloc[-1]['tradeDate']}")
    150. self.current_whole_df = self.whole_df.copy()
    151. self.caculate_and_show_data()
    152. pass
    153. def caculate_and_show_data(self):
    154. df = self.current_whole_df.copy()
    155. df.reset_index(inplace=True)
    156. df['i_count'] = [i for i in range(len(df))]
    157. tradeDate_list = df['tradeDate'].values.tolist()
    158. x = range(len(df))
    159. xTick_show = []
    160. x_dur = math.ceil(len(df)/20)
    161. for i in range(0,len(df),x_dur):
    162. xTick_show.append((i,tradeDate_list[i]))
    163. if len(df)%20 != 0:
    164. xTick_show.append((len(df)-1,tradeDate_list[-1]))
    165. candle_data = []
    166. for i,row in df.iterrows():
    167. candle_data.append((row['i_count'],row['openPrice'],row['closePrice'],row['lowestPrice'],row['highestPrice']))
    168. self.current_whole_data = df.loc[:,self.whole_pd_header].values.tolist()
    169. # 开始配置显示的内容
    170. self.pw.clear()
    171. xax = self.pw.getAxis('bottom')
    172. xax.setTicks([xTick_show])
    173. candle_fixed_target = CandlestickItem(candle_data)
    174. self.main_fixed_target_list.append(candle_fixed_target)
    175. self.pw.addItem(candle_fixed_target)
    176. # 曲线
    177. if len(self.line_pd_header)>0:
    178. for i,item in enumerate(self.line_pd_header):
    179. line_fixed_target = pg.PlotCurveItem(x=np.array(x), y=np.array(df[item].values.tolist()),
    180. pen=pg.mkPen({'color': self.color_list[self.ma_color[i]], 'width': 2}),
    181. connect='finite')
    182. self.main_fixed_target_list.append(line_fixed_target)
    183. self.pw.addItem(line_fixed_target)
    184. pass
    185. # 最近均线状态区间
    186. lr = pg.LinearRegionItem([self.duration_list[0], self.duration_list[1]], movable=False, brush=(
    187. self.duration_color[0], self.duration_color[1], self.duration_color[2], 100))
    188. lr.setZValue(-100)
    189. self.pw.addItem(lr)
    190. self.vLine = pg.InfiniteLine(angle=90, movable=False)
    191. self.hLine = pg.InfiniteLine(angle=0, movable=False)
    192. self.label = pg.TextItem()
    193. self.pw.addItem(self.vLine, ignoreBounds=True)
    194. self.pw.addItem(self.hLine, ignoreBounds=True)
    195. self.pw.addItem(self.label, ignoreBounds=True)
    196. self.vb = self.pw.getViewBox()
    197. self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
    198. self.pw.enableAutoRange()
    199. pass
    200. def mouseMoved(self,evt):
    201. pos = evt[0]
    202. if self.pw.sceneBoundingRect().contains(pos):
    203. mousePoint = self.vb.mapSceneToView(pos)
    204. index = int(mousePoint.x())
    205. if index>=0 and index<len(self.current_whole_data):
    206. target_data = self.current_whole_data[index]
    207. html_str = ''
    208. for i,item in enumerate(self.whole_header):
    209. html_str += f"
      {item}:{target_data[i]}"
    210. self.label.setHtml(html_str)
    211. self.label.setPos(mousePoint.x(),mousePoint.y())
    212. self.vLine.setPos(mousePoint.x())
    213. self.hLine.setPos(mousePoint.y())
    214. pass
    215. def mouseClicked(self,evt):
    216. pass
    217. def updateViews(self):
    218. pass
    219. def table_output_signal_emit(self,data:List):
    220. # ticker secName status_name status_num
    221. dailydata_dir = self.dailydata_dir_lineedit.text()
    222. dailydata_dir = dailydata_dir.strip()
    223. if len(dailydata_dir)<=0:
    224. QtWidgets.QMessageBox.information(
    225. self,
    226. '提示',
    227. '请选择股票日数据文件件',
    228. QtWidgets.QMessageBox.Yes
    229. )
    230. return
    231. daily_file_path = dailydata_dir + '/' + data[0] + '.csv'
    232. df = pd.read_csv(daily_file_path,encoding='utf-8')
    233. # 删除停牌的数据
    234. df = df.loc[df['openPrice'] > 0].copy()
    235. df['o_date'] = df['tradeDate']
    236. df['o_date'] = pd.to_datetime(df['o_date'])
    237. df = df.loc[df['o_date'] >= self.start_date_str].copy()
    238. # 保存未复权收盘价数据
    239. df['close'] = df['closePrice']
    240. # 计算前复权数据
    241. df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    242. df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    243. df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    244. df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
    245. columns_list = ['日期','收盘价','开盘价','最高价','最低价']
    246. columns_pd_list = ['tradeDate','closePrice','openPrice','highestPrice','lowestPrice']
    247. line_pd_header = []
    248. for ma_i in self.ma:
    249. df[f"ma{ma_i}"] = talib.MA(df['closePrice'],timeperiod=ma_i)
    250. columns_list.append(f"ma{ma_i}")
    251. columns_pd_list.append(f"ma{ma_i}")
    252. line_pd_header.append(f"ma{ma_i}")
    253. df.reset_index(inplace=True)
    254. duration_list = [len(df)-int(data[-1])-1,len(df)-1]
    255. duration_color = self.status_color_map[data[2]]
    256. line_data = {
    257. 'title_str':data[1],
    258. 'whole_header':columns_list,
    259. 'whole_df':df,
    260. 'whole_pd_header':columns_pd_list,
    261. 'line_pd_header':line_pd_header,
    262. 'duration_list':duration_list,
    263. 'duration_color':duration_color
    264. }
    265. self.set_data(line_data)
    266. pass
    267. def dailydata_choice_btn_clicked(self):
    268. path = QtWidgets.QFileDialog.getExistingDirectory(
    269. self,
    270. '打开股票日数据所在文件夹',
    271. self.pre_output_dir
    272. )
    273. if not path:
    274. return
    275. self.dailydata_path = path
    276. self.dailydata_dir_lineedit.setText(path)
    277. pass
    278. def json_combox_currentIndexChanged(self,cur_i:int):
    279. cur_txt = self.json_combox.currentText()
    280. if cur_txt == self.please_select_str:
    281. return
    282. current_json_file_path = self.results_output_dir + cur_txt
    283. with open(current_json_file_path,'r',encoding='utf-8') as fr:
    284. obj_json = json.load(fr)
    285. df = pd.DataFrame(obj_json['df_json'])
    286. obj_json['df'] = df
    287. self.set_json_data(obj_json)
    288. pass
    289. def status_combox_currentIndexChanged(self,cur_i:int):
    290. cur_txt = self.status_combox.currentText()
    291. if cur_txt == self.please_select_str:
    292. return
    293. df = self.table_df.copy()
    294. self.current_table_df = df.loc[df['status_name']==cur_txt].copy()
    295. self.fill_table_data()
    296. pass
    297. def query_btn_clicked(self):
    298. query_str = self.query_lineedit.text()
    299. query_str = query_str.strip()
    300. if len(query_str)<=0:
    301. QtWidgets.QMessageBox.information(
    302. self,
    303. '提示',
    304. '请输入要查询的内容',
    305. QtWidgets.QMessageBox.Yes
    306. )
    307. return
    308. self.status_combox.setCurrentText(self.please_select_str)
    309. df = self.table_df.copy()
    310. self.current_table_df = df.loc[df['secName'].str.contains(query_str)].copy()
    311. self.fill_table_data()
    312. pass
    313. def reset_btn_clicked(self):
    314. self.query_lineedit.setText('')
    315. self.status_combox.setCurrentText(self.please_select_str)
    316. self.current_table_df = self.table_df.copy()
    317. self.fill_table_data()
    318. pass
    319. def num_combox_currentIndexChanged(self,cur_i:int):
    320. cur_txt = self.num_combox.currentText()
    321. if cur_txt == self.please_select_str:
    322. return
    323. self.current_table_df.sort_values(by='status_num',ascending=self.num_sort_map[cur_txt],inplace=True)
    324. self.fill_table_data()
    325. pass
    326. def refresh_json_btn_clicked(self):
    327. # self.results_output_dir
    328. file_list = os.listdir(self.results_output_dir)
    329. json_file_list = []
    330. for item in file_list:
    331. if item.endswith('.json'):
    332. json_file_list.append(item)
    333. self.json_file_name_list.extend(json_file_list)
    334. json_file_set = set(self.json_file_name_list)
    335. self.json_file_name_list = list(json_file_set)
    336. self.json_combox.clear()
    337. self.json_combox.addItem(self.please_select_str)
    338. self.json_combox.addItems(self.json_file_name_list)
    339. pass
    340. pass

    策略运行操作主界面

    1. class StrategeMainWidget(QtWidgets.QWidget):
    2. signal_runcode = QtCore.pyqtSignal(object)
    3. signal_time = QtCore.pyqtSignal(object)
    4. def __init__(self):
    5. super().__init__()
    6. self.thread_run: Thread = None
    7. self.thread_time: Thread = None
    8. self.running_graph_widget: QtWidgets.QWidget = None
    9. self.init_data()
    10. self.init_ui()
    11. self.register_event()
    12. pass
    13. def init_data(self):
    14. self.pre_output_dir = './'
    15. self.results_output_dir = './results_output/'
    16. self.secID_name_file_name = 'secID_name.csv'
    17. self.please_select_str: str = '--请选择--'
    18. self.stratege_name_list: List = []
    19. self.tip_msg_0: str = '1.选择策略所在文件夹;2.选择策略;3.点击运行。'
    20. self.stratege_path: str = ''
    21. self.stratege_run_start_time = None
    22. self.stratege_start = False
    23. self.current_stratege_py_str: str = ''
    24. self.dailydata_path:str = ''
    25. pass
    26. def init_ui(self):
    27. self.setWindowTitle('股票均线状态工具')
    28. tip_2 = QtWidgets.QLabel('股票日数据文件夹:')
    29. self.dailydata_dir_lineedit = QtWidgets.QLineEdit()
    30. self.dailydata_dir_lineedit.setReadOnly(True)
    31. dailydata_choice_btn = QtWidgets.QPushButton('选择文件夹')
    32. dailydata_choice_btn.clicked.connect(self.dailydata_choice_btn_clicked)
    33. tip_0 = QtWidgets.QLabel('选择策略所在文件夹:')
    34. self.stratege_dir_lineedit = QtWidgets.QLineEdit()
    35. self.stratege_dir_lineedit.setReadOnly(True)
    36. stratege_choice_btn = QtWidgets.QPushButton('选择文件夹')
    37. stratege_choice_btn.clicked.connect(self.stratege_choice_btn_clicked)
    38. tip_1 = QtWidgets.QLabel('策略:')
    39. self.stratege_combox = QtWidgets.QComboBox()
    40. self.stratege_combox.addItem(self.please_select_str)
    41. self.stratege_combox.currentIndexChanged.connect(self.stratege_combox_currentIndexChanged)
    42. self.run_btn = QtWidgets.QPushButton('运行')
    43. self.run_btn.clicked.connect(self.run_btn_clicked)
    44. self.force_stop_btn = QtWidgets.QPushButton('强制停止')
    45. self.force_stop_btn.clicked.connect(self.force_stop_btn_clicked)
    46. layout_top_left = QtWidgets.QGridLayout()
    47. layout_top_left.addWidget(tip_2,0,0,1,1)
    48. layout_top_left.addWidget(self.dailydata_dir_lineedit,0,1,1,3)
    49. layout_top_left.addWidget(dailydata_choice_btn,0,4,1,1)
    50. layout_top_left.addWidget(tip_0,1,0,1,1)
    51. layout_top_left.addWidget(self.stratege_dir_lineedit,1,1,1,3)
    52. layout_top_left.addWidget(stratege_choice_btn,1,4,1,1)
    53. layout_top_left.addWidget(tip_1,2,0,1,1)
    54. layout_top_left.addWidget(self.stratege_combox,2,1,1,2)
    55. layout_top_left.addWidget(self.run_btn,2,3,1,1)
    56. layout_top_left.addWidget(self.force_stop_btn,2,4,1,1)
    57. self.tip_msg_label = QtWidgets.QLabel()
    58. self.tip_msg_label.setWordWrap(True)
    59. self.tip_msg_label.setText(self.tip_msg_0)
    60. results_output_look_btn = QtWidgets.QPushButton('结果查看')
    61. results_output_look_btn.clicked.connect(self.results_output_look_btn_clicked)
    62. layout_top_right = QtWidgets.QHBoxLayout()
    63. layout_top_right.addWidget(self.tip_msg_label)
    64. layout_top_right.addWidget(results_output_look_btn)
    65. layout_top = QtWidgets.QHBoxLayout()
    66. layout_top.addLayout(layout_top_left,3)
    67. layout_top.addSpacing(30)
    68. layout_top.addLayout(layout_top_right,1)
    69. self.code_textedit = QtWidgets.QTextEdit()
    70. self.code_textedit.setReadOnly(True)
    71. layout = QtWidgets.QVBoxLayout()
    72. layout.addLayout(layout_top)
    73. layout.addWidget(self.code_textedit)
    74. self.setLayout(layout)
    75. pass
    76. def register_event(self):
    77. self.signal_runcode.connect(self.thread_run_excuted)
    78. self.signal_time.connect(self.thread_time_excuted)
    79. pass
    80. def results_output_look_btn_clicked(self):
    81. '''策略运行结果查看'''
    82. if not self.running_graph_widget:
    83. self.running_graph_widget = PyQtGraphRunningWidget()
    84. self.running_graph_widget.showMaximized()
    85. pass
    86. def dailydata_choice_btn_clicked(self):
    87. path = QtWidgets.QFileDialog.getExistingDirectory(
    88. self,
    89. '打开股票日数据所在文件夹',
    90. self.pre_output_dir
    91. )
    92. if not path:
    93. return
    94. self.dailydata_path = path+'/'
    95. self.dailydata_dir_lineedit.setText(path)
    96. pass
    97. def stratege_choice_btn_clicked(self):
    98. '''选择策略所在文件夹'''
    99. path = QtWidgets.QFileDialog.getExistingDirectory(
    100. self,
    101. '打开策略所在文件夹',
    102. self.pre_output_dir
    103. )
    104. if not path:
    105. return
    106. self.stratege_path = path
    107. self.stratege_dir_lineedit.setText(path)
    108. file_list = os.listdir(path)
    109. temp_file_list = set(self.stratege_name_list)
    110. for item in file_list:
    111. if item.endswith('.py'):
    112. temp_file_list.add(item)
    113. self.stratege_name_list = list(temp_file_list)
    114. self.stratege_combox.clear()
    115. self.stratege_combox.addItem(self.please_select_str)
    116. self.stratege_combox.addItems(self.stratege_name_list)
    117. pass
    118. def stratege_combox_currentIndexChanged(self,cur_i:int):
    119. cur_txt = self.stratege_combox.currentText()
    120. if not cur_txt or cur_txt == self.please_select_str:
    121. self.code_textedit.clear()
    122. return
    123. file_path = self.stratege_path + os.path.sep + cur_txt
    124. with open(file_path,'r',encoding='utf-8') as fr:
    125. code_txt = fr.read()
    126. self.code_textedit.setPlainText(code_txt)
    127. pass
    128. def run_btn_clicked(self):
    129. '''运行按钮'''
    130. # 检查股票日数据文件夹
    131. dailydata_dir = self.dailydata_dir_lineedit.text()
    132. dailydata_dir = dailydata_dir.strip()
    133. if len(dailydata_dir)<=0:
    134. QtWidgets.QMessageBox.information(
    135. self,
    136. '提示',
    137. '请选择股票日数据文件夹',
    138. QtWidgets.QMessageBox.Yes
    139. )
    140. return
    141. dailydata_file_list = os.listdir(dailydata_dir)
    142. if len(dailydata_file_list)<=0:
    143. QtWidgets.QMessageBox.information(
    144. self,
    145. '提示',
    146. '股票日数据文件夹中没有文件',
    147. QtWidgets.QMessageBox.Yes
    148. )
    149. return
    150. secID_name_file = self.results_output_dir + self.secID_name_file_name
    151. if not os.path.exists(secID_name_file):
    152. QtWidgets.QMessageBox.information(
    153. self,
    154. '提示',
    155. '股票码与股票名基础文件不存在',
    156. QtWidgets.QMessageBox.Yes
    157. )
    158. return
    159. py_str = self.code_textedit.toPlainText()
    160. if len(py_str)<10:
    161. QtWidgets.QMessageBox.information(
    162. self,
    163. '提示',
    164. '请选择要执行的策略',
    165. QtWidgets.QMessageBox.Yes
    166. )
    167. return
    168. self.current_stratege_py_str = py_str
    169. base_data = {}
    170. base_df = pd.read_csv(secID_name_file,encoding='utf-8')
    171. for i,row in base_df.iterrows():
    172. secID = row['secID']
    173. secID_arr = secID.split('.')
    174. base_data[secID_arr[0]] = row['secShortName']
    175. pass
    176. self.run_btn.setDisabled(True)
    177. self.stratege_combox.setDisabled(True)
    178. self.stratege_run_start_time = datetime.now()
    179. self.stratege_start = True
    180. if self.thread_run:
    181. QtWidgets.QMessageBox.information(
    182. self,
    183. '提示',
    184. '有策略正在运行',
    185. QtWidgets.QMessageBox.Yes
    186. )
    187. return
    188. pre_data = {
    189. 'py_str':py_str,
    190. 'base_data':base_data
    191. }
    192. self.thread_run = Thread(
    193. target=self.running_run_thread,
    194. args=(pre_data,)
    195. )
    196. self.thread_run.start()
    197. self.thread_time = Thread(
    198. target=self.running_time_thread
    199. )
    200. self.thread_time.start()
    201. pass
    202. def force_stop_btn_clicked(self):
    203. '''强制停止按钮'''
    204. self.thread_run = None
    205. self.thread_time = None
    206. self.run_btn.setDisabled(False)
    207. self.stratege_combox.setDisabled(False)
    208. self.stratege_start = False
    209. pass
    210. def running_run_thread(self,data:Dict[str,Any]):
    211. '''执行代码线程'''
    212. py_str = data['py_str']
    213. base_data = data['base_data']
    214. namespace = {}
    215. fun_stragegy = compile(py_str,'','exec')
    216. exec(fun_stragegy,namespace)
    217. ret = namespace['excute_strategy'](base_data,self.dailydata_path)
    218. self.signal_runcode.emit(ret)
    219. pass
    220. def running_time_thread(self):
    221. '''计时线程'''
    222. while self.stratege_start:
    223. now = datetime.now()
    224. interval_time = (now-self.stratege_run_start_time).seconds
    225. res_map = {'res':interval_time}
    226. self.signal_time.emit(res_map)
    227. time.sleep(1)
    228. pass
    229. def thread_run_excuted(self,data:Dict):
    230. '''策略代码执行返回结果'''
    231. self.run_btn.setDisabled(False)
    232. self.stratege_combox.setDisabled(False)
    233. # 保存结果文件
    234. now_datetime_str = datetime.now().strftime('%Y%m%d%H%M%S')
    235. df = data['df']
    236. df_json = df.to_dict(orient='records')
    237. pre_save_data = {
    238. 'df_json':df_json,
    239. 'ma':data['ma'],
    240. 'ma_color':data['ma_color'],
    241. 'start_date_str':data['start_date_str'],
    242. 'status_bar_data':data['status_bar_data']
    243. }
    244. with open(self.results_output_dir + now_datetime_str + '.json','w',encoding='utf-8') as fw:
    245. json.dump(pre_save_data,fw)
    246. if not self.running_graph_widget:
    247. self.running_graph_widget = PyQtGraphRunningWidget()
    248. self.running_graph_widget.set_json_data(data)
    249. self.running_graph_widget.showMaximized()
    250. self.thread_run = None
    251. self.thread_time = None
    252. self.stratege_start = False
    253. QtWidgets.QMessageBox.information(
    254. self,
    255. '提示',
    256. '当前策略运行完毕',
    257. QtWidgets.QMessageBox.Yes
    258. )
    259. pass
    260. def thread_time_excuted(self,data:Dict):
    261. '''计时返回结果'''
    262. res = data['res']
    263. self.tip_msg_label.setText(f"{res}s")
    264. pass
    265. def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
    266. if self.thread_time:
    267. self.thread_time.join()
    268. if self.thread_run:
    269. self.thread_run.join()
    270. if self.running_graph_widget:
    271. self.running_graph_widget.close()
    272. self.close()

    工具使用

    1. if __name__ == '__main__':
    2. QtCore.QCoreApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
    3. app = QtWidgets.QApplication(sys.argv)
    4. t_win = StrategeMainWidget()
    5. t_win.showMaximized()
    6. app.exec()
    7. pass

    1. 在 StrategeMainWiget 代码的同一目录下创建‘’ results_output"目录,将 secID_name.csv 文件放入

    2. 每次运行结果会以json的文件的方式存储在‘results_output’目录下

    运行工具

    数据

     链接:https://pan.baidu.com/s/1QJORgvC2ay2BoKRAZGc7pw 
    提取码:v6ia

  • 相关阅读:
    QT6.5.2编译PostgreSql驱动
    艾美捷热转移稳定性检测试剂盒:简单、灵敏、均匀的荧光测定法
    笔记记录--基于ccpd数据集利用Paddle OCR训练车牌检测模型
    基于注解的Spring MVC实例开发过程
    pg嵌套子查询
    rar格式转换zip格式,如何做?
    基于STM32的蓝牙低功耗(BLE)通信方案设计与实现
    ros缺少catkin_pkg
    小程序 target 与 currentTarget(详细)
    【golang】sync包once 只执行一次代码
  • 原文地址:https://blog.csdn.net/m0_37967652/article/details/127647712