怎樣制作網(wǎng)頁(yè)北京網(wǎng)站建設(shè)優(yōu)化
文章目錄
- 7.1 pandas計(jì)算策略評(píng)估指標(biāo)
- 數(shù)據(jù)準(zhǔn)備
- 凈值曲線
- 年化收益率
- 波動(dòng)率
- 最大回撤
- Alpha系數(shù)和Beta系數(shù)
- 夏普比率
- 信息比率
- 7.2 聚寬平臺(tái)量化回測(cè)實(shí)踐
- 平臺(tái)介紹
- 策略實(shí)現(xiàn)
- 7.3 Backtrader平臺(tái)量化回測(cè)實(shí)踐
- Backtrader簡(jiǎn)介
- Backtrader量化回測(cè)框架實(shí)踐
- 7.4 BigQuant量化框架實(shí)戰(zhàn)
- BigQuant簡(jiǎn)介
- 策略實(shí)現(xiàn)
- 7.5 手寫回測(cè)代碼 - 手把手實(shí)現(xiàn)一個(gè)傻瓜式量化回測(cè)框架
- 為什么?
- 為什么要開(kāi)發(fā)?
- 為什么叫傻瓜式?
- 解決什么痛點(diǎn)?
- 怎么用?
- 引入StupidHead.py
- 編寫策略
- 回測(cè)策略
- 參數(shù)優(yōu)化
- 框架原理和細(xì)節(jié)
- more
7.1 pandas計(jì)算策略評(píng)估指標(biāo)
本章節(jié)介紹關(guān)于金融量化分析的一些基本概念,如年華收益率、基準(zhǔn)年化收益率、最大回撤等。在嚴(yán)格的量化策略回測(cè)中,這些概念都是需要掌握并熟練使用的,這樣能夠全面的評(píng)估量化策略。市面上,很多策略回測(cè)籠統(tǒng)地使用所謂的“勝率”來(lái)確定策略的好壞,這是一種非常業(yè)余而且不可靠的分析方法。在衡量基金業(yè)績(jī)好壞的時(shí)候,大部分人也只是看基金的年化收益,忽略了基金的風(fēng)險(xiǎn)情況(波動(dòng)率)。市場(chǎng)中充斥著大量類似的業(yè)余的分析方法,這些方法導(dǎo)致很多所謂的回測(cè)看起來(lái)很美好,其實(shí)在統(tǒng)計(jì)上根本站不住腳,即所謂的“統(tǒng)計(jì)騙局”。
因此在量化回測(cè)過(guò)程中,需要從收益、穩(wěn)定性、勝率、風(fēng)險(xiǎn)四個(gè)方面來(lái)綜合評(píng)估策略好壞。熟練掌握評(píng)估指標(biāo),還能夠幫助大家識(shí)別一些經(jīng)典“騙局”,如
- 只展示基金的年化收益,而不提基金的波動(dòng)率或者最大回撤
- 使用周收益率來(lái)計(jì)算夏普比率,而不是使用日收益率來(lái)計(jì)算
數(shù)據(jù)準(zhǔn)備
為了幫助大家深入了解指標(biāo)計(jì)算邏輯和實(shí)現(xiàn)方式,本章節(jié)采用指標(biāo)講解和代碼實(shí)現(xiàn)相結(jié)合的方式進(jìn)行講解。再具體計(jì)算過(guò)程中,選擇的目標(biāo)標(biāo)的是貴州茅臺(tái)(600519.SH)、工商銀行(601398.SH)、中國(guó)平安(601318.SH),策略基準(zhǔn)是滬深300指數(shù)(000300.XSHG),策略采用最簡(jiǎn)單的方式:買入持有。持有周期為20180101 - 20221231,共1826個(gè)自然日。數(shù)據(jù)獲取如下所示
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tushare as ts
%matplotlib inline # 無(wú)視warning
import warnings
warnings.filterwarnings("ignore")# 正常顯示畫圖時(shí)出現(xiàn)的中文和負(fù)號(hào)
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False#起始和結(jié)束日期可以自行輸入,否則使用默認(rèn)
def get_data(code, start_date, end_date):# 配置 tushare tokenmy_token = 'XXXXX' pro = ts.pro_api(my_token)df = pro.daily(ts_code=code, start_date=start_date, end_date=end_date)df.index = pd.to_datetime(df.trade_date)return df.close#以上證綜指、貴州茅臺(tái)、工商銀行、中國(guó)平安為例
stocks={'600519.SH':'貴州茅臺(tái)','601398.SH':'工商銀行','601318.SH':'中國(guó)平安'
}df = pd.DataFrame()
for code,name in stocks.items():df[name] = get_data(code, '20180101', '20221231')# 按照日期正序
df = df.sort_index()# 本地讀入滬深300合并
df_base = pd.read_csv('000300.XSHG_2018_2022.csv')
df_base.index = pd.to_datetime(df_base.trade_date)
df['滬深300'] = df_base['close']
凈值曲線
凈值曲線是一組時(shí)間序列的曲線,其含義表示為股票或基金在不同時(shí)間的價(jià)值相對(duì)于期初的價(jià)值的倍數(shù)。 再具體分析中,我們可以將期初價(jià)格定位1,那么如果你在當(dāng)前時(shí)間點(diǎn)手里的凈值是1.4,就意味著當(dāng)前時(shí)間的資產(chǎn)價(jià)值是期初的1.4倍。
# 以第一交易日2018年1月1日收盤價(jià)為基點(diǎn),計(jì)算凈值并繪制凈值曲線
df_worth = df / df.iloc[0]
df_worth.plot(figsize=(15,6))
plt.title('股價(jià)凈值走勢(shì)', fontsize=10)
plt.xticks(pd.date_range('20180101','20221231',freq='Y'),fontsize=10)
plt.show()
年化收益率
累計(jì)收益率:
R t = P T ? P t P t R_t = \frac{P_T - P_{t}} {P_{t}} Rt?=Pt?PT??Pt??
P T P_T PT? 表示在期末資產(chǎn)的價(jià)格
P t P_{t} Pt? 表示期初資產(chǎn)價(jià)格。
年化收益率:
R p = ( 1 + R ) m n ? 1 R_p = (1 + R)^\frac{m}{n} - 1 Rp?=(1+R)nm??1
R R R 表示期間總收益率,m是與n(可以是天數(shù)、周數(shù)、月數(shù))相對(duì)應(yīng)的計(jì)算周期,根據(jù)計(jì)算慣例,m=252、52、12分別指代日、周、月向年化的轉(zhuǎn)換;n為期間自然日天數(shù)。
年化收益的一個(gè)直觀的理解是,假設(shè)按照某種盈利能力,換算成一年的收益大概能有多少。這個(gè)概念常常會(huì)存在誤導(dǎo)性,比如,這個(gè)月股票賺了5%,在隨機(jī)波動(dòng)的市場(chǎng)中,這是很正常的現(xiàn)象。如果據(jù)此號(hào)稱年化收益為5%×12個(gè)月=60%,這就顯得不太可信了,實(shí)際上每個(gè)月的收益不可能都這么穩(wěn)定。所以在聽(tīng)到有人說(shuō)年化收益的時(shí)候,需要特別留意一下具體的情況,否則很容易被誤導(dǎo)。
# 區(qū)間累計(jì)收益率(絕對(duì)收益率)
total_return = df_worth.iloc[-1]-1
total_return = pd.DataFrame(total_return.values,columns=['累計(jì)收益率'],index=total_return.index)
total_return# 年化收益率
annual_return = pd.DataFrame((1 + total_return.values) ** (252 / 1826) - 1,columns=['年化收益率'],index=total_return.index)
annual_return
波動(dòng)率
波動(dòng)率是對(duì)收益變動(dòng)的一種衡量,本質(zhì)也是風(fēng)險(xiǎn),波動(dòng)率和風(fēng)險(xiǎn),都是用來(lái)衡量收益率的不確定性的。我們用方差來(lái)表示,年波動(dòng)率等于策略收益和無(wú)風(fēng)險(xiǎn)收益的標(biāo)準(zhǔn)差除以其均值,再除以交易日倒數(shù)的平方根,通常交易日取252天。
V o l a t i l i t y = 252 n ? 1 ∑ i = 1 n ( r p ? r p ^ ) 2 Volatility = \sqrt{\frac{252}{n-1} \sum\limits_{i=1}^n (r_p - \hat{r_p})^2} Volatility=n?1252?i=1∑n?(rp??rp?^?)2?
r p r_p rp?表示策略每日收益率
r p ^ \hat{r_p} rp?^?表示策略每日收益率的平均值
n n n表示策略執(zhí)行天數(shù)
df_return = df / df.shift(1) - 1
df_return = ((df_return.iloc[1:] - df_return.mean()) ** 2)volatility = pd.DataFrame(np.sqrt(df_return.sum() * 252 / (1826-1)),columns=['波動(dòng)率'],index=total_return.index)
volatility
最大回撤
選定周期內(nèi)任一歷史時(shí)點(diǎn)往后推,于最低點(diǎn)時(shí)的收益率回撤幅度的最大值。最大回撤用來(lái)描述可能出現(xiàn)的最糟糕的情況。最大回撤是一個(gè)重要的風(fēng)險(xiǎn)指標(biāo),對(duì)于量化策略交易,該指標(biāo)比波動(dòng)率還重要。
P為某一天的凈值,i為某一天,j為i后的某一天,Pi為第i天的產(chǎn)品凈值,Pj則是Pi后面某一天的凈值
則該資產(chǎn)的最大回撤計(jì)算如下:
M a x D r a w d o w n = m a x ( P i ? P j ) P i MaxDrawdown = \frac{max(P_i - P_j)} {P_{i}} MaxDrawdown=Pi?max(Pi??Pj?)?
def max_drawdown_cal(df):md = ((df.cummax() - df)/df.cummax()).max()return round(md, 4)max_drawdown = {}stocks={'600519.SH':'貴州茅臺(tái)','601398.SH':'工商銀行','601318.SH':'中國(guó)平安','000300.XSHG': '滬深300'
}for code,name in stocks.items():max_drawdown[name]=max_drawdown_cal(df[name])max_drawdown = pd.DataFrame(max_drawdown,index=['最大回撤']).T
max_drawdown
Alpha系數(shù)和Beta系數(shù)
關(guān)于Alpha系數(shù)和Beta系數(shù)有很多詳盡的解釋,這里就用最簡(jiǎn)單的一句話來(lái)幫助大家簡(jiǎn)單理解。Beta系數(shù)代表投資中的系統(tǒng)風(fēng)險(xiǎn),而在投資中除了系統(tǒng)風(fēng)險(xiǎn)外還面臨著市場(chǎng)波動(dòng)無(wú)關(guān)的非系統(tǒng)性風(fēng)險(xiǎn)。 Alpha系數(shù)就代表投資中的非系統(tǒng)性風(fēng)險(xiǎn),是投資者獲得與市場(chǎng)波動(dòng)無(wú)關(guān)的回報(bào)。
可以使用資本資產(chǎn)定價(jià)模型(CAPM)來(lái)估計(jì)策略的beta和alpha值,CAPM模型為:
E ( r i ) = r f + β ( E ( r m ) ? r f ) E(r_i) = r_f + \beta(E(r_m) - r_f) E(ri?)=rf?+β(E(rm?)?rf?)
E ( r i ) E(r_i) E(ri?)表示投資組合的預(yù)期收益率
r f r_f rf?表示無(wú)風(fēng)險(xiǎn)利率
r m r_m rm?表示市場(chǎng)指數(shù)收益率
β \beta β表示股市波動(dòng)風(fēng)險(xiǎn)與投資機(jī)會(huì)中的結(jié)構(gòu)性與系統(tǒng)性風(fēng)險(xiǎn)。
因此CAPM的計(jì)量模型可以表示為
r i = α + β r m + ? α r_i = \alpha + \beta r_m + \epsilon_\alpha ri?=α+βrm?+?α?
? α \epsilon_\alpha ?α?表示隨機(jī)擾動(dòng),可以理解為個(gè)體風(fēng)險(xiǎn)
from scipy import stats#計(jì)算每日收益率 收盤價(jià)缺失值(停牌),使用前值代替
rets=(df.iloc[:,:4].fillna(method='pad')).apply(lambda x:x/x.shift(1)-1)[1:]#市場(chǎng)指數(shù)為x,個(gè)股收益率為y
x = rets.iloc[:,3].values
y = rets.iloc[:,:3].values
capm = pd.DataFrame()
alpha = []
beta = []
for i in range(3):b, a, r_value, p_value, std_err=stats.linregress(x,y[:,i])#alpha轉(zhuǎn)化為年化alpha.append(round(a*250,3))beta.append(round(b,3))capm['alpha']=alpha
capm['beta']=beta
capm.index=rets.columns[:3]
#輸出結(jié)果:
capm
夏普比率
夏普比率(sharpe ratio)表示每承受一單位總風(fēng)險(xiǎn),會(huì)產(chǎn)生多少的超額報(bào)酬,該比率越高。夏普比率是在資本資產(chǎn)定價(jià)模型進(jìn)一步發(fā)展得來(lái)的。
S h a r p e R a t i o = R p ? R f σ p SharpeRatio = \frac{R_p - R_f} {\sigma_p} SharpeRatio=σp?Rp??Rf??
R p R_p Rp?表示策略年化收益率
R F R_F RF?表示無(wú)風(fēng)險(xiǎn)收益率
σ p \sigma_p σp?表示年化標(biāo)準(zhǔn)差
# 超額收益率以無(wú)風(fēng)險(xiǎn)收益率為基準(zhǔn) 假設(shè)無(wú)風(fēng)險(xiǎn)收益率為年化3%
ex_return=rets - 0.03/250# 計(jì)算夏普比率
sharpe_ratio=np.sqrt(len(ex_return))*ex_return.mean()/ex_return.std()
sharpe_ratio=pd.DataFrame(sharpe_ratio,columns=['夏普比率'])
sharpe_ratio
信息比率
信息比率含義與夏普比率類似,只不過(guò)其參照基準(zhǔn)不是無(wú)風(fēng)險(xiǎn)收益率,而是策略的市場(chǎng)基準(zhǔn)收益率。
I n f o r m a t i o n R a t i o = R p ? R f σ t InformationRatio = \frac{R_p - R_f} {\sigma_t} InformationRatio=σt?Rp??Rf??
R p R_p Rp?表示策略年化收益率
R F R_F RF?表示無(wú)風(fēng)險(xiǎn)收益率
σ t \sigma_t σt?表示策略與基準(zhǔn)每日收益率差值的年化標(biāo)準(zhǔn)差
###信息比率
ex_return = pd.DataFrame()
ex_return['貴州茅臺(tái)']=rets.iloc[:,0]-rets.iloc[:,3]
ex_return['工商銀行']=rets.iloc[:,1]-rets.iloc[:,3]
ex_return['中國(guó)平安']=rets.iloc[:,2]-rets.iloc[:,3]#計(jì)算信息比率
information_ratio = np.sqrt(len(ex_return))*ex_return.mean()/ex_return.std()
#信息比率的輸出結(jié)果
information_ratio = pd.DataFrame(information_ratio,columns=['信息比率'])
information_ratio
7.2 聚寬平臺(tái)量化回測(cè)實(shí)踐
平臺(tái)介紹
聚寬(https://www.joinquant.com/) 成立于2015年5月,是一家量化交易平臺(tái),為投資者提供做量化交易的工具與服務(wù),幫助投資者更好地做量化交易。
整體來(lái)看,聚寬具有以下幾點(diǎn)優(yōu)勢(shì)
- 聚寬讓做量化交易的成本極大降低
- 提供多種優(yōu)質(zhì)的便于取用的數(shù)據(jù)
- 提供投資研究功能,便于自由地統(tǒng)計(jì)、研究、學(xué)習(xí)等
- 提供多種的策略評(píng)價(jià)指標(biāo)與評(píng)價(jià)維度
- 支持多種策略的編寫、回測(cè)、模擬、實(shí)盤
- 具備豐富且活躍的量化社區(qū),可以發(fā)帖、學(xué)習(xí)、比賽等。
策略實(shí)現(xiàn)
本部分將介紹如何在聚寬平臺(tái)實(shí)現(xiàn)一個(gè)雙均線策略(具體參照ch05擇時(shí)策略),并且在聚寬平臺(tái)上進(jìn)行回測(cè),
來(lái)測(cè)試整體收益率。
策略代碼如下,核心點(diǎn)有:
- 選擇標(biāo)的為:002594.XSHE 比亞迪
- 選擇基準(zhǔn)為:000300.XSHG 滬深300
- 策略為:當(dāng)5日線金叉10日線,全倉(cāng)買入;當(dāng)5日線死叉10日線全倉(cāng)賣出。
# 導(dǎo)入函數(shù)庫(kù)
from jqdata import *# 初始化函數(shù),設(shè)定基準(zhǔn)等等
def initialize(context):# 設(shè)定滬深上證作為基準(zhǔn)set_benchmark('000300.XSHG')# 開(kāi)啟動(dòng)態(tài)復(fù)權(quán)模式(真實(shí)價(jià)格)set_option('use_real_price', True)# 輸出內(nèi)容到日志 log.info()log.info('初始函數(shù)開(kāi)始運(yùn)行且全局只運(yùn)行一次')# 過(guò)濾掉order系列API產(chǎn)生的比error級(jí)別低的log# log.set_level('order', 'error')### 股票相關(guān)設(shè)定 #### 股票類每筆交易時(shí)的手續(xù)費(fèi)是:買入時(shí)傭金萬(wàn)分之三,賣出時(shí)傭金萬(wàn)分之三加千分之一印花稅, 每筆交易傭金最低扣5塊錢set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')## 運(yùn)行函數(shù)(reference_security為運(yùn)行時(shí)間的參考標(biāo)的;傳入的標(biāo)的只做種類區(qū)分,因此傳入'000300.XSHG'或'510300.XSHG'是一樣的)# 開(kāi)盤前運(yùn)行run_daily(before_market_open, time='before_open', reference_security='000300.XSHG')# 開(kāi)盤時(shí)運(yùn)行run_daily(market_open, time='open', reference_security='000300.XSHG')# 收盤后運(yùn)行run_daily(after_market_close, time='after_close', reference_security='000300.XSHG')## 開(kāi)盤前運(yùn)行函數(shù)
def before_market_open(context):# 輸出運(yùn)行時(shí)間log.info('函數(shù)運(yùn)行時(shí)間(before_market_open):'+str(context.current_dt.time()))# 給微信發(fā)送消息(添加模擬交易,并綁定微信生效)# send_message('美好的一天~')# 要操作的股票:比亞迪(g.為全局變量)g.security = '002594.XSHE'## 開(kāi)盤時(shí)運(yùn)行函數(shù)
def market_open(context):log.info('函數(shù)運(yùn)行時(shí)間(market_open):'+str(context.current_dt.time()))security = g.security# 獲取股票的收盤價(jià)close_data5 = get_bars(security, count=5, unit='1d', fields=['close'])close_data10 = get_bars(security, count=10, unit='1d', fields=['close'])# close_data20 = get_bars(security, count=20, unit='1d', fields=['close'])# 取得過(guò)去五天,十天的平均價(jià)格MA5 = close_data5['close'].mean()MA10 = close_data10['close'].mean()# 取得上一時(shí)間點(diǎn)價(jià)格#current_price = close_data['close'][-1]# 取得當(dāng)前的現(xiàn)金cash = context.portfolio.available_cash# 五日均線上穿十日均線if (MA5 > MA10) and (cash > 0):# 記錄這次買入log.info("5日線金叉10日線,買入 %s" % (security))# 用所有 cash 買入股票order_value(security, cash)# 五日均線跌破十日均線elif (MA5 < MA10) and context.portfolio.positions[security].closeable_amount > 0:# 記錄這次賣出log.info("5日線死叉10日線, 賣出 %s" % (security))# 賣出所有股票,使這只股票的最終持有量為0for security in context.portfolio.positions.keys():order_target(security, 0)## 收盤后運(yùn)行函數(shù)
def after_market_close(context):log.info(str('函數(shù)運(yùn)行時(shí)間(after_market_close):'+str(context.current_dt.time())))#得到當(dāng)天所有成交記錄trades = get_trades()for _trade in trades.values():log.info('成交記錄:'+str(_trade))log.info('一天結(jié)束')log.info('##############################################################')
在聚寬上回測(cè)策略結(jié)果如下,雖然策略整體具備較好的收益,但需要提示的是該策略并不穩(wěn)定。
- 該策略帶入了后驗(yàn)知識(shí)。因?yàn)槲覀兇笾轮?018-2020年左右比亞迪處于上漲周期,該周期內(nèi)基本上五日線在10日線以上。
- 該策略會(huì)有很強(qiáng)的回撤。例如回測(cè)的后半段,該策略已經(jīng)開(kāi)始較大幅度回撤,因此需要結(jié)合其他策略來(lái)進(jìn)行止盈止損。
- 該策略回測(cè)周期不夠長(zhǎng)。本策略僅回測(cè)了兩年,并且處于較強(qiáng)周期內(nèi),因此不具備較強(qiáng)的回測(cè)意義。
7.3 Backtrader平臺(tái)量化回測(cè)實(shí)踐
Backtrader簡(jiǎn)介
Backtrader是一款基于Python的開(kāi)源的量化回測(cè)框架,功能完善,安裝簡(jiǎn)單。
Backtrader官方文檔(英文) https://www.backtrader.com/docu/
Backtrader非官方文檔(中文) https://www.heywhale.com/mw/project/63857587d0329ee911dcd7f2
Backtrader量化回測(cè)框架實(shí)踐
本部分將介紹如何在Backtrader實(shí)現(xiàn)一個(gè)雙均線策略(具體參照ch05擇時(shí)策略),并且在該平臺(tái)上進(jìn)行回測(cè),
來(lái)測(cè)試整體收益率。
策略代碼如下,核心點(diǎn)有:
- 選擇標(biāo)的為:002594.XSHE 比亞迪
- 選擇基準(zhǔn)為:000300.XSHG 滬深300
- 策略為:當(dāng)5日線金叉10日線,全倉(cāng)買入;當(dāng)5日線死叉10日線全倉(cāng)賣出。
# 導(dǎo)入函數(shù)庫(kù)
from __future__ import (absolute_import, division, print_function, unicode_literals)
import datetime
import pymysql
import pandas as pd
import backtrader as bt
import tushare as ts
import numpy as np# 數(shù)據(jù)獲取(從Tushare中獲取數(shù)據(jù))
"""
數(shù)據(jù)獲取一般都是通過(guò)連接數(shù)據(jù)庫(kù)從數(shù)據(jù)庫(kù)中讀取,對(duì)于不了解數(shù)據(jù)庫(kù)來(lái)源的新手可以從Tushare中直接獲取數(shù)據(jù)
"""
def get_data(stock_code):"""stock_code:股票代碼,類型: strreturn: 股票日線數(shù)據(jù),類型: DataFrame"""token = 'Tushare token' # 可通過(guò)進(jìn)入個(gè)人主頁(yè)-接口TOKEN獲得ts.set_token(token)pro = ts.pro_api(token)data_daily = pro.daily(ts_code = stock_code, start_date='20180101', end_date='20230101')data_daily['trade_date'] = pd.to_datetime(data_daily['trade_date'])data_daily = data_daily.rename(columns={'vol': 'volume'})data_daily.set_index('trade_date', inplace=True) data_daily = data_daily.sort_index(ascending=True)dataframe = data_dailydata_daily['openinterest'] = 0dataframe['openinterest'] = 0data = bt.feeds.PandasData(dataname=dataframe,fromdate=datetime.datetime(2018, 1, 1),todate=datetime.datetime(2023, 1, 1))return data# 雙均線策略實(shí)現(xiàn)
class DoubleAverages(bt.Strategy):# 設(shè)置均線周期params = (('period_data5', 5),('period_data10', 10))# 日志輸出def log(self, txt, dt=None):dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# 初始化數(shù)據(jù)參數(shù)self.dataclose = self.datas[0].close # 定義變量dataclose,保存收盤價(jià)self.order = None # 定義變量order,用于保存訂單self.buycomm = None # 定義變量buycomm,記錄訂單傭金self.buyprice = None # 定義變量buyprice,記錄訂單價(jià)格self.sma5 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.period_data5) # 計(jì)算5日均線self.sma10 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.period_data10) # 計(jì)算10日均線def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]: # 若訂單提交或者已經(jīng)接受則返回returnif order.status in [order.Completed]:if order.isbuy():self.log('Buy Executed, Price: %.2f, Cost: %.2f, Comm: %.2f' %(order.executed.price, order.executed.value, order.executed.comm))self.buyprice = order.executed.priceself.buycomm = order.executed.commelse:self.log('Sell Executed, Price: %.2f, Cost: %.2f, Comm: %.2f' %(order.executed.price, order.executed.value, order.executed.comm))self.bar_executed = len(self)elif order.status in [order.Canceled, order.Margin, order.Rejected]:self.log('Order Canceled/Margin/Rejected')self.order = Nonedef notify_trade(self, trade):if not trade.isclosed: # 若交易未關(guān)閉則返回returnself.log('Operation Profit, Total_Profit %.2f, Net_Profit: %.2f' %(trade.pnl, trade.pnlcomm)) # pnl表示盈利, pnlcomm表示手續(xù)費(fèi)def next(self): # 雙均線策略邏輯實(shí)現(xiàn)self.log('Close: %.2f' % self.dataclose[0]) # 打印收盤價(jià)格if self.order: # 檢查是否有訂單發(fā)送returnif not self.position: # 檢查是否有倉(cāng)位if self.sma5[0] > self.sma10[0]:self.log('Buy: %.2f' % self.dataclose[0])self.order = self.buy()else:if self.sma5[0] < self.sma10[0]:self.log('Sell: %.2f' % self.dataclose[0])self.order = self.sell()if __name__ == '__main__':cerebro = bt.Cerebro() # 創(chuàng)建策略容器cerebro.addstrategy(DoubleAverages) # 添加雙均線策略data = get_data('000001.SZ')cerebro.adddata(data) # 添加數(shù)據(jù)cerebro.broker.setcash(10000.0) # 設(shè)置資金cerebro.addsizer(bt.sizers.FixedSize, stake=100) # 設(shè)置每筆交易的股票數(shù)量cerebro.broker.setcommission(commission=0.01) # 設(shè)置手續(xù)費(fèi)print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) # 打印初始資金cerebro.run() # 運(yùn)行策略print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) # 打印最終資金cerebro.plot()
7.4 BigQuant量化框架實(shí)戰(zhàn)
BigQuant簡(jiǎn)介
BigQuant是一個(gè)人工智能量化投資平臺(tái)。
BigQuant官網(wǎng) https://bigquant.com/
策略實(shí)現(xiàn)
本部分將介紹如何在BigQuant實(shí)現(xiàn)一個(gè)雙均線策略(具體參照ch05擇時(shí)策略),并且在該平臺(tái)上進(jìn)行回測(cè),
來(lái)測(cè)試整體收益率。
策略代碼如下,核心點(diǎn)有:
- 選擇標(biāo)的為:600519.SHA 貴州茅臺(tái)、601392.SHA 工商銀行
- 選擇基準(zhǔn)為:000300.HIX 滬深300
- 策略為:當(dāng)5日線金叉10日線,全倉(cāng)買入;當(dāng)5日線死叉10日線全倉(cāng)賣出。
from bigdatasource.api import DataSource
from biglearning.api import M
from biglearning.api import tools as T
from biglearning.module2.common.data import Outputsimport pandas as pd
import numpy as np
import math
import warnings
import datetimefrom zipline.finance.commission import PerOrder
from zipline.api import get_open_orders
from zipline.api import symbolfrom bigtrader.sdk import *
from bigtrader.utils.my_collections import NumPyDeque
from bigtrader.constant import OrderType
from bigtrader.constant import Directiondef m3_initialize_bigquant_run(context):context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))def m3_handle_data_bigquant_run(context, data):today = data.current_dt.strftime('%Y-%m-%d') stock_hold_now = {e.symbol: p.amount * p.last_sale_pricefor e, p in context.perf_tracker.position_tracker.positions.items()}cash_for_buy = context.portfolio.cash try:buy_stock = context.daily_stock_buy[today] except:buy_stock=[] try:sell_stock = context.daily_stock_sell[today] except:sell_stock=[] stock_to_sell = [ i for i in stock_hold_now if i in sell_stock ]stock_to_buy = [ i for i in buy_stock if i not in stock_hold_now ] stock_to_adjust=[ i for i in stock_hold_now if i not in sell_stock ]if len(stock_to_sell)>0:for instrument in stock_to_sell:sid = context.symbol(instrument) cur_position = context.portfolio.positions[sid].amount if cur_position > 0 and data.can_trade(sid):context.order_target_percent(sid, 0) cash_for_buy += stock_hold_now[instrument]if len(stock_to_buy)+len(stock_to_adjust)>0:weight = 1/(len(stock_to_buy)+len(stock_to_adjust)) for instrument in stock_to_buy+stock_to_adjust:sid = context.symbol(instrument) if data.can_trade(sid):context.order_target_value(sid, weight*cash_for_buy) def m3_prepare_bigquant_run(context):df = context.options['data'].read_df()def open_pos_con(df):return list(df[df['buy_condition']>0].instrument)def close_pos_con(df):return list(df[df['sell_condition']>0].instrument)context.daily_stock_buy= df.groupby('date').apply(open_pos_con)context.daily_stock_sell= df.groupby('date').apply(close_pos_con)m1 = M.input_features.v1(features="""# #號(hào)開(kāi)始的表示注釋
# 多個(gè)特征,每行一個(gè),可以包含基礎(chǔ)特征和衍生特征
buy_condition=where(mean(close_0,5)>mean(close_0,10),1,0)
sell_condition=where(mean(close_0,5)<mean(close_0,10),1,0)""",m_cached=False
)m2 = M.instruments.v2(start_date=T.live_run_param('trading_date', '2019-03-01'),end_date=T.live_run_param('trading_date', '2021-06-01'),market='CN_STOCK_A',instrument_list="""600519.SHA
601392.SHA""",max_count=0
)m7 = M.general_feature_extractor.v7(instruments=m2.data,features=m1.data,start_date='',end_date='',before_start_days=60
)m8 = M.derived_feature_extractor.v3(input_data=m7.data,features=m1.data,date_col='date',instrument_col='instrument',drop_na=False,remove_extra_columns=False
)m4 = M.dropnan.v2(input_data=m8.data
)m3 = M.trade.v4(instruments=m2.data,options_data=m4.data,start_date='',end_date='',initialize=m3_initialize_bigquant_run,handle_data=m3_handle_data_bigquant_run,prepare=m3_prepare_bigquant_run,volume_limit=0.025,order_price_field_buy='open',order_price_field_sell='open',capital_base=1000000,auto_cancel_non_tradable_orders=True,data_frequency='daily',price_type='后復(fù)權(quán)',product_type='股票',plot_charts=True,backtest_only=False,benchmark='000300.HIX'
)
7.5 手寫回測(cè)代碼 - 手把手實(shí)現(xiàn)一個(gè)傻瓜式量化回測(cè)框架
為什么?
為什么要開(kāi)發(fā)?
- 為了避免重復(fù)造輪子,簡(jiǎn)化策略的回測(cè),開(kāi)發(fā)該框架
- VNPY等框架
- 過(guò)于復(fù)雜,繼承嵌套太多,不易理解
- 回測(cè)需填入合約名稱、保證金比率、手續(xù)費(fèi)等詳細(xì)參數(shù),但對(duì)于一個(gè)策略的雛形驗(yàn)證,往往不需要這么精細(xì)
- 有時(shí)候回測(cè)標(biāo)的是指數(shù)或估值,市場(chǎng)上并沒(méi)有相關(guān)合約
為什么叫傻瓜式?
🔴 無(wú)需安裝,只需引用一個(gè)StupidHead.py文件
🟢 架構(gòu)簡(jiǎn)單,沒(méi)有復(fù)雜的類繼承及嵌套關(guān)系,純函數(shù)模式
🟡 策略編寫簡(jiǎn)單
解決什么痛點(diǎn)?
🔴 回測(cè)結(jié)果表現(xiàn)無(wú)需手寫
🟢 封裝常用技術(shù)指標(biāo)
🔵 策略參數(shù)優(yōu)化問(wèn)題
🟣 可直接對(duì)接模擬盤、實(shí)盤
怎么用?
引入StupidHead.py
-
talib安裝
技術(shù)指標(biāo)庫(kù),調(diào)用C++的talib庫(kù),安裝有點(diǎn)麻煩,步驟如下:
🔴 先解壓下面文件到
C:\ta-lib
ta-lib-0.4.0-msvc.zip
🟢 安裝C++編譯包
vc_redist.x64.exe
🟡 安裝talib
Ctrl+F 找到對(duì)應(yīng)版本的的ta-lib包,下載到本地,pip安裝
https://www.lfd.uci.edu/~gohlke/pythonlibs/
pip install TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl
# %% 引入包
import pandas as pd
import math
import matplotlib.pyplot as plt
import talib # http://mrjbq7.github.io/ta-lib/doc_index.html
import numpy as np
from sqlalchemy import create_engine
from hyperopt import tpe, hp, fmin, STATUS_OK, Trials
from hyperopt.pyll.base import scope
import importlib
import warningswarnings.filterwarnings('ignore')plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False# %% 自定義函數(shù)def setpos(pos, *args):HQDf = args[1]idx = args[2]HQDf.loc[idx, 'pos'] = posdef CalculateResult(HQDf):def get_max_drawdown(array):array = pd.Series(array)cummax = array.cummax()return array / cummax - 1# HQDf = HQDf.fillna(method='ffill')HQDf = HQDf.fillna(0)HQDf['base_balance'] = HQDf.close / HQDf.close[0] # 基準(zhǔn)凈值HQDf['chg'] = HQDf.close.pct_change() # 單日漲跌幅# 計(jì)算策略凈值HQDf['strategy_balance'] = 1.0for i in range(0, len(HQDf)):if i > 0:HQDf.loc[HQDf.index[i], 'strategy_balance'] = HQDf.iloc[i - 1]['strategy_balance'] * (1. + HQDf.iloc[i]['chg'] * HQDf.iloc[i - 1]['pos'])HQDf['drawdown'] = get_max_drawdown(HQDf['strategy_balance']) # 回撤StatDf = {}StatDf['MaxDrawDown'] = min(HQDf['drawdown']) # 最大回撤StatDf['return'] = HQDf['strategy_balance'][-1] - 1 # 區(qū)間收益# 計(jì)算年化收益years = (HQDf.index[-1] - HQDf.index[0]).days / 365if years <= 1:StatDf['yearReturn'] = StatDf['return'] / yearselse:StatDf['yearReturn'] = (HQDf['strategy_balance'][-1] / 1) ** (1 / years) - 1StatDf['return/maxdrawdown'] = -1 * StatDf['return'] / StatDf['MaxDrawDown']# 計(jì)算夏普比x = HQDf["strategy_balance"] / HQDf["strategy_balance"].shift(1)x[x <= 0] = np.nanHQDf["return"] = np.log(x).fillna(0)daily_return = HQDf["return"].mean() * 100return_std = HQDf["return"].std() * 100daily_risk_free = 0.015 / np.sqrt(240)StatDf['sharpe_ratio'] = (daily_return - daily_risk_free) / return_std * np.sqrt(240)# HQDf = HQDf.dropna()return HQDf, StatDfdef plotResult(HQDf):fig, axes = plt.subplots(4, 1, figsize=(16, 12))HQDf.loc[:, ['base_balance', 'strategy_balance']].plot(ax=axes[0], title='凈值曲線')HQDf.loc[:, ['drawdown']].plot(ax=axes[1], title='回撤', kind='area')HQDf.loc[:, ['pos']].plot(ax=axes[2], title='倉(cāng)位', kind='area', stacked=False)HQDf['empty'] = HQDf.close[HQDf.pos == 0]HQDf['long'] = HQDf.close[HQDf.pos > 0]HQDf['short'] = HQDf.close[HQDf.pos < 0]HQDf.loc[:, ['long', 'short', 'empty']].plot(ax=axes[3], title='開(kāi)平倉(cāng)點(diǎn)位', color=["r", "g", "grey"])plt.show()def CTA(HQDf, loadBars, func, **kwargs):HQDf['pos'] = np.nan# for idx, hq in tqdm(HQDf.iterrows()):for idx, hq in HQDf.iterrows():TradedHQDf = HQDf[:idx]idx_num = TradedHQDf.shape[0]if idx_num < loadBars:continuefunc(TradedHQDf, HQDf, idx, idx_num, **kwargs)HQDf[:idx].pos = HQDf[:idx].pos.fillna(method='ffill')HQDf, StatDf = CalculateResult(HQDf)# print(StatDf)return HQDf, StatDfdef hypeFun(space, target):"""貝葉斯超參數(shù)優(yōu)化:param space: 參數(shù)空間:param target: 優(yōu)化目標(biāo):return:"""def hyperparameter_tuning(params):HQDf, StatDf = CTA(**params)return {"loss": -StatDf[target], "status": STATUS_OK}trials = Trials()best = fmin(fn=hyperparameter_tuning,space=space,algo=tpe.suggest,max_evals=100,trials=trials)print("Best: {}".format(best))return trials, best
from StupidHead import *
編寫策略
- 策略邏輯
TradedHQDf
- 歷史行情
DataFrame
- 函數(shù)內(nèi)第一行
TradedHQDf = args[0]
- 歷史行情
- 開(kāi)多、開(kāi)空、空倉(cāng)通過(guò)
setpos
函數(shù)即可:- setpos(1, *args) ——滿倉(cāng)開(kāi)多
- setpos(-1, *args) ——滿倉(cāng)開(kāi)空
- setpos(0, *args) ——空倉(cāng)
- setpos(0.5, *args) ——50%倉(cāng)位開(kāi)多
- setpos(-0.5, *args) ——50%倉(cāng)位開(kāi)空
def doubleMa(*args, **kwargs):TradedHQDf = args[0]fast_ma = talib.SMA(TradedHQDf.close, timeperiod=kwargs['fast'])fast_ma0 = fast_ma[-1]fast_ma1 = fast_ma[-2]slow_ma = talib.SMA(TradedHQDf.close, timeperiod=kwargs['slow'])slow_ma0 = slow_ma[-1]slow_ma1 = slow_ma[-2]cross_over = fast_ma0 > slow_ma0 and fast_ma1 < slow_ma1cross_below = fast_ma0 < slow_ma0 and fast_ma1 > slow_ma1if cross_over:setpos(1, *args)elif cross_below:setpos(-1, *args)
回測(cè)策略
T888_1d.csv
T888_15m.csv
from stupids.StupidHead import *def doubleMa(*args, **kwargs):TradedHQDf = args[0]fast_ma = talib.SMA(TradedHQDf.close, timeperiod=kwargs['fast'])fast_ma0 = fast_ma[-1]fast_ma1 = fast_ma[-2]slow_ma = talib.SMA(TradedHQDf.close, timeperiod=kwargs['slow'])slow_ma0 = slow_ma[-1]slow_ma1 = slow_ma[-2]cross_over = fast_ma0 > slow_ma0 and fast_ma1 < slow_ma1cross_below = fast_ma0 < slow_ma0 and fast_ma1 > slow_ma1if cross_over:setpos(1, *args)elif cross_below:setpos(-1, *args)if __name__ == '__main__':HQDf = pd.read_csv('data\T888_1d.csv', index_col='date')HQDf.index = pd.to_datetime(HQDf.index)ctaParas = {'fast': 5, 'slow': 10}ResultTSDf, StatDf = CTA(HQDf, 30, doubleMa, **ctaParas)plotResult(ResultTSDf)
參數(shù)優(yōu)化
📌采用機(jī)器學(xué)習(xí)中貝葉斯超參數(shù)優(yōu)化方法,以極短的時(shí)間尋找出最優(yōu)參數(shù)
# sapce 是參數(shù)空間,定義貝葉斯搜索的空間
# func 技術(shù)指標(biāo)名稱
# fast slow 為技術(shù)指標(biāo)的參數(shù)范圍
space = {"HQDf": HQDf,"loadBars": 40,"func": doubleMa,"fast": hp.quniform("fast", 3, 30, 1),"slow": hp.quniform("slow", 5, 40, 1),}# 調(diào)用貝葉斯搜索,第一個(gè)參數(shù)為參數(shù)空間,第二個(gè)為優(yōu)化目標(biāo)(求解優(yōu)化目標(biāo)極值)
trials, best = hypeFun(space, 'sharpe_ratio')BestResultTSDf, BestStatDf = CTA(HQDf, 30, doubleMa, **best)
plotResult(BestResultTSDf)
框架原理和細(xì)節(jié)
more
- 修改
setpos(pos, *args)
+行情訂閱,可以直接變?yōu)閷?shí)盤 - 可以添加組合回測(cè)功能
- 完善代碼可讀性