【大牛亲手教你】Python面向对象编程入门及股票数据管理应用实例

【文章来源】
知乎:CuteHand
参考:https://zhuanlan.zhihu.com/p/93660612

1 前言

一般而言,在学习或练习python的初级阶段,我们在Jupyter Notebook(spyder或pycharm)上进行逐条执行语句和代码,这样可以起到交互的良好效果。但是如果要进行大一点的项目实践,这种毫无规划的逐条执行语句与指令就显得不太适用了。为了使代码得到最大程度的重复使用,并且各模块之间逻辑更清晰,这时我们就有必要去学习模块化的抽象设计了。模块化的抽象设计基本思路是把主要框架和算法流程描述出来,再补充相应的细节。面向对象编程(Object-Oriented Programming)是模块化设计的重要方法之一,是Python进阶学习的必经之路。

本文主要介绍了面向对象编程的基础知识,并以股票数据管理为例,介绍面向对象编程的具体应用。后续推文将会以更复杂的形式呈现面向对象编程在实现股票策略量化回测系统中的应用,本文完整源码将分享在知识星球上(看最后)
image.png

2 class类编程入门

01class类的定义

类是具有相同属性与方法的对象集合。

python定义类的语法有两种:

class 类名称:

class 类名称(继承类名称): def 定义属性和方法

class后面是类名称,小括号里的继承类名称表示定义的类继承自哪一类。如果没有继承,则填object。比如下面定义一个股票stock类,属性和方法先不写,使用pass(跳过)表示。

#定义一个股票的类
class stock(object):
    pass
#创建了stock这个类之后,便可以使用“stock()”来创建实例
s=stock()
s
#输出结果:<__main__.stock at 0x21f3f5aa160>

s是一个创建自stock类的实例。Python是一种动态语言,能够动态地绑定一个实例的属性与方法,如给s绑定一个code的代码属性:

s.code='000001.SZ'
s.code
#输出结果:'000001.SZ' 

同理,也可以从stock类创建一个s1的实例,并绑定代表价格的属性:

s1=stock()
s1.price=15.8
s1.price
#输出结果: 15.8

通过动态绑定可以拥有不同的属性,但是这样不利于项目的开发与维护。若希望创建自同一个类的实例拥有一些共同的属性,则可以通过定义一个特殊的函数init方法(注意下划线是在英文语境下输入两次),来绑定在创建实例时非填不可的属性。

init()第一个参数根据惯例为self,用于代指被实例化出来的对象,参数code和price是用于给对象的属性赋值,比如一个股票的类假设有代码和价格两个属性,则定义stock的类可以写成:

class stock(object):
    def __init__(self,code,price):
        self.code=code
        self.price=price

python通过遵循一定的属性和方法命名规则来达到访问控制的效果:

1..以单下划线开头的名字代表protect(受保护的),如self._price
2..以双下划线开头的名字代表private,如self.__init_change(self):
3..以双下划线开头结尾的名字代表系统保留定义,如init(self)、str(self)、len(self)
创建实例时,init方法中的参数,除了self不用输入外,其他参数非填不可,否则会报错。

在创建完类的名称后,可以加入一些文字说明,简要阐述类的主要内容,以方便使用者快速了解该类的功能,这些注释存储成字符串的形式。注意python注释的写法,单行注释前面加“#”,多行注释使用三引号。

class stock(object):
    '''
    stock类中包含属性code和price
    '''
    def __init__(self,code,price):
        self.code=code
        self.price=price
#查看类中的注释内容
print(stock.__doc__)
输出结果:stock类中包含属性code和price

假如将上述代码保存为StockClass.py本地文件(可以使用Anaconda的Spyder来写脚本程序),保存在jupyter当前运行路径中,则可以通过导入类名的方式来使用类。

from StockClass import stock
print(stock.__doc__)

02类的调用方法

类的调用方法主要有三种:(1)类的实例;(2)静态方法(@装饰器);(3)类的方法(clc)。

实例调用最常见,一般使用“类名.方法”。静态方法由类调用,无默认参数。将实例方法参数中的self去掉,然后在方法定义上方加上@staticmethod(前面加@是python函数的装饰器方法),就成为静态方法。类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。执行类方法时,自动将调用该方法的类赋值给cls,建议使用类名.类方法的调用方式。

实例调用法:

class StockCode:
    def __init__(self,name,code):
        self.name=name
        self.code=code
    def get_stock(self):
        return (self.name,self.code)
s=StockCode('中国平安','601318.SH')
s.get_stock()
#输出结果:('中国平安', '601318.SH')

静态方法调用

class Codes(object):
    @staticmethod
    def get_code(s):
        if len(s)==6:
            return (s+'SH') if s.startswith('6') else (s+'SZ')
        else:
            print('股票代码必须为6位数字!')
