欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

金融科技之交易:动量效应选股策略

程序员文章站 2022-07-13 15:15:16
...

这是本蒟蒻在实习时做的一个小项目,主要是筛选与大盘偏离的股票,并制作成带有UI界面的exe文件,供基金经理使用。在征得公司同意后将其整理发布,记录自己的学习历程。

策略内容:

动量效应:动量效应一般又称“惯性效应”。是指股票的收益率有延续原来的运动方向的趋势,即过去一段时间收益率较高的股票在未来获得的收益率仍会高于过去收益率较低的股票。

在这里,我们用股票相对于上证综指的超额收益来表示收益率。我们希望筛选出在过去的一段时间内超额收益在特定范围的股票。
但是超额收益作为一个数值对于大部分的人来说并不直观,所以我们可以用股票的价格线与指数的价格线之间的夹角的角度来直观地感受超额收益。

而价格线的绘制可以有两种方法:1.逻辑回归线 2.第一天和最后一天的价格点的连线。同时价格要转换成价格相较于第一天的增幅,这样可以将股票和指数的两个起始点放在同一个点上。
如下图:
金融科技之交易:动量效应选股策略
金融科技之交易:动量效应选股策略

同时要注意数据的标准化,将角度的计算放在我们熟悉的单位长度相等的笛卡尔坐标系下,这样计算出来的结果才更接近我们直观观察的角度。

代码整理

因为本蒟蒻水平有限,接触编程的时间不长。如果有代码不规范、语句冗余等问题还希望不吝赐教。

同时因为源码细节较多,这里只给出核心的代码。

这里先给出效果图:

金融科技之交易:动量效应选股策略金融科技之交易:动量效应选股策略金融科技之交易:动量效应选股策略

金融科技之交易:动量效应选股策略

角度计算

标准化处理

因为参与运算的数据氛围不一致。价格变化率的绝对值一般不超过(-5,5)。而日期时间的序号是从0到数十或数百(由选择的时间范围觉得)。这样计算出的直线的斜率都是很小的值。所以将数据进行标准化处理,这样计算出的斜率才和观察的相近。因为我们估算直线斜率一般会把看到的直线默认为是在两个坐标轴的单位长度和压缩比率相同的坐标系中。

这里使用最大最小标准化处理,将价格变化率和日期序列都映射到【0,1】。

  def MaxMinNormalization(self,x, min, max):
      """[0,1] normaliaztion"""
      x = (x - min) / (max - min)
      return x

数据准备

将tushare中获取的数据进行初步处理。包括计算价格涨幅、标准化处理等

#这里的st、index都是从tushare中获取的数据。
        st_x = array([i for i in range(0, len(st['close']))])
      #日期转换成序号
        st_x = self.MaxMinNormalization(st_x, 0, len(st['close']) - 1)
        #标准化处理
        st_x = array([st_x]).T
       # T为矩阵转置把1xn变成nx1

        index_x = array([i for i in range(0, len(index['close']))])
        index_x = self.MaxMinNormalization(index_x, 0, len(index['close']) - 1)        
        index_x = array([index_x]).T
      #计算涨幅
        st_y = array([(close - st['close'][0]) / st['close'][0] for close in st['close']])
        index_y = array([(close - index['close'][0]) / index['close'][0] for close in index['close']])
        min = hstack((st_y, index_y)).min()
        max = hstack((st_y, index_y)).max()
        #因为两条直线要画在同一个图中,所以要一起进行标准化处理,最大和最小值是股票和指数共同的最大值和最小值。
        st_y = self.MaxMinNormalization(st_y, min, max)
        st_y = array([st_y]).T
        index_y = self.MaxMinNormalization(index_y, min, max)
        index_y = array([index_y]).T
        #标准化处理

回归线的斜率

#线性回归
regr_st = LinearRegression().fit(st_x, st_y) #训练,得到股票的回归线
regr_index = LinearRegression().fit(index_x, index_y)#得到指数的回归线
st_coef=regr_st.coef_[0][0],
index_coef =regr_index.coef_[0][0])
#分别是股票和指数回归线的斜率

