前文介紹了股票日線數(shù)據(jù)下載,從本文起開始記錄一些股票因子的計(jì)算方法,這些因子將用于后續(xù)策略的編寫。
我們將實(shí)現(xiàn)雙神穿多線策略,策略的選股條件是,股票當(dāng)日形成雙神(間隔的2個(gè)漲停),K線同時(shí)上穿5、10、20、30日均線,30日線在60日線上方,把當(dāng)日漲停收盤價(jià)格定義為買點(diǎn)buy_point。買入價(jià)格為后續(xù)日期開盤價(jià)與buy_point的較小值,賣出止盈價(jià)格為buy_point*(1+6.18%),止損價(jià)格為buy_point*(1-16.18%)。
在寫本系列文章之前,尚未對(duì)該策略進(jìn)行參數(shù)調(diào)整、勝率統(tǒng)計(jì)、回測(cè)分析,就目前觀察的幾只個(gè)股來(lái)看,都有不錯(cuò)的漲幅。讀者可以參與一起實(shí)現(xiàn)和優(yōu)化,共同打造成一個(gè)實(shí)盤策略。
該策略在2018年至2020年3年周期回測(cè)后,年化收益為41.7%,最大回撤為25.8%。
本文首先計(jì)算漲停因子。
新建源文件,命名為data_center_v3.py,全部?jī)?nèi)容見文末,v3主要涉及3個(gè)方面的改動(dòng):
def zt(df):
該函數(shù)用于計(jì)算漲停因子,其中:
這里以當(dāng)日收盤價(jià)較前一日收盤價(jià)上漲9.8%及以上作為漲停判斷標(biāo)準(zhǔn)。若漲停,則因子為True,否則為False。
df['zt'] = np.where((df['close'].values >= 1.098 * df['preclose'].values), True, False)
計(jì)算漲停因子,考慮到復(fù)權(quán)以及創(chuàng)業(yè)板和科創(chuàng)板漲停為漲幅20%,這里把當(dāng)日收盤價(jià)較前一日收盤價(jià)上漲9.8%及以上都作為漲停。
return df
返回包含擴(kuò)展因子的DataFrame
def extend_factor(df):
該函數(shù)用于計(jì)算擴(kuò)展因子,其中:
在v3中只計(jì)算漲停因子,后續(xù)會(huì)添加雙神等因子的計(jì)算。
df = df.pipe(zt)
使用pipe調(diào)用函數(shù)zt,計(jì)算漲停因子
return df
返回包含擴(kuò)展因子的DataFrame。
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ù),計(jì)算擴(kuò)展因子
:param stock_codes: 待下載數(shù)據(jù)的股票代碼
:param from_date: 日線開始日期
:param to_date: 日線結(jié)束日期
:param adjustflag: 復(fù)權(quán)選項(xiàng) 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ù)過(guò)濾及擴(kuò)展因子計(jì)算。
out_df.drop_duplicates(['date'], inplace=True)
曾遇到過(guò)出現(xiàn)重復(fù)數(shù)據(jù)的情況,加一道去重過(guò)濾。
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ù),來(lái)確保后續(xù)可以有效計(jì)算擴(kuò)展因子。
在文件開頭我們?cè)O(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)計(jì)算。
out_df.reset_index(drop=True, inplace=True)
重置索引,由于我們進(jìn)行了去重、剔除停盤數(shù)據(jù)等操作,需要對(duì)索引進(jìn)行重置,來(lái)保持索引的連續(xù)性。
out_df = extend_factor(out_df)
調(diào)用函數(shù)extend_factor計(jì)算擴(kuò)展因子。
print(out_df)
打印數(shù)據(jù)創(chuàng)建結(jié)果,我們來(lái)看一下三美股份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日為漲停,對(duì)應(yīng)看一下K線圖:
也可以看到2021年9月15日和17日均為漲停,計(jì)算結(jié)果正確。
本文主要介紹了要實(shí)現(xiàn)的策略的思路,完成了漲停因子的計(jì)算,后續(xù)繼續(xù)介紹策略所需的其他因子的實(shí)現(xiàn)方式。
到目前為止,創(chuàng)建的數(shù)據(jù)只是用于打印,未實(shí)現(xiàn)存儲(chǔ)。因此只要確保程序能正常運(yùn)行即可,不需要等待程序運(yùn)行結(jié)束。等后續(xù)文章所有因子都介紹完成后,我們會(huì)進(jìn)行多線程計(jì)算,并將結(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個(gè)交易日的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ù)長(zhǎng)度為0,表示日期date非交易日
if 0 == len(stock_df):
# 如果設(shè)置了參數(shù)date,則打印信息提示date為非交易日
if date is not None:
print('當(dāng)前選擇日期為非交易日或尚無(wú)交易數(shù)據(jù),請(qǐng)?jiān)O(shè)置date為歷史某交易日日期')
sys.exit(0)
# 未設(shè)置參數(shù)date,則向歷史查找最近的交易日,當(dāng)獲取股票數(shù)據(jù)長(zhǎng)度非0時(shí),即找到最近交易日
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ù),計(jì)算擴(kuò)展因子
:param stock_codes: 待下載數(shù)據(jù)的股票代碼
:param from_date: 日線開始日期
:param to_date: 日線結(jié)束日期
:param adjustflag: 復(fù)權(quán)選項(xiàng) 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)
# 計(jì)算擴(kuò)展因子
out_df = extend_factor(out_df)
print(out_df)
def extend_factor(df):
"""
計(jì)算擴(kuò)展因子
:param df: 待計(jì)算擴(kuò)展因子的DataFrame
:return: 包含擴(kuò)展因子的DataFrame
"""
# 使用pipe計(jì)算漲停因子
df = df.pipe(zt)
return df
def zt(df):
"""
計(jì)算漲停因子
若漲停,則因子為True,否則為False
以當(dāng)日收盤價(jià)較前一日收盤價(jià)上漲9.8%及以上作為漲停判斷標(biāo)準(zhǔn)
:param df: 待計(jì)算擴(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)
聯(lián)系客服