PyQt5_pyqtgraph股票执行策略标记买卖信号

1. 将策略以 excute_strategy 为方法名,写完后保存为.py文件,放置的策略目录下(可以自行指定)

2. 运行工具,选择策略目录,选择要执行的策略,运行策略

3. 根据结果检验买卖点信号是否正确发出,同时也能检查策略代码的严密性

目录

效果

代码

使用

数据


效果

红色竖线是买入信号,绿色竖线是卖出信号

代码

需要的包、单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

 运行结果K线与买卖信号图表控件

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.color_ma_5 = (248,248,255) # 幽灵的白色
        self.color_ma_10 = (255,255,0) # 纯黄
        self.color_ma_20 = (255,0,255) # 紫红色
        self.color_ma_30 = (0,128,0) # 纯绿
        self.color_ma_60 = (30,144,255) # 道奇蓝
        self.color_up = (220,20,60)
        self.color_down = (60,179,113)
        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 = []
        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('当前显示最宽边界:左边界~右边界')

        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.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)
        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((i,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.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

        # 买卖信号
        df_buy = df.loc[df['signal']==1].copy()
        df_sell = 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.color_up,'width':2}),
                                               label=str(row['buy_price']),labelOpts={'position':0.05,'color':self.color_up,'movable':True,'fill':(self.color_up[0],self.color_up[1],self.color_up[2],30)})
            self.pw.addItem(buy_fixed_target)
            pass
        for i,row in df_sell.iterrows():
            sell_fixed_target = pg.InfiniteLine(pos=(i, 0), movable=False, angle=90,
                                               pen=pg.mkPen({'color': self.color_down, 'width': 2}),
                                               label=str(row['sell_price']),
                                               labelOpts={'position': 0.05, 'color': self.color_down, 'movable': True,
                                                          'fill': (
                                                          self.color_down[0], self.color_down[1], self.color_down[2], 30)})
            self.pw.addItem(sell_fixed_target)

        df_signal = df.loc[df['signal']!=0].copy()
        table_data = df_signal.loc[:,['tradeDate','buy_price','sell_price']].values.tolist()
        self.table.set_table_full_data(table_data)

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

使用

本文为演示工具效果,编写了一个简单的策略,根据ma60判断买卖点,策略代码如下:

def excute_strategy():
    '''
    1. 买入点:60日均线斜率为正,股价站上60日线
    2. 卖出点:
        1). 股价低于买入价80%
        2). 股价跌破60日线
        3). 60日均线斜率为负,股价距离60日线大于等于股价的10%
    :return:
    '''
    import pandas as pd
    import numpy as np
    import talib
    df = pd.read_csv('E:/temp005/600660.csv',encoding='utf-8')
    df = df.loc[df['openPrice'] > 0].copy()
    df['open'] = df['openPrice']
    df['close'] = df['closePrice']
    df['high'] = df['highestPrice']
    df['low'] = df['lowestPrice']
    df['openPrice'] = df['openPrice'] * df['accumAdjFactor']
    df['closePrice'] = df['closePrice'] * df['accumAdjFactor']
    df['highestPrice'] = df['highestPrice'] * df['accumAdjFactor']
    df['lowestPrice'] = df['lowestPrice'] * df['accumAdjFactor']

    # 计算均线
    close_list = df['closePrice']
    df['ma60'] = talib.MA(close_list, 60)

    df['signal'] = 0 # 默认信号为0
    df['buy_price'] = np.nan # 买入价格
    df['sell_price'] = np.nan # 卖出价格
    df['ma60_slope'] = df['ma60']-df['ma60'].shift(1) # ma60的斜率
    first_buy = False
    buy_yeah = False
    buy_price = None

    df.reset_index(inplace=True)
    df00 = df.copy()
    for i,row in df.iterrows():
        if (not buy_yeah) and row['ma60_slope']>0 and row['closePrice']>=row['ma60']:
            first_buy = True
            buy_yeah = True
            buy_price = row['close']
            df00.at[i,'buy_price'] = row['close']
            df00.at[i,'signal'] = 1
        if first_buy and buy_yeah and (row['close']-buy_price*0.8<=0
             or row['closePrice']<row['ma60']
             or (row['ma60_slope']<0 and row['closePrice']*0.9-row['ma60']<0)):
            buy_yeah = False
            df00.at[i,'sell_price'] = row['close']
            df00.at[i,'signal'] = -1
        pass

    line_data = {
        'title_str':'福耀玻璃',
        'whole_header':['日期','收盘价','开盘价','最高价','最低价','ma60'],
        'whole_pd_header':['tradeDate','closePrice','openPrice','highestPrice','lowestPrice','ma60'],
        'line_pd_header':['ma60'],
        'line_color_list':[4],
        'whole_df':df00
    }

    return line_data

 将策略代码保存为.py文件,保存到策略目录下,本文策略目录为:E:\temp010

运行入口工具:

if __name__ == '__main__':
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
    app = QtWidgets.QApplication(sys.argv)
    t_win = StrategeMainWidget()
    t_win.showMaximized()
    app.exec()
    pass

 选择“策略目录”,策略下拉列表中会加载该目录下的策略文件名,选择要执行的策略,运行,运行完毕后,会显示K线与买卖点信号的图表

数据

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值