Codes.get_code('00001')
Codes.get_code('000001')
#输出结果:股票代码必须为6位数字!
#'000001SZ'

类调用法:

有的时候传入的参数并不是('中国平安','601318.SH')这样的格式,而是('中国平安-601318.SH')这样的,那该怎么做?首先要把这个拆分,但是要使用实例方法实现起来很麻烦,这个时候就可以使用类方法。

class StockCode:
    def __init__(self,stock,code):
        self.stock=stock
        self.code=code
    @classmethod
    # 装饰器,立马执行下面的函数
    def split(cls,sc):
        # cls是默认的这个类的init函数,sc是传入参数
        stock,code=map(str,sc.split('-'))
        # 这里转换成了格式化的结构
        dd = cls(stock,code)
        # 然后执行这个类第一个方法
        return dd
s=StockCode.split(('中国平安-601318.SH'))
#查看属性
s.stock,s.code
#输出结果:('中国平安', '601318.SH')

03类的三大特征:封装、继承与多态

类有三个特征,分别是封装、继承和多态。封装简单理解是,将属性与方法放在某个对象内部,使外部无法访问。接着之前的例子,若要打印股票代码code和price,则可以先定义一个打印的函数,对之前的类修改如下:

class stock(object):
    def __init__(self,code,price):
        self.code=code
        self.price=price
    #定义打印属性的函数
    def print_attr(self):
        print(f'股票代码为:{self.code}')
        print(f'股票价格为:{self.price}')
#使用“实例.方法”,即“实例名称.函数名()”的方式来调用
s1=stock('000001.SZ',15.8)
s1.print_attr()
#输出结果: 股票代码为:000001.SZ
#股票价格为:15.8

把方法写在对象内部,仍无法防止对象的属性被无关的函数意外改变或错误使用。为了对属性提供更加安全的保障,可以限制它们不被外界访问,可以在属性变量前面加上两个划线表示private属性,使其属性只能在类的内部进行访问,这时通过“实例.属性名称”从外部访问就会报错。通过priate属性访问限制,使对象内部状态得到保护,但若需要获取private属性,可以在类的内部编写一个获取属性的方法。

class stock(object):
    def __init__(self,code,price):
        self.__code=code
        self.__price=price
    def get_attr(self):
        return(self.__code,self.__price)
s1=stock('000001.SZ',15.8)
s1.get_attr()
#输出结果:('000001.SZ', 15.8)

与从外部直接访问属性的设计相比,把方法封装在类中的好处是可以赋予方法一些行为规范。比如,若stock类需要修改code值的功能,则可以在类的内部增加一个set_code()的方法,且在定义该方法时,规定传入的值必须为字符串类型,否则报错。

class stock(object):
    def __init__(self,code,price):
        self.__code=code
        self.__price=price
    def get_attr(self):
        return(self.__code,self.__price)
    def set_code(self,codevalue):
        if type(codevalue)!=str:
            return("错误,输入参数必须为字符型")
        self.__code=codevalue
s1=stock('000001.SZ',15.8)
print(s1.set_code(60000))
#重新定义是s1的代码
s1.set_code('600000.SH')
print(s1.get_attr())
#输出结果:
错误,输入参数必须为字符型
    ('600000.SH', 15.8)

继承是充分利用已有的类功能,在其基础上进行扩展和定义新的类。继承概念的实现方式主要有2类:实现继承、接口继承。实现继承是指使用基类的属性和方法而无需额外编码的能力。接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构父类方法)。在使用python编写量化回测系统时经常会用到class类的继承功能。

#实现继承
class Strategy(object):
    def print_info(self):
        print('策略模块的功能是生成“多”或“空”的交易信号')
class my_strategy(Strategy):
    pass
ss=my_strategy()
ss.print_info()
#输出结果:
策略模块的功能是生成“多”或“空”的交易信号

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Strategy实现了print_info()方法,因此,my_strategy作为它的子类,就自动拥有了print_info()方法。

