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作为定性线 

def excute_strategy():
    '''
    前置自定义规则:
    A. 支撑和阻碍,定义为股价与均线价格之间的差值小于等于当前股价的2%
    B. 股价下跌,连续三日收盘价涨跌幅为负
    C. 均线下行,连续三日均线涨跌幅为负
    D. 股价急速飙升,连续三日股价涨跌幅都大于等于5%
    E. 远离,定义为股价与均线价格之间的差值大于等于当前股价的50%
    1) 买入和持仓原则:
    A. 股价向上突破定性线,定性线上行,买入。
    翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    B. 定量线上穿定性线形成黄金交叉,买入。
    翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
    C. 股价下跌,遇定性线上行支撑止跌回升,买入。
    翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    D. 定性线上行,股价在定性线上方向上突破定量线,买入。
    翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    E. 定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    F. 股价、定量线、定性线多头排列,持股
    翻译:收盘价大于定量价格大于定性价格
    2) 卖出和空仓原则
    A. 股价跌破定性线,定性线走平或已拐头下行,卖出
    翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    B. 多头排列期间,股价跌破定量线,减仓
    翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    C. 股价急速飙升,远离定性线,减仓
    翻译:股价飙升 & 收盘价远离定性价格
    D. 定性线下行,空仓
    翻译:连续三日定性价格涨跌幅为负
    :return:
    '''
    import pandas as pd
    import talib
    df = pd.read_csv('E:/temp005/600660.csv',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']>='2020-01-01'].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']

    # 双均线组合
    # 定性线:20日均线  定量线:5日均线
    df['ma5'] = talib.MA(df['closePrice'],timeperiod=5)
    df['ma20'] = talib.MA(df['closePrice'],timeperiod=20)
    # 定性线涨跌幅
    df['ma20_rate'] = df['ma20']-df['ma20'].shift(1)
    # 定性线上行还是下行:连续三天涨跌幅为正,为上行,值为1;连续三天涨跌幅为负,为下行,值为-1
    df['ma20_direction'] = 0
    df.loc[(df['ma20_rate']>0) & (df['ma20_rate'].shift(1)>0) & (df['ma20_rate'].shift(2)>0),'ma20_direction'] = 1
    df.loc[(df['ma20_rate']<0) & (df['ma20_rate'].shift(1)<0) & (df['ma20_rate'].shift(2)<0),'ma20_direction'] = -1
    # 定量线涨跌幅
    df['ma5_rate'] = df['ma5'] - df['ma5'].shift(1)
    df['ma5_direction'] = 0
    df.loc[(df['ma5_rate']>0) & (df['ma5_rate'].shift(1)>0) & (df['ma5_rate'].shift(2)>0),'ma5_direction'] = 1
    df.loc[(df['ma5_rate']<0) & (df['ma5_rate'].shift(1)<0) & (df['ma5_rate'].shift(2)<0),'ma5_direction'] = -1
    # 收盘价涨跌幅
    df['close_rate'] = df['closePrice'] - df['closePrice'].shift(1)
    df['closePrice_direction'] = 0
    df.loc[(df['close_rate'] > 0) & (df['close_rate'].shift(1) > 0) & (df['close_rate'].shift(2) > 0), 'closePrice_direction'] = 1
    df.loc[(df['close_rate'] < 0) & (df['close_rate'].shift(1) < 0) & (df['close_rate'].shift(2) < 0), 'closePrice_direction'] = -1
    # 收盘价到定性线之间的距离
    df['close_ma20_distance'] = (abs(df['closePrice']-df['ma20'])/df['closePrice'])*100
    # 收盘价到定量线之间的距离
    df['close_ma5_distance'] = (abs(df['closePrice']-df['ma5'])/df['closePrice'])*100
    # 定量线到定性线之间的距离
    df['ma5_ma20_distance'] = (abs(df['ma5']-df['ma20'])/df['ma5'])*100
    # 定性线到定量线之间的距离
    df['ma20_ma5_distance'] = (abs(df['ma5']-df['ma20'])/df['ma20'])*100
    # 股价是否飙升
    df['pre_fast_up'] = 0
    df.loc[(df['close_rate']>0) & (df['close_rate']/df['closePrice']>=0.05),'pre_fast_up'] = 1
    df['fast_up'] = 0
    df.loc[(df['pre_fast_up']==1) & (df['pre_fast_up'].shift(1)==1) & (df['pre_fast_up'].shift(2)==1),'fast_up'] = 1

    # 1) 买入和持仓原则:
    # 买入=1 持股=2(区间) 卖出=3 减仓=4 空仓=5(区间) 无信号=0
    df['signal'] = 0
    df['signal_reason'] = ''
    # A. 股价向上突破定性线,定性线上行,买入。
    # 翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)<df['ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal'] = 1
    df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)<df['ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal_reason'] = 'A'
    # B.定量线上穿定性线形成黄金交叉,买入。
    # 翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
    df.loc[(df['ma5'].shift(1)<df['ma20'].shift(1)) & (df['ma5']>df['ma20']),'signal'] = 1
    df.loc[(df['ma5'].shift(1)<df['ma20'].shift(1)) & (df['ma5']>df['ma20']),'signal_reason'] = 'B'
    # C.股价下跌,遇定性线上行支撑止跌回升,买入。
    # 翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal'] = 1
    df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal_reason'] = 'C'
    # D.定性线上行,股价在定性线上方向上突破定量线,买入。
    # 翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)<df['ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal'] = 1
    df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)<df['ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal_reason'] = 'D'
    # E.定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    # 翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal'] = 1
    df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal_reason'] = 'E'
    # F.股价、定量线、定性线多头排列,持股
    # 翻译:收盘价大于定量价格大于定性价格
    df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal'] = 2
    df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal_reason'] = 'F'

    # 2) 卖出和空仓原则
    # A.股价跌破定性线,定性线走平或已拐头下行,卖出
    # 翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma20']) & (df['ma20_rate']<=0),'signal'] = 3
    df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma20']) & (df['ma20_rate']<=0),'signal_reason'] = 'A'
    # B.多头排列期间,股价跌破定量线,减仓
    # 翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma5']),'signal'] = 4
    df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma5']),'signal_reason'] = 'B'
    # C.股价急速飙升,远离定性线,减仓
    # 翻译:股价飙升 & 收盘价远离定性价格
    df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal'] = 4
    df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal_reason'] = 'C'
    # D.定性线下行,空仓
    # 翻译:连续三日定性价格涨跌幅为负
    df.loc[df['ma20_direction']==-1,'signal'] = 5
    df.loc[df['ma20_direction']==-1,'signal_reason'] = 'D'

    title_str = '双均线组合信号'
    whole_header = ['日期','收盘价','开盘价','最高价','最低价','ma5','ma20']
    whole_df = df
    whole_pd_header = ['tradeDate','closePrice','openPrice','highestPrice','lowestPrice','ma5','ma20']
    line_pd_header = ['ma5','ma20']
    line_color_list = [0,2]

    line_data = {
        'title_str':title_str,
        'whole_header':whole_header,
        'whole_df':whole_df,
        'whole_pd_header':whole_pd_header,
        'line_pd_header':line_pd_header,
        'line_color_list':line_color_list
    }
    return line_data

