目录
双线组合又称两条均线组合,是由一条时间周期较短的均线和一条时间周期较长的均线组合而成的均线分析系统。
其中时间周期较长的均线,主要用于预测和判断趋势方向。时间周期较短的均线,主要利用其与股价、长周期的均线之间的相互位置和关系,作为确定进出场的依据。为便于解说,时间周期较长的均线,我们称之为定性线;时间周期较短的均线,我们称之为定量线。
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)
'ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal'] = 1 - df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)
'ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal_reason'] = 'A' - # B.定量线上穿定性线形成黄金交叉,买入。
- # 翻译:前一日定量价格小于前一日定性价格 & 当日定量价格大于定性价格
- df.loc[(df['ma5'].shift(1)
'ma20'].shift(1)) & (df['ma5']>df['ma20']),'signal'] = 1 - df.loc[(df['ma5'].shift(1)
'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)
'ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal'] = 1 - df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)
'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']
'ma20']) & (df['ma20_rate']<=0),'signal'] = 3 - df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']
'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']
'ma5']),'signal'] = 4 - df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']
'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"
{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]
0]: - empty_end_count_list = empty_end_count_list[1:]
- if empty_end_count_list[-1]
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,'
' ,'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)
'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']
'ma20']) & (df['ma5'].shift(-1)'ma20'].shift(-1)) & (df['ma5'].shift(-2)'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)
'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']
'ma20']) & (df['ma5'].shift(-1)'ma20'].shift(-1)) & (df['ma5'].shift(-2)'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)
'ma20'].shift(1)) & (df['closePrice']>df['ma20']) & (df['ma20_direction']==1),'signal'] = 1 - df.loc[(df['close_rate']>0) & (df['closePrice'].shift(1)
'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)
'ma5'].shift(1)) & (df['closePrice']>df['ma5']),'signal'] = 1 - df.loc[(df['ma20_direction']==1) & (df['closePrice']>df['ma20']) & (df['closePrice'].shift(1)
'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']
'ma20']) & (df['ma20_rate']<=0),'signal'] = 3 - df.loc[(df['closePrice'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']
'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']
'ma5']),'signal'] = 4 - df.loc[(df['closePrice'].shift(1)>df['ma5'].shift(1)) & (df['ma5'].shift(1)>df['ma20'].shift(1)) & (df['closePrice']
'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