【手把手教你】Python面向?qū)ο缶幊倘腴T及股票數(shù)據(jù)管理應(yīng)用實(shí)例

1 前言

一般而言,在學(xué)習(xí)或練習(xí)python的初級階段,我們在Jupyter Notebook(spyder或pycharm)上進(jìn)行逐條執(zhí)行語句和代碼,這樣可以起到交互的良好效果。但是如果要進(jìn)行大一點(diǎn)的項(xiàng)目實(shí)踐,這種毫無規(guī)劃的逐條執(zhí)行語句與指令就顯得不太適用了。為了使代碼得到最大程度的重復(fù)使用,并且各模塊之間邏輯更清晰,這時我們就有必要去學(xué)習(xí)模塊化的抽象設(shè)計(jì)了。模塊化的抽象設(shè)計(jì)基本思路是把主要框架和算法流程描述出來,再補(bǔ)充相應(yīng)的細(xì)節(jié)。面向?qū)ο缶幊蹋∣bject-Oriented Programming)是模塊化設(shè)計(jì)的重要方法之一,是Python進(jìn)階學(xué)習(xí)的必經(jīng)之路。

本文主要介紹了面向?qū)ο缶幊痰幕A(chǔ)知識,并以股票數(shù)據(jù)管理為例,介紹面向?qū)ο缶幊痰木唧w應(yīng)用。后續(xù)推文將會以更復(fù)雜的形式呈現(xiàn)面向?qū)ο缶幊淘趯?shí)現(xiàn)股票策略量化回測系統(tǒng)中的應(yīng)用,本文完整源碼將分享在知識星球上,可掃描下方二維碼加入獲取。

2 class類編程入門

01 class類的定義

類是具有相同屬性與方法的對象集合。

python定義類的語法有兩種:

class 類名稱:

class 類名稱(繼承類名稱):

def 定義屬性和方法

class后面是類名稱,小括號里的繼承類名稱表示定義的類繼承自哪一類。如果沒有繼承,則填object。比如下面定義一個股票stock類,屬性和方法先不寫,使用pass(跳過)表示。

#定義一個股票的類

classstock(object):

pass

#創(chuàng)建了stock這個類之后,便可以使用“stock()”來創(chuàng)建實(shí)例

s=stock()

s

#輸出結(jié)果:<__main__.stock?at?0x21f3f5aa160>

s是一個創(chuàng)建自stock類的實(shí)例。Python是一種動態(tài)語言,能夠動態(tài)地綁定一個實(shí)例的屬性與方法,如給s綁定一個code的代碼屬性:

s.code='000001.SZ'

s.code

#輸出結(jié)果:'000001.SZ'

同理,也可以從stock類創(chuàng)建一個s1的實(shí)例,并綁定代表價格的屬性:

s1=stock()

s1.price=15.8

s1.price

#輸出結(jié)果:?15.8

通過動態(tài)綁定可以擁有不同的屬性,但是這樣不利于項(xiàng)目的開發(fā)與維護(hù)。若希望創(chuàng)建自同一個類的實(shí)例擁有一些共同的屬性,則可以通過定義一個特殊的函數(shù)__init__方法(注意下劃線是在英文語境下輸入兩次),來綁定在創(chuàng)建實(shí)例時非填不可的屬性。

__init__()第一個參數(shù)根據(jù)慣例為self,用于代指被實(shí)例化出來的對象,參數(shù)code和price是用于給對象的屬性賦值,比如一個股票的類假設(shè)有代碼和價格兩個屬性,則定義stock的類可以寫成:

classstock(object):

def__init__(self,code,price):

self.code=code

self.price=price

python通過遵循一定的屬性和方法命名規(guī)則來達(dá)到訪問控制的效果:

以單下劃線開頭的名字代表protect(受保護(hù)的),如self._price

以雙下劃線開頭的名字代表private,如self.__init_change(self):

以雙下劃線開頭結(jié)尾的名字代表系統(tǒng)保留定義,如__init__(self)、__str__(self)、__len__(self)

創(chuàng)建實(shí)例時,__init__方法中的參數(shù),除了self不用輸入外,其他參數(shù)非填不可,否則會報(bào)錯。

在創(chuàng)建完類的名稱后,可以加入一些文字說明,簡要闡述類的主要內(nèi)容,以方便使用者快速了解該類的功能,這些注釋存儲成字符串的形式。注意python注釋的寫法,單行注釋前面加“#”,多行注釋使用三引號。

classstock(object):

'''

stock類中包含屬性code和price

'''

def__init__(self,code,price):

self.code=code

self.price=price

#查看類中的注釋內(nèi)容

