• PyQt5_股票K线形态查看工具


    K线形态,诸如大阳线、螺旋桨、穿头破脚等,代码逻辑实现后,要使用股票数据验证,这类主要依靠视觉判断的K线形态,获得的结果能及时在图表中展示出来,对代码的开发、校验、结果的准确度确认等能起到事半功倍的效果,本工具就是基于此需求所做的开发,需要的朋友可以自取,具体K线形态判断的代码实现,请查看后续博文。

    本文以“大阳线”为例进行讲解。

    目录

    效果

    计算K线形态策略代码

    工具代码

    工具使用

    数据


    效果

    计算K线形态策略代码

     前置说明:

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

    2. 一个参数,股票日数据文件路径

    3. 策略代码写完后,命名,保存在‘k_recognition_strategy’文件夹中,‘k_recognition_strategy’的创建位置在下面的工具代码中会介绍

    4. 将策略文件名写入主程序类StrategeMainWidget 的 self.strategyname_code_map 对应的键值对中

    1. def excute_strategy(daily_file_path):
    2. '''
    3. 名称:大阳线
    4. 识别:K线实体长度为上一交易日价格4%以上。
    5. 前置条件:计算时间区间 2021-01-01 到 2022-01-01
    6. :param daily_file_path: 股票日数据文件路径
    7. :return:
    8. '''
    9. import pandas as pd
    10. import os
    11. start_date_str = '2021-01-01'
    12. end_date_str = '2022-01-01'
    13. df = pd.read_csv(daily_file_path,encoding='utf-8')
    14. # 删除停牌的数据
    15. df = df.loc[df['openPrice'] > 0].copy()
    16. df['o_date'] = df['tradeDate']
    17. df['o_date'] = pd.to_datetime(df['o_date'])
    18. df = df.loc[(df['o_date'] >= start_date_str) & (df['o_date']<=end_date_str)].copy()
    19. # 保存未复权收盘价数据
    20. df['close'] = df['closePrice']
    21. # 计算前复权数据
    22. df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    23. df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    24. df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    25. df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
    26. # 开始计算
    27. df['body_length'] = df['closePrice'] - df['openPrice']
    28. df['signal'] = 0
    29. df['signal_name'] = 0
    30. df.loc[(df['body_length']>0) & (df['body_length']/df['closePrice'].shift(1)>=0.04),'signal'] = 1
    31. df.loc[(df['body_length']>0) & (df['body_length']/df['closePrice'].shift(1)>=0.04),'signal_name'] = (df['body_length']/df['closePrice'].shift(1))*100
    32. df = df.round({'signal_name':2})
    33. file_name = os.path.basename(daily_file_path)
    34. title_str = file_name.split('.')[0]
    35. line_data = {
    36. 'title_str':title_str,
    37. 'whole_header':['日期','收','开','高','低'],
    38. 'whole_df':df,
    39. 'whole_pd_header':['tradeDate','closePrice','openPrice','highestPrice','lowestPrice'],
    40. 'start_date_str':start_date_str,
    41. 'end_date_str':end_date_str,
    42. 'signal_type':'line'
    43. }
    44. return line_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({'color':'w','width':1}))
    12. w = (self.data[1][0] - self.data[0][0]) / 3.
    13. for (t, open, close, min, max) in self.data:
    14. if open==close and open==max and max==min:
    15. p.drawLine(QtCore.QPointF(t-w, min), QtCore.QPointF(t+w, max))
    16. else:
    17. p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
    18. if open < close:
    19. p.setBrush(pg.mkBrush('r'))
    20. else:
    21. p.setBrush(pg.mkBrush('g'))
    22. p.drawRect(QtCore.QRectF(t-w, open, w * 2, close - open))
    23. p.end()
    24. def paint(self, p, *args):
    25. p.drawPicture(0, 0, self.picture)
    26. def boundingRect(self):
    27. ## boundingRect _must_ indicate the entire area that will be drawn on
    28. ## or else we will get artifacts and possibly crashing.
    29. ## (in this case, QPicture does all the work of computing the bouning rect for us)
    30. return QtCore.QRectF(self.picture.boundingRect())

     分页表格控件

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

    K线与结果显示控件 

    1. class PyQtGraphKWidget(QtWidgets.QWidget):
    2. def __init__(self):
    3. super().__init__()
    4. self.init_data()
    5. self.init_ui()
    6. def init_data(self):
    7. # https://www.sioe.cn/yingyong/yanse-rgb-16/
    8. self.color_line = (30, 144, 255)
    9. # 0 幽灵的白色; 1 纯黄; 2 紫红色; 3 纯绿; 4 道奇蓝
    10. self.color_list = [(248, 248, 255), (255, 255, 0), (255, 0, 255), (0, 128, 0), (30, 144, 255)]
    11. self.main_fixed_target_list = [] # 主体固定曲线,不能被删除
    12. self.whole_df = None
    13. self.whole_header = None
    14. self.whole_pd_header = None
    15. self.current_whole_data = None
    16. self.current_whole_df = None
    17. self.signal_type = None
    18. self.duration_len = None
    19. pass
    20. def init_ui(self):
    21. self.whole_duration_label = QtWidgets.QLabel('左边界~右边界')
    22. self.title_label = QtWidgets.QLabel('执行过程查看')
    23. self.title_label.setAlignment(Qt.AlignCenter)
    24. self.title_label.setStyleSheet('QLabel{font-size:18px;font-weight:bold}')
    25. xax = RotateAxisItem(orientation='bottom')
    26. xax.setHeight(h=80)
    27. self.pw = pg.PlotWidget(axisItems={'bottom': xax})
    28. self.pw.setMouseEnabled(x=True, y=True)
    29. # self.pw.enableAutoRange(x=False,y=True)
    30. self.pw.setAutoVisible(x=False, y=True)
    31. layout_right = QtWidgets.QVBoxLayout()
    32. layout_right.addWidget(self.title_label)
    33. layout_right.addWidget(self.whole_duration_label)
    34. layout_right.addWidget(self.pw)
    35. self.setLayout(layout_right)
    36. pass
    37. def set_data(self, data: Dict[str, Any]):
    38. title_str = data['title_str']
    39. whole_header = data['whole_header']
    40. whole_df = data['whole_df']
    41. whole_pd_header = data['whole_pd_header']
    42. signal_type = data['signal_type']
    43. if signal_type == 'duration' or signal_type == 'duration_detail':
    44. duration_len = data['duration_len']
    45. self.duration_len = duration_len
    46. pass
    47. self.whole_header = whole_header
    48. self.whole_df = whole_df
    49. self.whole_pd_header = whole_pd_header
    50. self.signal_type = signal_type
    51. self.title_label.setText(title_str)
    52. self.whole_duration_label.setText(f"{self.whole_df.iloc[0]['tradeDate']}~{self.whole_df.iloc[-1]['tradeDate']}")
    53. self.current_whole_df = self.whole_df.copy()
    54. self.caculate_and_show_data()
    55. pass
    56. def caculate_and_show_data(self):
    57. df = self.current_whole_df.copy()
    58. df.reset_index(inplace=True)
    59. df['i_count'] = [i for i in range(len(df))]
    60. tradeDate_list = df['tradeDate'].values.tolist()
    61. x = range(len(df))
    62. xTick_show = []
    63. x_dur = math.ceil(len(df) / 20)
    64. for i in range(0, len(df), x_dur):
    65. xTick_show.append((i, tradeDate_list[i]))
    66. if len(df) % 20 != 0:
    67. xTick_show.append((len(df) - 1, tradeDate_list[-1]))
    68. candle_data = []
    69. for i, row in df.iterrows():
    70. candle_data.append(
    71. (row['i_count'], row['openPrice'], row['closePrice'], row['lowestPrice'], row['highestPrice']))
    72. self.current_whole_data = df.loc[:, self.whole_pd_header].values.tolist()
    73. # 开始配置显示的内容
    74. self.pw.clear()
    75. xax = self.pw.getAxis('bottom')
    76. xax.setTicks([xTick_show])
    77. if self.signal_type == 'line':
    78. df_signal = df.loc[df['signal'] == 1].copy()
    79. for i,row in df_signal.iterrows():
    80. signal_fiexed_target = pg.InfiniteLine(pos=(row['i_count'],0),movable=False,angle=90,pen=pg.mkPen({'color':self.color_line,'width':1}),label=str(row['signal_name']),labelOpts={'position':0.05,'color':self.color_line,'movable':True,'fill':(self.color_line[0],self.color_line[1],self.color_line[2],30)})
    81. self.pw.addItem(signal_fiexed_target)
    82. pass
    83. elif self.signal_type == 'duration':
    84. for i,i_len in enumerate(self.duration_len):
    85. df_signal = df.loc[df['signal'] == i+1].copy()
    86. for i0, row in df_signal.iterrows():
    87. signal_fiexed_target = pg.LinearRegionItem([row['i_count'] - i_len + 1, row['i_count']],
    88. movable=False, brush=(
    89. self.color_line[0], self.color_line[1], self.color_line[2], 50))
    90. self.pw.addItem(signal_fiexed_target)
    91. pass
    92. elif self.signal_type == 'duration_detail':
    93. df['param_0'] = df['signal']-df['signal'].shift(1)
    94. df['param_1'] = df['signal']-df['signal'].shift(-1)
    95. df_start = df.loc[df['param_0']==1].copy()
    96. df_end = df.loc[df['param_1']==1].copy()
    97. start_list = df_start['i_count'].values.tolist()
    98. end_list = df_end['i_count'].values.tolist()
    99. for i,item in enumerate(start_list):
    100. signal_fiexed_target = pg.LinearRegionItem([item, end_list[i]],
    101. movable=False, brush=(
    102. self.color_line[0], self.color_line[1], self.color_line[2], 50))
    103. self.pw.addItem(signal_fiexed_target)
    104. pass
    105. candle_fixed_target = CandlestickItem(candle_data)
    106. self.main_fixed_target_list.append(candle_fixed_target)
    107. self.pw.addItem(candle_fixed_target)
    108. self.vLine = pg.InfiniteLine(angle=90, movable=False)
    109. self.hLine = pg.InfiniteLine(angle=0, movable=False)
    110. self.label = pg.TextItem()
    111. self.pw.addItem(self.vLine, ignoreBounds=True)
    112. self.pw.addItem(self.hLine, ignoreBounds=True)
    113. self.pw.addItem(self.label, ignoreBounds=True)
    114. self.vb = self.pw.getViewBox()
    115. self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
    116. self.pw.enableAutoRange()
    117. pass
    118. def mouseMoved(self, evt):
    119. pos = evt[0]
    120. if self.pw.sceneBoundingRect().contains(pos):
    121. mousePoint = self.vb.mapSceneToView(pos)
    122. index = int(mousePoint.x())
    123. if index >= 0 and index < len(self.current_whole_data):
    124. target_data = self.current_whole_data[index]
    125. html_str = ''
    126. for i, item in enumerate(self.whole_header):
    127. html_str += f"
      {item}:{target_data[i]}"
    128. self.label.setHtml(html_str)
    129. self.label.setPos(mousePoint.x(), mousePoint.y())
    130. self.vLine.setPos(mousePoint.x())
    131. self.hLine.setPos(mousePoint.y())
    132. pass
    133. def mouseClicked(self, evt):
    134. pass
    135. def updateViews(self):
    136. pass
    137. 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.init_data()
    9. self.init_ui()
    10. self.register_event()
    11. pass
    12. def init_data(self):
    13. self.pre_output_dir = './'
    14. self.results_output_dir = './k_recognition_output/'
    15. self.please_select_str: str = '--请选择--'
    16. self.k_strategy_path: str = '../k_recognition_strategy/'
    17. self.stratege_run_start_time = None
    18. self.stratege_start = False
    19. self.current_stratege_py_str: str = ''
    20. self.dailydata_path:str = ''
    21. self.k_strategy_map: Dict[str,Any] = {
    22. '表上升和下跌的K线形态':['大阳线(长阳线)','大阴线(长阴线)','小阳线和小阴线','十字星和十字线','长十字线','螺旋桨','一字线','T字线和倒T字线','锤头线和吊颈线(绞刑线)','倒锤头线和射击之星(流星、扫帚星)','揉搓线','尽头线','镊子线','穿头破脚','身怀六甲和十字胎','下跌三连阴'],
    23. '表见底和上升的K线形态':['向下加速度线','早晨(希望)之星和早晨(希望)十字星','好友反攻','曙光初现','旭日东升','平底','塔形底','圆底','低位并排阳线','低档五阳线','连续跳空三阴线','红三兵(三个白色武士)','冉冉上升','稳步上涨','徐缓上升','上升抵抗','上升(向上)弧形线','下探上涨','上涨两颗星','跳空上扬(升势鹤鸦缺口)','高位并排阳线(升势恋人肩并肩缺口)','跳空下跌三颗星','上升三部曲(升势三鸦)','多方尖兵(仙人指路)','两阳夹一阴(两红夹一黑)','上档盘旋形'],
    24. '表见顶和下跌的K线形态':['向上加速度线','黄昏之星和黄昏十字星','淡友反攻','乌云盖顶','倾盆大雨','平顶(镊子顶)','塔形顶','圆顶','双飞乌鸦','高档五阴线','连续跳空三阳线','黑三兵','三只乌鸦(暴跌三杰)','绵绵阴跌','下跌不止','徐缓下降','下跌抵抗','下降弧形线','高开出逃','下跌三颗星','倒三阳','升势受阻','升势停顿','下降三部曲(降势三鹤)','空方尖兵','两阴夹一阳(两黑夹一红)','跛脚阳线','低档盘旋形','下降覆盖线']
    25. }
    26. self.strategyname_code_map:Dict[str,str] = {
    27. '大阳线(长阳线)':'',
    28. '大阴线(长阴线)':'',
    29. '小阳线和小阴线':'',
    30. '十字星和十字线':'',
    31. '长十字线':'',
    32. '螺旋桨':'',
    33. '一字线':'',
    34. 'T字线和倒T字线':'',
    35. '锤头线和吊颈线(绞刑线)':'',
    36. '倒锤头线和射击之星(流星、扫帚星)':'',
    37. '揉搓线':'',
    38. '尽头线':'',
    39. '镊子线':'',
    40. '穿头破脚':'',
    41. '身怀六甲和十字胎':'',
    42. '下跌三连阴':'',
    43. '向下加速度线':'',
    44. '早晨(希望)之星和早晨(希望)十字星':'',
    45. '好友反攻':'',
    46. '曙光初现':'',
    47. '旭日东升':'',
    48. '平底':'',
    49. '塔形底':'',
    50. '圆底':'',
    51. '低位并排阳线':'',
    52. '低档五阳线':'',
    53. '连续跳空三阴线':'',
    54. '红三兵(三个白色武士)':'',
    55. '冉冉上升':'',
    56. '稳步上涨':'',
    57. '徐缓上升':'',
    58. '上升抵抗':'',
    59. '上升(向上)弧形线':'',
    60. '下探上涨':'',
    61. '上涨两颗星':'',
    62. '跳空上扬(升势鹤鸦缺口)':'',
    63. '高位并排阳线(升势恋人肩并肩缺口)':'',
    64. '跳空下跌三颗星':'',
    65. '上升三部曲(升势三鸦)':'',
    66. '多方尖兵(仙人指路)':'',
    67. '两阳夹一阴(两红夹一黑)':'',
    68. '上档盘旋形':'',
    69. '向上加速度线':'',
    70. '黄昏之星和黄昏十字星':'',
    71. '淡友反攻':'',
    72. '乌云盖顶':'',
    73. '倾盆大雨':'',
    74. '平顶(镊子顶)':'',
    75. '塔形顶':'',
    76. '圆顶':'',
    77. '双飞乌鸦':'',
    78. '高档五阴线':'',
    79. '连续跳空三阳线':'',
    80. '黑三兵':'',
    81. '三只乌鸦(暴跌三杰)':'',
    82. '绵绵阴跌':'',
    83. '下跌不止':'',
    84. '徐缓下降':'',
    85. '下跌抵抗':'',
    86. '下降弧形线':'',
    87. '高开出逃':'',
    88. '下跌三颗星':'',
    89. '倒三阳':'',
    90. '升势受阻':'',
    91. '升势停顿':'',
    92. '下降三部曲(降势三鹤)':'',
    93. '空方尖兵':'',
    94. '两阴夹一阳(两黑夹一红)':'',
    95. '跛脚阳线':'',
    96. '低档盘旋形':'',
    97. '下降覆盖线':'',
    98. }
    99. self.table_header: List = ['日期','信号','备注']
    100. pass
    101. def init_ui(self):
    102. self.setWindowTitle('股票K线形态识别工具')
    103. tip_0 = QtWidgets.QLabel('股票日数据文件:')
    104. self.dailydata_filepath_lineedit = QtWidgets.QLineEdit()
    105. self.dailydata_filepath_lineedit.setReadOnly(True)
    106. dailydata_choice_btn = QtWidgets.QPushButton('选择文件')
    107. dailydata_choice_btn.clicked.connect(self.dailydata_choice_btn_clicked)
    108. layout_top = QtWidgets.QHBoxLayout()
    109. layout_top.addWidget(tip_0)
    110. layout_top.addWidget(self.dailydata_filepath_lineedit)
    111. layout_top.addWidget(dailydata_choice_btn)
    112. layout_top.addStretch(1)
    113. tip_1 = QtWidgets.QLabel('类别:')
    114. self.type_combox = QtWidgets.QComboBox()
    115. self.type_combox.addItem(self.please_select_str)
    116. self.type_combox.addItems(list(self.k_strategy_map.keys()))
    117. self.type_combox.currentIndexChanged.connect(self.type_combox_currentIndexChanged)
    118. tip_2 = QtWidgets.QLabel('形态:')
    119. self.shape_combox = QtWidgets.QComboBox()
    120. self.shape_combox.addItem(self.please_select_str)
    121. self.shape_combox.currentIndexChanged.connect(self.shape_combox_currentIndexChanged)
    122. self.run_btn = QtWidgets.QPushButton('运行')
    123. self.run_btn.clicked.connect(self.run_btn_clicked)
    124. self.time_label = QtWidgets.QLabel('')
    125. layout_combox = QtWidgets.QFormLayout()
    126. layout_combox.addRow(tip_1,self.type_combox)
    127. layout_combox.addRow(tip_2,self.shape_combox)
    128. layout_combox.addRow(self.run_btn,self.time_label)
    129. self.code_textedit = QtWidgets.QTextEdit()
    130. self.code_textedit.setReadOnly(True)
    131. self.results_table = PageTableWidget()
    132. self.results_table.set_table_init_data({'headers':self.table_header})
    133. layout_left = QtWidgets.QVBoxLayout()
    134. layout_left.addLayout(layout_combox)
    135. layout_left.addWidget(self.code_textedit)
    136. layout_left.addWidget(self.results_table)
    137. self.line_widget = PyQtGraphKWidget()
    138. layout_down = QtWidgets.QHBoxLayout()
    139. layout_down.addLayout(layout_left,1)
    140. layout_down.addWidget(self.line_widget,2)
    141. layout = QtWidgets.QVBoxLayout()
    142. layout.addLayout(layout_top)
    143. layout.addLayout(layout_down)
    144. self.setLayout(layout)
    145. pass
    146. def register_event(self):
    147. self.signal_runcode.connect(self.thread_run_excuted)
    148. self.signal_time.connect(self.thread_time_excuted)
    149. pass
    150. def dailydata_choice_btn_clicked(self):
    151. path, _ = QtWidgets.QFileDialog.getOpenFileName(
    152. self,
    153. '打开股票日数据所在文件',
    154. self.pre_output_dir
    155. )
    156. if not path:
    157. return
    158. self.dailydata_filepath_lineedit.setText(path)
    159. pass
    160. def type_combox_currentIndexChanged(self,cur_i:int):
    161. cur_txt = self.type_combox.currentText()
    162. if not cur_txt or cur_txt == self.please_select_str:
    163. return
    164. shape_name_list = self.k_strategy_map[cur_txt]
    165. self.shape_combox.clear()
    166. self.shape_combox.addItem(self.please_select_str)
    167. self.shape_combox.addItems(shape_name_list)
    168. pass
    169. def shape_combox_currentIndexChanged(self,cur_i:int):
    170. cur_txt = self.shape_combox.currentText()
    171. if not cur_txt or cur_txt == self.please_select_str:
    172. return
    173. cur_strategy_name = self.strategyname_code_map[cur_txt]
    174. if len(cur_strategy_name)<=0:
    175. QtWidgets.QMessageBox.information(
    176. self,
    177. '提示',
    178. '该K线形态的策略还没上线',
    179. QtWidgets.QMessageBox.Yes
    180. )
    181. return
    182. strategy_file_path = self.k_strategy_path + cur_strategy_name
    183. with open(strategy_file_path,'r',encoding='utf-8') as fr:
    184. py_str = fr.read()
    185. if len(py_str)<=20:
    186. QtWidgets.QMessageBox.information(
    187. self,
    188. '提示',
    189. '策略文件代码为空',
    190. QtWidgets.QMessageBox.Yes
    191. )
    192. return
    193. self.code_textedit.setPlainText(py_str)
    194. pass
    195. def run_btn_clicked(self):
    196. '''运行按钮'''
    197. # 检查股票日数据文件夹
    198. dailydata_filepath = self.dailydata_filepath_lineedit.text()
    199. dailydata_filepath = dailydata_filepath.strip()
    200. if len(dailydata_filepath)<=0:
    201. QtWidgets.QMessageBox.information(
    202. self,
    203. '提示',
    204. '请选择股票日数据文件',
    205. QtWidgets.QMessageBox.Yes
    206. )
    207. return
    208. py_str = self.code_textedit.toPlainText()
    209. if len(py_str)<20:
    210. QtWidgets.QMessageBox.information(
    211. self,
    212. '提示',
    213. '请选择要执行的策略',
    214. QtWidgets.QMessageBox.Yes
    215. )
    216. return
    217. self.current_stratege_py_str = py_str
    218. self.run_btn.setDisabled(True)
    219. self.shape_combox.setDisabled(True)
    220. self.stratege_run_start_time = datetime.now()
    221. self.stratege_start = True
    222. if self.thread_run:
    223. QtWidgets.QMessageBox.information(
    224. self,
    225. '提示',
    226. '有策略正在运行',
    227. QtWidgets.QMessageBox.Yes
    228. )
    229. return
    230. pre_data = {
    231. 'py_str':py_str,
    232. 'dailydata_filepath':dailydata_filepath
    233. }
    234. self.thread_run = Thread(
    235. target=self.running_run_thread,
    236. args=(pre_data,)
    237. )
    238. self.thread_run.start()
    239. self.thread_time = Thread(
    240. target=self.running_time_thread
    241. )
    242. self.thread_time.start()
    243. pass
    244. def running_run_thread(self,data:Dict[str,Any]):
    245. '''执行代码线程'''
    246. py_str = data['py_str']
    247. dailydata_filepath = data['dailydata_filepath']
    248. namespace = {}
    249. fun_stragegy = compile(py_str,'','exec')
    250. exec(fun_stragegy,namespace)
    251. ret = namespace['excute_strategy'](dailydata_filepath)
    252. self.signal_runcode.emit(ret)
    253. pass
    254. def thread_run_excuted(self,data:Dict):
    255. '''策略代码执行返回结果'''
    256. self.run_btn.setDisabled(False)
    257. self.shape_combox.setDisabled(False)
    258. # 保存结果文件
    259. df = data['whole_df']
    260. df00 = df.loc[df['signal']==1].copy()
    261. table_data = df00.loc[:,['tradeDate','signal','signal_name']].values.tolist()
    262. self.results_table.set_table_full_data(table_data)
    263. self.line_widget.set_data(data)
    264. self.thread_run = None
    265. self.thread_time = None
    266. self.stratege_start = False
    267. QtWidgets.QMessageBox.information(
    268. self,
    269. '提示',
    270. '当前策略运行完毕',
    271. QtWidgets.QMessageBox.Yes
    272. )
    273. pass
    274. def running_time_thread(self):
    275. '''计时线程'''
    276. while self.stratege_start:
    277. now = datetime.now()
    278. interval_time = (now-self.stratege_run_start_time).seconds
    279. res_map = {'res':interval_time}
    280. self.signal_time.emit(res_map)
    281. time.sleep(1)
    282. pass
    283. def thread_time_excuted(self,data:Dict):
    284. '''计时返回结果'''
    285. res = data['res']
    286. self.time_label.setText(f"{res}s")
    287. pass
    288. def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
    289. if self.thread_time:
    290. self.thread_time = None
    291. if self.thread_run:
    292. self.thread_run = None
    293. 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. 在入口代码对应py文件的上一级目录下创建 k_recognition_strategy 文件夹,后续K线形态判别的策略代码都保存在这个文件夹下

     

    数据

     链接:https://pan.baidu.com/s/1acghH4GQyu_OaOUmEjPdHw 
    提取码:6toe

     

  • 相关阅读:
    html5新增_webStorage
    我眼中的大数据(一)
    C#开发的OpenRA游戏之系统参数选项按钮
    ubuntu 开启笔记本摄像头并修复画面颠倒问题
    数据结构 | 堆的向上调整和向下调整算法【奇妙的堆排序】
    haskell 基本布局和组成元素
    想要高效运行SolidWorks,云上设计了解一下
    后台任务 window.requestIdleCallback 方法的使用
    代码随想录 动态规划Ⅳ
    Hadoop学习记录1
  • 原文地址:https://blog.csdn.net/m0_37967652/article/details/127723113