• PyQt5_pyqtgraph双均线组合工具


    目录

    双线组合概念

    双线组合交易原则

    交易原则翻译为代码逻辑

    策略代码实现

    运行工具代码

    执行

    验证策略思想

    优化策略

    数据


    双线组合概念

            双线组合又称两条均线组合,是由一条时间周期较短的均线和一条时间周期较长的均线组合而成的均线分析系统。

            其中时间周期较长的均线,主要用于预测和判断趋势方向。时间周期较短的均线,主要利用其与股价、长周期的均线之间的相互位置和关系,作为确定进出场的依据。为便于解说,时间周期较长的均线,我们称之为定性线;时间周期较短的均线,我们称之为定量线。

    PS:以上概念来自书籍《均线技术分析》by 邱立波

    双线组合交易原则

    1)买入和持仓原则。

    A. 股价向上突破定性线,定性线上行,买入。

    B. 定量线上穿定性线形成黄金交叉,买入

    C. 股价下跌,遇定性线上行支撑止跌回升,买入

    D. 定性线上行,股价在定性线上方向上突破定量线,买入

    E. 定量线下行,遇定性线上行支撑止跌后再度上升,买入。

    F. 股价、定量线、定性线多头排列,持股

    2)卖出和空仓原则

    A. 股价跌破定性线,定性线走平或已拐头下行,卖出

    B. 多头排列期间,股价跌破定量线,减仓

    C. 股价急速飙升,远离定性线,减仓

    D. 定性线下行,空仓

    PS:以上交易原则来自书籍《均线技术分析》by 邱立波

    交易原则翻译为代码逻辑

             一些概念诸如上行、飙升、下跌等描述要置换成代码逻辑,需要进行开发者做自定义的量化,这部分量化会影响信号发出的频次,先定义好一个默认量,后续策略优化有需要再行修改。

    本文策略自定义规则:

    描述代码逻辑规则(自定义)
    支撑和阻碍股价与均线价格之间的差值小于等于当前股价的2%
    股价下跌连续三日收盘价涨跌幅为负        
    均线下行连续三日均线涨跌幅为负
    股价急速飙升连续三日股价涨跌幅都大于等于5%
    远离股价与均线价格之间的差值大于等于当前股价的50%

     下面开始逐句翻译交易原则:

    1) 买入和持仓原则:
        A. 股价向上突破定性线,定性线上行,买入。
        翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
        B. 定量线上穿定性线形成黄金交叉,买入。
        翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
        C. 股价下跌,遇定性线上行支撑止跌回升,买入。
        翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
        D. 定性线上行,股价在定性线上方向上突破定量线,买入。
        翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
        E. 定量线下行,遇定性线上行支撑止跌后再度上升,买入。
        翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
        F. 股价、定量线、定性线多头排列,持股
        翻译:收盘价大于定量价格大于定性价格
        2) 卖出和空仓原则
        A. 股价跌破定性线,定性线走平或已拐头下行,卖出
        翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
        B. 多头排列期间,股价跌破定量线,减仓
        翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
        C. 股价急速飙升,远离定性线,减仓
        翻译:股价飙升 & 收盘价远离定性价格
        D. 定性线下行,空仓
        翻译:连续三日定性价格涨跌幅为负

    策略代码实现

    本文策略使用ma5作为定量线,ma20作为定性线 

    1. def excute_strategy():
    2. '''
    3. 前置自定义规则:
    4. A. 支撑和阻碍,定义为股价与均线价格之间的差值小于等于当前股价的2%
    5. B. 股价下跌,连续三日收盘价涨跌幅为负
    6. C. 均线下行,连续三日均线涨跌幅为负
    7. D. 股价急速飙升,连续三日股价涨跌幅都大于等于5%
    8. E. 远离,定义为股价与均线价格之间的差值大于等于当前股价的50%
    9. 1) 买入和持仓原则:
    10. A. 股价向上突破定性线,定性线上行,买入。
    11. 翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    12. B. 定量线上穿定性线形成黄金交叉,买入。
    13. 翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
    14. C. 股价下跌,遇定性线上行支撑止跌回升,买入。
    15. 翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    16. D. 定性线上行,股价在定性线上方向上突破定量线,买入。
    17. 翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    18. E. 定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    19. 翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    20. F. 股价、定量线、定性线多头排列,持股
    21. 翻译:收盘价大于定量价格大于定性价格
    22. 2) 卖出和空仓原则
    23. A. 股价跌破定性线,定性线走平或已拐头下行,卖出
    24. 翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    25. B. 多头排列期间,股价跌破定量线,减仓
    26. 翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    27. C. 股价急速飙升,远离定性线,减仓
    28. 翻译:股价飙升 & 收盘价远离定性价格
    29. D. 定性线下行,空仓
    30. 翻译:连续三日定性价格涨跌幅为负
    31. :return:
    32. '''
    33. import pandas as pd
    34. import talib
    35. df = pd.read_csv('E:/temp005/600660.csv',encoding='utf-8')
    36. # 删除停牌的数据
    37. df = df.loc[df['openPrice'] > 0].copy()
    38. df['o_date'] = df['tradeDate']
    39. df['o_date'] = pd.to_datetime(df['o_date'])
    40. df = df.loc[df['o_date']>='2020-01-01'].copy()
    41. # 保存未复权收盘价数据
    42. df['close'] = df['closePrice']
    43. # 计算前复权数据
    44. df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    45. df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    46. df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    47. df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
    48. # 双均线组合
    49. # 定性线:20日均线 定量线:5日均线
    50. df['ma5'] = talib.MA(df['closePrice'],timeperiod=5)
    51. df['ma20'] = talib.MA(df['closePrice'],timeperiod=20)
    52. # 定性线涨跌幅
    53. df['ma20_rate'] = df['ma20']-df['ma20'].shift(1)
    54. # 定性线上行还是下行:连续三天涨跌幅为正,为上行,值为1;连续三天涨跌幅为负,为下行,值为-1
    55. df['ma20_direction'] = 0
    56. df.loc[(df['ma20_rate']>0) & (df['ma20_rate'].shift(1)>0) & (df['ma20_rate'].shift(2)>0),'ma20_direction'] = 1
    57. df.loc[(df['ma20_rate']<0) & (df['ma20_rate'].shift(1)<0) & (df['ma20_rate'].shift(2)<0),'ma20_direction'] = -1
    58. # 定量线涨跌幅
    59. df['ma5_rate'] = df['ma5'] - df['ma5'].shift(1)
    60. df['ma5_direction'] = 0
    61. df.loc[(df['ma5_rate']>0) & (df['ma5_rate'].shift(1)>0) & (df['ma5_rate'].shift(2)>0),'ma5_direction'] = 1
    62. df.loc[(df['ma5_rate']<0) & (df['ma5_rate'].shift(1)<0) & (df['ma5_rate'].shift(2)<0),'ma5_direction'] = -1
    63. # 收盘价涨跌幅
    64. df['close_rate'] = df['closePrice'] - df['closePrice'].shift(1)
    65. df['closePrice_direction'] = 0
    66. df.loc[(df['close_rate'] > 0) & (df['close_rate'].shift(1) > 0) & (df['close_rate'].shift(2) > 0), 'closePrice_direction'] = 1
    67. df.loc[(df['close_rate'] < 0) & (df['close_rate'].shift(1) < 0) & (df['close_rate'].shift(2) < 0), 'closePrice_direction'] = -1
    68. # 收盘价到定性线之间的距离
    69. df['close_ma20_distance'] = (abs(df['closePrice']-df['ma20'])/df['closePrice'])*100
    70. # 收盘价到定量线之间的距离
    71. df['close_ma5_distance'] = (abs(df['closePrice']-df['ma5'])/df['closePrice'])*100
    72. # 定量线到定性线之间的距离
    73. df['ma5_ma20_distance'] = (abs(df['ma5']-df['ma20'])/df['ma5'])*100
    74. # 定性线到定量线之间的距离
    75. df['ma20_ma5_distance'] = (abs(df['ma5']-df['ma20'])/df['ma20'])*100
    76. # 股价是否飙升
    77. df['pre_fast_up'] = 0
    78. df.loc[(df['close_rate']>0) & (df['close_rate']/df['closePrice']>=0.05),'pre_fast_up'] = 1
    79. df['fast_up'] = 0
    80. df.loc[(df['pre_fast_up']==1) & (df['pre_fast_up'].shift(1)==1) & (df['pre_fast_up'].shift(2)==1),'fast_up'] = 1
    81. # 1) 买入和持仓原则:
    82. # 买入=1 持股=2(区间) 卖出=3 减仓=4 空仓=5(区间) 无信号=0
    83. df['signal'] = 0
    84. df['signal_reason'] = ''
    85. # A. 股价向上突破定性线,定性线上行,买入。
    86. # 翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    87. df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)'ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal'] = 1
    88. df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)'ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal_reason'] = 'A'
    89. # B.定量线上穿定性线形成黄金交叉,买入。
    90. # 翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
    91. df.loc[(df['ma5'].shift(1)'ma20'].shift(1)) & (df['ma5']>df['ma20']),'signal'] = 1
    92. df.loc[(df['ma5'].shift(1)'ma20'].shift(1)) & (df['ma5']>df['ma20']),'signal_reason'] = 'B'
    93. # C.股价下跌,遇定性线上行支撑止跌回升,买入。
    94. # 翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    95. df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal'] = 1
    96. df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal_reason'] = 'C'
    97. # D.定性线上行,股价在定性线上方向上突破定量线,买入。
    98. # 翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    99. df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)'ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal'] = 1
    100. df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)'ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal_reason'] = 'D'
    101. # E.定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    102. # 翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    103. df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal'] = 1
    104. df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal_reason'] = 'E'
    105. # F.股价、定量线、定性线多头排列,持股
    106. # 翻译:收盘价大于定量价格大于定性价格
    107. df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal'] = 2
    108. df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal_reason'] = 'F'
    109. # 2) 卖出和空仓原则
    110. # A.股价跌破定性线,定性线走平或已拐头下行,卖出
    111. # 翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    112. df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma20']) & (df['ma20_rate']<=0),'signal'] = 3
    113. df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma20']) & (df['ma20_rate']<=0),'signal_reason'] = 'A'
    114. # B.多头排列期间,股价跌破定量线,减仓
    115. # 翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    116. df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma5']),'signal'] = 4
    117. df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma5']),'signal_reason'] = 'B'
    118. # C.股价急速飙升,远离定性线,减仓
    119. # 翻译:股价飙升 & 收盘价远离定性价格
    120. df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal'] = 4
    121. df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal_reason'] = 'C'
    122. # D.定性线下行,空仓
    123. # 翻译:连续三日定性价格涨跌幅为负
    124. df.loc[df['ma20_direction']==-1,'signal'] = 5
    125. df.loc[df['ma20_direction']==-1,'signal_reason'] = 'D'
    126. title_str = '双均线组合信号'
    127. whole_header = ['日期','收盘价','开盘价','最高价','最低价','ma5','ma20']
    128. whole_df = df
    129. whole_pd_header = ['tradeDate','closePrice','openPrice','highestPrice','lowestPrice','ma5','ma20']
    130. line_pd_header = ['ma5','ma20']
    131. line_color_list = [0,2]
    132. line_data = {
    133. 'title_str':title_str,
    134. 'whole_header':whole_header,
    135. 'whole_df':whole_df,
    136. 'whole_pd_header':whole_pd_header,
    137. 'line_pd_header':line_pd_header,
    138. 'line_color_list':line_color_list
    139. }
    140. return line_data

    运行工具代码

     导入需要的包、K线控件、日期横坐标控件、分页表格控件

    1. import sys,json,os,math,time
    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')
    14. class RotateAxisItem(pg.AxisItem):
    15. def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
    16. p.setRenderHint(p.Antialiasing,False)
    17. p.setRenderHint(p.TextAntialiasing,True)
    18. ## draw long line along axis
    19. pen,p1,p2 = axisSpec
    20. p.setPen(pen)
    21. p.drawLine(p1,p2)
    22. p.translate(0.5,0) ## resolves some damn pixel ambiguity
    23. ## draw ticks
    24. for pen,p1,p2 in tickSpecs:
    25. p.setPen(pen)
    26. p.drawLine(p1,p2)
    27. ## draw all text
    28. # if self.tickFont is not None:
    29. # p.setFont(self.tickFont)
    30. p.setPen(self.pen())
    31. for rect,flags,text in textSpecs:
    32. # this is the important part
    33. p.save()
    34. p.translate(rect.x(),rect.y())
    35. p.rotate(-30)
    36. p.drawText(-rect.width(),rect.height(),rect.width(),rect.height(),flags,text)
    37. # restoring the painter is *required*!!!
    38. p.restore()
    39. class CandlestickItem(pg.GraphicsObject):
    40. def __init__(self, data):
    41. pg.GraphicsObject.__init__(self)
    42. self.data = data ## data must have fields: time, open, close, min, max
    43. self.generatePicture()
    44. def generatePicture(self):
    45. ## pre-computing a QPicture object allows paint() to run much more quickly,
    46. ## rather than re-drawing the shapes every time.
    47. self.picture = QtGui.QPicture()
    48. p = QtGui.QPainter(self.picture)
    49. p.setPen(pg.mkPen('d'))
    50. w = (self.data[1][0] - self.data[0][0]) / 3.
    51. for (t, open, close, min, max) in self.data:
    52. p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
    53. if open < close:
    54. p.setBrush(pg.mkBrush('r'))
    55. else:
    56. p.setBrush(pg.mkBrush('g'))
    57. p.drawRect(QtCore.QRectF(t-w, open, w * 2, close - open))
    58. p.end()
    59. def paint(self, p, *args):
    60. p.drawPicture(0, 0, self.picture)
    61. def boundingRect(self):
    62. ## boundingRect _must_ indicate the entire area that will be drawn on
    63. ## or else we will get artifacts and possibly crashing.
    64. ## (in this case, QPicture does all the work of computing the bouning rect for us)
    65. return QtCore.QRectF(self.picture.boundingRect())
    66. # 分页表格控件,单选
    67. class PageTableWidget(QtWidgets.QWidget):
    68. def __init__(self):
    69. super().__init__()
    70. self.init_data()
    71. self.init_ui()
    72. pass
    73. def init_data(self):
    74. # 表格全数据 二维数组
    75. self.table_full_data: List[Any] = []
    76. # 表格右键菜单 {菜单名:索引指向}
    77. self.table_right_menus: Dict[str, str] = {}
    78. self.total_page_count: int = 0
    79. self.total_rows_count: int = 0
    80. self.current_page: int = 1
    81. self.single_page_rows: int = 50
    82. pass
    83. def init_ui(self):
    84. pre_page_btn = QtWidgets.QPushButton('上一页')
    85. pre_page_btn.clicked.connect(self.pre_page_btn_clicked)
    86. next_page_btn = QtWidgets.QPushButton('下一页')
    87. next_page_btn.clicked.connect(self.next_page_btn_clicked)
    88. tip_label_0 = QtWidgets.QLabel('第')
    89. self.witch_page_lineedit = QtWidgets.QLineEdit()
    90. self.int_validator = QtGui.QIntValidator()
    91. self.witch_page_lineedit.setValidator(self.int_validator)
    92. self.witch_page_lineedit.setMaximumWidth(20)
    93. tip_label_1 = QtWidgets.QLabel('页')
    94. go_page_btn = QtWidgets.QPushButton('前往')
    95. go_page_btn.clicked.connect(self.go_page_btn_clicked)
    96. layout_witch_page = QtWidgets.QHBoxLayout()
    97. layout_witch_page.addWidget(tip_label_0)
    98. layout_witch_page.addWidget(self.witch_page_lineedit)
    99. layout_witch_page.addWidget(tip_label_1)
    100. layout_witch_page.addWidget(go_page_btn)
    101. layout_witch_page.addStretch(1)
    102. layout_pagechange = QtWidgets.QHBoxLayout()
    103. layout_pagechange.addWidget(pre_page_btn)
    104. layout_pagechange.addWidget(next_page_btn)
    105. layout_pagechange.addLayout(layout_witch_page)
    106. self.total_page_count_label = QtWidgets.QLabel(f"共0页")
    107. self.total_rows_count_label = QtWidgets.QLabel(f"共0行")
    108. self.current_page_label = QtWidgets.QLabel(f"当前第0页")
    109. layout_pagestatus = QtWidgets.QHBoxLayout()
    110. layout_pagestatus.addWidget(self.total_page_count_label)
    111. layout_pagestatus.addWidget(self.total_rows_count_label)
    112. layout_pagestatus.addWidget(self.current_page_label)
    113. layout_pagestatus.addStretch(1)
    114. layout_top = QtWidgets.QVBoxLayout()
    115. layout_top.addLayout(layout_pagechange)
    116. layout_top.addLayout(layout_pagestatus)
    117. self.target_table = QtWidgets.QTableWidget()
    118. self.target_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
    119. self.target_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
    120. layout = QtWidgets.QVBoxLayout()
    121. layout.addLayout(layout_top)
    122. layout.addWidget(self.target_table)
    123. self.setLayout(layout)
    124. pass
    125. def set_table_init_data(self, data: Dict[str, Any]):
    126. '''设置表头、右键菜单等初始化内容'''
    127. headers: List[str] = data['headers']
    128. self.target_table.setColumnCount(len(headers))
    129. self.target_table.setHorizontalHeaderLabels(headers)
    130. pass
    131. def set_table_full_data(self, data: List[Any]):
    132. '''表格全数据'''
    133. self.table_full_data = data
    134. self.total_rows_count = len(data)
    135. self.total_page_count = math.ceil(self.total_rows_count / self.single_page_rows)
    136. self.current_page = 1
    137. self.int_validator.setRange(1, self.total_page_count)
    138. self.total_page_count_label.setText(f"共{self.total_page_count}页")
    139. self.total_rows_count_label.setText(f"共{self.total_rows_count}行")
    140. self.caculate_current_show_data()
    141. pass
    142. def setting_current_pagestatus_label(self):
    143. self.current_page_label.setText(f"当前第{self.current_page}页")
    144. pass
    145. def caculate_current_show_data(self):
    146. '''计算当前要显示的数据'''
    147. start_dot = (self.current_page - 1) * self.single_page_rows
    148. end_dot = start_dot + self.single_page_rows
    149. current_data = self.table_full_data[start_dot:end_dot]
    150. self.fill_table_content(current_data)
    151. self.setting_current_pagestatus_label()
    152. pass
    153. def fill_table_content(self, data: List[Any]):
    154. self.target_table.clearContents()
    155. self.target_table.setRowCount(len(data))
    156. for r_i, r_v in enumerate(data):
    157. for c_i, c_v in enumerate(r_v):
    158. cell = QtWidgets.QTableWidgetItem(str(c_v))
    159. self.target_table.setItem(r_i, c_i, cell)
    160. pass
    161. pass
    162. self.target_table.resizeColumnsToContents()
    163. pass
    164. def go_page_btn_clicked(self):
    165. '''前往按钮点击'''
    166. input_page = self.witch_page_lineedit.text()
    167. input_page = input_page.strip()
    168. if not input_page:
    169. QtWidgets.QMessageBox.information(
    170. self,
    171. '提示',
    172. '请输入要跳转的页码',
    173. QtWidgets.QMessageBox.Yes
    174. )
    175. return
    176. input_page = int(input_page)
    177. if input_page < 0 or input_page > self.total_page_count:
    178. QtWidgets.QMessageBox.information(
    179. self,
    180. '提示',
    181. '输入的页码超出范围',
    182. QtWidgets.QMessageBox.Yes
    183. )
    184. return
    185. self.current_page = input_page
    186. self.caculate_current_show_data()
    187. pass
    188. def pre_page_btn_clicked(self):
    189. '''上一页按钮点击'''
    190. if self.current_page <= 1:
    191. QtWidgets.QMessageBox.information(
    192. self,
    193. '提示',
    194. '已经是首页',
    195. QtWidgets.QMessageBox.Yes
    196. )
    197. return
    198. self.current_page -= 1
    199. self.caculate_current_show_data()
    200. pass
    201. def next_page_btn_clicked(self):
    202. '''下一页按钮点击'''
    203. if self.current_page >= self.total_page_count:
    204. QtWidgets.QMessageBox.information(
    205. self,
    206. '提示',
    207. '已经是最后一页',
    208. QtWidgets.QMessageBox.Yes
    209. )
    210. return
    211. self.current_page += 1
    212. self.caculate_current_show_data()
    213. pass
    214. 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.table_header = ['日期','信号','未复权收盘价','解说']
    9. self.please_select_str = '---请选择---'
    10. self.func_map = {
    11. '标记多头排列区间':'a',
    12. '标记空头排列区间':'b',
    13. '20日线斜率为正':'c'
    14. }
    15. self.func_item_list = []
    16. self.duration_map = {
    17. '今年':'a',
    18. '最近一年':'b',
    19. '最近两年':'c'
    20. }
    21. # https://www.sioe.cn/yingyong/yanse-rgb-16/
    22. self.color_line = (30, 144, 255)
    23. # 0 幽灵的白色; 1 纯黄; 2 紫红色; 3 纯绿; 4 道奇蓝
    24. self.color_list = [(248,248,255),(255,255,0),(255,0,255),(0,128,0),(30,144,255)]
    25. self.buy_color = (220, 20, 60) # 猩红
    26. self.hold_color = (255, 140, 0) # 深橙色
    27. self.sell_color = (50, 205, 50) # 酸橙绿
    28. self.subpos_color = (24, 252, 0) # 草坪绿
    29. self.emptypos_color = (0, 128, 0) # 纯绿
    30. self.main_fixed_target_list = [] # 主体固定曲线,不能被删除
    31. self.whole_df = None
    32. self.whole_header = None
    33. self.whole_pd_header = None
    34. self.current_whole_data = None
    35. self.current_whole_df = None
    36. self.line_pd_header = []
    37. self.line_color_list = []
    38. self.signal_show_list = []
    39. pass
    40. def init_ui(self):
    41. # 控制面板 start
    42. left_tip = QtWidgets.QLabel('左边界')
    43. self.left_point = QtWidgets.QDateEdit()
    44. self.left_point.setDisplayFormat('yyyy-MM-dd')
    45. self.left_point.setCalendarPopup(True)
    46. right_tip = QtWidgets.QLabel('右边界')
    47. self.right_point = QtWidgets.QDateEdit()
    48. self.right_point.setDisplayFormat('yyyy-MM-dd')
    49. self.right_point.setCalendarPopup(True)
    50. duration_sel_btn = QtWidgets.QPushButton('确定')
    51. duration_sel_btn.clicked.connect(self.duration_sel_btn_clicked)
    52. duration_reset_btn = QtWidgets.QPushButton('重置')
    53. duration_reset_btn.clicked.connect(self.duration_reset_btn_clicked)
    54. self.whole_duration_label = QtWidgets.QLabel('原始最宽边界:左边界~右边界')
    55. self.now_duration_label = QtWidgets.QLabel('当前显示最宽边界:左边界~右边界')
    56. self.buy_checkbox = QtWidgets.QCheckBox('买入')
    57. self.buy_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.buy_checkbox))
    58. self.hold_checkbox = QtWidgets.QCheckBox('持股')
    59. self.hold_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.hold_checkbox))
    60. self.sell_checkbox = QtWidgets.QCheckBox('卖出')
    61. self.sell_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.sell_checkbox))
    62. self.subpos_checkbox = QtWidgets.QCheckBox('减仓')
    63. self.subpos_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.subpos_checkbox))
    64. self.emptypos_checkbox = QtWidgets.QCheckBox('空仓')
    65. self.emptypos_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.emptypos_checkbox))
    66. layout_date = QtWidgets.QHBoxLayout()
    67. layout_date.addWidget(left_tip)
    68. layout_date.addWidget(self.left_point)
    69. layout_date.addWidget(right_tip)
    70. layout_date.addWidget(self.right_point)
    71. layout_date.addWidget(duration_sel_btn)
    72. layout_date.addWidget(duration_reset_btn)
    73. layout_date.addWidget(self.buy_checkbox)
    74. layout_date.addWidget(self.hold_checkbox)
    75. layout_date.addWidget(self.sell_checkbox)
    76. layout_date.addWidget(self.subpos_checkbox)
    77. layout_date.addWidget(self.emptypos_checkbox)
    78. layout_date.addStretch(1)
    79. layout_duration = QtWidgets.QHBoxLayout()
    80. layout_duration.addWidget(self.whole_duration_label)
    81. layout_duration.addSpacing(30)
    82. layout_duration.addWidget(self.now_duration_label)
    83. layout_duration.addStretch(1)
    84. # 控制面板 end
    85. self.title_label = QtWidgets.QLabel('执行过程查看')
    86. self.title_label.setAlignment(Qt.AlignCenter)
    87. self.title_label.setStyleSheet('QLabel{font-size:18px;font-weight:bold}')
    88. xax = RotateAxisItem(orientation='bottom')
    89. xax.setHeight(h=80)
    90. self.pw = pg.PlotWidget(axisItems={'bottom': xax})
    91. self.pw.setMouseEnabled(x=True, y=True)
    92. # self.pw.enableAutoRange(x=False,y=True)
    93. self.pw.setAutoVisible(x=False, y=True)
    94. self.table = PageTableWidget()
    95. self.table.set_table_init_data({'headers':self.table_header})
    96. layout_down = QtWidgets.QHBoxLayout()
    97. layout_down.addWidget(self.pw,6)
    98. layout_down.addWidget(self.table,1)
    99. layout = QtWidgets.QVBoxLayout()
    100. layout.addWidget(self.title_label)
    101. layout.addLayout(layout_date)
    102. layout.addLayout(layout_duration)
    103. layout.addLayout(layout_down)
    104. self.setLayout(layout)
    105. pass
    106. def set_data(self,data:Dict[str,Any]):
    107. title_str = data['title_str']
    108. whole_header = data['whole_header']
    109. whole_df = data['whole_df']
    110. whole_pd_header = data['whole_pd_header']
    111. line_pd_header = data['line_pd_header']
    112. line_color_list = data['line_color_list']
    113. self.whole_header = whole_header
    114. self.whole_df = whole_df
    115. self.whole_pd_header = whole_pd_header
    116. self.line_pd_header = line_pd_header
    117. self.line_color_list = line_color_list
    118. self.title_label.setText(title_str)
    119. self.whole_duration_label.setText(f"原始最宽边界:{self.whole_df.iloc[0]['tradeDate']}~{self.whole_df.iloc[-1]['tradeDate']}")
    120. self.current_whole_df = self.whole_df.copy()
    121. self.caculate_and_show_data()
    122. pass
    123. def caculate_and_show_data(self):
    124. df = self.current_whole_df.copy()
    125. df.reset_index(inplace=True)
    126. df['i_count'] = [i for i in range(len(df))]
    127. tradeDate_list = df['tradeDate'].values.tolist()
    128. x = range(len(df))
    129. xTick_show = []
    130. x_dur = math.ceil(len(df)/20)
    131. for i in range(0,len(df),x_dur):
    132. xTick_show.append((i,tradeDate_list[i]))
    133. if len(df)%20 != 0:
    134. xTick_show.append((len(df)-1,tradeDate_list[-1]))
    135. candle_data = []
    136. for i,row in df.iterrows():
    137. candle_data.append((row['i_count'],row['openPrice'],row['closePrice'],row['lowestPrice'],row['highestPrice']))
    138. self.current_whole_data = df.loc[:,self.whole_pd_header].values.tolist()
    139. # 开始配置显示的内容
    140. self.pw.clear()
    141. self.func_item_list.clear()
    142. self.nocheck_checkbox()
    143. self.remove_signal_show()
    144. self.now_duration_label.setText(f"当前显示最宽边界:{df.iloc[0]['tradeDate']}~{df.iloc[-1]['tradeDate']}")
    145. xax = self.pw.getAxis('bottom')
    146. xax.setTicks([xTick_show])
    147. candle_fixed_target = CandlestickItem(candle_data)
    148. self.main_fixed_target_list.append(candle_fixed_target)
    149. self.pw.addItem(candle_fixed_target)
    150. # 曲线
    151. if len(self.line_pd_header)>0:
    152. for i,item in enumerate(self.line_pd_header):
    153. line_fixed_target = pg.PlotCurveItem(x=np.array(x), y=np.array(df[item].values.tolist()),
    154. pen=pg.mkPen({'color': self.color_list[self.line_color_list[i]], 'width': 2}),
    155. connect='finite')
    156. self.main_fixed_target_list.append(line_fixed_target)
    157. self.pw.addItem(line_fixed_target)
    158. pass
    159. self.vLine = pg.InfiniteLine(angle=90, movable=False)
    160. self.hLine = pg.InfiniteLine(angle=0, movable=False)
    161. self.label = pg.TextItem()
    162. self.pw.addItem(self.vLine, ignoreBounds=True)
    163. self.pw.addItem(self.hLine, ignoreBounds=True)
    164. self.pw.addItem(self.label, ignoreBounds=True)
    165. self.vb = self.pw.getViewBox()
    166. self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
    167. self.pw.enableAutoRange()
    168. pass
    169. def mouseMoved(self,evt):
    170. pos = evt[0]
    171. if self.pw.sceneBoundingRect().contains(pos):
    172. mousePoint = self.vb.mapSceneToView(pos)
    173. index = int(mousePoint.x())
    174. if index>=0 and index<len(self.current_whole_data):
    175. target_data = self.current_whole_data[index]
    176. html_str = ''
    177. for i,item in enumerate(self.whole_header):
    178. html_str += f"
      {item}:{target_data[i]}"
    179. self.label.setHtml(html_str)
    180. self.label.setPos(mousePoint.x(),mousePoint.y())
    181. self.vLine.setPos(mousePoint.x())
    182. self.hLine.setPos(mousePoint.y())
    183. pass
    184. def mouseClicked(self,evt):
    185. pass
    186. def updateViews(self):
    187. pass
    188. def duration_sel_btn_clicked(self):
    189. '''边界选择'''
    190. left_point = self.left_point.date().toString('yyyy-MM-dd')
    191. right_point = self.right_point.date().toString('yyyy-MM-dd')
    192. df = self.whole_df.copy()
    193. df['o_date'] = pd.to_datetime(df['tradeDate'])
    194. self.current_whole_df = df.loc[(df['o_date']>=left_point) & (df['o_date']<=right_point)].copy()
    195. self.caculate_and_show_data()
    196. pass
    197. def duration_reset_btn_clicked(self):
    198. '''边界重置'''
    199. self.current_whole_df = self.whole_df.copy()
    200. self.caculate_and_show_data()
    201. pass
    202. def nocheck_checkbox(self):
    203. self.buy_checkbox.setChecked(False)
    204. self.hold_checkbox.setChecked(False)
    205. self.sell_checkbox.setChecked(False)
    206. self.subpos_checkbox.setChecked(False)
    207. self.emptypos_checkbox.setChecked(False)
    208. pass
    209. def remove_signal_show(self):
    210. for item in self.signal_show_list:
    211. self.pw.removeItem(item)
    212. self.signal_show_list.clear()
    213. pass
    214. def signal_checkbox_state(self,cb):
    215. self.remove_signal_show()
    216. signal_list = []
    217. if self.buy_checkbox.isChecked():
    218. signal_list.append(1)
    219. if self.hold_checkbox.isChecked():
    220. signal_list.append(2)
    221. if self.sell_checkbox.isChecked():
    222. signal_list.append(3)
    223. if self.subpos_checkbox.isChecked():
    224. signal_list.append(4)
    225. if self.emptypos_checkbox.isChecked():
    226. signal_list.append(5)
    227. if len(signal_list)<=0:
    228. # 删除所有的信号
    229. self.remove_signal_show()
    230. pass
    231. else:
    232. df = self.current_whole_df.copy()
    233. df.reset_index(inplace=True)
    234. if 1 in signal_list:
    235. # 买 220,20,60 猩红
    236. df_buy = df.loc[df['signal']==1].copy()
    237. for i, row in df_buy.iterrows():
    238. buy_fixed_target = pg.InfiniteLine(pos=(i, 0), movable=False, angle=90,
    239. pen=pg.mkPen({'color': self.buy_color, 'width': 2}),
    240. label=str(row['close']),
    241. labelOpts={'position': 0.05, 'color': self.buy_color,
    242. 'movable': True, 'fill': (
    243. self.buy_color[0], self.buy_color[1], self.buy_color[2], 30)})
    244. self.signal_show_list.append(buy_fixed_target)
    245. self.pw.addItem(buy_fixed_target)
    246. pass
    247. pass
    248. if 2 in signal_list:
    249. # 持股 255,140,0 深橙色
    250. df['ext_hold_count'] = [i for i in range(len(df))]
    251. df['ext_hold_0'] = 0
    252. df.loc[df['signal'] == 2, 'ext_hold_0'] = 1
    253. # start 值
    254. df['ext_hold_1'] = df['ext_hold_0'] - df['ext_hold_0'].shift(1)
    255. # end 值
    256. df['ext_hold_2'] = df['ext_hold_0'] - df['ext_hold_0'].shift(-1)
    257. df_hold_start = df.loc[df['ext_hold_1'] == 1].copy()
    258. df_hold_end = df.loc[df['ext_hold_2'] == 1].copy()
    259. hold_start_count_list = df_hold_start['ext_hold_count'].values.tolist()
    260. hold_end_count_list = df_hold_end['ext_hold_count'].values.tolist()
    261. if hold_end_count_list[0] < hold_start_count_list[0]:
    262. hold_end_count_list = hold_end_count_list[1:]
    263. if hold_end_count_list[-1] < hold_start_count_list[-1]:
    264. hold_start_count_list = hold_start_count_list[:-1]
    265. for i, item in enumerate(hold_end_count_list):
    266. lr = pg.LinearRegionItem([hold_start_count_list[i], item], movable=False, brush=(
    267. self.hold_color[0], self.hold_color[1], self.hold_color[2], 100))
    268. lr.setZValue(-100)
    269. self.signal_show_list.append(lr)
    270. self.pw.addItem(lr)
    271. pass
    272. pass
    273. if 3 in signal_list:
    274. # 卖出 50,205,50 酸橙绿
    275. df_sell = df.loc[df['signal'] == 3].copy()
    276. for i, row in df_sell.iterrows():
    277. sell_fixed_target = pg.InfiniteLine(pos=(i, 0), movable=False, angle=90,
    278. pen=pg.mkPen({'color': self.sell_color, 'width': 2}),
    279. label=str(row['close']),
    280. labelOpts={'position': 0.05, 'color': self.sell_color,
    281. 'movable': True, 'fill': (
    282. self.sell_color[0], self.sell_color[1], self.sell_color[2],
    283. 30)})
    284. self.signal_show_list.append(sell_fixed_target)
    285. self.pw.addItem(sell_fixed_target)
    286. pass
    287. pass
    288. if 4 in signal_list:
    289. # 减仓 24,252,0 草坪绿
    290. df_subpos = df.loc[df['signal'] == 4].copy()
    291. for i, row in df_subpos.iterrows():
    292. subpos_fixed_target = pg.InfiniteLine(pos=(i, 0), movable=False, angle=90,
    293. pen=pg.mkPen({'color': self.subpos_color, 'width': 2}),
    294. label=str(row['close']),
    295. labelOpts={'position': 0.05, 'color': self.subpos_color,
    296. 'movable': True, 'fill': (
    297. self.subpos_color[0], self.subpos_color[1],
    298. self.subpos_color[2],
    299. 30)})
    300. self.signal_show_list.append(subpos_fixed_target)
    301. self.pw.addItem(subpos_fixed_target)
    302. pass
    303. pass
    304. if 5 in signal_list:
    305. # 空仓 0,128,0 纯绿
    306. df['ext_empty_count'] = [i for i in range(len(df))]
    307. df['ext_empty_0'] = 0
    308. df.loc[df['signal']==5,'ext_empty_0'] = 1
    309. # start 值
    310. df['ext_empty_1'] = df['ext_empty_0']-df['ext_empty_0'].shift(1)
    311. # end 值
    312. df['ext_empty_2'] = df['ext_empty_0']-df['ext_empty_0'].shift(-1)
    313. df_empty_start = df.loc[df['ext_empty_1']==1].copy()
    314. df_empty_end = df.loc[df['ext_empty_2']==1].copy()
    315. empty_start_count_list = df_empty_start['ext_empty_count'].values.tolist()
    316. empty_end_count_list = df_empty_end['ext_empty_count'].values.tolist()
    317. if empty_end_count_list[0]0]:
    318. empty_end_count_list = empty_end_count_list[1:]
    319. if empty_end_count_list[-1]1]:
    320. empty_start_count_list = empty_start_count_list[:-1]
    321. for i,item in enumerate(empty_end_count_list):
    322. lr = pg.LinearRegionItem([empty_start_count_list[i],item],movable=False,brush=(self.emptypos_color[0],self.emptypos_color[1],self.emptypos_color[2],100))
    323. lr.setZValue(-100)
    324. self.signal_show_list.append(lr)
    325. self.pw.addItem(lr)
    326. pass
    327. pass
    328. df['signal_name'] = ''
    329. for item in signal_list:
    330. signal_name = ''
    331. if item == 1:
    332. signal_name = '买入'
    333. if item == 2:
    334. signal_name = '持股'
    335. if item == 3:
    336. signal_name = '卖出'
    337. if item == 4:
    338. signal_name = '减仓'
    339. if item == 5:
    340. signal_name = '空仓'
    341. df.loc[df['signal'] == item,'signal_name'] = signal_name
    342. df_table = df.loc[df['signal'].isin(signal_list)].copy()
    343. table_data = df_table.loc[:, ['tradeDate', 'signal_name', 'close','signal_reason']].values.tolist()
    344. self.table.set_table_full_data(table_data)
    345. pass
    346. 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.please_select_str: str = '--请选择--'
    16. self.stratege_name_list: List = []
    17. self.tip_msg_0: str = '1.选择策略所在文件夹;2.选择策略;3.点击运行。'
    18. self.stratege_path: str = ''
    19. self.stratege_run_start_time = None
    20. self.stratege_start = False
    21. self.current_stratege_py_str: str = ''
    22. pass
    23. def init_ui(self):
    24. self.setWindowTitle('策略运行小工具')
    25. tip_0 = QtWidgets.QLabel('选择策略所在文件夹:')
    26. self.stratege_dir_lineedit = QtWidgets.QLineEdit()
    27. self.stratege_dir_lineedit.setReadOnly(True)
    28. stratege_choice_btn = QtWidgets.QPushButton('选择文件夹')
    29. stratege_choice_btn.clicked.connect(self.stratege_choice_btn_clicked)
    30. tip_1 = QtWidgets.QLabel('策略:')
    31. self.stratege_combox = QtWidgets.QComboBox()
    32. self.stratege_combox.addItem(self.please_select_str)
    33. self.stratege_combox.currentIndexChanged.connect(self.stratege_combox_currentIndexChanged)
    34. self.run_btn = QtWidgets.QPushButton('运行')
    35. self.run_btn.clicked.connect(self.run_btn_clicked)
    36. self.force_stop_btn = QtWidgets.QPushButton('强制停止')
    37. self.force_stop_btn.clicked.connect(self.force_stop_btn_clicked)
    38. layout_top_left = QtWidgets.QGridLayout()
    39. layout_top_left.addWidget(tip_0,0,0,1,1)
    40. layout_top_left.addWidget(self.stratege_dir_lineedit,0,1,1,3)
    41. layout_top_left.addWidget(stratege_choice_btn,0,4,1,1)
    42. layout_top_left.addWidget(tip_1,1,0,1,1)
    43. layout_top_left.addWidget(self.stratege_combox,1,1,1,2)
    44. layout_top_left.addWidget(self.run_btn,1,3,1,1)
    45. layout_top_left.addWidget(self.force_stop_btn,1,4,1,1)
    46. self.tip_msg_label = QtWidgets.QLabel()
    47. self.tip_msg_label.setWordWrap(True)
    48. self.tip_msg_label.setText(self.tip_msg_0)
    49. layout_top = QtWidgets.QHBoxLayout()
    50. layout_top.addLayout(layout_top_left,3)
    51. layout_top.addWidget(self.tip_msg_label,1)
    52. self.code_textedit = QtWidgets.QTextEdit()
    53. self.code_textedit.setReadOnly(True)
    54. layout = QtWidgets.QVBoxLayout()
    55. layout.addLayout(layout_top)
    56. layout.addWidget(self.code_textedit)
    57. self.setLayout(layout)
    58. pass
    59. def register_event(self):
    60. self.signal_runcode.connect(self.thread_run_excuted)
    61. self.signal_time.connect(self.thread_time_excuted)
    62. pass
    63. def stratege_choice_btn_clicked(self):
    64. '''选择策略所在文件夹'''
    65. path = QtWidgets.QFileDialog.getExistingDirectory(
    66. self,
    67. '打开策略所在文件夹',
    68. self.pre_output_dir
    69. )
    70. if not path:
    71. return
    72. self.stratege_path = path
    73. self.stratege_dir_lineedit.setText(path)
    74. file_list = os.listdir(path)
    75. temp_file_list = set(self.stratege_name_list)
    76. for item in file_list:
    77. if item.endswith('.py'):
    78. temp_file_list.add(item)
    79. self.stratege_name_list = list(temp_file_list)
    80. self.stratege_combox.clear()
    81. self.stratege_combox.addItem(self.please_select_str)
    82. self.stratege_combox.addItems(self.stratege_name_list)
    83. pass
    84. def stratege_combox_currentIndexChanged(self,cur_i:int):
    85. cur_txt = self.stratege_combox.currentText()
    86. if not cur_txt or cur_txt == self.please_select_str:
    87. self.code_textedit.clear()
    88. return
    89. file_path = self.stratege_path + os.path.sep + cur_txt
    90. with open(file_path,'r',encoding='utf-8') as fr:
    91. code_txt = fr.read()
    92. self.code_textedit.setPlainText(code_txt)
    93. pass
    94. def run_btn_clicked(self):
    95. '''运行按钮'''
    96. py_str = self.code_textedit.toPlainText()
    97. if len(py_str)<10:
    98. QtWidgets.QMessageBox.information(
    99. self,
    100. '提示',
    101. '请选择要执行的策略',
    102. QtWidgets.QMessageBox.Yes
    103. )
    104. return
    105. self.current_stratege_py_str = py_str
    106. self.run_btn.setDisabled(True)
    107. self.stratege_combox.setDisabled(True)
    108. self.stratege_run_start_time = datetime.now()
    109. self.stratege_start = True
    110. if self.thread_run:
    111. QtWidgets.QMessageBox.information(
    112. self,
    113. '提示',
    114. '有策略正在运行',
    115. QtWidgets.QMessageBox.Yes
    116. )
    117. return
    118. self.thread_run = Thread(
    119. target=self.running_run_thread,
    120. args=(py_str,)
    121. )
    122. self.thread_run.start()
    123. self.thread_time = Thread(
    124. target=self.running_time_thread
    125. )
    126. self.thread_time.start()
    127. pass
    128. def force_stop_btn_clicked(self):
    129. '''强制停止按钮'''
    130. self.thread_run = None
    131. self.thread_time = None
    132. self.run_btn.setDisabled(False)
    133. self.stratege_combox.setDisabled(False)
    134. self.stratege_start = False
    135. pass
    136. def running_run_thread(self,data:str):
    137. '''执行代码线程'''
    138. namespace = {}
    139. fun_stragegy = compile(data,'','exec')
    140. exec(fun_stragegy,namespace)
    141. ret = namespace['excute_strategy']()
    142. self.signal_runcode.emit(ret)
    143. pass
    144. def running_time_thread(self):
    145. '''计时线程'''
    146. while self.stratege_start:
    147. now = datetime.now()
    148. interval_time = (now-self.stratege_run_start_time).seconds
    149. res_map = {'res':interval_time}
    150. self.signal_time.emit(res_map)
    151. time.sleep(1)
    152. pass
    153. def thread_run_excuted(self,data:Dict):
    154. '''策略代码执行返回结果'''
    155. self.run_btn.setDisabled(False)
    156. self.stratege_combox.setDisabled(False)
    157. if not self.running_graph_widget:
    158. self.running_graph_widget = PyQtGraphRunningWidget()
    159. self.running_graph_widget.set_data(data)
    160. self.running_graph_widget.showMaximized()
    161. self.thread_run = None
    162. self.thread_time = None
    163. self.stratege_start = False
    164. QtWidgets.QMessageBox.information(
    165. self,
    166. '提示',
    167. '当前策略运行完毕',
    168. QtWidgets.QMessageBox.Yes
    169. )
    170. pass
    171. def thread_time_excuted(self,data:Dict):
    172. '''计时返回结果'''
    173. res = data['res']
    174. self.tip_msg_label.setText(f"{res}s")
    175. pass
    176. def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
    177. if self.thread_time:
    178. self.thread_time.join()
    179. if self.thread_run:
    180. self.thread_run.join()
    181. if self.running_graph_widget:
    182. self.running_graph_widget.close()
    183. 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

     运行,选择策略py文件所在目录,选择要执行的策略对应的py文件,点击运行

     策略执行完毕后,会弹出新的窗体显示策略执行结果图表,在该图表窗体中可以选择指定日期期间,分别查看各个信号的情况

    验证策略思想

    由于双均线涉及的交易原则太多,本文以验证“ 1)买入和持仓原则中的 A”为例,讲述工具验证策略思想的过程

    从右侧表格中可以看出 2020-09-10 的买入信号是通过买入交易原则 A 获得的,该交易原则的描述是“股价向上突破定性线,定性线上行,买入。”,查看左侧图,可以看出与描述相符

    优化策略

     优化策略是一个很细的活,这里以“1)买入和持仓原则中的 B”为例,讲述策略优化的过程。

    右侧表格中总共出现了两次B, 一次是2020-04-23, 另一次是2020-10-14;放大看左侧图

    2020-04-23这次的黄金交叉在次日就变为死亡交叉 ,这种黄金交叉有效时长太短暂,可以认为无效

    2020-10-14这次的黄金交叉也和上面的一样,太短暂,可以当做无效

    优化策略,增加自定义规则:

    描述代码逻辑(自定义)
    黄金交叉前一日定量价格小于定性价格,当日定量价格大于定性价格,后续两日定量价格都大于定性价格
    死亡交叉前一日定量价格大于定性价格,当日定量价格小于定性价格,后续两日定量价格都小于定性价格

     修改策略代码:

    在实现原则的代码前加入

    1. # 黄金交叉
    2. df['gold_x'] = 0
    3. df.loc[(df['ma5'].shift(1)'ma20'].shift(1)) & (df['ma5']>df['ma20']) & (df['ma5'].shift(-1)>df['ma20'].shift(-1)) & (df['ma5'].shift(-2)>df['ma20'].shift(-2)),'gold_x'] = 1
    4. # 死亡交叉
    5. df['dead_x'] = 0
    6. df.loc[(df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['ma5']'ma20']) & (df['ma5'].shift(-1)'ma20'].shift(-1)) & (df['ma5'].shift(-2)'ma20'].shift(-2)),'dead_x'] = 1

    将买入B原则的代码修改为

    1. # B.定量线上穿定性线形成黄金交叉,买入。
    2. # 翻译:黄金交叉
    3. df.loc[df['gold_x']==1,'signal'] = 1
    4. df.loc[df['gold_x']==1,'signal_reason'] = 'B'

     修正后完整的策略代码为

    1. def excute_strategy():
    2. '''
    3. 前置自定义规则:
    4. A. 支撑和阻碍,定义为股价与均线价格之间的差值小于等于当前股价的2%
    5. B. 股价下跌,连续三日收盘价涨跌幅为负
    6. C. 均线下行,连续三日均线涨跌幅为负
    7. D. 股价急速飙升,连续三日股价涨跌幅都大于等于5%
    8. E. 远离,定义为股价与均线价格之间的差值大于等于当前股价的50%
    9. 1) 买入和持仓原则:
    10. A. 股价向上突破定性线,定性线上行,买入。
    11. 翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    12. B. 定量线上穿定性线形成黄金交叉,买入。
    13. 翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
    14. C. 股价下跌,遇定性线上行支撑止跌回升,买入。
    15. 翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    16. D. 定性线上行,股价在定性线上方向上突破定量线,买入。
    17. 翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    18. E. 定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    19. 翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    20. F. 股价、定量线、定性线多头排列,持股
    21. 翻译:收盘价大于定量价格大于定性价格
    22. 2) 卖出和空仓原则
    23. A. 股价跌破定性线,定性线走平或已拐头下行,卖出
    24. 翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    25. B. 多头排列期间,股价跌破定量线,减仓
    26. 翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    27. C. 股价急速飙升,远离定性线,减仓
    28. 翻译:股价飙升 & 收盘价远离定性价格
    29. D. 定性线下行,空仓
    30. 翻译:连续三日定性价格涨跌幅为负
    31. :return:
    32. '''
    33. import pandas as pd
    34. import talib
    35. df = pd.read_csv('E:/temp005/600660.csv',encoding='utf-8')
    36. # 删除停牌的数据
    37. df = df.loc[df['openPrice'] > 0].copy()
    38. df['o_date'] = df['tradeDate']
    39. df['o_date'] = pd.to_datetime(df['o_date'])
    40. df = df.loc[df['o_date']>='2010-01-01'].copy()
    41. # 保存未复权收盘价数据
    42. df['close'] = df['closePrice']
    43. # 计算前复权数据
    44. df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    45. df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    46. df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    47. df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']
    48. # 双均线组合
    49. # 定性线:20日均线 定量线:5日均线
    50. df['ma5'] = talib.MA(df['closePrice'],timeperiod=5)
    51. df['ma20'] = talib.MA(df['closePrice'],timeperiod=20)
    52. # 定性线涨跌幅
    53. df['ma20_rate'] = df['ma20']-df['ma20'].shift(1)
    54. # 定性线上行还是下行:连续三天涨跌幅为正,为上行,值为1;连续三天涨跌幅为负,为下行,值为-1
    55. df['ma20_direction'] = 0
    56. df.loc[(df['ma20_rate']>0) & (df['ma20_rate'].shift(1)>0) & (df['ma20_rate'].shift(2)>0),'ma20_direction'] = 1
    57. df.loc[(df['ma20_rate']<0) & (df['ma20_rate'].shift(1)<0) & (df['ma20_rate'].shift(2)<0),'ma20_direction'] = -1
    58. # 定量线涨跌幅
    59. df['ma5_rate'] = df['ma5'] - df['ma5'].shift(1)
    60. df['ma5_direction'] = 0
    61. df.loc[(df['ma5_rate']>0) & (df['ma5_rate'].shift(1)>0) & (df['ma5_rate'].shift(2)>0),'ma5_direction'] = 1
    62. df.loc[(df['ma5_rate']<0) & (df['ma5_rate'].shift(1)<0) & (df['ma5_rate'].shift(2)<0),'ma5_direction'] = -1
    63. # 收盘价涨跌幅
    64. df['close_rate'] = df['closePrice'] - df['closePrice'].shift(1)
    65. df['closePrice_direction'] = 0
    66. df.loc[(df['close_rate'] > 0) & (df['close_rate'].shift(1) > 0) & (df['close_rate'].shift(2) > 0), 'closePrice_direction'] = 1
    67. df.loc[(df['close_rate'] < 0) & (df['close_rate'].shift(1) < 0) & (df['close_rate'].shift(2) < 0), 'closePrice_direction'] = -1
    68. # 收盘价到定性线之间的距离
    69. df['close_ma20_distance'] = (abs(df['closePrice']-df['ma20'])/df['closePrice'])*100
    70. # 收盘价到定量线之间的距离
    71. df['close_ma5_distance'] = (abs(df['closePrice']-df['ma5'])/df['closePrice'])*100
    72. # 定量线到定性线之间的距离
    73. df['ma5_ma20_distance'] = (abs(df['ma5']-df['ma20'])/df['ma5'])*100
    74. # 定性线到定量线之间的距离
    75. df['ma20_ma5_distance'] = (abs(df['ma5']-df['ma20'])/df['ma20'])*100
    76. # 股价是否飙升
    77. df['pre_fast_up'] = 0
    78. df.loc[(df['close_rate']>0) & (df['close_rate']/df['closePrice']>=0.05),'pre_fast_up'] = 1
    79. df['fast_up'] = 0
    80. df.loc[(df['pre_fast_up']==1) & (df['pre_fast_up'].shift(1)==1) & (df['pre_fast_up'].shift(2)==1),'fast_up'] = 1
    81. # 黄金交叉
    82. df['gold_x'] = 0
    83. df.loc[(df['ma5'].shift(1)'ma20'].shift(1)) & (df['ma5']>df['ma20']) & (df['ma5'].shift(-1)>df['ma20'].shift(-1)) & (df['ma5'].shift(-2)>df['ma20'].shift(-2)),'gold_x'] = 1
    84. # 死亡交叉
    85. df['dead_x'] = 0
    86. df.loc[(df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['ma5']'ma20']) & (df['ma5'].shift(-1)'ma20'].shift(-1)) & (df['ma5'].shift(-2)'ma20'].shift(-2)),'dead_x'] = 1
    87. # 1) 买入和持仓原则:
    88. # 买入=1 持股=2(区间) 卖出=3 减仓=4 空仓=5(区间) 无信号=0
    89. df['signal'] = 0
    90. df['signal_reason'] = ''
    91. # A. 股价向上突破定性线,定性线上行,买入。
    92. # 翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    93. df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)'ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal'] = 1
    94. df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)'ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal_reason'] = 'A'
    95. # B.定量线上穿定性线形成黄金交叉,买入。
    96. # 翻译:黄金交叉
    97. df.loc[df['gold_x']==1,'signal'] = 1
    98. df.loc[df['gold_x']==1,'signal_reason'] = 'B'
    99. # C.股价下跌,遇定性线上行支撑止跌回升,买入。
    100. # 翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    101. df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal'] = 1
    102. df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal_reason'] = 'C'
    103. # D.定性线上行,股价在定性线上方向上突破定量线,买入。
    104. # 翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    105. df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)'ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal'] = 1
    106. df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)'ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal_reason'] = 'D'
    107. # E.定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    108. # 翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    109. df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal'] = 1
    110. df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal_reason'] = 'E'
    111. # F.股价、定量线、定性线多头排列,持股
    112. # 翻译:收盘价大于定量价格大于定性价格
    113. df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal'] = 2
    114. df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal_reason'] = 'F'
    115. # 2) 卖出和空仓原则
    116. # A.股价跌破定性线,定性线走平或已拐头下行,卖出
    117. # 翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    118. df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma20']) & (df['ma20_rate']<=0),'signal'] = 3
    119. df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma20']) & (df['ma20_rate']<=0),'signal_reason'] = 'A'
    120. # B.多头排列期间,股价跌破定量线,减仓
    121. # 翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    122. df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma5']),'signal'] = 4
    123. df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']'ma5']),'signal_reason'] = 'B'
    124. # C.股价急速飙升,远离定性线,减仓
    125. # 翻译:股价飙升 & 收盘价远离定性价格
    126. df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal'] = 4
    127. df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal_reason'] = 'C'
    128. # D.定性线下行,空仓
    129. # 翻译:连续三日定性价格涨跌幅为负
    130. df.loc[df['ma20_direction']==-1,'signal'] = 5
    131. df.loc[df['ma20_direction']==-1,'signal_reason'] = 'D'
    132. title_str = '双均线组合信号'
    133. whole_header = ['日期','收盘价','开盘价','最高价','最低价','ma5','ma20']
    134. whole_df = df
    135. whole_pd_header = ['tradeDate','closePrice','openPrice','highestPrice','lowestPrice','ma5','ma20']
    136. line_pd_header = ['ma5','ma20']
    137. line_color_list = [0,2]
    138. line_data = {
    139. 'title_str':title_str,
    140. 'whole_header':whole_header,
    141. 'whole_df':whole_df,
    142. 'whole_pd_header':whole_pd_header,
    143. 'line_pd_header':line_pd_header,
    144. 'line_color_list':line_color_list
    145. }
    146. return line_data

    运行优化后的策略,为方便验证优化结果,把数据的时间往前拉长到10年,执行

     2014-05-06这个时间点的黄金交叉就靠谱多了

    数据

    链接:https://pan.baidu.com/s/1HPkMsDDyXTEgffoAVIhbZw 
    提取码:h80x 

  • 相关阅读:
    web前端设计与开发期末作品 用DIV CSS技术设计的凤阳旅游网站(web前端网页制作课作业)html css javascript
    Xshell工具连接本地虚拟机Linux系统
    leetcode(力扣) 221. 最大正方形(动态规划)
    操作系统:操作系统相关概念博客系统整理
    centos密码过期导致navicat无法通过SSH登录阿里云RDS问题
    iPaaS中API接口管理平台的作用
    基于C#的壁纸管理器(插件版) - 开源研究系列文章 - 个人小作品
    解决QT中文乱码
    企业使用微信管理系统更安全更高效更智能
    IIS 部署.NetCore,最细步骤
  • 原文地址:https://blog.csdn.net/m0_37967652/article/details/127614450