#接口继承
#这里需要先引入一个抽象模块abc
from abc import ABCMeta, abstractmethod
#python中使用@表示装饰器的意思,具体可以参照python装饰器的相关教程
class Strategy(object):
    """Strategy是一个抽象基类"""
    # abc.ABCMeta是实现抽象类的一个基础类
    __metaclass__ = ABCMeta
    #子类必须有这个方法,否则报错
    @abstractmethod  # 定义抽象方法,无需实现功能
    def generate_signals(self):
        """输入数据产生多空的交易信号"""
        raise NotImplementedError("应包含方法:
                        generate_signals()!")

#定义子类
class my_strategy2(Strategy):
    def generate_signals(self):
        pass

ss2.generate_signals()
#输出结果:实现交易信号功能

ss1.generate_signals()

尽管上面的接口很简单,但是当为每种特定类型的策略继承此类时,它将变得更加复杂。策略类的目标是为投资组合模块中提供多/空/持有信号。继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写;有了继承,才能有多态。多态的意思是,当子类(strategy2)与父类(Strategy)具有相同的方法(print_info())时,在实例子类的时候会自动调用子类的该方法(而不是父类的方法)。

class Strategy(object):
    def print_info(self):
        print('策略模块的功能是生成“多”或“空”的交易信号')
#继承        
class strategy1(Strategy):
    pass
#多态        
class strategy2(Strategy):
    def print_info(self):
        print('这是个人的交易策略')
#实例化
strategy1().print_info()
strategy2().print_info()
#输出结果:
策略模块的功能是生成“多”或“空”的交易信号
    这是个人的交易策略

3 class类编程在金融量化中的应用

应用背景:使用tushare获取所有A股每日交易数据,保存到本地数据库,同时每日更新数据库;根据行情数据进行可视化和简单的策略分析与回测。由于篇幅有限,本文着重介绍股票数据管理(下载、数据更新)的面向对象编程应用实例。

#导入需要用到的模块
import numpy as np
import pandas as pd
from dateutil.parser import parse
from datetime import datetime,timedelta
#操作数据库的第三方包,使用前先安装pip install sqlalchemy
from sqlalchemy import create_engine
#tushare包设置
import tushare as ts
token='输入你在tushare上获得的token'
pro=ts.pro_api(token)

#使用python3自带的sqlite数据库
#本人创建的数据库地址为c:\zjy\db_stock\
file='sqlite:///c:\\zjy\\db_stock\\'
#数据库名称
db_name='stock_data.db'
engine = create_engine(file+db_name)
class Data(object):
    def __init__(self,
                 start='20050101',
                 end='20191115',
                 table_name='daily_data'):
        self.start=start
        self.end=end
        self.table_name=table_name
        self.codes=self.get_code()
        self.cals=self.get_cals()       
    #获取股票代码列表    
    def get_code(self):
        codes = pro.stock_basic(list_status='L').ts_code.values
        return codes
    #获取股票交易日历
    def get_cals(self):
        #获取交易日历
        cals=pro.trade_cal(exchange='')
        cals=cals[cals.is_open==1].cal_date.values
        return cals
    #每日行情数据
    def daily_data(self,code):
        try:
            df0=pro.daily(ts_code=code,start_date=self.start,
                end_date=self.end)            
            df1=pro.adj_factor(ts_code=code,trade_date='') 
            #复权因子
            df=pd.merge(df0,df1)  #合并数据
        except Exception as e:
            print(code)
            print(e)
        return df
    #保存数据到数据库
    def save_sql(self):
        for code in self.codes:
            data=self.daily_data(code)
            data.to_sql(self.table_name,engine,
                 index=False,if_exists='append')
    #获取最新交易日期
    def get_trade_date(self):
        #获取当天日期时间
        pass
    #更新数据库数据
    def update_sql(self):
        pass #代码省略
    #查询数据库信息            
    def info_sql(self):
        pass #代码省略

代码运行

#假设你将上述代码封装成class Data
#保存在'C:\zjy\db_stock'目录下的down_data.py中
import sys
#添加到当前工作路径
sys.path.append(r'C:\zjy\db_stock')

#导入py文件中的Data类
from download_data import Data
#实例类
data=Data()
#data.save_sql() #只需运行一次即可
data.update_sql()      
data.info_sql() 

输出结果:数据已经是最新的!统计查询的总数:7747184数据期间:20050104——20191125数据库包含股票个数:3724

#另外又根据画图需要,从数据库中提取数据画K线图
#写了一个stock_plot类保存在plot_stock.py文件中
from plot_stock import stock_plot
shfz=stock_plot('双汇发展')
shfz.kline_plot() #普通K线图
image.png

画修正版K线图

shfz.kline_plot(ktype=1)


image.png

4 结语

本文主要介绍了面向对象编程的基础知识和股票数据管理应用实例。

通常我们会以各种数据信息来刻画描述一个对象,而这个对象包含某些属性或特征。比如,将某只股票视为一个对象,那么该股票对象包含的信息可能有股票代码、公司名称、所属行业、收盘价等。这些用以描述对象特征的数据信息称为对象的属性(Attribute);而存取属性的函数则称为方法(Method),是该对象与外界沟通的接口。具有相同属性与方法的对象构成了一个类别(Class)。换句话说,类是一种将对象抽象化而形成的概念,而对象则是类具体实现的例子(实例)。以对象为基础的编程思想具有封装、继承和多态三大特征,在构建量化交易系统中发挥了非常重要的作用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容