两点连线的斜率

st_coef = list(st_y)[-1][0]-list(st_y)[0][0])/list(st_x)[-1][0]
index_coef =list(index_y)[-1][0]-list(index_y)[0][0])/list(index_x)[-1][0]

由斜率计算角度

x = array([1, st_coef])  # 方向向量
y = array([1, index])

Lx = sqrt(x.dot(x))
Ly = sqrt(y.dot(y))
# 求得斜线的长度
cos_angle = x.dot(y) / (Lx * Ly)
# 求得cos_sita的值再反过来计算,绝对长度乘以cos角度为矢量长度
angle = arccos(cos_angle) * 360 / 2 / 3.1415926

计算模块的整合

将上面介绍的整合成一个类

import tushare as ts
import datetime
from sklearn.linear_model import LinearRegression
from numpy import array,hstack,sqrt,arccos

class clac():
   #数据标准化处理函数
    def MaxMinNormalization(self,x, min, max):
        """[0,1] normaliaztion"""
        x = (x - min) / (max - min)
        return x
   #得到两个直线的斜率
    def get_coef(self,st, index,type):
        st_x = array([i for i in range(0, len(st['close']))])
        st_x = self.MaxMinNormalization(st_x, 0, len(st['close']) - 1)
        # T为矩阵转置把1xn变成nx1
        st_x = array([st_x]).T
        index_x = array([i for i in range(0, len(index['close']))])
        index_x = self.MaxMinNormalization(index_x, 0, len(index['close']) - 1)
        # T为矩阵转置把1xn变成nx1
        index_x = array([index_x]).T
        st_y = array([(close - st['close'][0]) / st['close'][0] for close in st['close']])
        index_y = array([(close - index['close'][0]) / index['close'][0] for close in index['close']])
        min = hstack((st_y, index_y)).min()
        max = hstack((st_y, index_y)).max()
        st_y = self.MaxMinNormalization(st_y, min, max)
        st_y = array([st_y]).T
        index_y = self.MaxMinNormalization(index_y, min, max)
        index_y = array([index_y]).T
        if type == '回归线':  # 是回归线版本
            # regr为回归过程,fit(x,y)进行回归
            regr_st = LinearRegression().fit(st_x, st_y)
            regr_index = LinearRegression().fit(index_x, index_y)
            return regr_st.coef_[0][0], regr_index.coef_[0][0]
        else:#是两点连线
            return (list(st_y)[-1][0]-list(st_y)[0][0])/list(st_x)[-1][0],(list(index_y)[-1][0]-list(index_y)[0][0])/list(index_x)[-1][0]

    def get_angle(self,x, y):#计算两个直线的角度
        # x和y是方向向量
        Lx = sqrt(x.dot(x))
        Ly = sqrt(y.dot(y))
        # 相当于勾股定理,求得斜线的长度
        cos_angle = x.dot(y) / (Lx * Ly)
        # 求得cos_sita的值再反过来计算,绝对长度乘以cos角度为矢量长度
        angle = arccos(cos_angle) * 360 / 2 / 3.1415926
        return angle
    def angle(self,st_code,months):#最终的包装,由外部调用
    
     #获取数据
        pro = ts.pro_api('你的token')
        start_date = datetime.datetime.today() - datetime.timedelta(days=int(30*months))
        start_date = start_date.date().strftime("%Y%m%d")
        st_price = pro.daily(ts_code=st_code, start_date=start_date, fields='ts_code,trade_date,close')
        end_date=st_price['trade_date'][0]
        st_price=st_price.sort_values(["trade_date"], ascending=True).reset_index(drop=True)
        start_date = st_price['trade_date'][0]
        index_close = pro.index_daily(ts_code='000001.SH', start_date=start_date, end_date=end_date,fields='ts_code,trade_date,close')
        index_close = index_close.sort_values(["trade_date"], ascending=True).reset_index(drop=True)
        
    #得到斜率
        st_coef, index_coef = self.get_coef(st_price, index_close,'回归线')
        x = array([1, st_coef])  # 回归线的方向向量
        y = array([1, index_coef])
        angle0 = self.get_angle(x, y)
        if st_coef < index_coef:
            angle0 = -1 * angle0
        st_coef, index_coef = self.get_coef(st_price, index_close, '两点连线')
        x = array([1, st_coef])  # 两点连线方向向量
        y = array([1, index_coef])
        angle1 = self.get_angle(x, y)
        if st_coef < index_coef:
            angle1 = -1 * angle1
        return (angle0,angle1)

