• PyQt5_股票策略校验工具_升级版


    相对 PyQt5_股票策略校验工具 博文,图形展示方面增加多行图展示,原本只有K线图,升级版工具可以动态添加多行指标图

    股票策略有效与否的确认,需要在不同股票,不同时间段,运行对比,确认在哪些条件下策略是最有效的。在整个校验过程中,有可以实时运行并实时查看结果的工具对整个效率的提升起着至关重要的作用。本工具基于此做的开发。

    本文以“卖出口诀 - 顶天立地,卖出要急”为例进行讲解。

    目录

    效果

    策略代码

    工具代码

    工具使用


    效果

    策略代码

    前置说明:

    1. 必须以 excute_strategy 为方法名

    2. 策略代码保存为.py文件,将该文件保存到一个目录下(自定义),运行工具后,手动选择这个目录

     指标计算的py文件 和 指标显示用的控件py文件 要放置在项目可以导入的位置

    指标计算的代码,后续有新增的指标在该py文件后面继续追加就可。指标计算的代码请查看 该博文

    指标显示用的控件,可以自行编写,也可以在本人 PyQt5 栏目下找对应的控件。

    1. def excute_strategy(base_data,data_dir):
    2. '''
    3. 卖出口诀 - 顶天立地,卖出要急
    4. 解析:
    5. 1. 出现上长影K线(或长实体K线),同时放出了大成交量
    6. 自定义:
    7. 1. 上长影K线 =》上影线是实体1倍以上
    8. 2. 长实体K线 =》 K线实体是昨收2%以上
    9. 3. 大成交量 =》成交量是昨日的2倍以上
    10. 4. 卖出时点 =》 形态出现后下一交易日
    11. 5. 胜 =》 卖出后第三个交易日收盘价下跌,为胜
    12. 只计算最近两年的数据
    13. :param base_data:股票代码与股票简称 键值对
    14. :param data_dir:股票日数据文件所在目录
    15. :return:
    16. '''
    17. import pandas as pd
    18. import numpy as np
    19. import talib,os
    20. from datetime import datetime
    21. from dateutil.relativedelta import relativedelta
    22. from tools import stock_factor_caculate
    23. def res_pre_two_year_first_day():
    24. pre_year_day = (datetime.now() - relativedelta(years=2)).strftime('%Y-%m-%d')
    25. return pre_year_day
    26. caculate_start_date_str = res_pre_two_year_first_day()
    27. dailydata_file_list = os.listdir(data_dir)
    28. total_count = 0
    29. total_win = 0
    30. check_count = 0
    31. list_list = []
    32. detail_map = {}
    33. factor_list = ['VOL']
    34. for item in dailydata_file_list:
    35. item_arr = item.split('.')
    36. ticker = item_arr[0]
    37. secName = base_data[ticker]
    38. file_path = data_dir + item
    39. df = pd.read_csv(file_path,encoding='utf-8')
    40. # 删除停牌的数据
    41. df = df.loc[df['openPrice'] > 0].copy()
    42. df['o_date'] = df['tradeDate']
    43. df['o_date'] = pd.to_datetime(df['o_date'])
    44. df = df.loc[df['o_date'] >= caculate_start_date_str].copy()
    45. # 保存未复权收盘价数据
    46. df['close'] = df['closePrice']
    47. # 计算前复权数据
    48. df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    49. df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    50. df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    51. df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
    52. if len(df)<=0:
    53. continue
    54. # 开始计算
    55. df.reset_index(inplace=True)
    56. df['i_row'] = [i for i in range(len(df))]
    57. df['body_length'] = abs(df['closePrice']-df['openPrice'])
    58. df['up_shadow'] = 0
    59. df.loc[df['closePrice']>df['openPrice'],'up_shadow'] = df['highestPrice'] - df['closePrice']
    60. df.loc[df['closePrice']'openPrice'],'up_shadow'] = df['highestPrice'] - df['openPrice']
    61. df['median_body'] = 0
    62. df.loc[(df['body_length']/df['closePrice'].shift(1)>0.02) & (df['up_shadow']/df['body_length']>=1),'median_body'] = 1
    63. df['vol_yeah'] = 0
    64. df.loc[df['turnoverVol']/df['turnoverVol'].shift(1)>=2,'vol_yeah'] = 1
    65. df['three_chg'] = round(((df['close'].shift(-3) - df['close'])/df['close'])*100,4)
    66. df['three_after_close'] = df['close'].shift(-3)
    67. df['target_yeah'] = 0
    68. df.loc[(df['median_body']==1) & (df['vol_yeah']==1),'target_yeah'] = 1
    69. df_target = df.loc[df['target_yeah']==1].copy()
    70. node_count = 0
    71. node_win = 0
    72. duration_list = []
    73. table_list = []
    74. i_row_list = df_target['i_row'].values.tolist()
    75. for i,row0 in enumerate(i_row_list):
    76. row = row0 + 1
    77. if row >= len(df):
    78. continue
    79. date_str = df.iloc[row]['tradeDate']
    80. cur_close = df.iloc[row]['close']
    81. three_after_close = df.iloc[row]['three_after_close']
    82. three_chg = df.iloc[row]['three_chg']
    83. table_list.append([
    84. i,date_str,cur_close,three_after_close,three_chg
    85. ])
    86. duration_list.append([row-2,row+3])
    87. node_count += 1
    88. if three_chg<0:
    89. node_win +=1
    90. pass
    91. list_list.append({
    92. 'ticker':ticker,
    93. 'secName':secName,
    94. 'count':node_count,
    95. 'win':0 if node_count<=0 else round((node_win/node_count)*100,2)
    96. })
    97. detail_map[ticker] = {
    98. 'table_list': table_list,
    99. 'duration_list': duration_list
    100. }
    101. total_count += node_count
    102. total_win += node_win
    103. check_count += 1
    104. pass
    105. df = pd.DataFrame(list_list)
    106. results_data = {
    107. 'check_count':check_count,
    108. 'total_count':total_count,
    109. 'total_win':0 if total_count<=0 else round((total_win/total_count)*100,2),
    110. 'start_date_str':caculate_start_date_str,
    111. 'df':df,
    112. 'detail_map':detail_map,
    113. 'factor_list':factor_list
    114. }
    115. return results_data

    工具代码

    需要导入的包、pyqtgraph日期横坐标控件、pyqtgraph蜡烛图控件、分页表格控件,这些代码请查看本栏目标题有“工具”字眼的博文

    K线和结果图形显示控件

    1. class PyQtGraphScrollKWidget(QtWidgets.QWidget):
    2. def __init__(self):
    3. super().__init__()
    4. self.factor_widgets = []
    5. self.init_data()
    6. self.init_ui()
    7. pass
    8. def init_data(self):
    9. # https://www.sioe.cn/yingyong/yanse-rgb-16/
    10. # self.color_line = (30, 144, 255)
    11. self.color_line = (255, 255, 0)
    12. self.color_highligh = (220,20,60)
    13. # 0 幽灵的白色; 1 纯黄; 2 紫红色; 3 纯绿; 4 道奇蓝
    14. self.color_list = [(248, 248, 255), (255, 255, 0), (255, 0, 255), (0, 128, 0), (30, 144, 255)]
    15. self.main_fixed_target_list = [] # 主体固定曲线,不能被删除
    16. self.whole_df = None
    17. self.whole_header = None
    18. self.whole_pd_header = None
    19. self.current_whole_data = None
    20. self.current_whole_df = None
    21. self.duration_list = None
    22. self.current_highligh_duration = None
    23. self.current_highligh_duration_list = []
    24. self.factor_list = None
    25. self.factor_code_widgetname_map = {
    26. 'VOL':'VOL_PlotWidget',
    27. 'SAR':'SAR_PlotWidget'
    28. }
    29. pass
    30. def init_ui(self):
    31. self.whole_duration_label = QtWidgets.QLabel('左边界~右边界')
    32. pic_download_btn = QtWidgets.QPushButton('滚动截图')
    33. pic_download_btn.clicked.connect(self.pic_download_btn_clicked)
    34. layout_top = QtWidgets.QHBoxLayout()
    35. layout_top.addWidget(self.whole_duration_label)
    36. layout_top.addStretch(1)
    37. layout_top.addWidget(pic_download_btn)
    38. self.title_label = QtWidgets.QLabel('执行过程查看')
    39. self.title_label.setAlignment(Qt.AlignCenter)
    40. self.title_label.setStyleSheet('QLabel{font-size:18px;font-weight:bold}')
    41. # 滚动区域开始
    42. self.pw_layout = QtWidgets.QVBoxLayout()
    43. self.scroll_area = QtWidgets.QScrollArea()
    44. self.scroll_area.setWidgetResizable(True)
    45. # self.scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
    46. layout_right = QtWidgets.QVBoxLayout()
    47. layout_right.addWidget(self.title_label)
    48. layout_right.addLayout(layout_top)
    49. layout_right.addWidget(self.scroll_area)
    50. self.setLayout(layout_right)
    51. pass
    52. def set_data(self, data: Dict[str, Any]):
    53. title_str = data['title_str']
    54. whole_header = data['whole_header']
    55. whole_df = data['whole_df']
    56. whole_pd_header = data['whole_pd_header']
    57. duration_list = data['duration_list']
    58. factor_list = data['factor_list']
    59. self.whole_header = whole_header
    60. self.whole_df = whole_df
    61. self.whole_pd_header = whole_pd_header
    62. self.duration_list = duration_list
    63. self.factor_list = factor_list
    64. self.title_label.setText(title_str)
    65. self.whole_duration_label.setText(f"{self.whole_df.iloc[0]['tradeDate']}~{self.whole_df.iloc[-1]['tradeDate']}")
    66. self.current_whole_df = self.whole_df.copy()
    67. self.caculate_and_show_data()
    68. pass
    69. def caculate_and_show_data(self):
    70. df = self.current_whole_df.copy()
    71. df.reset_index(inplace=True)
    72. df['i_count'] = [i for i in range(len(df))]
    73. tradeDate_list = df['tradeDate'].values.tolist()
    74. x = range(len(df))
    75. xTick_show = []
    76. x_dur = math.ceil(len(df) / 20)
    77. for i in range(0, len(df), x_dur):
    78. xTick_show.append((i, tradeDate_list[i]))
    79. if len(df) % 20 != 0:
    80. xTick_show.append((len(df) - 1, tradeDate_list[-1]))
    81. candle_data = []
    82. for i, row in df.iterrows():
    83. candle_data.append(
    84. (row['i_count'], row['openPrice'], row['closePrice'], row['lowestPrice'], row['highestPrice']))
    85. self.current_whole_data = df.loc[:, self.whole_pd_header].values.tolist()
    86. # 开始配置显示的内容
    87. self.create_candle_widget()
    88. self.factor_widgets.clear()
    89. xax = self.pw.getAxis('bottom')
    90. xax.setTicks([xTick_show])
    91. candle_fixed_target = CandlestickItem(candle_data)
    92. self.main_fixed_target_list.append(candle_fixed_target)
    93. self.pw.addItem(candle_fixed_target)
    94. # 标记技术图形 start
    95. if len(self.duration_list)>0:
    96. for item in self.duration_list:
    97. signal_fiexed_target = pg.LinearRegionItem([item[0], item[1]],
    98. movable=False, brush=(
    99. self.color_line[0], self.color_line[1], self.color_line[2], 50))
    100. self.pw.addItem(signal_fiexed_target)
    101. pass
    102. # 标记技术图形 end
    103. self.vLine = pg.InfiniteLine(angle=90, movable=False)
    104. self.hLine = pg.InfiniteLine(angle=0, movable=False)
    105. self.label = pg.TextItem()
    106. self.pw.addItem(self.vLine, ignoreBounds=True)
    107. self.pw.addItem(self.hLine, ignoreBounds=True)
    108. self.pw.addItem(self.label, ignoreBounds=True)
    109. self.vb = self.pw.getViewBox()
    110. self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
    111. self.pw.enableAutoRange()
    112. # 其他项
    113. for item in self.factor_list:
    114. item_widget = eval(self.factor_code_widgetname_map[item])()
    115. item_widget.setMinimumHeight(300)
    116. item_widget.set_data({'df':self.current_whole_df.copy()})
    117. item_widget.setXLink(self.pw)
    118. # 标记技术图形 start
    119. if len(self.duration_list) > 0:
    120. for item in self.duration_list:
    121. signal_fiexed_target = pg.LinearRegionItem([item[0], item[1]],
    122. movable=False, brush=(
    123. self.color_line[0], self.color_line[1], self.color_line[2], 50))
    124. item_widget.addItem(signal_fiexed_target)
    125. pass
    126. # 标记技术图形 end
    127. self.factor_widgets.append(item_widget)
    128. self.fill_pw_widget()
    129. pass
    130. def mouseMoved(self, evt):
    131. pos = evt[0]
    132. if self.pw.sceneBoundingRect().contains(pos):
    133. mousePoint = self.vb.mapSceneToView(pos)
    134. index = int(mousePoint.x())
    135. if index >= 0 and index < len(self.current_whole_data):
    136. target_data = self.current_whole_data[index]
    137. html_str = ''
    138. for i, item in enumerate(self.whole_header):
    139. html_str += f"
      {item}:{target_data[i]}"
    140. self.label.setHtml(html_str)
    141. self.label.setPos(mousePoint.x(), mousePoint.y())
    142. self.vLine.setPos(mousePoint.x())
    143. self.hLine.setPos(mousePoint.y())
    144. pass
    145. def mouseClicked(self, evt):
    146. pass
    147. def updateViews(self):
    148. pass
    149. def set_highligh_duration(self,dur_index:int):
    150. highligh_dur = self.duration_list[dur_index]
    151. self.pw.removeItem(self.current_highligh_duration)
    152. signal_fiexed_target = pg.LinearRegionItem([highligh_dur[0], highligh_dur[1]],
    153. movable=False, brush=(
    154. self.color_highligh[0], self.color_highligh[1], self.color_highligh[2], 50))
    155. self.pw.addItem(signal_fiexed_target)
    156. self.current_highligh_duration = signal_fiexed_target
    157. if self.current_highligh_duration_list:
    158. for i,item in enumerate(self.current_highligh_duration_list):
    159. item_w = self.factor_widgets[i]
    160. item_w.removeItem(item)
    161. pass
    162. self.current_highligh_duration_list.clear()
    163. for item in self.factor_widgets:
    164. signal_fiexed_target0 = pg.LinearRegionItem([highligh_dur[0], highligh_dur[1]],
    165. movable=False, brush=(
    166. self.color_highligh[0], self.color_highligh[1], self.color_highligh[2], 50))
    167. item.addItem(signal_fiexed_target0)
    168. self.current_highligh_duration_list.append(signal_fiexed_target0)
    169. pass
    170. pass
    171. def fill_pw_widget(self):
    172. # 清空控件
    173. while self.pw_layout.count():
    174. item = self.pw_layout.takeAt(0)
    175. widget = item.widget()
    176. if widget is not None:
    177. widget.deleteLater()
    178. pass
    179. pass
    180. sc_child_widget = self.scroll_area.takeWidget()
    181. if sc_child_widget is not None:
    182. sc_child_widget.deleteLater()
    183. self.pw_layout.addWidget(self.pw)
    184. for item in self.factor_widgets:
    185. self.pw_layout.addWidget(item)
    186. one_sc_child_widget = QtWidgets.QWidget()
    187. one_sc_child_widget.setLayout(self.pw_layout)
    188. self.scroll_area.setWidget(one_sc_child_widget)
    189. pass
    190. def create_candle_widget(self):
    191. xax = RotateAxisItem(orientation='bottom')
    192. xax.setHeight(h=60)
    193. self.pw = pg.PlotWidget(axisItems={'bottom': xax})
    194. self.pw.setMinimumHeight(400)
    195. self.pw.setMouseEnabled(x=True, y=True)
    196. # self.pw.enableAutoRange(x=False,y=True)
    197. self.pw.setAutoVisible(x=False, y=True)
    198. pass
    199. def pic_download_btn_clicked(self):
    200. now_str = datetime.now().strftime('%Y%m%d%H%M%S')
    201. path,_ = QtWidgets.QFileDialog.getSaveFileName(
    202. self,
    203. '选择图片保存路径',
    204. f"pic_{now_str}",
    205. 'JPG(*.jpg)'
    206. )
    207. if not path:
    208. return
    209. widget = self.scroll_area.widget()
    210. pix = widget.grab()
    211. pix.save(path)
    212. pass
    213. 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 = './strategy_check_output/'
    10. self.json_file_name_list = []
    11. self.dailydata_path: str = ''
    12. self.total_table_header: List = ['股票数量','总次数','总胜率']
    13. self.list_table_header: List = ['股票代码','简称','次数','胜率']
    14. self.list_pd_header: List = ['ticker','secName','count','win']
    15. self.detail_table_header: List = ['序号','日期','收盘价','三日后收盘价','涨跌幅']
    16. self.please_select_str = '---请选择---'
    17. self.num_sort_map = {
    18. '升序':True,
    19. '降序':False
    20. }
    21. self.list_table_df = None
    22. self.current_list_table_df = None
    23. self.detail_map = None
    24. self.factor_list = []
    25. pass
    26. def init_ui(self):
    27. tip_0 = QtWidgets.QLabel('股票日数据文件夹:')
    28. self.dailydata_dir_lineedit = QtWidgets.QLineEdit()
    29. self.dailydata_dir_lineedit.setReadOnly(True)
    30. dailydata_choice_btn = QtWidgets.QPushButton('选择文件夹')
    31. dailydata_choice_btn.clicked.connect(self.dailydata_choice_btn_clicked)
    32. tip_1 = QtWidgets.QLabel('Json结果文件选择:')
    33. self.json_combox = QtWidgets.QComboBox()
    34. self.json_combox.addItem(self.please_select_str)
    35. self.json_combox.currentIndexChanged.connect(self.json_combox_currentIndexChanged)
    36. refresh_json_btn = QtWidgets.QPushButton('刷新结果下拉表')
    37. refresh_json_btn.clicked.connect(self.refresh_json_btn_clicked)
    38. layout_top = QtWidgets.QGridLayout()
    39. layout_top.addWidget(tip_0,0,0,1,1)
    40. layout_top.addWidget(self.dailydata_dir_lineedit,0,1,1,3)
    41. layout_top.addWidget(dailydata_choice_btn,0,4,1,1)
    42. layout_top.addWidget(tip_1,1,0,1,1)
    43. layout_top.addWidget(self.json_combox,1,1,1,3)
    44. layout_top.addWidget(refresh_json_btn,1,4,1,1)
    45. self.total_table = QtWidgets.QTableWidget()
    46. self.total_table.setRowCount(1)
    47. self.total_table.setColumnCount(len(self.total_table_header))
    48. self.total_table.setHorizontalHeaderLabels(self.total_table_header)
    49. self.total_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
    50. self.total_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
    51. tip_3 = QtWidgets.QLabel('股票名模糊查询')
    52. self.query_lineedit = QtWidgets.QLineEdit()
    53. query_btn = QtWidgets.QPushButton('查询')
    54. query_btn.clicked.connect(self.query_btn_clicked)
    55. reset_btn = QtWidgets.QPushButton('重置')
    56. reset_btn.clicked.connect(self.reset_btn_clicked)
    57. tip_2 = QtWidgets.QLabel('次数:')
    58. self.count_combox = QtWidgets.QComboBox()
    59. self.count_combox.addItem(self.please_select_str)
    60. self.count_combox.addItems(list(self.num_sort_map.keys()))
    61. self.count_combox.currentIndexChanged.connect(self.count_combox_currentIndexChanged)
    62. tip_4 = QtWidgets.QLabel('胜率:')
    63. self.num_combox = QtWidgets.QComboBox()
    64. self.num_combox.addItem(self.please_select_str)
    65. self.num_combox.addItems(list(self.num_sort_map.keys()))
    66. self.num_combox.currentIndexChanged.connect(self.num_combox_currentIndexChanged)
    67. layout_query = QtWidgets.QGridLayout()
    68. layout_query.addWidget(tip_3,0,0,1,1)
    69. layout_query.addWidget(self.query_lineedit,0,1,1,3)
    70. layout_query.addWidget(query_btn,0,4,1,1)
    71. layout_query.addWidget(reset_btn,0,5,1,1)
    72. layout_query.addWidget(tip_2,1,0,1,1)
    73. layout_query.addWidget(self.count_combox,1,1,1,2)
    74. layout_query.addWidget(tip_4,1,3,1,1)
    75. layout_query.addWidget(self.num_combox,1,4,1,2)
    76. self.list_table = PageTableWidget()
    77. self.list_table.set_table_init_data({'headers': self.list_table_header})
    78. self.list_table.output_signal.connect(self.table_output_signal_emit)
    79. layout_left = QtWidgets.QVBoxLayout()
    80. layout_left.addWidget(self.total_table,1)
    81. layout_left.addLayout(layout_query,1)
    82. layout_left.addWidget(self.list_table,8)
    83. # self.k_widget = PyQtGraphKWidget()
    84. self.k_widget = PyQtGraphScrollKWidget()
    85. self.detail_table = PageTableWidget()
    86. self.detail_table.set_table_init_data({'headers': self.detail_table_header})
    87. self.detail_table.output_signal.connect(self.detail_table_output_signal_emit)
    88. layout_right = QtWidgets.QVBoxLayout()
    89. layout_right.addWidget(self.k_widget,3)
    90. layout_right.addWidget(self.detail_table,1)
    91. layout_down = QtWidgets.QHBoxLayout()
    92. layout_down.addLayout(layout_left,1)
    93. layout_down.addSpacing(20)
    94. layout_down.addLayout(layout_right,3)
    95. layout = QtWidgets.QVBoxLayout()
    96. layout.addLayout(layout_top)
    97. layout.addLayout(layout_down)
    98. self.setLayout(layout)
    99. pass
    100. def set_json_data(self,data:Dict[str,Any]):
    101. self.list_table_df = data['df']
    102. self.start_date_str = data['start_date_str']
    103. check_count = data['check_count']
    104. total_count = data['total_count']
    105. total_win = data['total_win']
    106. self.detail_map = data['detail_map']
    107. if data.get('factor_list') is not None:
    108. self.factor_list = data['factor_list']
    109. else:
    110. self.factor_list = []
    111. self.total_table.setItem(0,0,QtWidgets.QTableWidgetItem(str(check_count)))
    112. self.total_table.setItem(0,1,QtWidgets.QTableWidgetItem(str(total_count)))
    113. self.total_table.setItem(0,2,QtWidgets.QTableWidgetItem(str(total_win)+'%'))
    114. self.current_list_table_df = self.list_table_df.copy()
    115. self.fill_table_data()
    116. pass
    117. def fill_table_data(self):
    118. table_data = self.current_list_table_df.loc[:, self.list_pd_header].values.tolist()
    119. self.list_table.set_table_full_data(table_data)
    120. pass
    121. def table_output_signal_emit(self,data:List):
    122. # ticker secName count win
    123. dailydata_dir = self.dailydata_dir_lineedit.text()
    124. dailydata_dir = dailydata_dir.strip()
    125. if len(dailydata_dir)<=0:
    126. QtWidgets.QMessageBox.information(
    127. self,
    128. '提示',
    129. '请选择股票日数据文件件',
    130. QtWidgets.QMessageBox.Yes
    131. )
    132. return
    133. daily_file_path = dailydata_dir + '/' + data[0] + '.csv'
    134. df = pd.read_csv(daily_file_path,encoding='utf-8')
    135. # 删除停牌的数据
    136. df = df.loc[df['openPrice'] > 0].copy()
    137. df['o_date'] = df['tradeDate']
    138. df['o_date'] = pd.to_datetime(df['o_date'])
    139. df = df.loc[df['o_date'] >= self.start_date_str].copy()
    140. # 保存未复权收盘价数据
    141. df['close'] = df['closePrice']
    142. # 计算前复权数据
    143. df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    144. df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    145. df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    146. df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
    147. columns_list = ['日期','收盘价','开盘价','最高价','最低价']
    148. columns_pd_list = ['tradeDate','closePrice','openPrice','highestPrice','lowestPrice']
    149. if self.factor_list:
    150. for item in self.factor_list:
    151. df = stock_factor_caculate.caculate_factor(df,item)
    152. pass
    153. df.reset_index(inplace=True)
    154. node_detail = self.detail_map[data[0]]
    155. table_list = node_detail['table_list']
    156. duration_list = node_detail['duration_list']
    157. self.detail_table.set_table_full_data(table_list)
    158. line_data = {
    159. 'title_str': data[1],
    160. 'whole_header': columns_list,
    161. 'whole_df': df,
    162. 'whole_pd_header': columns_pd_list,
    163. 'duration_list': duration_list,
    164. 'factor_list':self.factor_list
    165. }
    166. self.k_widget.set_data(line_data)
    167. pass
    168. def detail_table_output_signal_emit(self,data:List):
    169. self.k_widget.set_highligh_duration(int(data[0]))
    170. pass
    171. def dailydata_choice_btn_clicked(self):
    172. path = QtWidgets.QFileDialog.getExistingDirectory(
    173. self,
    174. '打开股票日数据所在文件夹',
    175. self.pre_output_dir
    176. )
    177. if not path:
    178. return
    179. self.dailydata_path = path
    180. self.dailydata_dir_lineedit.setText(path)
    181. pass
    182. def json_combox_currentIndexChanged(self,cur_i:int):
    183. cur_txt = self.json_combox.currentText()
    184. if not cur_txt or cur_txt == self.please_select_str:
    185. return
    186. current_json_file_path = self.results_output_dir + cur_txt
    187. with open(current_json_file_path,'r',encoding='utf-8') as fr:
    188. obj_json = json.load(fr)
    189. df = pd.DataFrame(obj_json['df_json'])
    190. obj_json['df'] = df
    191. self.set_json_data(obj_json)
    192. pass
    193. def count_combox_currentIndexChanged(self,cur_i:int):
    194. cur_txt = self.count_combox.currentText()
    195. if not cur_txt or cur_txt == self.please_select_str:
    196. return
    197. self.current_list_table_df.sort_values(by='count', ascending=self.num_sort_map[cur_txt], inplace=True)
    198. self.fill_table_data()
    199. pass
    200. def query_btn_clicked(self):
    201. query_str = self.query_lineedit.text()
    202. query_str = query_str.strip()
    203. if len(query_str)<=0:
    204. QtWidgets.QMessageBox.information(
    205. self,
    206. '提示',
    207. '请输入要查询的内容',
    208. QtWidgets.QMessageBox.Yes
    209. )
    210. return
    211. self.count_combox.setCurrentText(self.please_select_str)
    212. df = self.list_table_df.copy()
    213. self.current_list_table_df = df.loc[df['secName'].str.contains(query_str)].copy()
    214. self.fill_table_data()
    215. pass
    216. def reset_btn_clicked(self):
    217. self.query_lineedit.setText('')
    218. self.count_combox.setCurrentText(self.please_select_str)
    219. self.current_list_table_df = self.list_table_df.copy()
    220. self.fill_table_data()
    221. pass
    222. def num_combox_currentIndexChanged(self,cur_i:int):
    223. cur_txt = self.num_combox.currentText()
    224. if not cur_txt or cur_txt == self.please_select_str:
    225. return
    226. self.current_list_table_df.sort_values(by='win',ascending=self.num_sort_map[cur_txt],inplace=True)
    227. self.fill_table_data()
    228. pass
    229. def refresh_json_btn_clicked(self):
    230. # self.results_output_dir
    231. file_list = os.listdir(self.results_output_dir)
    232. json_file_list = []
    233. for item in file_list:
    234. if item.endswith('.json'):
    235. json_file_list.append(item)
    236. self.json_file_name_list.extend(json_file_list)
    237. json_file_set = set(self.json_file_name_list)
    238. self.json_file_name_list = list(json_file_set)
    239. self.json_combox.clear()
    240. self.json_combox.addItem(self.please_select_str)
    241. self.json_combox.addItems(self.json_file_name_list)
    242. pass
    243. 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 = './strategy_check_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. 'check_count':data['check_count'],
    240. 'total_count':data['total_count'],
    241. 'total_win':data['total_win'],
    242. 'start_date_str':data['start_date_str'],
    243. 'detail_map':data['detail_map']
    244. }
    245. with open(self.results_output_dir + now_datetime_str + '.json','w',encoding='utf-8') as fw:
    246. json.dump(pre_save_data,fw)
    247. if not self.running_graph_widget:
    248. self.running_graph_widget = PyQtGraphRunningWidget()
    249. self.running_graph_widget.set_json_data(data)
    250. self.running_graph_widget.showMaximized()
    251. self.thread_run = None
    252. self.thread_time = None
    253. self.stratege_start = False
    254. QtWidgets.QMessageBox.information(
    255. self,
    256. '提示',
    257. '当前策略运行完毕',
    258. QtWidgets.QMessageBox.Yes
    259. )
    260. pass
    261. def thread_time_excuted(self,data:Dict):
    262. '''计时返回结果'''
    263. res = data['res']
    264. self.tip_msg_label.setText(f"{res}s")
    265. pass
    266. def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
    267. if self.thread_time:
    268. self.thread_time.join()
    269. if self.thread_run:
    270. self.thread_run.join()
    271. if self.running_graph_widget:
    272. self.running_graph_widget.close()
    273. 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 在StrategeMainWidget代码的同一目录下创建“strategy_check_output” 文件夹,并把数据中的secID_name.csv文件放入这个文件夹

    2. 每次运行结果会以json文件存储在strategy_check_output文件夹下

    运行工具

     滚动截图

  • 相关阅读:
    2022年危险化学品经营单位安全管理人员考试练习题及模拟考试
    openssl 常用命令demo
    暑假补题[6-30](AtCoder)
    如何用蓝牙实现无线定位(一)--系统原理
    arm栈推导
    优秀智慧园区案例 - 中建科技产业园(中建·光谷之星),万字长文解析先进智慧园区建设方案经验
    六月集训(第30天) —— 拓扑排序
    【SpringMVC】springmvc中的数据校验
    牛顿迭代法求平方根--C++简单实现
    学习笔记二十四:K8S四层代理Service
  • 原文地址:https://blog.csdn.net/m0_37967652/article/details/127929379