自己做量化交易软件(11)通通量化AI框架的核心–框架结构
既然我说了要开源通通量化AI框架,就算大家得到了代码,也不清楚怎么去改进和修改。因此我在最后完善框架的空闲,逐步介绍框架的核心设计思想,大家也会明白要改进功能,去那里修改。
一、程序的目录结构
通通量化有3个目录,都需要放在根目录中,例如:D盘跟目录.
d:\py36 py36的运行和开发系统
d:\ttdata 各种tt数据
d:\tt tt模块和主运行程序.
其中tt目录中存放用户回测程序和画面定义程序
d:\tt\view 用户自定义窗口和画面模板
d:\tt\user 用户各种程序,包含回测程序
spyder软件开发命令,通通量化AI框架主程序tt_main.py
d:\py36\py36.bat #python代码开发调试编辑环境。发送到桌面快捷。
程序能够独立运行后,可独立运行.把tt.exe发送到桌面快捷。
d:\tt\tt.exe
第一次使用,要运行一次py36,如果运行成功.就可以运行tt.exe程序了
二、程序的功能结构
首先,程序设计分为面对过程和面对对象。
面对过程讲究自顶向下,逐步求精的设计思想。因此程序设计过程非常简单,通通量化软件设计初期采用了这个设计思想。因此很多功能、模块、函数都是独立存在于不同py文件中。对于这些模块函数的引用,设计初期不懂import的完全作用。使用了这样的用法from HP_global import * ,后来发现这样会出现同名函数的混乱。又改为这样使用import HP_global as g,最后明白了import命令的真实用法。对于通通量化软件框架中,两种形式都存在,因为重新修改写代码需要时间,我后面会逐步进行规范统一。
对于这个文件HP_formula.py的使用,大家必须这样写:from HP_formula import *
这个文件包含了很多通达信公式的基础函数,例如MA(),HHV()等等,如果不这样写,通达信股票指标公式移植写起来很麻烦。
from HP_formula import *
#用户自定义指标绘图
def draw_UFN(ax1,mydf):
CLOSE=mydf['close']
LOW=mydf['low']
HIGH=mydf['high']
OPEN=mydf['open']
VOL=mydf['volume']
C=mydf['close']
L=mydf['low']
H=mydf['high']
O=mydf['open']
V=mydf['volume']
#df格式必须使用tushare数据格式,
df=mydf.copy()
#KDJ python随机指标
def KDJ(N=9, M1=3, M2=3):
RSV = (CLOSE - LLV(LOW, N)) / (HHV(HIGH, N) - LLV(LOW, N)) * 100
K = SMA(RSV,M1,1)
D = SMA(K,M2,1)
J = 3*K-2*D
return K, D, J
#使用KDJ指标,返回K,D,J序列。
K,D,J=KDJ(9,3,3)
df = df.join(pd.Series( K,name='K'))
df = df.join(pd.Series( D,name='D'))
df = df.join(pd.Series( J,name='J'))
上面看到,KDJ指标跟通达信指标写法相近,否则要使用别名,例如import HP_formula as gs,指标公是就要写D=gs.SMA(K,M2,1),这样使用就很麻烦了。其他HP的py文件,大家都要加别名,别名自己任意取名。
通通量化软件其中一个定义全局变量的HP_global.py文件,只能固定这样写:import HP_global as g,否则你的回测程序就像断了线的风筝,飞跑了。
#HP_global.py 的部分内容如下:
#软件环境设置
global G_name #软件名称
global G_title #软件标题
global G_winW #默认主窗口宽度
global G_winH #默认主窗口高度
global G_ver #软件版本号
global G_user #用户名
global G_ico #软件图标
global G_login #用户登录标记
global G_root #窗口根句柄
global G_canvas #绘图canvas
global G_figure #绘图figure
global G_plot #绘图plot
#通通框架控件
global G_frame
global G_mainform
global tabControl
global tab1
global tab2
global tab3
global tab4
global tab5
global tab6
global tab7
global tab8
global tab9
global plotPage
global UserFrame
global UserPlot
global ttree
global scrollBarA
global scrollBarB
上面给出了HP_global.py部分全局变量的定义。
#用户程序开始都必须写下面一条命令,这样用户程序就有接管通通量化软件的全部权限和控制改变通通量化软件的能力。
import HP_global as g
其中控件结构顺序 g.G_root -〉 g.tabControl -〉g.tab1
如果用户想在主框架顶级弹出窗口,可以使用g.G_root,如果想增加新的g.tab7,就可以引用 g.tabControl.
下面是一个用户自定义的显示2个K线图的模板程序。程序存放位置:tt/view/用户新窗口2.py
# -*- coding: utf-8 -*-
# 用户自定义显示2个K线图的模板
#用户新窗口2.py
import tkinter as tk
import HP_global as g
import HP_data as hp
from HP_view import * #菜单栏对应的各个子页面
#系统设定了g.tab1--g.tab9,系统只是用了g.tab1--g.tab6
#控件结构 g.G_root -〉 g.tabControl -〉g.tab1
#增加tab,用add()
#删除tab,用forget()
#当然用户可以设置更多的tab窗口。必须使用全局变量g.变量名
#重复建立新tab窗会出错,所以我们先检测是否None,不是就先做删除旧tab窗口。
if g.tab7!=None:
g.tabControl.forget(g.tab7)
g.tab7=None
#用户自建新画面
g.tab7 = tk.Frame(g.tabControl)
g.tabControl.add(g.tab7, text='用户新窗口')
#新设全局变量
g.frame_a=tk.Frame(g.tab7, bg = 'blue',width = 500, height = 320)
g.frame_a.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
g.frame_b=tk.Frame(g.tab7, bg = 'red',width = 500, height = 320)
g.frame_b.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
#读取数据源,并显示K线图
df1=hp.get_k_data('600088',ktype='D',start='2018-01-01',end='2018-08-31',index=False,autype='qfq')
df2=hp.tstojq(df1)
g.plot_a = plotFrame(g.frame_a,df2,'600088','KDJ')
g.plot_a.pack(fill=X)
df3=hp.get_k_data('600619',ktype='D',start='2018-01-01',end='2018-08-31',index=False,autype='qfq')
df4=hp.tstojq(df3)
g.plot_a = plotFrame(g.frame_b,df4,'600619','MACD')
g.plot_a.pack(fill=X)
上面用户自定义画面的运行结果如下图:
一些绘图等功能模块我放到了HP_tk.py文件中了。使用代码如下,这是早期设计的影子,以后要修改。
from HP_tk import *
myed=myedit(self.tab4) #在tab4显示程序代码编辑器
ssq=hp.get_ssq() #获取双色球彩票数据
mygrid(self.tab6,ssq) #在tab6,显示双色球数据表格
在后面的通通量化AI框架设计中,首先我被全局变量global被捆住了,我以为全局变量global和Visual FoxPro 9.0的全局变量public的使用一样的,一次定义,全部程序和子程序,以及装载的新py文件,都是有效的。实际上在from HP_global import * 这样使用时,全局变量都是未定义状态。因此我想到了用类做为全局变量传递的手段。
说到类,就要讲面对对象设计思路。
面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
面对对象设计核心就是类定义,主类中定义的数据,self.abc,在所有下面的子类中都有效,类的子孙都能获取到父类的self格式的变量。我就是用这样的笨办法来传递全局变量G_root,这可是全部程序的根,子程序获取不到G_root,子程序要输出的信息和画面到哪里去显示?
又有点跑题。
面对对象有很多优点,因此是现在的主流设计方法, 我依照别人的代码,把window下面的frame进行了封装,想使用什么画面就去调用。主画面封装类在HP_view.py文件中。
tt_mian.py的主程序就使用了主画面类。
# -*- coding: utf-8 -*-
"""
#功能:通通股票分析软件框架主程序
#版本:Ver1.00
#设计人:独狼荷蒲
#电话:18578755056
#QQ:2775205
#百度:荷蒲指标
#开始设计日期: 2018-07-08
#公众号:独狼股票分析
#使用者请同意最后<版权声明>
#最后修改日期:2018年9月14日
#主程序:HP_main.py
"""
import HP_global as g
import HP_set
from HP_Login import *
from HP_MainPage import *
from HP_tk import *
from PIL import Image, ImageTk
from tkinter import *
import threading
from HP_robot import *
if __name__ == "__main__":
HP_init()
exec(g.G_tk)
exec(g.G_tk1)
exec(g.G_tk2)
from PIL import Image, ImageTk
g.G_root = tk.Tk()
g.G_root.title(g.G_title)
g.G_login=True
window1 = tk.IntVar(g.G_root, value=0)
window1.set(1)
photo=ImageTk.PhotoImage(Image.open('tt_welcome2.jpg'))
w=photo.width()
h=photo.height()
w1 = myWindow(g.G_root,g.G_title,w,h)
showIco(w1.top,g.G_ico)
label=tk.Label(w1.top,image=photo)
label.grid(row=0, column=0, padx=1, pady=1, sticky=tk.E)
setCenter(w1.top,photo.width()+4,photo.height()+4)
reSizable(w1.top,False, False)
def fun_timer():
global timer
w1.destroy()
timer.cancel()
timer = threading.Timer(15, fun_timer)
timer.start()
window1.set(0)
g.ttext=g.ttext+'通通AI量化系统开始启动!\n'
robot_init()
MainPage(g.G_root)
#主程序结束
HP_view.py文件是复杂的类定义文件,我经过了多次修改,已经不堪入目了,读者有兴趣去看源代码,我只给出点类定义的开始部分。
class gridFrame(Frame): # 继承Frame类
def __init__(self, master=None):
Frame.__init__(self, master)
self.root = master #定义内部变量root
self.parenta = master
#...不尽言中...
class plotFrame(Frame): # 继承Frame类
def __init__(self, master,df,stn,index):
Frame.__init__(self, master)
#...不尽言中...
class MainFrame(Frame): # 继承Frame类
def __init__(self, master=None):
Frame.__init__(self, master)
#...不尽言中...
总而言之,言而总之。你想在window中显示什么画面,就引用上面的Frame类画面。
尽管我使用了大量类来写通通量化AI框架的代码,不过这些类都是一个局部小控件,小功能而已。
通通量化AI框架是以面对过程设计为主体思想,这就是我说的“积木”堆积思想。但是每个小积木,又是采用面对对象设计思想,进行封装。在你的改进代码,回测代码使用中,到底使用面对对象?还是是面对过程?你想怎么用,就怎么用。