print(stock.__doc__)

輸出結(jié)果:stock類中包含屬性code和price

假如將上述代碼保存為StockClass.py本地文件(可以使用Anaconda的Spyder來寫腳本程序),保存在jupyter當(dāng)前運(yùn)行路徑中,則可以通過導(dǎo)入類名的方式來使用類。

fromStockClassimportstock

print(stock.__doc__)

02 類的調(diào)用方法

類的調(diào)用方法主要有三種:(1)類的實(shí)例;(2)靜態(tài)方法(@裝飾器);(3)類的方法(clc)。

實(shí)例調(diào)用最常見,一般使用“類名.方法”。靜態(tài)方法由類調(diào)用,無默認(rèn)參數(shù)。將實(shí)例方法參數(shù)中的self去掉,然后在方法定義上方加上@staticmethod(前面加@是python函數(shù)的裝飾器方法),就成為靜態(tài)方法。類方法由類調(diào)用,采用@classmethod裝飾,至少傳入一個cls(代指類本身,類似self)參數(shù)。執(zhí)行類方法時,自動將調(diào)用該方法的類賦值給cls,建議使用類名.類方法的調(diào)用方式。

實(shí)例調(diào)用法:

classStockCode:

def__init__(self,name,code):

self.name=name

self.code=code

defget_stock(self):

return(self.name,self.code)

s=StockCode('中國平安','601318.SH')

s.get_stock()

#輸出結(jié)果:('中國平安',?'601318.SH')

靜態(tài)方法調(diào)用

classCodes(object):

@staticmethod

defget_code(s):

iflen(s)==6:

return(s+'SH')ifs.startswith('6')else(s+'SZ')

else:

print('股票代碼必須為6位數(shù)字!')

Codes.get_code('00001')

Codes.get_code('000001')

#輸出結(jié)果:股票代碼必須為6位數(shù)字!

#'000001SZ'

類調(diào)用法:

有的時候傳入的參數(shù)并不是('中國平安','601318.SH')這樣的格式,而是('中國平安-601318.SH')這樣的,那該怎么做?首先要把這個拆分,但是要使用實(shí)例方法實(shí)現(xiàn)起來很麻煩,這個時候就可以使用類方法。

classStockCode:

def__init__(self,stock,code):

self.stock=stock

self.code=code

@classmethod

#?裝飾器,立馬執(zhí)行下面的函數(shù)

defsplit(cls,sc):

#?cls是默認(rèn)的這個類的init函數(shù),sc是傳入?yún)?shù)

stock,code=map(str,sc.split('-'))

#?這里轉(zhuǎn)換成了格式化的結(jié)構(gòu)

dd?=?cls(stock,code)

#?然后執(zhí)行這個類第一個方法

returndd

s=StockCode.split(('中國平安-601318.SH'))

#查看屬性

s.stock,s.code

#輸出結(jié)果:('中國平安',?'601318.SH')

03 類的三大特征:封裝、繼承與多態(tài)

類有三個特征,分別是封裝、繼承和多態(tài)。封裝簡單理解是,將屬性與方法放在某個對象內(nèi)部,使外部無法訪問。接著之前的例子,若要打印股票代碼code和price,則可以先定義一個打印的函數(shù),對之前的類修改如下:

classstock(object):

def__init__(self,code,price):

self.code=code

self.price=price

#定義打印屬性的函數(shù)

defprint_attr(self):

print(f'股票代碼為:{self.code}')

print(f'股票價格為:{self.price}')

#使用“實(shí)例.方法”,即“實(shí)例名稱.函數(shù)名()”的方式來調(diào)用

s1=stock('000001.SZ',15.8)

s1.print_attr()

#輸出結(jié)果:?股票代碼為:000001.SZ

#股票價格為:15.8

把方法寫在對象內(nèi)部,仍無法防止對象的屬性被無關(guān)的函數(shù)意外改變或錯誤使用。為了對屬性提供更加安全的保障,可以限制它們不被外界訪問,可以在屬性變量前面加上兩個劃線表示private屬性,使其屬性只能在類的內(nèi)部進(jìn)行訪問,這時通過“實(shí)例.屬性名稱”從外部訪問就會報(bào)錯。通過priate屬性訪問限制,使對象內(nèi)部狀態(tài)得到保護(hù),但若需要獲取private屬性,可以在類的內(nèi)部編寫一個獲取屬性的方法。

classstock(object):

def__init__(self,code,price):

self.__code=code

self.__price=price

defget_attr(self):

return(self.__code,self.__price)

s1=stock('000001.SZ',15.8)

s1.get_attr()