运行工具代码

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

import sys,json,os,math,time
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')

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()

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('d'))
        w = (self.data[1][0] - self.data[0][0]) / 3.
        for (t, open, close, min, max) in self.data:
            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

执行结果图表显示控件

class PyQtGraphRunningWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.init_data()
        self.init_ui()
        pass
    def init_data(self):
        self.table_header = ['日期','信号','未复权收盘价','解说']
        self.please_select_str = '---请选择---'
        self.func_map = {
            '标记多头排列区间':'a',
            '标记空头排列区间':'b',
            '20日线斜率为正':'c'
        }
        self.func_item_list = []
        self.duration_map = {
            '今年':'a',
            '最近一年':'b',
            '最近两年':'c'
        }
        # 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.buy_color = (220, 20, 60)  # 猩红
        self.hold_color = (255, 140, 0)  # 深橙色
        self.sell_color = (50, 205, 50)  # 酸橙绿
        self.subpos_color = (24, 252, 0)  # 草坪绿
        self.emptypos_color = (0, 128, 0)  # 纯绿
        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.line_pd_header = []
        self.line_color_list = []
        self.signal_show_list = []
        pass
    def init_ui(self):
        # 控制面板 start
        left_tip = QtWidgets.QLabel('左边界')
        self.left_point = QtWidgets.QDateEdit()
        self.left_point.setDisplayFormat('yyyy-MM-dd')
        self.left_point.setCalendarPopup(True)
        right_tip = QtWidgets.QLabel('右边界')
        self.right_point = QtWidgets.QDateEdit()
        self.right_point.setDisplayFormat('yyyy-MM-dd')
        self.right_point.setCalendarPopup(True)
        duration_sel_btn = QtWidgets.QPushButton('确定')
        duration_sel_btn.clicked.connect(self.duration_sel_btn_clicked)
        duration_reset_btn = QtWidgets.QPushButton('重置')
        duration_reset_btn.clicked.connect(self.duration_reset_btn_clicked)

        self.whole_duration_label = QtWidgets.QLabel('原始最宽边界:左边界~右边界')
        self.now_duration_label = QtWidgets.QLabel('当前显示最宽边界:左边界~右边界')

        self.buy_checkbox = QtWidgets.QCheckBox('买入')
        self.buy_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.buy_checkbox))
        self.hold_checkbox = QtWidgets.QCheckBox('持股')
        self.hold_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.hold_checkbox))
        self.sell_checkbox = QtWidgets.QCheckBox('卖出')
        self.sell_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.sell_checkbox))
        self.subpos_checkbox = QtWidgets.QCheckBox('减仓')
        self.subpos_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.subpos_checkbox))
        self.emptypos_checkbox = QtWidgets.QCheckBox('空仓')
        self.emptypos_checkbox.stateChanged.connect(lambda: self.signal_checkbox_state(self.emptypos_checkbox))

        layout_date = QtWidgets.QHBoxLayout()
        layout_date.addWidget(left_tip)
        layout_date.addWidget(self.left_point)
        layout_date.addWidget(right_tip)
        layout_date.addWidget(self.right_point)
        layout_date.addWidget(duration_sel_btn)
        layout_date.addWidget(duration_reset_btn)
        layout_date.addWidget(self.buy_checkbox)
        layout_date.addWidget(self.hold_checkbox)
        layout_date.addWidget(self.sell_checkbox)
        layout_date.addWidget(self.subpos_checkbox)
        layout_date.addWidget(self.emptypos_checkbox)
        layout_date.addStretch(1)
        layout_duration = QtWidgets.QHBoxLayout()
        layout_duration.addWidget(self.whole_duration_label)
        layout_duration.addSpacing(30)
        layout_duration.addWidget(self.now_duration_label)
        layout_duration.addStretch(1)
        # 控制面板 end

        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)

        self.table = PageTableWidget()
        self.table.set_table_init_data({'headers':self.table_header})

        layout_down = QtWidgets.QHBoxLayout()
        layout_down.addWidget(self.pw,6)
        layout_down.addWidget(self.table,1)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.title_label)
        layout.addLayout(layout_date)
        layout.addLayout(layout_duration)
        layout.addLayout(layout_down)
        self.setLayout(layout)
        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']
        line_pd_header = data['line_pd_header']
        line_color_list = data['line_color_list']

        self.whole_header = whole_header
        self.whole_df = whole_df
        self.whole_pd_header = whole_pd_header
        self.line_pd_header = line_pd_header
        self.line_color_list = line_color_list

        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()
        self.func_item_list.clear()
        self.nocheck_checkbox()
        self.remove_signal_show()

        self.now_duration_label.setText(f"当前显示最宽边界:{df.iloc[0]['tradeDate']}~{df.iloc[-1]['tradeDate']}")

        xax = self.pw.getAxis('bottom')
        xax.setTicks([xTick_show])

        candle_fixed_target = CandlestickItem(candle_data)
        self.main_fixed_target_list.append(candle_fixed_target)
        self.pw.addItem(candle_fixed_target)

        # 曲线
        if len(self.line_pd_header)>0:
            for i,item in enumerate(self.line_pd_header):
                line_fixed_target = pg.PlotCurveItem(x=np.array(x), y=np.array(df[item].values.tolist()),
                                            pen=pg.mkPen({'color': self.color_list[self.line_color_list[i]], 'width': 2}),
                                            connect='finite')
                self.main_fixed_target_list.append(line_fixed_target)
                self.pw.addItem(line_fixed_target)
            pass

        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"<br/>{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

    def duration_sel_btn_clicked(self):
        '''边界选择'''
        left_point = self.left_point.date().toString('yyyy-MM-dd')
        right_point = self.right_point.date().toString('yyyy-MM-dd')
        df = self.whole_df.copy()
        df['o_date'] = pd.to_datetime(df['tradeDate'])
        self.current_whole_df = df.loc[(df['o_date']>=left_point) & (df['o_date']<=right_point)].copy()
        self.caculate_and_show_data()
        pass
    def duration_reset_btn_clicked(self):
        '''边界重置'''
        self.current_whole_df = self.whole_df.copy()
        self.caculate_and_show_data()
        pass
    def nocheck_checkbox(self):
        self.buy_checkbox.setChecked(False)
        self.hold_checkbox.setChecked(False)
        self.sell_checkbox.setChecked(False)
        self.subpos_checkbox.setChecked(False)
        self.emptypos_checkbox.setChecked(False)
        pass
    def remove_signal_show(self):
        for item in self.signal_show_list:
            self.pw.removeItem(item)
        self.signal_show_list.clear()
        pass
    def signal_checkbox_state(self,cb):
        self.remove_signal_show()
        signal_list = []
        if self.buy_checkbox.isChecked():
            signal_list.append(1)
        if self.hold_checkbox.isChecked():
            signal_list.append(2)
        if self.sell_checkbox.isChecked():
            signal_list.append(3)
        if self.subpos_checkbox.isChecked():
            signal_list.append(4)
        if self.emptypos_checkbox.isChecked():
            signal_list.append(5)

        if len(signal_list)<=0:
            # 删除所有的信号
            self.remove_signal_show()
            pass
        else:
            df = self.current_whole_df.copy()
            df.reset_index(inplace=True)
            if 1 in signal_list:
                # 买 220,20,60  猩红
                df_buy = df.loc[df['signal']==1].copy()
                for i, row in df_buy.iterrows():
                    buy_fixed_target = pg.InfiniteLine(pos=(i, 0), movable=False, angle=90,
                                                       pen=pg.mkPen({'color': self.buy_color, 'width': 2}),
                                                       label=str(row['close']),
                                                       labelOpts={'position': 0.05, 'color': self.buy_color,
                                                                  'movable': True, 'fill': (
                                                           self.buy_color[0], self.buy_color[1], self.buy_color[2], 30)})
                    self.signal_show_list.append(buy_fixed_target)
                    self.pw.addItem(buy_fixed_target)
                    pass
                pass
            if 2 in signal_list:
                # 持股 255,140,0 深橙色
                df['ext_hold_count'] = [i for i in range(len(df))]
                df['ext_hold_0'] = 0
                df.loc[df['signal'] == 2, 'ext_hold_0'] = 1
                # start 值
                df['ext_hold_1'] = df['ext_hold_0'] - df['ext_hold_0'].shift(1)
                # end 值
                df['ext_hold_2'] = df['ext_hold_0'] - df['ext_hold_0'].shift(-1)
                df_hold_start = df.loc[df['ext_hold_1'] == 1].copy()
                df_hold_end = df.loc[df['ext_hold_2'] == 1].copy()
                hold_start_count_list = df_hold_start['ext_hold_count'].values.tolist()
                hold_end_count_list = df_hold_end['ext_hold_count'].values.tolist()
                if hold_end_count_list[0] < hold_start_count_list[0]:
                    hold_end_count_list = hold_end_count_list[1:]
                if hold_end_count_list[-1] < hold_start_count_list[-1]:
                    hold_start_count_list = hold_start_count_list[:-1]
                for i, item in enumerate(hold_end_count_list):
                    lr = pg.LinearRegionItem([hold_start_count_list[i], item], movable=False, brush=(
                    self.hold_color[0], self.hold_color[1], self.hold_color[2], 100))
                    lr.setZValue(-100)
                    self.signal_show_list.append(lr)
                    self.pw.addItem(lr)
                    pass
                pass
            if 3 in signal_list:
                # 卖出 50,205,50 酸橙绿
                df_sell = df.loc[df['signal'] == 3].copy()
                for i, row in df_sell.iterrows():
                    sell_fixed_target = pg.InfiniteLine(pos=(i, 0), movable=False, angle=90,
                                                       pen=pg.mkPen({'color': self.sell_color, 'width': 2}),
                                                       label=str(row['close']),
                                                       labelOpts={'position': 0.05, 'color': self.sell_color,
                                                                  'movable': True, 'fill': (
                                                               self.sell_color[0], self.sell_color[1], self.sell_color[2],
                                                               30)})
                    self.signal_show_list.append(sell_fixed_target)
                    self.pw.addItem(sell_fixed_target)
                    pass
                pass
            if 4 in signal_list:
                # 减仓 24,252,0 草坪绿
                df_subpos = df.loc[df['signal'] == 4].copy()
                for i, row in df_subpos.iterrows():
                    subpos_fixed_target = pg.InfiniteLine(pos=(i, 0), movable=False, angle=90,
                                                        pen=pg.mkPen({'color': self.subpos_color, 'width': 2}),
                                                        label=str(row['close']),
                                                        labelOpts={'position': 0.05, 'color': self.subpos_color,
                                                                   'movable': True, 'fill': (
                                                                self.subpos_color[0], self.subpos_color[1],
                                                                self.subpos_color[2],
                                                                30)})
                    self.signal_show_list.append(subpos_fixed_target)
                    self.pw.addItem(subpos_fixed_target)
                    pass
                pass
            if 5 in signal_list:
                # 空仓 0,128,0 纯绿
                df['ext_empty_count'] = [i for i in range(len(df))]
                df['ext_empty_0'] = 0
                df.loc[df['signal']==5,'ext_empty_0'] = 1
                # start 值
                df['ext_empty_1'] = df['ext_empty_0']-df['ext_empty_0'].shift(1)
                # end 值
                df['ext_empty_2'] = df['ext_empty_0']-df['ext_empty_0'].shift(-1)
                df_empty_start = df.loc[df['ext_empty_1']==1].copy()
                df_empty_end = df.loc[df['ext_empty_2']==1].copy()
                empty_start_count_list = df_empty_start['ext_empty_count'].values.tolist()
                empty_end_count_list = df_empty_end['ext_empty_count'].values.tolist()
                if empty_end_count_list[0]<empty_start_count_list[0]:
                    empty_end_count_list = empty_end_count_list[1:]
                if empty_end_count_list[-1]<empty_start_count_list[-1]:
                    empty_start_count_list = empty_start_count_list[:-1]
                for i,item in enumerate(empty_end_count_list):
                    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))
                    lr.setZValue(-100)
                    self.signal_show_list.append(lr)
                    self.pw.addItem(lr)
                    pass
                pass
            df['signal_name'] = ''
            for item in signal_list:
                signal_name = ''
                if item == 1:
                    signal_name = '买入'
                if item == 2:
                    signal_name = '持股'
                if item == 3:
                    signal_name = '卖出'
                if item == 4:
                    signal_name = '减仓'
                if item == 5:
                    signal_name = '空仓'
                df.loc[df['signal'] == item,'signal_name'] = signal_name
            df_table = df.loc[df['signal'].isin(signal_list)].copy()
            table_data = df_table.loc[:, ['tradeDate', 'signal_name', 'close','signal_reason']].values.tolist()
            self.table.set_table_full_data(table_data)
        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.running_graph_widget: QtWidgets.QWidget = None

        self.init_data()
        self.init_ui()
        self.register_event()
        pass
    def init_data(self):
        self.pre_output_dir = './'
        self.please_select_str: str = '--请选择--'
        self.stratege_name_list: List = []
        self.tip_msg_0: str = '1.选择策略所在文件夹;2.选择策略;3.点击运行。'
        self.stratege_path: str = ''
        self.stratege_run_start_time = None
        self.stratege_start = False
        self.current_stratege_py_str: str = ''
        pass
    def init_ui(self):
        self.setWindowTitle('策略运行小工具')
        tip_0 = QtWidgets.QLabel('选择策略所在文件夹:')
        self.stratege_dir_lineedit = QtWidgets.QLineEdit()
        self.stratege_dir_lineedit.setReadOnly(True)
        stratege_choice_btn = QtWidgets.QPushButton('选择文件夹')
        stratege_choice_btn.clicked.connect(self.stratege_choice_btn_clicked)

        tip_1 = QtWidgets.QLabel('策略:')
        self.stratege_combox = QtWidgets.QComboBox()
        self.stratege_combox.addItem(self.please_select_str)
        self.stratege_combox.currentIndexChanged.connect(self.stratege_combox_currentIndexChanged)

        self.run_btn = QtWidgets.QPushButton('运行')
        self.run_btn.clicked.connect(self.run_btn_clicked)
        self.force_stop_btn = QtWidgets.QPushButton('强制停止')
        self.force_stop_btn.clicked.connect(self.force_stop_btn_clicked)

        layout_top_left = QtWidgets.QGridLayout()
        layout_top_left.addWidget(tip_0,0,0,1,1)
        layout_top_left.addWidget(self.stratege_dir_lineedit,0,1,1,3)
        layout_top_left.addWidget(stratege_choice_btn,0,4,1,1)
        layout_top_left.addWidget(tip_1,1,0,1,1)
        layout_top_left.addWidget(self.stratege_combox,1,1,1,2)
        layout_top_left.addWidget(self.run_btn,1,3,1,1)
        layout_top_left.addWidget(self.force_stop_btn,1,4,1,1)

        self.tip_msg_label = QtWidgets.QLabel()
        self.tip_msg_label.setWordWrap(True)
        self.tip_msg_label.setText(self.tip_msg_0)

        layout_top = QtWidgets.QHBoxLayout()
        layout_top.addLayout(layout_top_left,3)
        layout_top.addWidget(self.tip_msg_label,1)

        self.code_textedit = QtWidgets.QTextEdit()
        self.code_textedit.setReadOnly(True)

        layout = QtWidgets.QVBoxLayout()
        layout.addLayout(layout_top)
        layout.addWidget(self.code_textedit)
        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 stratege_choice_btn_clicked(self):
        '''选择策略所在文件夹'''
        path = QtWidgets.QFileDialog.getExistingDirectory(
            self,
            '打开策略所在文件夹',
            self.pre_output_dir
        )
        if not path:
            return
        self.stratege_path = path
        self.stratege_dir_lineedit.setText(path)
        file_list = os.listdir(path)
        temp_file_list = set(self.stratege_name_list)
        for item in file_list:
            if item.endswith('.py'):
                temp_file_list.add(item)
        self.stratege_name_list = list(temp_file_list)

        self.stratege_combox.clear()
        self.stratege_combox.addItem(self.please_select_str)
        self.stratege_combox.addItems(self.stratege_name_list)
        pass
    def stratege_combox_currentIndexChanged(self,cur_i:int):
        cur_txt = self.stratege_combox.currentText()
        if not cur_txt or cur_txt == self.please_select_str:
            self.code_textedit.clear()
            return
        file_path = self.stratege_path + os.path.sep + cur_txt
        with open(file_path,'r',encoding='utf-8') as fr:
            code_txt = fr.read()
        self.code_textedit.setPlainText(code_txt)
        pass
    def run_btn_clicked(self):
        '''运行按钮'''
        py_str = self.code_textedit.toPlainText()
        if len(py_str)<10:
            QtWidgets.QMessageBox.information(
                self,
                '提示',
                '请选择要执行的策略',
                QtWidgets.QMessageBox.Yes
            )
            return
        self.current_stratege_py_str = py_str
        self.run_btn.setDisabled(True)
        self.stratege_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
        self.thread_run = Thread(
            target=self.running_run_thread,
            args=(py_str,)
        )
        self.thread_run.start()
        self.thread_time = Thread(
            target=self.running_time_thread
        )
        self.thread_time.start()
        pass
    def force_stop_btn_clicked(self):
        '''强制停止按钮'''
        self.thread_run = None
        self.thread_time = None

        self.run_btn.setDisabled(False)
        self.stratege_combox.setDisabled(False)
        self.stratege_start = False
        pass
    def running_run_thread(self,data:str):
        '''执行代码线程'''
        namespace = {}
        fun_stragegy = compile(data,'<string>','exec')
        exec(fun_stragegy,namespace)
        ret = namespace['excute_strategy']()
        self.signal_runcode.emit(ret)
        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_run_excuted(self,data:Dict):
        '''策略代码执行返回结果'''
        self.run_btn.setDisabled(False)
        self.stratege_combox.setDisabled(False)

        if not self.running_graph_widget:
            self.running_graph_widget = PyQtGraphRunningWidget()
        self.running_graph_widget.set_data(data)
        self.running_graph_widget.showMaximized()
        self.thread_run = None
        self.thread_time = None
        self.stratege_start = False

        QtWidgets.QMessageBox.information(
            self,
            '提示',
            '当前策略运行完毕',
            QtWidgets.QMessageBox.Yes
        )
        pass
    def thread_time_excuted(self,data:Dict):
        '''计时返回结果'''
        res = data['res']
        self.tip_msg_label.setText(f"{res}s")
        pass
    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
        if self.thread_time:
            self.thread_time.join()
        if self.thread_run:
            self.thread_run.join()
        if self.running_graph_widget:
            self.running_graph_widget.close()
        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

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

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

