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

前置说明:
1. 必须以“excute_strategy”为方法名
2. 一个参数,股票日数据文件路径
3. 策略代码写完后,命名,保存在‘k_recognition_strategy’文件夹中,‘k_recognition_strategy’的创建位置在下面的工具代码中会介绍
4. 将策略文件名写入主程序类StrategeMainWidget 的 self.strategyname_code_map 对应的键值对中
- def excute_strategy(daily_file_path):
- '''
- 名称:大阳线
- 识别:K线实体长度为上一交易日价格4%以上。
- 前置条件:计算时间区间 2021-01-01 到 2022-01-01
- :param daily_file_path: 股票日数据文件路径
- :return:
- '''
- import pandas as pd
- import os
- start_date_str = '2021-01-01'
- end_date_str = '2022-01-01'
- df = pd.read_csv(daily_file_path,encoding='utf-8')
- # 删除停牌的数据
- df = df.loc[df['openPrice'] > 0].copy()
- df['o_date'] = df['tradeDate']
- df['o_date'] = pd.to_datetime(df['o_date'])
- df = df.loc[(df['o_date'] >= start_date_str) & (df['o_date']<=end_date_str)].copy()
- # 保存未复权收盘价数据
- df['close'] = df['closePrice']
- # 计算前复权数据
- df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
- df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
- df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
- df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
-
- # 开始计算
- df['body_length'] = df['closePrice'] - df['openPrice']
- df['signal'] = 0
- df['signal_name'] = 0
- df.loc[(df['body_length']>0) & (df['body_length']/df['closePrice'].shift(1)>=0.04),'signal'] = 1
- 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
- df = df.round({'signal_name':2})
-
- file_name = os.path.basename(daily_file_path)
- title_str = file_name.split('.')[0]
-
- line_data = {
- 'title_str':title_str,
- 'whole_header':['日期','收','开','高','低'],
- 'whole_df':df,
- 'whole_pd_header':['tradeDate','closePrice','openPrice','highestPrice','lowestPrice'],
- 'start_date_str':start_date_str,
- 'end_date_str':end_date_str,
- 'signal_type':'line'
- }
- return line_data
导入需要的包
- import sys,json,os,math,time,talib
- from threading import Thread
- import numpy as np
- import pandas as pd
- from datetime import datetime
- from dateutil.relativedelta import relativedelta
- from typing import Dict,Any,List
- from PyQt5 import QtCore,QtGui,QtWidgets
- from PyQt5.QtCore import Qt
- import pyqtgraph as pg
- import pyqtgraph.examples
- pg.setConfigOption('background','k')
- pg.setConfigOption('foreground','w')
pyqtgraph 日期横坐标控件
- class RotateAxisItem(pg.AxisItem):
- def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
- p.setRenderHint(p.Antialiasing,False)
- p.setRenderHint(p.TextAntialiasing,True)
-
- ## draw long line along axis
- pen,p1,p2 = axisSpec
- p.setPen(pen)
- p.drawLine(p1,p2)
- p.translate(0.5,0) ## resolves some damn pixel ambiguity
-
- ## draw ticks
- for pen,p1,p2 in tickSpecs:
- p.setPen(pen)
- p.drawLine(p1,p2)
-
- ## draw all text
- # if self.tickFont is not None:
- # p.setFont(self.tickFont)
- p.setPen(self.pen())
- for rect,flags,text in textSpecs:
- # this is the important part
- p.save()
- p.translate(rect.x(),rect.y())
- p.rotate(-30)
- p.drawText(-rect.width(),rect.height(),rect.width(),rect.height(),flags,text)
- # restoring the painter is *required*!!!
- p.restore()
pyqtgraph 蜡烛图控件
- class CandlestickItem(pg.GraphicsObject):
- def __init__(self, data):
- pg.GraphicsObject.__init__(self)
- self.data = data ## data must have fields: time, open, close, min, max
- self.generatePicture()
-
- def generatePicture(self):
- ## pre-computing a QPicture object allows paint() to run much more quickly,
- ## rather than re-drawing the shapes every time.
- self.picture = QtGui.QPicture()
- p = QtGui.QPainter(self.picture)
- p.setPen(pg.mkPen({'color':'w','width':1}))
- w = (self.data[1][0] - self.data[0][0]) / 3.
- for (t, open, close, min, max) in self.data:
- if open==close and open==max and max==min:
- p.drawLine(QtCore.QPointF(t-w, min), QtCore.QPointF(t+w, max))
- else:
- p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
- if open < close:
- p.setBrush(pg.mkBrush('r'))
- else:
- p.setBrush(pg.mkBrush('g'))
- p.drawRect(QtCore.QRectF(t-w, open, w * 2, close - open))
- p.end()
-
- def paint(self, p, *args):
- p.drawPicture(0, 0, self.picture)
-
- def boundingRect(self):
- ## boundingRect _must_ indicate the entire area that will be drawn on
- ## or else we will get artifacts and possibly crashing.
- ## (in this case, QPicture does all the work of computing the bouning rect for us)
- return QtCore.QRectF(self.picture.boundingRect())
分页表格控件
- class PageTableWidget(QtWidgets.QWidget):
- def __init__(self):
- super().__init__()
- self.init_data()
- self.init_ui()
- pass
-
- def init_data(self):
- # 表格全数据 二维数组
- self.table_full_data: List[Any] = []
- # 表格右键菜单 {菜单名:索引指向}
- self.table_right_menus: Dict[str, str] = {}
-
- self.total_page_count: int = 0
- self.total_rows_count: int = 0
- self.current_page: int = 1
- self.single_page_rows: int = 50
- pass
-
- def init_ui(self):
- pre_page_btn = QtWidgets.QPushButton('上一页')
- pre_page_btn.clicked.connect(self.pre_page_btn_clicked)
- next_page_btn = QtWidgets.QPushButton('下一页')
- next_page_btn.clicked.connect(self.next_page_btn_clicked)
-
- tip_label_0 = QtWidgets.QLabel('第')
- self.witch_page_lineedit = QtWidgets.QLineEdit()
- self.int_validator = QtGui.QIntValidator()
- self.witch_page_lineedit.setValidator(self.int_validator)
- self.witch_page_lineedit.setMaximumWidth(20)
- tip_label_1 = QtWidgets.QLabel('页')
- go_page_btn = QtWidgets.QPushButton('前往')
- go_page_btn.clicked.connect(self.go_page_btn_clicked)
- layout_witch_page = QtWidgets.QHBoxLayout()
- layout_witch_page.addWidget(tip_label_0)
- layout_witch_page.addWidget(self.witch_page_lineedit)
- layout_witch_page.addWidget(tip_label_1)
- layout_witch_page.addWidget(go_page_btn)
- layout_witch_page.addStretch(1)
-
- layout_pagechange = QtWidgets.QHBoxLayout()
- layout_pagechange.addWidget(pre_page_btn)
- layout_pagechange.addWidget(next_page_btn)
- layout_pagechange.addLayout(layout_witch_page)
-
- self.total_page_count_label = QtWidgets.QLabel(f"共0页")
- self.total_rows_count_label = QtWidgets.QLabel(f"共0行")
- self.current_page_label = QtWidgets.QLabel(f"当前第0页")
- layout_pagestatus = QtWidgets.QHBoxLayout()
- layout_pagestatus.addWidget(self.total_page_count_label)
- layout_pagestatus.addWidget(self.total_rows_count_label)
- layout_pagestatus.addWidget(self.current_page_label)
- layout_pagestatus.addStretch(1)
-
- layout_top = QtWidgets.QVBoxLayout()
- layout_top.addLayout(layout_pagechange)
- layout_top.addLayout(layout_pagestatus)
-
- self.target_table = QtWidgets.QTableWidget()
- self.target_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
- self.target_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
-
- layout = QtWidgets.QVBoxLayout()
- layout.addLayout(layout_top)
- layout.addWidget(self.target_table)
- self.setLayout(layout)
- pass
-
- def set_table_init_data(self, data: Dict[str, Any]):
- '''设置表头、右键菜单等初始化内容'''
- headers: List[str] = data['headers']
-
- self.target_table.setColumnCount(len(headers))
- self.target_table.setHorizontalHeaderLabels(headers)
- pass
-
- def set_table_full_data(self, data: List[Any]):
- '''表格全数据'''
- self.table_full_data = data
- self.total_rows_count = len(data)
- self.total_page_count = math.ceil(self.total_rows_count / self.single_page_rows)
- self.current_page = 1
-
- self.int_validator.setRange(1, self.total_page_count)
-
- self.total_page_count_label.setText(f"共{self.total_page_count}页")
- self.total_rows_count_label.setText(f"共{self.total_rows_count}行")
- self.caculate_current_show_data()
- pass
-
- def setting_current_pagestatus_label(self):
- self.current_page_label.setText(f"当前第{self.current_page}页")
- pass
-
- def caculate_current_show_data(self):
- '''计算当前要显示的数据'''
- start_dot = (self.current_page - 1) * self.single_page_rows
- end_dot = start_dot + self.single_page_rows
- current_data = self.table_full_data[start_dot:end_dot]
- self.fill_table_content(current_data)
- self.setting_current_pagestatus_label()
- pass
-
- def fill_table_content(self, data: List[Any]):
- self.target_table.clearContents()
- self.target_table.setRowCount(len(data))
- for r_i, r_v in enumerate(data):
- for c_i, c_v in enumerate(r_v):
- cell = QtWidgets.QTableWidgetItem(str(c_v))
- self.target_table.setItem(r_i, c_i, cell)
- pass
- pass
- self.target_table.resizeColumnsToContents()
- pass
-
- def go_page_btn_clicked(self):
- '''前往按钮点击'''
- input_page = self.witch_page_lineedit.text()
- input_page = input_page.strip()
- if not input_page:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '请输入要跳转的页码',
- QtWidgets.QMessageBox.Yes
- )
- return
- input_page = int(input_page)
- if input_page < 0 or input_page > self.total_page_count:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '输入的页码超出范围',
- QtWidgets.QMessageBox.Yes
- )
- return
- self.current_page = input_page
- self.caculate_current_show_data()
- pass
-
- def pre_page_btn_clicked(self):
- '''上一页按钮点击'''
- if self.current_page <= 1:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '已经是首页',
- QtWidgets.QMessageBox.Yes
- )
- return
- self.current_page -= 1
- self.caculate_current_show_data()
- pass
-
- def next_page_btn_clicked(self):
- '''下一页按钮点击'''
- if self.current_page >= self.total_page_count:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '已经是最后一页',
- QtWidgets.QMessageBox.Yes
- )
- return
- self.current_page += 1
- self.caculate_current_show_data()
- pass
- pass
K线与结果显示控件
- class PyQtGraphKWidget(QtWidgets.QWidget):
- def __init__(self):
- super().__init__()
- self.init_data()
- self.init_ui()
- def init_data(self):
- # https://www.sioe.cn/yingyong/yanse-rgb-16/
- self.color_line = (30, 144, 255)
- # 0 幽灵的白色; 1 纯黄; 2 紫红色; 3 纯绿; 4 道奇蓝
- self.color_list = [(248, 248, 255), (255, 255, 0), (255, 0, 255), (0, 128, 0), (30, 144, 255)]
- self.main_fixed_target_list = [] # 主体固定曲线,不能被删除
- self.whole_df = None
- self.whole_header = None
- self.whole_pd_header = None
- self.current_whole_data = None
- self.current_whole_df = None
- self.signal_type = None
- self.duration_len = None
- pass
- def init_ui(self):
- self.whole_duration_label = QtWidgets.QLabel('左边界~右边界')
-
- self.title_label = QtWidgets.QLabel('执行过程查看')
- self.title_label.setAlignment(Qt.AlignCenter)
- self.title_label.setStyleSheet('QLabel{font-size:18px;font-weight:bold}')
-
- xax = RotateAxisItem(orientation='bottom')
- xax.setHeight(h=80)
- self.pw = pg.PlotWidget(axisItems={'bottom': xax})
- self.pw.setMouseEnabled(x=True, y=True)
- # self.pw.enableAutoRange(x=False,y=True)
- self.pw.setAutoVisible(x=False, y=True)
-
- layout_right = QtWidgets.QVBoxLayout()
- layout_right.addWidget(self.title_label)
- layout_right.addWidget(self.whole_duration_label)
- layout_right.addWidget(self.pw)
- self.setLayout(layout_right)
- pass
-
- def set_data(self, data: Dict[str, Any]):
- title_str = data['title_str']
- whole_header = data['whole_header']
- whole_df = data['whole_df']
- whole_pd_header = data['whole_pd_header']
- signal_type = data['signal_type']
- if signal_type == 'duration' or signal_type == 'duration_detail':
- duration_len = data['duration_len']
- self.duration_len = duration_len
- pass
-
- self.whole_header = whole_header
- self.whole_df = whole_df
- self.whole_pd_header = whole_pd_header
- self.signal_type = signal_type
-
- self.title_label.setText(title_str)
- self.whole_duration_label.setText(f"{self.whole_df.iloc[0]['tradeDate']}~{self.whole_df.iloc[-1]['tradeDate']}")
-
- self.current_whole_df = self.whole_df.copy()
- self.caculate_and_show_data()
- pass
-
- def caculate_and_show_data(self):
- df = self.current_whole_df.copy()
- df.reset_index(inplace=True)
- df['i_count'] = [i for i in range(len(df))]
- tradeDate_list = df['tradeDate'].values.tolist()
- x = range(len(df))
- xTick_show = []
- x_dur = math.ceil(len(df) / 20)
- for i in range(0, len(df), x_dur):
- xTick_show.append((i, tradeDate_list[i]))
- if len(df) % 20 != 0:
- xTick_show.append((len(df) - 1, tradeDate_list[-1]))
- candle_data = []
- for i, row in df.iterrows():
- candle_data.append(
- (row['i_count'], row['openPrice'], row['closePrice'], row['lowestPrice'], row['highestPrice']))
-
- self.current_whole_data = df.loc[:, self.whole_pd_header].values.tolist()
- # 开始配置显示的内容
- self.pw.clear()
-
- xax = self.pw.getAxis('bottom')
- xax.setTicks([xTick_show])
-
- if self.signal_type == 'line':
- df_signal = df.loc[df['signal'] == 1].copy()
- for i,row in df_signal.iterrows():
- 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)})
- self.pw.addItem(signal_fiexed_target)
- pass
- elif self.signal_type == 'duration':
- for i,i_len in enumerate(self.duration_len):
- df_signal = df.loc[df['signal'] == i+1].copy()
- for i0, row in df_signal.iterrows():
- signal_fiexed_target = pg.LinearRegionItem([row['i_count'] - i_len + 1, row['i_count']],
- movable=False, brush=(
- self.color_line[0], self.color_line[1], self.color_line[2], 50))
- self.pw.addItem(signal_fiexed_target)
- pass
- elif self.signal_type == 'duration_detail':
- df['param_0'] = df['signal']-df['signal'].shift(1)
- df['param_1'] = df['signal']-df['signal'].shift(-1)
- df_start = df.loc[df['param_0']==1].copy()
- df_end = df.loc[df['param_1']==1].copy()
- start_list = df_start['i_count'].values.tolist()
- end_list = df_end['i_count'].values.tolist()
- for i,item in enumerate(start_list):
- signal_fiexed_target = pg.LinearRegionItem([item, end_list[i]],
- movable=False, brush=(
- self.color_line[0], self.color_line[1], self.color_line[2], 50))
- self.pw.addItem(signal_fiexed_target)
- pass
-
- candle_fixed_target = CandlestickItem(candle_data)
- self.main_fixed_target_list.append(candle_fixed_target)
- self.pw.addItem(candle_fixed_target)
-
- self.vLine = pg.InfiniteLine(angle=90, movable=False)
- self.hLine = pg.InfiniteLine(angle=0, movable=False)
- self.label = pg.TextItem()
-
- self.pw.addItem(self.vLine, ignoreBounds=True)
- self.pw.addItem(self.hLine, ignoreBounds=True)
- self.pw.addItem(self.label, ignoreBounds=True)
-
- self.vb = self.pw.getViewBox()
- self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
- self.pw.enableAutoRange()
- pass
-
- def mouseMoved(self, evt):
- pos = evt[0]
- if self.pw.sceneBoundingRect().contains(pos):
- mousePoint = self.vb.mapSceneToView(pos)
- index = int(mousePoint.x())
- if index >= 0 and index < len(self.current_whole_data):
- target_data = self.current_whole_data[index]
- html_str = ''
- for i, item in enumerate(self.whole_header):
- html_str += f"
{item}:{target_data[i]}" - self.label.setHtml(html_str)
- self.label.setPos(mousePoint.x(), mousePoint.y())
- self.vLine.setPos(mousePoint.x())
- self.hLine.setPos(mousePoint.y())
- pass
-
- def mouseClicked(self, evt):
- pass
-
- def updateViews(self):
- pass
- pass
控制面板,也是主程序控件
- class StrategeMainWidget(QtWidgets.QWidget):
- signal_runcode = QtCore.pyqtSignal(object)
- signal_time = QtCore.pyqtSignal(object)
- def __init__(self):
- super().__init__()
- self.thread_run: Thread = None
- self.thread_time: Thread = None
-
- self.init_data()
- self.init_ui()
- self.register_event()
- pass
- def init_data(self):
- self.pre_output_dir = './'
- self.results_output_dir = './k_recognition_output/'
- self.please_select_str: str = '--请选择--'
- self.k_strategy_path: str = '../k_recognition_strategy/'
- self.stratege_run_start_time = None
- self.stratege_start = False
- self.current_stratege_py_str: str = ''
- self.dailydata_path:str = ''
- self.k_strategy_map: Dict[str,Any] = {
- '表上升和下跌的K线形态':['大阳线(长阳线)','大阴线(长阴线)','小阳线和小阴线','十字星和十字线','长十字线','螺旋桨','一字线','T字线和倒T字线','锤头线和吊颈线(绞刑线)','倒锤头线和射击之星(流星、扫帚星)','揉搓线','尽头线','镊子线','穿头破脚','身怀六甲和十字胎','下跌三连阴'],
- '表见底和上升的K线形态':['向下加速度线','早晨(希望)之星和早晨(希望)十字星','好友反攻','曙光初现','旭日东升','平底','塔形底','圆底','低位并排阳线','低档五阳线','连续跳空三阴线','红三兵(三个白色武士)','冉冉上升','稳步上涨','徐缓上升','上升抵抗','上升(向上)弧形线','下探上涨','上涨两颗星','跳空上扬(升势鹤鸦缺口)','高位并排阳线(升势恋人肩并肩缺口)','跳空下跌三颗星','上升三部曲(升势三鸦)','多方尖兵(仙人指路)','两阳夹一阴(两红夹一黑)','上档盘旋形'],
- '表见顶和下跌的K线形态':['向上加速度线','黄昏之星和黄昏十字星','淡友反攻','乌云盖顶','倾盆大雨','平顶(镊子顶)','塔形顶','圆顶','双飞乌鸦','高档五阴线','连续跳空三阳线','黑三兵','三只乌鸦(暴跌三杰)','绵绵阴跌','下跌不止','徐缓下降','下跌抵抗','下降弧形线','高开出逃','下跌三颗星','倒三阳','升势受阻','升势停顿','下降三部曲(降势三鹤)','空方尖兵','两阴夹一阳(两黑夹一红)','跛脚阳线','低档盘旋形','下降覆盖线']
- }
- self.strategyname_code_map:Dict[str,str] = {
- '大阳线(长阳线)':'',
- '大阴线(长阴线)':'',
- '小阳线和小阴线':'',
- '十字星和十字线':'',
- '长十字线':'',
- '螺旋桨':'',
- '一字线':'',
- 'T字线和倒T字线':'',
- '锤头线和吊颈线(绞刑线)':'',
- '倒锤头线和射击之星(流星、扫帚星)':'',
- '揉搓线':'',
- '尽头线':'',
- '镊子线':'',
- '穿头破脚':'',
- '身怀六甲和十字胎':'',
- '下跌三连阴':'',
- '向下加速度线':'',
- '早晨(希望)之星和早晨(希望)十字星':'',
- '好友反攻':'',
- '曙光初现':'',
- '旭日东升':'',
- '平底':'',
- '塔形底':'',
- '圆底':'',
- '低位并排阳线':'',
- '低档五阳线':'',
- '连续跳空三阴线':'',
- '红三兵(三个白色武士)':'',
- '冉冉上升':'',
- '稳步上涨':'',
- '徐缓上升':'',
- '上升抵抗':'',
- '上升(向上)弧形线':'',
- '下探上涨':'',
- '上涨两颗星':'',
- '跳空上扬(升势鹤鸦缺口)':'',
- '高位并排阳线(升势恋人肩并肩缺口)':'',
- '跳空下跌三颗星':'',
- '上升三部曲(升势三鸦)':'',
- '多方尖兵(仙人指路)':'',
- '两阳夹一阴(两红夹一黑)':'',
- '上档盘旋形':'',
- '向上加速度线':'',
- '黄昏之星和黄昏十字星':'',
- '淡友反攻':'',
- '乌云盖顶':'',
- '倾盆大雨':'',
- '平顶(镊子顶)':'',
- '塔形顶':'',
- '圆顶':'',
- '双飞乌鸦':'',
- '高档五阴线':'',
- '连续跳空三阳线':'',
- '黑三兵':'',
- '三只乌鸦(暴跌三杰)':'',
- '绵绵阴跌':'',
- '下跌不止':'',
- '徐缓下降':'',
- '下跌抵抗':'',
- '下降弧形线':'',
- '高开出逃':'',
- '下跌三颗星':'',
- '倒三阳':'',
- '升势受阻':'',
- '升势停顿':'',
- '下降三部曲(降势三鹤)':'',
- '空方尖兵':'',
- '两阴夹一阳(两黑夹一红)':'',
- '跛脚阳线':'',
- '低档盘旋形':'',
- '下降覆盖线':'',
- }
- self.table_header: List = ['日期','信号','备注']
- pass
- def init_ui(self):
- self.setWindowTitle('股票K线形态识别工具')
- tip_0 = QtWidgets.QLabel('股票日数据文件:')
- self.dailydata_filepath_lineedit = QtWidgets.QLineEdit()
- self.dailydata_filepath_lineedit.setReadOnly(True)
- dailydata_choice_btn = QtWidgets.QPushButton('选择文件')
- dailydata_choice_btn.clicked.connect(self.dailydata_choice_btn_clicked)
-
- layout_top = QtWidgets.QHBoxLayout()
- layout_top.addWidget(tip_0)
- layout_top.addWidget(self.dailydata_filepath_lineedit)
- layout_top.addWidget(dailydata_choice_btn)
- layout_top.addStretch(1)
-
- tip_1 = QtWidgets.QLabel('类别:')
- self.type_combox = QtWidgets.QComboBox()
- self.type_combox.addItem(self.please_select_str)
- self.type_combox.addItems(list(self.k_strategy_map.keys()))
- self.type_combox.currentIndexChanged.connect(self.type_combox_currentIndexChanged)
-
- tip_2 = QtWidgets.QLabel('形态:')
- self.shape_combox = QtWidgets.QComboBox()
- self.shape_combox.addItem(self.please_select_str)
- self.shape_combox.currentIndexChanged.connect(self.shape_combox_currentIndexChanged)
-
- self.run_btn = QtWidgets.QPushButton('运行')
- self.run_btn.clicked.connect(self.run_btn_clicked)
-
- self.time_label = QtWidgets.QLabel('')
-
- layout_combox = QtWidgets.QFormLayout()
- layout_combox.addRow(tip_1,self.type_combox)
- layout_combox.addRow(tip_2,self.shape_combox)
- layout_combox.addRow(self.run_btn,self.time_label)
-
- self.code_textedit = QtWidgets.QTextEdit()
- self.code_textedit.setReadOnly(True)
-
- self.results_table = PageTableWidget()
- self.results_table.set_table_init_data({'headers':self.table_header})
-
- layout_left = QtWidgets.QVBoxLayout()
- layout_left.addLayout(layout_combox)
- layout_left.addWidget(self.code_textedit)
- layout_left.addWidget(self.results_table)
-
- self.line_widget = PyQtGraphKWidget()
-
- layout_down = QtWidgets.QHBoxLayout()
- layout_down.addLayout(layout_left,1)
- layout_down.addWidget(self.line_widget,2)
-
- layout = QtWidgets.QVBoxLayout()
- layout.addLayout(layout_top)
- layout.addLayout(layout_down)
- self.setLayout(layout)
- pass
- def register_event(self):
- self.signal_runcode.connect(self.thread_run_excuted)
- self.signal_time.connect(self.thread_time_excuted)
- pass
- def dailydata_choice_btn_clicked(self):
- path, _ = QtWidgets.QFileDialog.getOpenFileName(
- self,
- '打开股票日数据所在文件',
- self.pre_output_dir
- )
- if not path:
- return
- self.dailydata_filepath_lineedit.setText(path)
- pass
- def type_combox_currentIndexChanged(self,cur_i:int):
- cur_txt = self.type_combox.currentText()
- if not cur_txt or cur_txt == self.please_select_str:
- return
- shape_name_list = self.k_strategy_map[cur_txt]
- self.shape_combox.clear()
- self.shape_combox.addItem(self.please_select_str)
- self.shape_combox.addItems(shape_name_list)
- pass
- def shape_combox_currentIndexChanged(self,cur_i:int):
- cur_txt = self.shape_combox.currentText()
- if not cur_txt or cur_txt == self.please_select_str:
- return
- cur_strategy_name = self.strategyname_code_map[cur_txt]
- if len(cur_strategy_name)<=0:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '该K线形态的策略还没上线',
- QtWidgets.QMessageBox.Yes
- )
- return
- strategy_file_path = self.k_strategy_path + cur_strategy_name
- with open(strategy_file_path,'r',encoding='utf-8') as fr:
- py_str = fr.read()
- if len(py_str)<=20:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '策略文件代码为空',
- QtWidgets.QMessageBox.Yes
- )
- return
- self.code_textedit.setPlainText(py_str)
- pass
- def run_btn_clicked(self):
- '''运行按钮'''
- # 检查股票日数据文件夹
- dailydata_filepath = self.dailydata_filepath_lineedit.text()
- dailydata_filepath = dailydata_filepath.strip()
- if len(dailydata_filepath)<=0:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '请选择股票日数据文件',
- QtWidgets.QMessageBox.Yes
- )
- return
-
- py_str = self.code_textedit.toPlainText()
- if len(py_str)<20:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '请选择要执行的策略',
- QtWidgets.QMessageBox.Yes
- )
- return
- self.current_stratege_py_str = py_str
-
- self.run_btn.setDisabled(True)
- self.shape_combox.setDisabled(True)
- self.stratege_run_start_time = datetime.now()
- self.stratege_start = True
-
- if self.thread_run:
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '有策略正在运行',
- QtWidgets.QMessageBox.Yes
- )
- return
- pre_data = {
- 'py_str':py_str,
- 'dailydata_filepath':dailydata_filepath
- }
- self.thread_run = Thread(
- target=self.running_run_thread,
- args=(pre_data,)
- )
- self.thread_run.start()
- self.thread_time = Thread(
- target=self.running_time_thread
- )
- self.thread_time.start()
- pass
- def running_run_thread(self,data:Dict[str,Any]):
- '''执行代码线程'''
- py_str = data['py_str']
- dailydata_filepath = data['dailydata_filepath']
-
- namespace = {}
- fun_stragegy = compile(py_str,'
' ,'exec') - exec(fun_stragegy,namespace)
- ret = namespace['excute_strategy'](dailydata_filepath)
- self.signal_runcode.emit(ret)
- pass
- def thread_run_excuted(self,data:Dict):
- '''策略代码执行返回结果'''
- self.run_btn.setDisabled(False)
- self.shape_combox.setDisabled(False)
-
- # 保存结果文件
- df = data['whole_df']
- df00 = df.loc[df['signal']==1].copy()
- table_data = df00.loc[:,['tradeDate','signal','signal_name']].values.tolist()
- self.results_table.set_table_full_data(table_data)
-
- self.line_widget.set_data(data)
-
- self.thread_run = None
- self.thread_time = None
- self.stratege_start = False
-
- QtWidgets.QMessageBox.information(
- self,
- '提示',
- '当前策略运行完毕',
- QtWidgets.QMessageBox.Yes
- )
- pass
- def running_time_thread(self):
- '''计时线程'''
- while self.stratege_start:
- now = datetime.now()
- interval_time = (now-self.stratege_run_start_time).seconds
- res_map = {'res':interval_time}
- self.signal_time.emit(res_map)
- time.sleep(1)
- pass
- def thread_time_excuted(self,data:Dict):
- '''计时返回结果'''
- res = data['res']
- self.time_label.setText(f"{res}s")
- pass
-
- def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
- if self.thread_time:
- self.thread_time = None
- if self.thread_run:
- self.thread_run = None
- self.close()
- if __name__ == '__main__':
- QtCore.QCoreApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
- app = QtWidgets.QApplication(sys.argv)
- t_win = StrategeMainWidget()
- t_win.showMaximized()
- app.exec()
- pass
1. 在入口代码对应py文件的上一级目录下创建 k_recognition_strategy 文件夹,后续K线形态判别的策略代码都保存在这个文件夹下
链接:https://pan.baidu.com/s/1acghH4GQyu_OaOUmEjPdHw
提取码:6toe