绘制叠加图

在详细窗口界面展示了两个图像,一个是K线图,一个是指数叠加图。K线图是从东方财富网爬取下来的。已经在上面介绍过了。而指数叠加图需要我们自己绘制。在Pyqt5窗口中展示matplotlib绘制的图,需要创建画布。定义一个画布类:

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

class PlotCanvas(FigureCanvas):##画布
    def __init__(self, parent=None,width=5, height=4, dpi=100):#初始化
        fig = Figure(figsize=(width, height), dpi=dpi, edgecolor="blue")
        plt.rcParams['font.sans-serif'] = ['SimHei']
        plt.rcParams['axes.unicode_minus'] = False
        self.axes = fig.add_subplot(111)
        self.parent=parent
        fig.subplots_adjust(left=0.1, bottom=0.1, top=0.9, right=0.9)
        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)


        self.drawLine(self.parent.ui.comboBox_1.currentText())

    def drawLine(self,type):#绘制图像
        pro = ts.pro_api('请填入你的token')
        start_date = datetime.datetime.today() - datetime.timedelta(days=int(30 * self.parent.months))
        start_date = start_date.date().strftime("%Y%m%d")

        st = pro.daily(ts_code=self.parent.stCode, start_date=start_date, fields='ts_code,trade_date,close')
        end_date=st['trade_date'][0]
        st = st.sort_values(["trade_date"], ascending=True).reset_index(drop=True)
        st_close = [(close - st['close'][0]) / st['close'][0] for close in st['close']]
        start_date=st['trade_date'][0]

        index = pro.index_daily(ts_code='000001.SH', start_date=start_date,end_date=end_date, fields='ts_code,trade_date,close')
        index = index.sort_values(["trade_date"], ascending=True).reset_index(drop=True)
        index_close = [(close - index['close'][0]) / index['close'][0] for close in index['close']]

        index_date = ['{}-{}-{}'.format(date[:4],date[4:6],date[6:]) for date in index['trade_date']]

        st_x = array([i for i in range(0, len(st['trade_date']))])
        st_x = array([st_x]).T

        index_x = array([i for i in range(0, len(index['trade_date']))])
        index_x = array([index_x]).T

        st_y = array([(close - st['close'][0]) / st['close'][0] for close in st['close']])
        index_y = array([(close - index['close'][0]) / index['close'][0] for close in index['close']])


        st_y = array([st_y]).T
        index_y = array([index_y]).T
        self.st_x=st_x
        self.st_y=st_y
        self.index_x=index_x
        self.index_y=index_y
        self.axes.cla()
        self.axes.set_title("大盘指数叠加")
        
        def to_percent(temp, position):
            return '%1.0f' % (100 * t

        self.axes.yaxis.set_major_formatter(FuncFormatter(to_percent))  # 更改y轴的显示内容为百分数
        self.axes.plot(index_x, index_close, color='red', label='上证指数', linewidth=0.5)
        self.axes.plot(st_x, st_close, color='blue', label=self.parent.stCode, linewidth=0.5)
        self.axes.legend(loc='best')
        x=list(index_x[::len(index_x) // 4-1])
        self.axes.set_xticks(x)
        date=index_date[::len(index_date)//4-1]
        self.axes.set_xticklabels(date, rotation=15)
        st_x=self.st_x
        st_y=self.st_y
        index_x=self.index_x
        index_y=self.index_y
        if type=='回归线':
            regr_st = LinearRegression().fit(st_x, st_y)
            regr_index = LinearRegression().fit(index_x, index_y)
            self.axes.plot(st_x, regr_st.predict(st_x),color='blue', linestyle='-.',label='股票回归', linewidth=1)


            self.axes.plot(index_x, regr_index.predict(index_x), color='red',
                           linestyle='-.', label='指数回归', linewidth=1)
            self.axes.legend(loc='best')
        else:
            self.axes.plot([st_x[0],st_x[-1]],[st_y[0],st_y[-1]],color='blue', linestyle='-.',label='股票连线', linewidth=1)
            self.axes.plot([index_x[0], index_x[-1]], [index_y[0], index_y[-1]], color='red', linestyle='-.', label='指数连线',
                           linewidth=1)
            self.axes.legend(loc='best')

UI界面控件:

UI界面的设计使用的是Python的pyqt5并借助了QT designer工具来辅助设置UI界面。

Pyqt5的内容很多,这里我们只介绍我们所用到的几个重要控件的常用方法,如果想了解更多pyqt5的内容,可以去网络上查找相关的介绍,CSDN中有很多介绍Pyqt5的文章。

QLabel

QLabel是标签,对应的是展示效果图中的“角度范围”、“时间范围“等文字信息。

QLabel中部分常用的方法是:

text() 获得Qlabel的文本内容
setText() 设置Qlabel的文本内容
setGeometry() 设置Qlabel在窗口中的位置和大小
setFont() 设置Qlabel中文本的字体
setPixmap() 在QLabel中填充图片

QLineEdit

QLineEdit是单行文本输入框,即效果图中输入角度和月份数的三个框框。

text() 获得QLineEdit中的文本内容
setGeometry() 设置QLineEdit在窗口中的位置和大小
setGeometry() 设置Qlabel在窗口中的位置和大小

QPushButton

QPushButton是按钮,即效果图中的”选择目标股票表格“和”开始筛选“两个按钮。

setText() 设置按钮上的文本
setStyleSheet() 设置按钮的样式,如背景颜色
setGeometry() 设置Qlabel在窗口中的位置和大小

QComboBox

QComboBox是下拉框,即效果中的选择(回归线/两点连线/二者任一)的控件。

addItem() 添加选项
currentText() 获取选中的文本
setGeometry() 设置Qlabel在窗口中的位置和大小

QTableWidget

QTableWidget是表格,即效果图中展示筛选结果的表格。

setColumnCount() 设置列数
setRowCount() 设置行数
setHorizontalHeaderItem() 设置表头(一次设置一个)
setHorizontalHeaderLabels() 设置表头(参数为列表,一次多个)
tableWidget.setItem() 设置单元格内容
tableWidget.insertRow() 插入一行单元格
tableWidget.setSpan() 合并单元格
setGeometry() 设置Qlabel在窗口中的位置和大小

信号与槽函数

上一个板块为UI添加控件,但设计出来的只是静态的窗口。点击按钮、选择下拉框都没有任何反应。如果我们希望点击按钮或者做其他事情时我们的程序可以执行某些功能。就需要利用信号与槽函数。Pyqt5的控件几乎都支持绑定信号和槽函数。信号可以理解为一个事件,比如单击\双击按钮、单击表格的文本框、改变下拉选择框当前的选项等等。槽函数就是与这些信号绑定的函数。当信号发生时,会自动执行与其绑定的槽函数。

下面给出该程序的槽函数与信号:

选择目标股票表格

有时候,我们并不想筛选全部的A股,而是只关注特定的若干只股票,有目的性地筛选。
单击”选择模板股票表格“按钮,可弹出一个文件选择框。选择含有存储目标股票代码的excel文件。
金融科技之交易:动量效应选股策略

金融科技之交易:动量效应选股策略

该功能的槽函数如下:

def set_target_stock(self):
    try:
        filechoose = QFileDialog.getOpenFileName(self);
        filechoosed = filechoose[0]
        self.target_stock=list(pd.read_excel(filechoosed)['ts_code'])
        self.main_ui.label_7.setText(filechoosed)#更改窗口中的目标股票提示内容
    except:
        print(traceback.format_exc())

触发该槽函数的信号为单击该按钮:

self.main_ui.btn_set_target_stock.clicked.connect(self.set_target_stock)#btn_set_target_stock就是按钮控件

开始筛选

单击”开始筛选“按钮,执行程序。

该函数调用了一个自定义的子线程,该子线程执行筛选操作,其具体的内容会在后面介绍。

def run(self):
    try:
        if self.main_ui.btn_run.text()=='开始筛选':#开始执行
            self.main_ui.btn_run.setText('正在筛选')
            self.main_ui.btn_run.setStyleSheet("background-color: rgb(255, 0, 0);")
            type=self.main_ui.comboBox.currentText()#下拉框的选项
            angle1=int(self.main_ui.angle_range1.text())
            angle2=int(self.main_ui.angle_range2.text())
            months=int(self.main_ui.time_range.text())
            #在窗口中输入的三个变量,angle_rang1等三个空间都是QLineEdit
            self.thread = thread_run(self.target_stock,angle1,angle2,months,type)
            #这里创建子线程,并传入参数
            self.thread._signal.connect(self.update)
            #设置子线程的信号与槽函数,子线程返回筛选结果时会执行update函数.
            self.thread.start()
            #开始执行
          
        else:#停止执行
            self.thread.terminate()#关闭子线程
            self.thread.wait()
            self.main_ui.tableWidget.insertRow(0)
            self.main_ui.tableWidget.setSpan(0, 0, 1, 4)
            redBrush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
            item = QtWidgets.QTableWidgetItem(
                str(datetime.datetime.now().strftime("%H:%M:%S")+':暂停筛选'))
            self.main_ui.tableWidget.setItem(0, 0, item)
            self.main_ui.tableWidget.item(0, 0).setForeground(redBrush)
            #在表格中插入一行,并写入提示信息。
            self.main_ui.btn_run.setText('开始筛选')
            self.main_ui.btn_run.setStyleSheet("background-color: rgb(255, 255, 255);")
    except:
        print(traceback.format_exc())

信号:

self.main_ui.btn_run.clicked.connect(self.run)

展示筛选结果

子线程会将满足条件的股票的代码和角度发送到槽函数update中。

def update(self, msg):#用于更新表格
#msg返回就是线程返回的内容
    if msg=='finish':
        self.main_ui.tableWidget.insertRow(0)  # 插入一行
        self.main_ui.tableWidget.setSpan(0, 0, 1, 4)
        redBrush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        item = QtWidgets.QTableWidgetItem(
            str(datetime.datetime.now().strftime("%H:%M:%S") + ':筛选完成'))
        self.main_ui.tableWidget.setItem(0, 0, item)
        self.main_ui.tableWidget.item(0, 0).setForeground(redBrush)
        self.main_ui.btn_run.setText('开始筛选')
        self.main_ui.btn_run.setStyleSheet("background-color: rgb(255, 255, 255);")
        return
    try:
        st_code=msg.split(',')[0]
        angle0=msg.split(',')[1]#回归线角度
        angle1=msg.split(',')[2]#两点连线角度
        st_code=st_code[-2:].lower()+\
                st_code[:6]
        
        url='http://hq.sinajs.cn/list='+st_code
        data=requests.get(url).text
        data=data.split('"')
        data=data[1].split(',')
        #从新浪财经获取股票名称、今开、昨收
        self.main_ui.tableWidget.insertRow(0)#插入一行
        item0 = QtWidgets.QTableWidgetItem(msg.split(',')[0])
        item1 = QtWidgets.QTableWidgetItem(data[0])
        item2 = QtWidgets.QTableWidgetItem(data[1])
        item3 = QtWidgets.QTableWidgetItem(data[2])
        item4 = QtWidgets.QTableWidgetItem(angle0)
        item5 = QtWidgets.QTableWidgetItem(angle1)
        self.main_ui.tableWidget.setItem(0, 0, item0)
        self.main_ui.tableWidget.setItem(0, 1, item1)
        self.main_ui.tableWidget.setItem(0, 2, item2)
        self.main_ui.tableWidget.setItem(0, 3, item3)
        self.main_ui.tableWidget.setItem(0, 4, item4)
        self.main_ui.tableWidget.setItem(0, 5, item5)
        #填入表格
    except:
        print(traceback.format_exc())

该槽函数与信号的绑定是在上面的 run()函数中设置的:

self.thread._signal.connect(self.update)

双击单元格打开详细信息窗口

双击筛选结果中表格的某一列,可以弹出新窗口,新窗口中可以展示一支股票更多的信息,比如K线图和超级叠加图。

槽函数:

def more_Information(self,x,y):#x和y是信号自动传送的值,是被双击的单元格的位置
    try:
        stCode = self.main_ui.tableWidget.item(x, 0).text()
        stName=self.main_ui.tableWidget.item(x,1).text()
        angle=(self.main_ui.tableWidget.item(x,4).text(),self.main_ui.tableWidget.item(x,5).text())
        type=self.main_ui.comboBox.currentText()
       #被双击的单元格对应的股票的信息获取参数
       
        window = moreInfWindow(stCode,stName,int(self.main_ui.time_range.text()),angle,type=type)
        window.show()
        window.exec_()
        #打开新窗口,这里的moreInfWindow是一个新的窗口,具体参见源代码。
    except:
        print(traceback.format_exc())

信号:

self.main_ui.tableWidget.cellDoubleClicked.connect(self.more_Information)

更换展示的K线图和超级叠加图

金融科技之交易:动量效应选股策略

如上图,详细窗口会展示股票的一些数据同时展示该股吧的K线图和与指数的超级叠加图,以及对应的回归线或者两点连线。

可以选择想要展示的K线图的时间范围和叠加图中的直线类型(回归线或者两点连线)。

K线图槽函数:

def Kpic(self):
    codest={'SH':'1','SZ':'0'}
    Ksize = {'1年':'-3','6月':'-4','3月':'-6','1月':'-8'}
    size=Ksize[self.ui.comboBox.currentText()[:2]]
    #获取图片
    stimgUrl='http://webquoteklinepic.eastmoney.com/GetPic.aspx?nid='+codest[self.stCode[-2:]]+'.'+self.stCode[:-3]+'&UnitWidth={}&imageType=KXL&EF=&Formula=RSI&AT=0'.format(size)
    # print(cbimgUrl)
    print(stimgUrl)
    try:
        response = requests.get(stimgUrl)
        wb_data = response.content
        with open('stimg.png', 'wb') as f:
            f.write(wb_data)
        pix = QPixmap('stimg.png')
        self.ui.imagelb.setPixmap(pix)#在标签中填充图片
    except:
        pass

K线图信号:

self.ui.comboBox.currentIndexChanged.connect(self.Kpic)

叠加图槽函数:(这里用到了画布,m是一个画布,会在后面介绍)

def Lpic(self):
    self.m.axes.cla()#清空重画
    self.m.drawLine(self.ui.comboBox_1.currentText())
    self.m.draw()

叠加图信号:

self.ui.comboBox_1.currentIndexChanged.connect(self.Lpic)

双击打开东方财富股票行情页面

若想要了解更多关于某只股票的信息,可以双击详细信息窗口中的表格,会打开东方财富相应股票的页面。

金融科技之交易:动量效应选股策略

槽函数:

def openUrl(self):
    url="http://quote.eastmoney.com/{}.html".format(self.stCode[-2:].lower()+ self.stCode[0:6])
    QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))

信号:

self.ui.tableWidget.cellDoubleClicked.connect(self.openUrl)

子线程

筛选的任务是在子线程中完成的。因为如果不使用子线程,在筛选完成之前我们的程序不能做任何操作,也无法暂停。所以我们需要调用子线程,由主线程控制子线程的执行和停止。

主线程的对应函数以及在上面给出,也就是run()函数。这里再贴一下:

def run(self):
    try:
        if self.main_ui.btn_run.text()=='开始筛选':#开始执行
            self.main_ui.btn_run.setText('正在筛选')
            self.main_ui.btn_run.setStyleSheet("background-color: rgb(255, 0, 0);")
            type=self.main_ui.comboBox.currentText()#下拉框的选项
            angle1=int(self.main_ui.angle_range1.text())
            angle2=int(self.main_ui.angle_range2.text())
            months=int(self.main_ui.time_range.text())
            #在窗口中输入的三个变量,angle_rang1等三个空间都是QLineEdit          
            
            self.thread = thread_run(self.target_stock,angle1,angle2,months,type)
            #这里创建子线程,并传入参数
            self.thread._signal.connect(self.update)
            #设置子线程的信号与槽函数,子线程返回筛选结果时会执行update函数.
            self.thread.start()
            #开始执行,会自动执行thread中的run()
        else:#停止执行
            self.thread.terminate()#关闭子线程
            self.thread.wait()
            
            self.main_ui.tableWidget.insertRow(0)
            self.main_ui.tableWidget.setSpan(0, 0, 1, 4)
            redBrush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
            item = QtWidgets.QTableWidgetItem(
                str(datetime.datetime.now().strftime("%H:%M:%S")+':暂停筛选'))
            self.main_ui.tableWidget.setItem(0, 0, item)
            self.main_ui.tableWidget.item(0, 0).setForeground(redBrush)
            #在表格中插入一行,并写入提示信息。
            self.main_ui.btn_run.setText('开始筛选')
            self.main_ui.btn_run.setStyleSheet("background-color: rgb(255, 255, 255);")
    except:
        print(traceback.format_exc())

现在给出子线程类:

class thread_run(clac,QtCore.QThread):#clac是自定义的计算角度的类,会在后面介绍。
  
    _signal = pyqtSignal(str)

    def __init__(self,st_list,angle1,angle2,months,type):
        self.st_list=st_list
        if self.st_list==None:#如果没有设置目标股票,就默认筛选全部A股
            pro=ts.pro_api('你自己的tushare的token')
            self.st_list = list(pro.stock_basic(exchange='', list_status='L', fields='ts_code')['ts_code'])
        self.months=months
        self.angle1=angle1
        self.angle2=angle2
        self.type=type
        QtCore.QThread.__init__(self)
        clac.__init__(self)

    def run(self):
            for st in self.st_list:
                try:
                    angle=self.angle(st,self.months)#clac类的方法被继承过来。angle是一个元组,第一个值是回归线角度,第二个是两点连线角度
                    print(angle)
                    if  angle[0]>=self.angle1 and angle[0] <=self.angle2 and (self.type=='回归线' or self.type=='两者任一'):
                        self._signal.emit(st+','+str(round(angle[0],4))+','+str(round(angle[1],4)))#筛选出一个满足条件的股票,将其数据作为信号发送给update函数
                        time.sleep(0.5)
                        continue

                    if  angle[1]>=self.angle1 and angle[1] <=self.angle2 and (self.type=='两点连线' or self.type=='两者任一'):
                        self._signal.emit(st+','+str(round(angle[0],4))+','+str(round(angle[1],4)))
                        time.sleep(0.5)
                        continue
                except:
                    print(traceback.format_exc())
            self._signal.emit('finish')#全部的股票筛选完毕

待完善的工作

1.通过历史数据回测,得到结果最好的角度范围
2.第三种角度的计算方法:找在某一时间段内的最大角度。