#輸出結(jié)果:('000001.SZ',?15.8)

與從外部直接訪問屬性的設(shè)計(jì)相比,把方法封裝在類中的好處是可以賦予方法一些行為規(guī)范。比如,若stock類需要修改code值的功能,則可以在類的內(nèi)部增加一個set_code()的方法,且在定義該方法時,規(guī)定傳入的值必須為字符串類型,否則報(bào)錯。

classstock(object):

def__init__(self,code,price):

self.__code=code

self.__price=price

defget_attr(self):

return(self.__code,self.__price)

defset_code(self,codevalue):

iftype(codevalue)!=str:

return("錯誤,輸入?yún)?shù)必須為字符型")

self.__code=codevalue

s1=stock('000001.SZ',15.8)

print(s1.set_code(60000))

#重新定義是s1的代碼

s1.set_code('600000.SH')

print(s1.get_attr())

#輸出結(jié)果:

錯誤,輸入?yún)?shù)必須為字符型

('600000.SH',15.8)

繼承是充分利用已有的類功能,在其基礎(chǔ)上進(jìn)行擴(kuò)展和定義新的類。繼承概念的實(shí)現(xiàn)方式主要有2類:實(shí)現(xiàn)繼承、接口繼承。實(shí)現(xiàn)繼承是指使用基類的屬性和方法而無需額外編碼的能力。接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實(shí)現(xiàn)的能力(子類重構(gòu)父類方法)。在使用python編寫量化回測系統(tǒng)時經(jīng)常會用到class類的繼承功能。

#實(shí)現(xiàn)繼承

classStrategy(object):

defprint_info(self):

print('策略模塊的功能是生成“多”或“空”的交易信號')

classmy_strategy(Strategy):

pass

ss=my_strategy()

ss.print_info()

#輸出結(jié)果:

策略模塊的功能是生成“多”或“空”的交易信號

繼承有什么好處?最大的好處是子類獲得了父類的全部功能。由于Strategy實(shí)現(xiàn)了print_info()方法,因此,my_strategy作為它的子類,就自動擁有了print_info()方法。

#接口繼承

#這里需要先引入一個抽象模塊abc

fromabcimportABCMeta,?abstractmethod

#python中使用@表示裝飾器的意思,具體可以參照python裝飾器的相關(guān)教程

classStrategy(object):

"""Strategy是一個抽象基類"""

#?abc.ABCMeta是實(shí)現(xiàn)抽象類的一個基礎(chǔ)類

__metaclass__?=?ABCMeta

#子類必須有這個方法,否則報(bào)錯

@abstractmethod#?定義抽象方法,無需實(shí)現(xiàn)功能

defgenerate_signals(self):

"""輸入數(shù)據(jù)產(chǎn)生多空的交易信號"""

raiseNotImplementedError("應(yīng)包含方法:

generate_signals()!"

)

#定義子類

classmy_strategy2(Strategy):

defgenerate_signals(self):

pass

ss2.generate_signals()

#輸出結(jié)果:實(shí)現(xiàn)交易信號功能

ss1.generate_signals()

盡管上面的接口很簡單,但是當(dāng)為每種特定類型的策略繼承此類時,它將變得更加復(fù)雜。策略類的目標(biāo)是為投資組合模塊中提供多/空/持有信號。繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫;有了繼承,才能有多態(tài)。多態(tài)的意思是,當(dāng)子類(strategy2)與父類(Strategy)具有相同的方法(print_info())時,在實(shí)例子類的時候會自動調(diào)用子類的該方法(而不是父類的方法)。

classStrategy(object):

defprint_info(self):

print('策略模塊的功能是生成“多”或“空”的交易信號')

#繼承????????

classstrategy1(Strategy):

pass

#多態(tài)????????

classstrategy2(Strategy):

defprint_info(self):

print('這是個人的交易策略')

#實(shí)例化

strategy1().print_info()

strategy2().print_info()

#輸出結(jié)果:

策略模塊的功能是生成“多”或“空”的交易信號

這是個人的交易策略

3 class類編程在金融量化中的應(yīng)用

應(yīng)用背景:使用tushare獲取所有A股每日交易數(shù)據(jù),保存到本地?cái)?shù)據(jù)庫,同時每日更新數(shù)據(jù)庫;根據(jù)行情數(shù)據(jù)進(jìn)行可視化和簡單的策略分析與回測。由于篇幅有限,本文著重介紹股票數(shù)據(jù)管理(下載、數(shù)據(jù)更新)的面向?qū)ο缶幊虘?yīng)用實(shí)例。

#導(dǎo)入需要用到的模塊

importnumpyasnp

