技术图形,诸如头肩顶、头肩底、W底、圆底等,代码逻辑实现后,要使用股票数据验证,这类主要依靠视觉判断的技术图形,获得的结果能及时在图表中展示出来,对代码的开发、校验、结果的准确度确认等能起到事半功倍的效果,本工具就是基于此需求所做的开发。
本文以“头肩底”为例进行讲解
目录
效果
计算技术图形策略代码
工具代码
工具使用
数据
前置说明:
1. 必须以“excute_strategy”为方法名
2. 一个参数,股票日数据文件路径
3. 策略代码写完后,命名,保存在“shape_recognition_strategy”文件夹中,‘shape_recognition_strategy’文件夹的创建位置在下文中会介绍
4. 将策略文件名写入主程序类StrategeMainWidget的self.strategyname_code_map对应的兼职对中
def excute_strategy(daily_file_path):
'''
名称:头肩底
处理过程:结合图形说明,从最近的日期往前找
1. (1)号位为起点位
2. 从(1)号位开始找最近最小值设为(2)号位
3. 找到(2)号位后,找最近最大值设为(3)号位
4. 判断(3)号位是否小于(1)号位:
5. (3)号位小于(1)号位,找最近最小值设为(4)号位
6. (3)号位大于等于(1)号位,将(3)号位设为(1)号位,从第二步开始,直到找到(4)号位
7. 找到(4)号位后,判断(4)号位是否小于(2)号位:
8. (4)号位小于(2)号位,找最近最大值设为(5)号位
9. (4)号位大于等于(2)号位,将(3)号位设为(1)号位,从第二步开始,直到找到(5)号位
10. 找到(5)号位后,判断(5)号位是否和(3)号位在相近的水平位置:
11. (5)号位和(3)号位在相近的水平位置,找最近最小值设为(6)号位
12. 找到(6)号位后,判断(6)号位是否大于(4)号位:
13. (6)号位大于(4)号位,找最近最大值设为(7)号位
14. (6)号位小于等于(4)号位,将(5)号位设为(1)号位,从第二步开始,直到找到(7)号位
15. 找到(7)号位后,判断(7)号位是否大于(5)号位:
16. (7)号位大于(5)号位,找图完毕。
17. (7)号位小于等于(5)号位,将(5)号位设为(1)号位,从第二步开始,直到完毕。
前置条件:计算时间区间 2021-01-01 到 2022-01-01
:param daily_file_path: 股票日数据文件路径
:return:
'''
import pandas as pd
import os
start_date_str = '2021-01-01'
end_date_str = '2022-01-01'
df = pd.read_csv(daily_file_path,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'] >= start_date_str) & (df['o_date']<=end_date_str)].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']
df.reset_index(inplace=True)
df['i_row'] = [i for i in range(len(df))]
# 开始计算
one_point = None
one_val = None
two_point = None
two_val = None
three_point = None
three_val = None
four_point = None
four_val = None
five_point = None
five_val = None
six_point = None
six_val = None
seven_point = None
seven_val = None
gap_pct = 0.005
for i in range(len(df)-1,0,-1):
if seven_point:
if six_point-seven_point < 5 or seven_val < five_val:
one_point = seven_point
one_val = seven_val
two_point = None
two_val = None
three_point = None
three_val = None
four_point = None
four_val = None
five_point = None
five_val = None
six_point = None
six_val = None
seven_point = None
seven_val = None
else:
break
if six_point:
if five_point - six_point < 3 or six_val < four_val:
one_point = five_point
one_val = five_val
two_point = six_point
two_val = six_val
three_point = None
three_val = None
four_point = None
four_val = None
five_point = None
five_val = None
six_point = None
six_val = None
seven_point = None
seven_val = None
pass
if five_point:
if four_point - five_point < 3 or five_val*(1+gap_pct)<=three_val or five_val*(1-gap_pct)>=three_val:
one_point = five_point
one_val = five_val
two_point = None
two_val = None
three_point = None
three_val = None
four_point = None
four_val = None
five_point = None
five_val = None
six_point = None
six_val = None
seven_point = None
seven_val = None
pass
if four_point:
if three_point - four_point < 3 or four_val > two_val:
one_point = three_point
one_val = three_val
two_point = four_point
two_val = four_val
three_point = None
three_val = None
four_point = None
four_val = None
five_point = None
five_val = None
six_point = None
six_val = None
seven_point = None
seven_val = None
pass
if three_point:
if two_point - three_point < 3 or three_val > one_val:
one_point = three_point
one_val = three_val
two_point = None
two_val = None
three_point = None
three_val = None
four_point = None
four_val = None
five_point = None
five_val = None
six_point = None
six_val = None
seven_point = None
seven_val = None
pass
if two_point:
if one_point - two_point < 5:
one_point = None
one_val = None
two_point = None
two_val = None
three_point = None
three_val = None
four_point = None
four_val = None
five_point = None
five_val = None
six_point = None
six_val = None
seven_point = None
seven_val = None
if one_point is None:
if one_val is None:
one_val = df.iloc[i]['highestPrice']
else:
if one_val < df.iloc[i]['highestPrice']:
one_val = df.iloc[i]['highestPrice']
else:
if one_val*(1-gap_pct) <= df.iloc[i]['highestPrice']:
pass
else:
one_point = i + 1
two_val = df.iloc[i]['lowestPrice']
if one_point and two_point is None:
if two_val is None:
two_val = df.iloc[i]['lowestPrice']
else:
if two_val > df.iloc[i]['lowestPrice']:
two_val = df.iloc[i]['lowestPrice']
else:
if two_val*(1+gap_pct) >= df.iloc[i]['lowestPrice']:
pass
else:
two_point = i+1
three_val = df.iloc[i]['highestPrice']
if one_point and two_point and three_point is None:
if three_val is None:
three_val = df.iloc[i]['highestPrice']
else:
if three_val < df.iloc[i]['highestPrice']:
three_val = df.iloc[i]['highestPrice']
else:
if three_val*(1-gap_pct) <= df.iloc[i]['highestPrice']:
pass
else:
three_point = i+1
four_val = df.iloc[i]['lowestPrice']
if one_point and two_point and three_point and four_point is None:
if four_val is None:
four_val = df.iloc[i]['lowestPrice']
else:
if four_val > df.iloc[i]['lowestPrice']:
four_val = df.iloc[i]['lowestPrice']
else:
if four_val*(1+gap_pct) >= df.iloc[i]['lowestPrice']:
pass
else:
four_point = i + 1
five_val = df.iloc[i]['highestPrice']
if one_point and two_point and three_point and four_point and five_point is None:
if five_val is None:
five_val = df.iloc[i]['highestPrice']
else:
if five_val < df.iloc[i]['highestPrice']:
five_val = df.iloc[i]['highestPrice']
else:
if five_val*(1-gap_pct) <= df.iloc[i]['highestPrice']:
pass
else:
five_point = i + 1
six_val = df.iloc[i]['lowestPrice']
if one_point and two_point and three_point and four_point and five_point and six_point is None:
if six_val is None:
six_val = df.iloc[i]['lowestPrice']
else:
if six_val > df.iloc[i]['lowestPrice']:
six_val = df.iloc[i]['lowestPrice']
else:
if six_val*(1+gap_pct) >= df.iloc[i]['lowestPrice']:
pass
else:
six_point = i+1
seven_val = df.iloc[i]['highestPrice']
if one_point and two_point and three_point and four_point and five_point and six_point and seven_point is None:
if seven_val is None:
seven_val = df.iloc[i]['highestPrice']
else:
if seven_val < df.iloc[i]['highestPrice']:
seven_val = df.iloc[i]['highestPrice']
else:
if seven_val*(1-gap_pct) <= df.iloc[i]['highestPrice']:
pass
else:
seven_point = i + 1
pass
res_duration = []
res_line = []
res_table = []
if one_point and two_point and three_point and four_point and five_point and six_point and seven_point:
res_duration.append([seven_point,one_point])
res_line.append([(five_point,five_val),(three_point,three_val)])
res_table = [
['1',df.iloc[one_point]['tradeDate'],one_val],
['2',df.iloc[two_point]['tradeDate'],two_val],
['3',df.iloc[three_point]['tradeDate'],three_val],
['4',df.iloc[four_point]['tradeDate'],four_val],
['5',df.iloc[five_point]['tradeDate'],five_val],
['6',df.iloc[six_point]['tradeDate'],six_val],
['7',df.iloc[seven_point]['tradeDate'],seven_val]
]
pass
file_name = os.path.basename(daily_file_path)
title_str = file_name.split('.')[0]
line_data = {
'title_str':title_str,
'whole_header':['日期','收','开','高','低'],
'whole_df':df,
'whole_pd_header':['tradeDate','closePrice','openPrice','highestPrice','lowestPrice'],
'start_date_str':start_date_str,
'end_date_str':end_date_str,
'res_duration':res_duration,
'res_line':res_line,
'res_table':res_table,
'temp':len(res_duration)
}
return line_data
导入需要的包
import sys,json,os,math,time,talib
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')
pygtgraph 日期横坐标控件、蜡烛图控件、分页表格控件,请查看该博文。
K线与结果显示控件
class PyQtGraphKWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_data()
self.init_ui()
def init_data(self):
# https://www.sioe.cn/yingyong/yanse-rgb-16/
# self.color_line = (30, 144, 255)
self.color_line = (255, 255, 0)
# 0 幽灵的白色; 1 纯黄; 2 紫红色; 3 纯绿; 4 道奇蓝
self.color_list = [(248, 248, 255), (255, 255, 0), (255, 0, 255), (0, 128, 0), (30, 144, 255)]
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.res_duration = None
self.res_line = None
self.res_table = None
pass
def init_ui(self):
self.whole_duration_label = QtWidgets.QLabel('左边界~右边界')
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)
layout_right = QtWidgets.QVBoxLayout()
layout_right.addWidget(self.title_label)
layout_right.addWidget(self.whole_duration_label)
layout_right.addWidget(self.pw)
self.setLayout(layout_right)
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']
res_duration = data['res_duration']
res_line = data['res_line']
res_table = data['res_table']
self.whole_header = whole_header
self.whole_df = whole_df
self.whole_pd_header = whole_pd_header
self.res_duration = res_duration
self.res_line = res_line
self.res_table = res_table
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()
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)
# 标记技术图形 start
if len(self.res_duration)>0:
for item in self.res_duration:
signal_fiexed_target = pg.LinearRegionItem([item[0], item[1]],
movable=False, brush=(
self.color_line[0], self.color_line[1], self.color_line[2], 50))
self.pw.addItem(signal_fiexed_target)
pass
if len(self.res_line)>0:
for item in self.res_line:
angle = math.atan2((item[1][1] - item[0][1]),
(item[1][0] - item[0][0]))
theta = angle * (180 / math.pi)
signal_fiexed_target = pg.InfiniteLine(pos=item[0], movable=False, angle=theta,
pen=pg.mkPen({'color': self.color_line, 'width': 1}))
self.pw.addItem(signal_fiexed_target)
pass
# 标记技术图形 end
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
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.init_data()
self.init_ui()
self.register_event()
pass
def init_data(self):
self.pre_output_dir = './'
self.results_output_dir = './k_recognition_output/'
self.please_select_str: str = '--请选择--'
self.k_strategy_path: str = '../shape_recognition_strategy/'
self.stratege_run_start_time = None
self.stratege_start = False
self.current_stratege_py_str: str = ''
self.dailydata_path:str = ''
self.k_strategy_map: Dict[str,Any] = {
'转势技术图形':['头肩顶','头肩底','复合头肩模式','圆底(蝶形底)','圆顶(蝶形顶)','双底(W底)','双顶(M顶)','三重顶和三重底','潜伏底','V形反转(V形底)','倒置V形反转(尖顶)','缺口','底部岛形反转','顶部岛形反转'],
'整理技术图形':['上升三角形','下降三角形','底部三角形','扩散三角形(喇叭形)','收敛三角形','菱形(钻石形)','上升旗形(下飘旗形)','下降旗形(上飘旗形)','上升楔形','下降楔形','矩形(箱体)']
}
self.strategyname_code_map:Dict[str,str] = {
'头肩顶': '',
'头肩底': '',
'复合头肩模式': '000',
'圆底(蝶形底)': '',
'圆顶(蝶形顶)': '',
'双底(W底)': '',
'双顶(M顶)': '',
'三重顶和三重底': '',
'潜伏底': '',
'V形反转(V形底)': '',
'倒置V形反转(尖顶)': '',
'缺口': '',
'底部岛形反转': '',
'顶部岛形反转': '',
'上升三角形': '',
'下降三角形': '',
'底部三角形': '',
'扩散三角形(喇叭形)': '',
'收敛三角形': '',
'菱形(钻石形)': '',
'上升旗形(下飘旗形)': '',
'下降旗形(上飘旗形)': '',
'上升楔形': '',
'下降楔形': '',
'矩形(箱体)': ''
}
self.table_header: List = ['点位','日期','值']
pass
def init_ui(self):
self.setWindowTitle('股票技术图形识别工具')
tip_0 = QtWidgets.QLabel('股票日数据文件:')
self.dailydata_filepath_lineedit = QtWidgets.QLineEdit()
self.dailydata_filepath_lineedit.setReadOnly(True)
dailydata_choice_btn = QtWidgets.QPushButton('选择文件')
dailydata_choice_btn.clicked.connect(self.dailydata_choice_btn_clicked)
layout_top = QtWidgets.QHBoxLayout()
layout_top.addWidget(tip_0)
layout_top.addWidget(self.dailydata_filepath_lineedit)
layout_top.addWidget(dailydata_choice_btn)
layout_top.addStretch(1)
tip_1 = QtWidgets.QLabel('类别:')
self.type_combox = QtWidgets.QComboBox()
self.type_combox.addItem(self.please_select_str)
self.type_combox.addItems(list(self.k_strategy_map.keys()))
self.type_combox.currentIndexChanged.connect(self.type_combox_currentIndexChanged)
tip_2 = QtWidgets.QLabel('技术图形:')
self.shape_combox = QtWidgets.QComboBox()
self.shape_combox.addItem(self.please_select_str)
self.shape_combox.currentIndexChanged.connect(self.shape_combox_currentIndexChanged)
self.run_btn = QtWidgets.QPushButton('运行')
self.run_btn.clicked.connect(self.run_btn_clicked)
self.time_label = QtWidgets.QLabel('')
layout_combox = QtWidgets.QFormLayout()
layout_combox.addRow(tip_1,self.type_combox)
layout_combox.addRow(tip_2,self.shape_combox)
layout_combox.addRow(self.run_btn,self.time_label)
self.code_textedit = QtWidgets.QTextEdit()
self.code_textedit.setReadOnly(True)
self.results_table = PageTableWidget()
self.results_table.set_table_init_data({'headers':self.table_header})
layout_left = QtWidgets.QVBoxLayout()
layout_left.addLayout(layout_combox)
layout_left.addWidget(self.code_textedit)
layout_left.addWidget(self.results_table)
self.line_widget = PyQtGraphKWidget()
layout_down = QtWidgets.QHBoxLayout()
layout_down.addLayout(layout_left,1)
layout_down.addWidget(self.line_widget,2)
layout = QtWidgets.QVBoxLayout()
layout.addLayout(layout_top)
layout.addLayout(layout_down)
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 dailydata_choice_btn_clicked(self):
path, _ = QtWidgets.QFileDialog.getOpenFileName(
self,
'打开股票日数据所在文件',
self.pre_output_dir
)
if not path:
return
self.dailydata_filepath_lineedit.setText(path)
pass
def type_combox_currentIndexChanged(self,cur_i:int):
cur_txt = self.type_combox.currentText()
if not cur_txt or cur_txt == self.please_select_str:
return
shape_name_list = self.k_strategy_map[cur_txt]
self.shape_combox.clear()
self.shape_combox.addItem(self.please_select_str)
self.shape_combox.addItems(shape_name_list)
pass
def shape_combox_currentIndexChanged(self,cur_i:int):
cur_txt = self.shape_combox.currentText()
if not cur_txt or cur_txt == self.please_select_str:
return
cur_strategy_name = self.strategyname_code_map[cur_txt]
if cur_strategy_name == '000':
QtWidgets.QMessageBox.information(
self,
'提示',
'代码实现太复杂,还是靠肉眼吧。。。',
QtWidgets.QMessageBox.Yes
)
return
if len(cur_strategy_name)<=0:
QtWidgets.QMessageBox.information(
self,
'提示',
'该K线形态的策略还没上线',
QtWidgets.QMessageBox.Yes
)
return
strategy_file_path = self.k_strategy_path + cur_strategy_name
with open(strategy_file_path,'r',encoding='utf-8') as fr:
py_str = fr.read()
if len(py_str)<=20:
QtWidgets.QMessageBox.information(
self,
'提示',
'策略文件代码为空',
QtWidgets.QMessageBox.Yes
)
return
self.code_textedit.setPlainText(py_str)
pass
def run_btn_clicked(self):
'''运行按钮'''
# 检查股票日数据文件夹
dailydata_filepath = self.dailydata_filepath_lineedit.text()
dailydata_filepath = dailydata_filepath.strip()
if len(dailydata_filepath)<=0:
QtWidgets.QMessageBox.information(
self,
'提示',
'请选择股票日数据文件',
QtWidgets.QMessageBox.Yes
)
return
py_str = self.code_textedit.toPlainText()
if len(py_str)<20:
QtWidgets.QMessageBox.information(
self,
'提示',
'请选择要执行的策略',
QtWidgets.QMessageBox.Yes
)
return
self.current_stratege_py_str = py_str
self.run_btn.setDisabled(True)
self.shape_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
pre_data = {
'py_str':py_str,
'dailydata_filepath':dailydata_filepath
}
self.thread_run = Thread(
target=self.running_run_thread,
args=(pre_data,)
)
self.thread_run.start()
self.thread_time = Thread(
target=self.running_time_thread
)
self.thread_time.start()
pass
def running_run_thread(self,data:Dict[str,Any]):
'''执行代码线程'''
py_str = data['py_str']
dailydata_filepath = data['dailydata_filepath']
namespace = {}
fun_stragegy = compile(py_str,'','exec')
exec(fun_stragegy,namespace)
ret = namespace['excute_strategy'](dailydata_filepath)
self.signal_runcode.emit(ret)
pass
def thread_run_excuted(self,data:Dict):
'''策略代码执行返回结果'''
self.run_btn.setDisabled(False)
self.shape_combox.setDisabled(False)
# 保存结果文件
self.results_table.set_table_full_data(data['res_table'])
self.line_widget.set_data(data)
self.thread_run = None
self.thread_time = None
self.stratege_start = False
QtWidgets.QMessageBox.information(
self,
'提示',
'当前策略运行完毕',
QtWidgets.QMessageBox.Yes
)
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_time_excuted(self,data:Dict):
'''计时返回结果'''
res = data['res']
self.time_label.setText(f"{res}s")
pass
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
if self.thread_time:
self.thread_time = None
if self.thread_run:
self.thread_run = None
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
1. 在入口代码对应的py文件的上一级目录下创建 shape_recognition_strategy 文件夹,后续K线技术图形判别的策略代码都保存在这个文件夹下
链接:https://pan.baidu.com/s/1acghH4GQyu_OaOUmEjPdHw
提取码:6toe