前文介紹了股票日線數(shù)據(jù)下載,從本文起開始記錄一些股票因子的計算方法,這些因子將用于后續(xù)策略的編寫。
我們將實現(xiàn)雙神穿多線策略,策略的選股條件是,股票當(dāng)日形成雙神(間隔的2個漲停),K線同時上穿5、10、20、30日均線,30日線在60日線上方,把當(dāng)日漲停收盤價格定義為買點buy_point。買入價格為后續(xù)日期開盤價與buy_point的較小值,賣出止盈價格為buy_point*(1+6.18%),止損價格為buy_point*(1-16.18%)。
在寫本系列文章之前,尚未對該策略進(jìn)行參數(shù)調(diào)整、勝率統(tǒng)計、回測分析,就目前觀察的幾只個股來看,都有不錯的漲幅。讀者可以參與一起實現(xiàn)和優(yōu)化,共同打造成一個實盤策略。
該策略在2018年至2020年3年周期回測后,年化收益為41.7%,最大回撤為25.8%。
本文首先計算漲停因子。
主要代碼分析
新建源文件,命名為data_center_v3.py,全部內(nèi)容見文末,v3主要涉及3個方面的改動:
新增計算漲停因子函數(shù)
def zt(df):
該函數(shù)用于計算漲停因子,其中:
- 參數(shù)df為待計算擴(kuò)展因子的DataFrame
- 返回值為包含擴(kuò)展因子的DataFrame
這里以當(dāng)日收盤價較前一日收盤價上漲9.8%及以上作為漲停判斷標(biāo)準(zhǔn)。若漲停,則因子為True,否則為False。
df['zt'] = np.where((df['close'].values >= 1.098 * df['preclose'].values), True, False)
計算漲停因子,考慮到復(fù)權(quán)以及創(chuàng)業(yè)板和科創(chuàng)板漲停為漲幅20%,這里把當(dāng)日收盤價較前一日收盤價上漲9.8%及以上都作為漲停。
return df
返回包含擴(kuò)展因子的DataFrame
新增計算擴(kuò)展因子
def extend_factor(df):
該函數(shù)用于計算擴(kuò)展因子,其中:
- 參數(shù)df為待計算擴(kuò)展因子的DataFrame
- 返回值為包含擴(kuò)展因子的DataFrame
在v3中只計算漲停因子,后續(xù)會添加雙神等因子的計算。
df = df.pipe(zt)
使用pipe調(diào)用函數(shù)zt,計算漲停因子
return df
返回包含擴(kuò)展因子的DataFrame。
修改創(chuàng)建數(shù)據(jù)函數(shù)
def create_data(stock_codes, from_date='1990-12-19', to_date=datetime.date.today().strftime('%Y-%m-%d'), adjustflag='2'):
"""
下載指定日期內(nèi),指定股票的日線數(shù)據(jù),計算擴(kuò)展因子
:param stock_codes: 待下載數(shù)據(jù)的股票代碼
:param from_date: 日線開始日期
:param to_date: 日線結(jié)束日期
:param adjustflag: 復(fù)權(quán)選項 1:后復(fù)權(quán) 2:前復(fù)權(quán) 3:不復(fù)權(quán) 默認(rèn)為前復(fù)權(quán)
:return: None
"""
# 下載股票循環(huán)
for code in stock_codes:
print('正在下載{}...'.format(code))
# 登錄BaoStock
bs.login()
# 下載日線數(shù)據(jù)
out_df = bs.query_history_k_data_plus(code, g_baostock_data_fields, start_date=from_date, end_date=to_date,
frequency='d', adjustflag=adjustflag).get_data()
# 注銷登錄
bs.logout()
# 剔除停盤數(shù)據(jù)
if out_df.shape[0]:
out_df = out_df[(out_df['volume'] != '0') & (out_df['volume'] != '')]
以上內(nèi)容與v2相同,可參考v2分析內(nèi)容。
if not out_df.shape[0]:
continue
如果數(shù)據(jù)為空,則不進(jìn)行后續(xù)過濾及擴(kuò)展因子計算。
out_df.drop_duplicates(['date'], inplace=True)
曾遇到過出現(xiàn)重復(fù)數(shù)據(jù)的情況,加一道去重過濾。
if out_df.shape[0] < g_available_days_limit:
continue
這里設(shè)置只處理已有多于等于g_available_days_limit根日線數(shù)據(jù)的股票,如果可用的日線數(shù)據(jù)少于g_available_days_limit根,則不創(chuàng)建數(shù)據(jù),來確保后續(xù)可以有效計算擴(kuò)展因子。 在文件開頭我們設(shè)置了全局變量:
g_available_days_limit = 250
convert_list = ['open', 'high', 'low', 'close', 'preclose', 'volume', 'amount', 'turn', 'pctChg']
out_df[convert_list] = out_df[convert_list].astype(float)
把相關(guān)字段類型轉(zhuǎn)化為float類型,BaoStock下載得到的這些字段默認(rèn)為str類型,需要轉(zhuǎn)化為float類型,才能用于后續(xù)指標(biāo)計算。
out_df.reset_index(drop=True, inplace=True)
重置索引,由于我們進(jìn)行了去重、剔除停盤數(shù)據(jù)等操作,需要對索引進(jìn)行重置,來保持索引的連續(xù)性。
out_df = extend_factor(out_df)
調(diào)用函數(shù)extend_factor計算擴(kuò)展因子。
print(out_df)
打印數(shù)據(jù)創(chuàng)建結(jié)果,我們來看一下三美股份sh.603379的打印結(jié)果:
date open high ... pcfNcfTTM isST zt
0 2019-04-02 32.652593 32.652593 ... 76.815822 0 True
1 2019-04-03 35.917853 35.917853 ... 84.497404 0 True
2 2019-04-04 39.511735 39.511735 ... 92.952079 0 True
3 2019-04-08 43.462210 43.462210 ... 102.245642 0 True
4 2019-04-09 47.755292 47.755292 ... 97.278109 0 False
.. ... ... ... ... ... ... ...
598 2021-09-13 31.580000 33.380000 ... -70.841885 0 False
599 2021-09-14 32.520000 35.000000 ... -71.822773 0 False
600 2021-09-15 33.020000 36.250000 ... -79.015949 0 True
601 2021-09-16 37.900000 38.000000 ... -77.381136 0 False
602 2021-09-17 35.510000 39.050000 ... -85.119250 0 True
[603 rows x 18 columns]
倒數(shù)第三行和最后一行顯示2021年9月15日和17日為漲停,對應(yīng)看一下K線圖:

也可以看到2021年9月15日和17日均為漲停,計算結(jié)果正確。
小結(jié)
本文主要介紹了要實現(xiàn)的策略的思路,完成了漲停因子的計算,后續(xù)繼續(xù)介紹策略所需的其他因子的實現(xiàn)方式。
到目前為止,創(chuàng)建的數(shù)據(jù)只是用于打印,未實現(xiàn)存儲。因此只要確保程序能正常運行即可,不需要等待程序運行結(jié)束。等后續(xù)文章所有因子都介紹完成后,我們會進(jìn)行多線程計算,并將結(jié)果保存到MySQL中。
data_center_v3.py的全部代碼如下:
import baostock as bs
import datetime
import sys
import numpy as np
# 可用日線數(shù)量約束
g_available_days_limit = 250
# BaoStock日線數(shù)據(jù)字段
g_baostock_data_fields = 'date,open,high,low,close,preclose,volume,amount,adjustflag,turn,tradestatus,pctChg,peTTM,pbMRQ, psTTM,pcfNcfTTM,isST'
def get_stock_codes(date=None):
"""
獲取指定日期的A股代碼列表
若參數(shù)date為空,則返回最近1個交易日的A股代碼列表
若參數(shù)date不為空,且為交易日,則返回date當(dāng)日的A股代碼列表
若參數(shù)date不為空,但不為交易日,則打印提示非交易日信息,程序退出
:param date: 日期
:return: A股代碼的列表
"""
# 登錄baostock
bs.login()
# 從BaoStock查詢股票數(shù)據(jù)
stock_df = bs.query_all_stock(date).get_data()
# 如果獲取數(shù)據(jù)長度為0,表示日期date非交易日
if 0 == len(stock_df):
# 如果設(shè)置了參數(shù)date,則打印信息提示date為非交易日
if date is not None:
print('當(dāng)前選擇日期為非交易日或尚無交易數(shù)據(jù),請設(shè)置date為歷史某交易日日期')
sys.exit(0)
# 未設(shè)置參數(shù)date,則向歷史查找最近的交易日,當(dāng)獲取股票數(shù)據(jù)長度非0時,即找到最近交易日
delta = 1
while 0 == len(stock_df):
stock_df = bs.query_all_stock(datetime.date.today() - datetime.timedelta(days=delta)).get_data()
delta += 1
# 注銷登錄
bs.logout()
# 篩選股票數(shù)據(jù),上證和深證股票代碼在sh.600000與sz.39900之間
stock_df = stock_df[(stock_df['code'] >= 'sh.600000') & (stock_df['code'] < 'sz.399000')]
# 返回股票列表
return stock_df['code'].tolist()
def create_data(stock_codes, from_date='1990-12-19', to_date=datetime.date.today().strftime('%Y-%m-%d'),
adjustflag='2'):
"""
下載指定日期內(nèi),指定股票的日線數(shù)據(jù),計算擴(kuò)展因子
:param stock_codes: 待下載數(shù)據(jù)的股票代碼
:param from_date: 日線開始日期
:param to_date: 日線結(jié)束日期
:param adjustflag: 復(fù)權(quán)選項 1:后復(fù)權(quán) 2:前復(fù)權(quán) 3:不復(fù)權(quán) 默認(rèn)為前復(fù)權(quán)
:return: None
"""
# 下載股票循環(huán)
for code in stock_codes:
print('正在下載{}...'.format(code))
# 登錄BaoStock
bs.login()
# 下載日線數(shù)據(jù)
out_df = bs.query_history_k_data_plus(code, g_baostock_data_fields, start_date=from_date, end_date=to_date,
frequency='d', adjustflag=adjustflag).get_data()
# 注銷登錄
bs.logout()
# 剔除停盤數(shù)據(jù)
if out_df.shape[0]:
out_df = out_df[(out_df['volume'] != '0') & (out_df['volume'] != '')]
# 如果數(shù)據(jù)為空,則不創(chuàng)建
if not out_df.shape[0]:
continue
# 刪除重復(fù)數(shù)據(jù)
out_df.drop_duplicates(['date'], inplace=True)
# 日線數(shù)據(jù)少于g_available_days_limit,則不創(chuàng)建
if out_df.shape[0] < g_available_days_limit:
continue
# 將數(shù)值數(shù)據(jù)轉(zhuǎn)為float型,便于后續(xù)處理
convert_list = ['open', 'high', 'low', 'close', 'preclose', 'volume', 'amount', 'turn', 'pctChg']
out_df[convert_list] = out_df[convert_list].astype(float)
# 重置索引
out_df.reset_index(drop=True, inplace=True)
# 計算擴(kuò)展因子
out_df = extend_factor(out_df)
print(out_df)
def extend_factor(df):
"""
計算擴(kuò)展因子
:param df: 待計算擴(kuò)展因子的DataFrame
:return: 包含擴(kuò)展因子的DataFrame
"""
# 使用pipe計算漲停因子
df = df.pipe(zt)
return df
def zt(df):
"""
計算漲停因子
若漲停,則因子為True,否則為False
以當(dāng)日收盤價較前一日收盤價上漲9.8%及以上作為漲停判斷標(biāo)準(zhǔn)
:param df: 待計算擴(kuò)展因子的DataFrame
:return: 包含擴(kuò)展因子的DataFrame
"""
df['zt'] = np.where((df['close'].values >= 1.098 * df['preclose'].values), True, False)
return df
if __name__ == '__main__':
stock_codes = get_stock_codes()
create_data(stock_codes)
|