importpandasaspd

fromdateutil.parserimportparse

fromdatetimeimportdatetime,timedelta

#操作數(shù)據(jù)庫的第三方包,使用前先安裝pip?install?sqlalchemy

fromsqlalchemyimportcreate_engine

#tushare包設(shè)置

importtushareasts

token='輸入你在tushare上獲得的token'

pro=ts.pro_api(token)

#使用python3自帶的sqlite數(shù)據(jù)庫

#本人創(chuàng)建的數(shù)據(jù)庫地址為c:\zjy\db_stock\

file='sqlite:///c:\\zjy\\db_stock\\'

#數(shù)據(jù)庫名稱

db_name='stock_data.db'

engine?=?create_engine(file+db_name)

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

#獲取股票代碼列表????

defget_code(self):

codes?=?pro.stock_basic(list_status='L').ts_code.values

returncodes

#獲取股票交易日歷

defget_cals(self):

#獲取交易日歷

cals=pro.trade_cal(exchange='')

cals=cals[cals.is_open==1].cal_date.values

returncals

#每日行情數(shù)據(jù)

defdaily_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='')

#復(fù)權(quán)因子

df=pd.merge(df0,df1)#合并數(shù)據(jù)

exceptExceptionase:

print(code)

print(e)

returndf

#保存數(shù)據(jù)到數(shù)據(jù)庫

defsave_sql(self):

forcodeinself.codes:

data=self.daily_data(code)

data.to_sql(self.table_name,engine,

index=False,if_exists='append')

#獲取最新交易日期

defget_trade_date(self):

#獲取當(dāng)天日期時間

pass

#更新數(shù)據(jù)庫數(shù)據(jù)

defupdate_sql(self):

pass#代碼省略

#查詢數(shù)據(jù)庫信息????????????

definfo_sql(self):

pass#代碼省略

代碼運(yùn)行

#假設(shè)你將上述代碼封裝成class?Data

#保存在'C:\zjy\db_stock'目錄下的down_data.py中

importsys

#添加到當(dāng)前工作路徑

sys.path.append(r'C:\zjy\db_stock')

#導(dǎo)入py文件中的Data類

fromdownload_dataimportData

#實(shí)例類

data=Data()

#data.save_sql()?#只需運(yùn)行一次即可

data.update_sql()

data.info_sql()

輸出結(jié)果:

數(shù)據(jù)已經(jīng)是最新的!

統(tǒng)計(jì)查詢的總數(shù):7747184

數(shù)據(jù)期間:20050104——20191125

數(shù)據(jù)庫包含股票個數(shù):3724

#另外又根據(jù)畫圖需要,從數(shù)據(jù)庫中提取數(shù)據(jù)畫K線圖

#寫了一個stock_plot類保存在plot_stock.py文件中

fromplot_stockimportstock_plot

shfz=stock_plot('雙匯發(fā)展')

shfz.kline_plot()#普通K線圖

#畫修正版K線圖

shfz.kline_plot(ktype=1)

4 結(jié)語

本文主要介紹了面向?qū)ο缶幊痰幕A(chǔ)知識和股票數(shù)據(jù)管理應(yīng)用實(shí)例。

通常我們會以各種數(shù)據(jù)信息來刻畫描述一個對象,而這個對象包含某些屬性或特征。比如,將某只股票視為一個對象,那么該股票對象包含的信息可能有股票代碼、公司名稱、所屬行業(yè)、收盤價等。這些用以描述對象特征的數(shù)據(jù)信息稱為對象的屬性(Attribute);而存取屬性的函數(shù)則稱為方法(Method),是該對象與外界溝通的接口。具有相同屬性與方法的對象構(gòu)成了一個類別(Class)。換句話說,類是一種將對象抽象化而形成的概念,而對象則是類具體實(shí)現(xiàn)的例子(實(shí)例)。以對象為基礎(chǔ)的編程思想具有封裝、繼承和多態(tài)三大特征,在構(gòu)建量化交易系統(tǒng)中發(fā)揮了非常重要的作用。后續(xù)推文將介紹使用面向?qū)ο缶幊痰牧炕呗曰販y。

參考資料:廖雪峰的官方網(wǎng)站Python3教程——面向?qū)ο缶幊?/p>

關(guān)于Python金融量化

專注于分享Python在金融量化領(lǐng)域的應(yīng)用。加入知識星球,可以免費(fèi)獲取30多g的量化投資視頻資料、量化金融相關(guān)PDF資料、公眾號文章Python完整源碼、量化投資前沿分析框架,與博主直接交流、結(jié)識圈內(nèi)朋友等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容