验证策略思想

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

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

优化策略

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

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

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

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

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

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

 修改策略代码:

在实现原则的代码前加入

 # 黄金交叉
    df['gold_x'] = 0
    df.loc[(df['ma5'].shift(1)<df['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
    # 死亡交叉
    df['dead_x'] = 0
    df.loc[(df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['ma5']<df['ma20']) & (df['ma5'].shift(-1)<df['ma20'].shift(-1)) & (df['ma5'].shift(-2)<df['ma20'].shift(-2)),'dead_x'] = 1

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

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

 修正后完整的策略代码为

def excute_strategy():
    '''
    前置自定义规则:
    A. 支撑和阻碍,定义为股价与均线价格之间的差值小于等于当前股价的2%
    B. 股价下跌,连续三日收盘价涨跌幅为负
    C. 均线下行,连续三日均线涨跌幅为负
    D. 股价急速飙升,连续三日股价涨跌幅都大于等于5%
    E. 远离,定义为股价与均线价格之间的差值大于等于当前股价的50%
    1) 买入和持仓原则:
    A. 股价向上突破定性线,定性线上行,买入。
    翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    B. 定量线上穿定性线形成黄金交叉,买入。
    翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
    C. 股价下跌,遇定性线上行支撑止跌回升,买入。
    翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    D. 定性线上行,股价在定性线上方向上突破定量线,买入。
    翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    E. 定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    F. 股价、定量线、定性线多头排列,持股
    翻译:收盘价大于定量价格大于定性价格
    2) 卖出和空仓原则
    A. 股价跌破定性线,定性线走平或已拐头下行,卖出
    翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    B. 多头排列期间,股价跌破定量线,减仓
    翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    C. 股价急速飙升,远离定性线,减仓
    翻译:股价飙升 & 收盘价远离定性价格
    D. 定性线下行,空仓
    翻译:连续三日定性价格涨跌幅为负
    :return:
    '''
    import pandas as pd
    import talib
    df = pd.read_csv('E:/temp005/600660.csv',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']>='2010-01-01'].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']

    # 双均线组合
    # 定性线:20日均线  定量线:5日均线
    df['ma5'] = talib.MA(df['closePrice'],timeperiod=5)
    df['ma20'] = talib.MA(df['closePrice'],timeperiod=20)
    # 定性线涨跌幅
    df['ma20_rate'] = df['ma20']-df['ma20'].shift(1)
    # 定性线上行还是下行:连续三天涨跌幅为正,为上行,值为1;连续三天涨跌幅为负,为下行,值为-1
    df['ma20_direction'] = 0
    df.loc[(df['ma20_rate']>0) & (df['ma20_rate'].shift(1)>0) & (df['ma20_rate'].shift(2)>0),'ma20_direction'] = 1
    df.loc[(df['ma20_rate']<0) & (df['ma20_rate'].shift(1)<0) & (df['ma20_rate'].shift(2)<0),'ma20_direction'] = -1
    # 定量线涨跌幅
    df['ma5_rate'] = df['ma5'] - df['ma5'].shift(1)
    df['ma5_direction'] = 0
    df.loc[(df['ma5_rate']>0) & (df['ma5_rate'].shift(1)>0) & (df['ma5_rate'].shift(2)>0),'ma5_direction'] = 1
    df.loc[(df['ma5_rate']<0) & (df['ma5_rate'].shift(1)<0) & (df['ma5_rate'].shift(2)<0),'ma5_direction'] = -1
    # 收盘价涨跌幅
    df['close_rate'] = df['closePrice'] - df['closePrice'].shift(1)
    df['closePrice_direction'] = 0
    df.loc[(df['close_rate'] > 0) & (df['close_rate'].shift(1) > 0) & (df['close_rate'].shift(2) > 0), 'closePrice_direction'] = 1
    df.loc[(df['close_rate'] < 0) & (df['close_rate'].shift(1) < 0) & (df['close_rate'].shift(2) < 0), 'closePrice_direction'] = -1
    # 收盘价到定性线之间的距离
    df['close_ma20_distance'] = (abs(df['closePrice']-df['ma20'])/df['closePrice'])*100
    # 收盘价到定量线之间的距离
    df['close_ma5_distance'] = (abs(df['closePrice']-df['ma5'])/df['closePrice'])*100
    # 定量线到定性线之间的距离
    df['ma5_ma20_distance'] = (abs(df['ma5']-df['ma20'])/df['ma5'])*100
    # 定性线到定量线之间的距离
    df['ma20_ma5_distance'] = (abs(df['ma5']-df['ma20'])/df['ma20'])*100
    # 股价是否飙升
    df['pre_fast_up'] = 0
    df.loc[(df['close_rate']>0) & (df['close_rate']/df['closePrice']>=0.05),'pre_fast_up'] = 1
    df['fast_up'] = 0
    df.loc[(df['pre_fast_up']==1) & (df['pre_fast_up'].shift(1)==1) & (df['pre_fast_up'].shift(2)==1),'fast_up'] = 1
    # 黄金交叉
    df['gold_x'] = 0
    df.loc[(df['ma5'].shift(1)<df['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
    # 死亡交叉
    df['dead_x'] = 0
    df.loc[(df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['ma5']<df['ma20']) & (df['ma5'].shift(-1)<df['ma20'].shift(-1)) & (df['ma5'].shift(-2)<df['ma20'].shift(-2)),'dead_x'] = 1


    # 1) 买入和持仓原则:
    # 买入=1 持股=2(区间) 卖出=3 减仓=4 空仓=5(区间) 无信号=0
    df['signal'] = 0
    df['signal_reason'] = ''
    # A. 股价向上突破定性线,定性线上行,买入。
    # 翻译:收盘价涨跌幅为正 & 前一日收盘价小于定性价格 & 收盘价大于定性价格 & 定性线连续三日涨跌幅为正
    df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)<df['ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal'] = 1
    df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)<df['ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal_reason'] = 'A'
    # B.定量线上穿定性线形成黄金交叉,买入。
    # 翻译:黄金交叉
    df.loc[df['gold_x']==1,'signal'] = 1
    df.loc[df['gold_x']==1,'signal_reason'] = 'B'
    # C.股价下跌,遇定性线上行支撑止跌回升,买入。
    # 翻译:连续三日收盘价涨跌幅为负 & 定性线连续三日涨跌幅为正 & 股价被定性线支撑 & 下一日收盘价正跌幅为正
    df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal'] = 1
    df.loc[(df['closePrice_direction']==-1) & (df['ma20_direction']==1) & (df['close_ma20_distance']<=2) & (df['close_rate'].shift(-1)>0),'signal_reason'] = 'C'
    # D.定性线上行,股价在定性线上方向上突破定量线,买入。
    # 翻译:定性线连续三日涨跌幅为正 & 收盘价大于定性线价格 & (前一日收盘价小于前一日定量价格 & 当日收盘价大于定量价格)
    df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)<df['ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal'] = 1
    df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)<df['ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal_reason'] = 'D'
    # E.定量线下行,遇定性线上行支撑止跌后再度上升,买入。
    # 翻译:连续三日定量线涨跌幅为负 & 连续三日定性线涨跌幅为正 & 定量线被定性线支撑 & 下一日定量线涨跌幅为正
    df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal'] = 1
    df.loc[(df['ma5_direction']==-1) & (df['ma20_direction']==1) & (df['ma5_ma20_distance']<=2) & (df['ma5_rate'].shift(-1)>0),'signal_reason'] = 'E'
    # F.股价、定量线、定性线多头排列,持股
    # 翻译:收盘价大于定量价格大于定性价格
    df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal'] = 2
    df.loc[(df['closePrice']>df['ma5']) & (df['ma5']>df['ma20']),'signal_reason'] = 'F'

    # 2) 卖出和空仓原则
    # A.股价跌破定性线,定性线走平或已拐头下行,卖出
    # 翻译:前一日收盘价大于定性价格 & 当日收盘价小于定性价格 & 定性价格涨跌幅小于等于0
    df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma20']) & (df['ma20_rate']<=0),'signal'] = 3
    df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma20']) & (df['ma20_rate']<=0),'signal_reason'] = 'A'
    # B.多头排列期间,股价跌破定量线,减仓
    # 翻译:前一日收盘价大于定量价格大于定性价格 & 当日收盘价小于定量价格
    df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma5']),'signal'] = 4
    df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']<df['ma5']),'signal_reason'] = 'B'
    # C.股价急速飙升,远离定性线,减仓
    # 翻译:股价飙升 & 收盘价远离定性价格
    df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal'] = 4
    df.loc[(df['fast_up']==1) & (df['close_ma5_distance']>=50),'signal_reason'] = 'C'
    # D.定性线下行,空仓
    # 翻译:连续三日定性价格涨跌幅为负
    df.loc[df['ma20_direction']==-1,'signal'] = 5
    df.loc[df['ma20_direction']==-1,'signal_reason'] = 'D'

    title_str = '双均线组合信号'
    whole_header = ['日期','收盘价','开盘价','最高价','最低价','ma5','ma20']
    whole_df = df
    whole_pd_header = ['tradeDate','closePrice','openPrice','highestPrice','lowestPrice','ma5','ma20']
    line_pd_header = ['ma5','ma20']
    line_color_list = [0,2]

    line_data = {
        'title_str':title_str,
        'whole_header':whole_header,
        'whole_df':whole_df,
        'whole_pd_header':whole_pd_header,
        'line_pd_header':line_pd_header,
        'line_color_list':line_color_list
    }
    return line_data

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

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

数据

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值