研究目的
本文參考東吳證券研報《A股市場的周內效應》內容,對A股市場的日曆效應在周內表現進行探索。日曆效應,是一個魚龍混雜的題目。有詼諧逗笑的,如馬克吐溫老先生的段子:十月,這是股市最危險的月份,其他危險的月份有七月、一月、九月、四月、十一月、五月、三月、六月、十二月、八月和二月。有認真總結的,如華爾街那個著名諺語:Sell in May and go away。然而從量化的角度來講由於其樣本數量過小,邏輯支撐也不夠直接,對於以月份為支撐的日曆效應一直被人持以懷疑的態度。相對地,以周為時間單位的周內效應更多地成為了我們關注的對象。我們考察了時間周期內股票在周一至周五每一天漲跌幅的中位數以觀察周內效應的存在性。
研究思路
【1】觀察周內不同交易日指數等效組合收益分解前後的漲跌幅中位數趨勢變化。
【2】觀察周內不同交易日全A股收益分解前後的漲跌幅中位數趨勢變化。
【3】觀察周內不同交易日收益分解前後的股指期貨漲跌幅變化分析異象原因。
【4】嘗試構建基於周內異象的策略獲取來自異象的超額收益
【4】嘗試構建不同半倉t 0策略獲取觀察到的日內收益。
研究結論
【1】周內異象在我國A股市場內廣泛存在,且該異象主要來源於日內收益,日間收益並無明顯規律。
【2】周內異象的存在原因可能與資金流轉、股票的t 1機制有關。
【3】在考慮了手續費以及印花稅後,操作頻率較低的基礎策略有着明顯的超額收益,而操作頻率較高的以獲得日內收益為目標的t 0策略超額收益會被幾乎抹平,在兩種情況下,周內異象的考量均可以明顯改善策略績效。
引言¶
研究目的¶
本文參考東吳證券研報《A股市場的周內效應》內容,對A股市場的周內異象進行探索。日曆效應,是一個魚龍混雜的題目。有詼諧逗笑的,如馬克吐溫老先生的段子:十月,這是股市最危險的月份,其他危險的月份有七月、一月、九月、四月、十一月、五月、三月、六月、十二月、八月和二月。有認真總結的,如華爾街那個著名諺語:Sell in May and go away。然而從量化的角度來講由於其樣本數量過小,邏輯支撐也不夠直接,對於以月份為支撐的日曆效應一直被人持以懷疑的態度。相對地,以周為時間單位的周內效應更多地成為了我們關注的對象。我們考察了時間周期內股票在周一至周五每一天漲跌幅的中位數以觀察周內效應的存在性。
研究思路¶
【1】觀察周內不同交易日指數等效組合收益分解前後的漲跌幅中位數趨勢變化。
【2】觀察周內不同交易日全A股收益分解前後的漲跌幅中位數趨勢變化。
【3】觀察周內不同交易日收益分解前後的股指期貨漲跌幅變化分析異象原因。
【4】嘗試構建基於周內異象的策略獲取來自異象的超額收益
【4】嘗試構建不同半倉t+0策略獲取觀察到的日內收益。
研究結論¶
【1】周內異象在我國A股市場內廣泛存在,且該異象主要來源於日內收益,日間收益並無明顯規律。
【2】周內異象的存在原因可能與股票的t+1機制有關。
【3】在考慮了手續費以及印花稅後,操作頻率較低的基礎策略有着明顯的超額收益,而操作頻率較高的以獲得日內收益為目標的t+0策略超額收益會被幾乎抹平,在兩種情況下,周內異象的考量均可以明顯改善策略績效。
研究周期¶
2007.1 ~ 2019.6
from jqdata import *
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
#交易日獲取函數
def get_trade_day_list(start_date, end_date):
date_df = get_price('000001.XSHG', start_date = start_date, end_date = end_date)
date_list = []
for item in list(date_df.index):
item = str(item)
y = int(item[0:4])
m = int(item[5:7])
d = int(item[8:10])
date_list.append(datetime.date(y,m,d))
return date_list
trade_day_list = get_trade_day_list(datetime.date(2007,1,16),datetime.date(2019,6,14))
指數組合¶
首先,我們將收益區分為了日內收益(一天之中開盤價到收盤價的收益),日間收益(前一天收盤價至當天開盤價之間的收益)與總收益,並計算了2007年1月至29019年6月上證50,滬深300以及中證500三個指數等效組合對應的漲跌幅中位數,觀察其在一周不同交易日內的變化趨勢,發現周內的漲跌幅呈現出了強烈的“V”型趨勢:從周一開始漲跌幅逐漸下降,到周日跌至低點,周五則出現了輕微的反彈,在分解收益後,我們發現這一現象主要來自日內收益,日間收益在周二至周五的幾天間並沒出現明顯的規律。
#定義股票列表漲跌幅獲取函數(全部收益)
def median_return(trade_day_list, security_list):
median_df = pd.DataFrame()
for security in security_list:
start = get_security_info(security).start_date
if start < trade_day_list[0]:
start = trade_day_list[0]
median_list = []
mon = []
tue = []
wed = []
thu = []
fri = []
price_df = get_price(security, start_date = start, end_date = trade_day_list[-1], fields = ['open', 'close'])
date_list = []
for item in list(price_df.index):
item = str(item)
y = int(item[0:4])
m = int(item[5:7])
d = int(item[8:10])
date_list.append(datetime.date(y,m,d))
for day in date_list[1:]:
location = date_list.index(day)
last_day = date_list[location - 1]
delta_price = (price_df.loc[day, 'close'] / price_df.loc[last_day, 'close']) - 1
weekday = day.weekday()
if weekday == 0:
mon.append(delta_price)
elif weekday == 1:
tue.append(delta_price)
elif weekday == 2:
wed.append(delta_price)
elif weekday == 3:
thu.append(delta_price)
elif weekday == 4:
fri.append(delta_price)
for item in [mon, tue, wed, thu, fri]:
item_df = pd.DataFrame()
item_df[str(item)] = item
item_df = item_df.dropna()
item_list = list(item_df[str(item)])
median_list.append(np.median(item_list))
median_df[security] = median_list
median_df = median_df.T
median_df.columns = ['mon', 'tue', 'wed', 'thu', 'fri']
return median_df
#定義股票列表漲跌幅獲取函數(日內收益)
def median_return_inday(trade_day_list, security_list):
median_df = pd.DataFrame()
for security in security_list:
start = get_security_info(security).start_date
if start < trade_day_list[0]:
start = trade_day_list[0]
median_list = []
mon = []
tue = []
wed = []
thu = []
fri = []
price_df = get_price(security, start_date = start, end_date = trade_day_list[-1], fields = ['open', 'close'])
date_list = []
for item in list(price_df.index):
item = str(item)
y = int(item[0:4])
m = int(item[5:7])
d = int(item[8:10])
date_list.append(datetime.date(y,m,d))
for day in date_list:
delta_price = (price_df.loc[day, 'close'] / price_df.loc[day, 'open']) - 1
weekday = day.weekday()
if weekday == 0:
mon.append(delta_price)
elif weekday == 1:
tue.append(delta_price)
elif weekday == 2:
wed.append(delta_price)
elif weekday == 3:
thu.append(delta_price)
elif weekday == 4:
fri.append(delta_price)
for item in [mon, tue, wed, thu, fri]:
item_df = pd.DataFrame()
item_df[str(item)] = item
item_df = item_df.dropna()
item_list = list(item_df[str(item)])
median_list.append(np.median(item_list))
median_df[security] = median_list
median_df = median_df.T
median_df.columns = ['mon', 'tue', 'wed', 'thu', 'fri']
return median_df
#定義股票列表漲跌幅獲取函數(日間收益)
def median_return_overnight(trade_day_list, security_list):
median_df = pd.DataFrame()
for security in security_list:
start = get_security_info(security).start_date
if start < trade_day_list[0]:
start = trade_day_list[0]
median_list = []
mon = []
tue = []
wed = []
thu = []
fri = []
price_df = get_price(security, start_date = start, end_date = trade_day_list[-1], fields = ['open', 'close'])
date_list = []
for item in list(price_df.index):
item = str(item)
y = int(item[0:4])
m = int(item[5:7])
d = int(item[8:10])
date_list.append(datetime.date(y,m,d))
for day in date_list[1:]:
location = date_list.index(day)
last_day = date_list[location - 1]
delta_price = (price_df.loc[day, 'open'] / price_df.loc[last_day, 'close']) - 1
weekday = day.weekday()
if weekday == 0:
mon.append(delta_price)
elif weekday == 1:
tue.append(delta_price)
elif weekday == 2:
wed.append(delta_price)
elif weekday == 3:
thu.append(delta_price)
elif weekday == 4:
fri.append(delta_price)
for item in [mon, tue, wed, thu, fri]:
item_df = pd.DataFrame()
item_df[str(item)] = item
item_df = item_df.dropna()
item_list = list(item_df[str(item)])
median_list.append(np.median(item_list))
median_df[security] = median_list
median_df = median_df.T
median_df.columns = ['mon', 'tue', 'wed', 'thu', 'fri']
return median_df
#上證50,滬深300,中證500總漲跌幅中位數
market_median = median_return(trade_day_list, ['000016.XSHG', '000300.XSHG', '000905.XSHG'])
market_median.T.plot(figsize = (20,12))
#上證50,滬深300,中證500日內漲跌幅中位數
market_median_inday = median_return_inday(trade_day_list, ['000016.XSHG', '000300.XSHG', '000905.XSHG'])
market_median_inday.T.plot(figsize = (20,12))
#上證50,滬深300,中證500日間漲跌幅中位數
market_median_overnight = median_return_overnight(trade_day_list, ['000016.XSHG', '000300.XSHG', '000905.XSHG'])
market_median_overnight.T.plot(figsize = (20,12))
個股漲跌幅¶
為了進一步觀察現象的存在性,我們將研究擴展到了全部A股,以下是全部A股在一周不同交易日下的漲跌幅變化趨勢,觀察圖表可知,與指數相同的周內異象在全A股的範圍內依然顯著存在,從周一起漲跌幅逐漸下降,跌至周四達到了最低點,在周五有着一定程度上的回升。
#定義股票列表總漲跌幅獲取函數
def all_median_return(trade_day_list, security_list):
median_df = pd.DataFrame()
median_list = []
mon = []
tue = []
wed = []
thu = []
fri = []
for security in security_list:
start = get_security_info(security).start_date
if start < trade_day_list[0]:
start = trade_day_list[0]
price_df = get_price(security, start_date = start, end_date = trade_day_list[-1], fields = ['open', 'close'])
date_list = []
for item in list(price_df.index):
item = str(item)
y = int(item[0:4])
m = int(item[5:7])
d = int(item[8:10])
date_list.append(datetime.date(y,m,d))
for day in date_list[1:]:
location = date_list.index(day)
last_day = date_list[location - 1]
delta_price = (price_df.loc[day, 'close'] / price_df.loc[last_day, 'close']) - 1
weekday = day.weekday()
if weekday == 0:
mon.append(delta_price)
elif weekday == 1:
tue.append(delta_price)
elif weekday == 2:
wed.append(delta_price)
elif weekday == 3:
thu.append(delta_price)
elif weekday == 4:
fri.append(delta_price)
for item in [mon, tue, wed, thu, fri]:
item_df = pd.DataFrame()
item_df[str(item)] = item
item_df = item_df.dropna()
item_df = item_df[item_df[str(item)] != 0]
item_list = list(item_df[str(item)])
median_list.append(np.median(item_list))
median_df['all_listed_stock'] = median_list
median_df = median_df.T
median_df.columns = ['mon', 'tue', 'wed', 'thu', 'fri']
return median_df
#定義股票列表日內漲跌幅獲取函數
def all_median_return_inday(trade_day_list, security_list):
median_df = pd.DataFrame()
median_list = []
mon = []
tue = []
wed = []
thu = []
fri = []
for security in security_list:
start = get_security_info(security).start_date
if start < trade_day_list[0]:
start = trade_day_list[0]
price_df = get_price(security, start_date = start, end_date = trade_day_list[-1], fields = ['open', 'close'])
date_list = []
for item in list(price_df.index):
item = str(item)
y = int(item[0:4])
m = int(item[5:7])
d = int(item[8:10])
date_list.append(datetime.date(y,m,d))
for day in date_list:
delta_price = (price_df.loc[day, 'close'] / price_df.loc[day, 'open']) - 1
weekday = day.weekday()
if weekday == 0:
mon.append(delta_price)
elif weekday == 1:
tue.append(delta_price)
elif weekday == 2:
wed.append(delta_price)
elif weekday == 3:
thu.append(delta_price)
elif weekday == 4:
fri.append(delta_price)
for item in [mon, tue, wed, thu, fri]:
item_df = pd.DataFrame()
item_df[str(item)] = item
item_df = item_df.dropna()
item_df = item_df[item_df[str(item)] != 0]
item_list = list(item_df[str(item)])
median_list.append(np.median(item_list))
median_df['all_listed_stock'] = median_list
median_df = median_df.T
median_df.columns = ['mon', 'tue', 'wed', 'thu', 'fri']
return median_df
#定義股票列表日間漲跌幅獲取函數
def all_median_return_overnight(trade_day_list, security_list):
median_df = pd.DataFrame()
median_list = []
mon = []
tue = []
wed = []
thu = []
fri = []
for security in security_list:
start = get_security_info(security).start_date
if start < trade_day_list[0]:
start = trade_day_list[0]
price_df = get_price(security, start_date = start, end_date = trade_day_list[-1], fields = ['open', 'close'])
date_list = []
for item in list(price_df.index):
item = str(item)
y = int(item[0:4])
m = int(item[5:7])
d = int(item[8:10])
date_list.append(datetime.date(y,m,d))
for day in date_list:
location = date_list.index(day)
last_day = date_list[location - 1]
delta_price = (price_df.loc[day, 'open'] / price_df.loc[last_day, 'close']) - 1
weekday = day.weekday()
if weekday == 0:
mon.append(delta_price)
elif weekday == 1:
tue.append(delta_price)
elif weekday == 2:
wed.append(delta_price)
elif weekday == 3:
thu.append(delta_price)
elif weekday == 4:
fri.append(delta_price)
for item in [mon, tue, wed, thu, fri]:
item_df = pd.DataFrame()
item_df[str(item)] = item
item_df = item_df.dropna()
item_df = item_df[item_df[str(item)] != 0]
item_list = list(item_df[str(item)])
median_list.append(np.median(item_list))
median_df['all_listed_stock'] = median_list
median_df = median_df.T
median_df.columns = ['mon', 'tue', 'wed', 'thu', 'fri']
return median_df
#全A總漲跌幅中位數
all_stock_list = get_index_stocks('000985.XSHG')
all_median = all_median_return(trade_day_list, all_stock_list)
all_median.plot(kind = 'bar', figsize = (20,12))
#全A日內漲跌幅中位數
all_stock_list = get_index_stocks('000985.XSHG')
all_median_inday = all_median_return_inday(trade_day_list, all_stock_list)
all_median_inday.plot(kind = 'bar', figsize = (20,12))
#全A日間漲跌幅中位數
all_stock_list = get_index_stocks('000985.XSHG')
all_median_overnight = all_median_return_overnight(trade_day_list, all_stock_list)
all_median_overnight.plot(kind = 'bar', figsize = (20,12))
形成原因¶
對於周內異象的形成原因,我們猜測與A股的t+1交易機制有關:周五賣出的股票,需等到下周一,才能通過銀證轉賬取得現金;周末需要取用的現金,必須提早至周四將股票賣出。因此,周四的股票交易中,會存在一個額外的賣壓,容易成為一周中行情最低迷的一天。
為了進一步驗證這一結論,我們將沒有t+0交易限制的期貨市場行情數據加以同樣的處理方式。觀察圖表可以發現,期指市場並未像A股市場一樣出現顯著的周內效應,進一步驗證了我們的結論:周內異象的形成與A股的t+1交易機制有關。
#上證50,滬深300,中證500股指期貨漲跌幅中位數
futures_market_median = median_return(trade_day_list, ['IH9999.CCFX', 'IF9999.CCFX', 'IC9999.CCFX'])
futures_market_median.T.plot(figsize = (20,12))
周內收益——基礎策略¶
通過前文的研究我們可以發現,在我國A股市場中周一、周二、周三、周五平均來講存在正向收益,而正向收益的來源基本為日內收益,因此我們將研究策略主要分為直接獲取周內收益以及通過半倉t+0獲取日內收益兩種,我們首先着眼於直接獲得周內收益的基礎策略,即周五開盤時買入,持有至周三收盤時賣出。
我們測試了上證50、滬深300以及中證500的等效組合收益情況。通過測試我們可以發現,由於較低的操作頻率,在規避掉周四以後三種組合均獲得了明顯的超額收益,優於周內收益主要來自於日內而日間收益則基本為負,接下來我們將觀察基於半倉t+0策略僅獲取日間收益的可行性。
#基礎周內策略——周五買入周三賣出
#(買入時萬分之三,賣出時萬分之三加千分之一印花稅)
def weekly_value_cal(security, trade_day_list, fee_rate = 0.0003, tax_rate = 0.001):
value_df = pd.DataFrame()
price_df = get_price(security, start_date = trade_day_list[0], end_date = trade_day_list[-1], fields = ['open', 'close'])
price_df = price_df.dropna()
value_df['benchmark'] = price_df['close'] / price_df.loc[price_df.index[0], 'close']
date_list = list(value_df.index)
if date_list[0].weekday() == 3:
date_list = date_list.remove(date_list[0])
value_df = value_df.drop([date_list[0]])
value_list = [(1 - fee_rate) * price_df.loc[date_list[0], 'close']]
for date in date_list[1:]:
weekday = date.weekday()
location = date_list.index(date)
last_date = date_list[location - 1]
if weekday == 0 or weekday == 1:
value = value_list[location - 1] * price_df.loc[date, 'close'] / price_df.loc[last_date, 'close']
value_list.append(value)
elif weekday == 2:
value = value_list[location - 1] * price_df.loc[date, 'close'] * (1 - fee_rate - tax_rate) / price_df.loc[last_date, 'close']
value_list.append(value)
elif weekday == 3:
value = value_list[location - 1]
value_list.append(value)
elif weekday == 4:
value = value_list[location - 1] * (1 - fee_rate) * price_df.loc[date, 'close'] / price_df.loc[date, 'open']
value_list.append(value)
value_df['value'] = value_list
value_df['value'] = value_df['value'] / value_list[0]
return value_df
#上證50指數等效組合價值曲線(基礎)
SZ50_value_weekly = weekly_value_cal('000016.XSHG', trade_day_list)
SZ50_value_weekly['zero_cost'] = weekly_value_cal('000016.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
SZ50_value_weekly['fee'] = weekly_value_cal('000016.XSHG', trade_day_list, tax_rate = 0)['value']
SZ50_value_weekly['tax'] = weekly_value_cal('000016.XSHG', trade_day_list, fee_rate = 0)['value']
SZ50_value_weekly.plot(figsize = (20,12))
#上證50指數等效組合價值曲線(基礎)
SZ50_value_weekly = weekly_value_cal('000016.XSHG', trade_day_list)
SZ50_value_weekly['zero_cost'] = weekly_value_cal('000016.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
SZ50_value_weekly['fee'] = weekly_value_cal('000016.XSHG', trade_day_list, tax_rate = 0)['value']
SZ50_value_weekly['tax'] = weekly_value_cal('000016.XSHG', trade_day_list, fee_rate = 0)['value']
SZ50_value_weekly.plot(figsize = (20,12))
#滬深300指數等效組合價值曲線(基礎)
HS300_value_weekly = weekly_value_cal('000300.XSHG', trade_day_list)
HS300_value_weekly['zero_cost'] = weekly_value_cal('000300.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
HS300_value_weekly['fee'] = weekly_value_cal('000300.XSHG', trade_day_list, tax_rate = 0)['value']
HS300_value_weekly['tax'] = weekly_value_cal('000300.XSHG', trade_day_list, fee_rate = 0)['value']
HS300_value_weekly.plot(figsize = (20,12))
#中證500指數等效組合價值曲線(基礎)
ZZ500_value_weekly = weekly_value_cal('000905.XSHG', trade_day_list)
ZZ500_value_weekly['zero_cost'] = weekly_value_cal('000905.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
ZZ500_value_weekly['fee'] = weekly_value_cal('000905.XSHG', trade_day_list, tax_rate = 0)['value']
ZZ500_value_weekly['tax'] = weekly_value_cal('000905.XSHG', trade_day_list, fee_rate = 0)['value']
ZZ500_value_weekly.plot(figsize = (20,12))
半倉t+0——基礎策略¶
獲取A股日內收益的方法最直接的是每日開盤買入、收盤賣出,但是由於t+1交易機制的限制,當日買入的股票無法當日賣出。因此如果想獲取這部分收益,需要一部分底倉的存在。所謂半倉策略,即初始倉位里有一半現金和一半股票,每日開盤時現金購入股票,倉位變為滿倉,收盤時將倉位中原有的股票賣出,回到半倉狀態,如此循環往複。基礎操作如下:
【1】初始淨值假設為1,定義基準為證券本身。
【2】在第一日開盤時買入半倉,第二天開盤時再補至滿倉,收盤時賣出一半股票,恢複半倉狀態,如此循環往複。
【3】計算手續費,印花稅下策略淨值走勢和績效表現。
首先我們測試了在不考慮周內異象的半倉t+0策略績效,可以發現,從指數的角度來講,由於高昂的手續費以及印花稅,本來可觀的日內收益收益被完全抹平,其數值低於基準收益很多。接下來,我們將通過兩種不同方式從周內異象對層面上嘗試對半倉t+0策略進行優化。
#不考慮周內異象獲取價值數據
#(買入時萬分之三,賣出時萬分之三加千分之一印花稅)
def basic_value_cal(security, trade_day_list, fee_rate = 0.0003, tax_rate = 0.001):
value_df = pd.DataFrame()
price_df = get_price(security, start_date = trade_day_list[0], end_date = trade_day_list[-1], fields = ['open', 'close'])
price_df = price_df.dropna()
value_df['benchmark'] = price_df['close'] / price_df.loc[price_df.index[0], 'close']
date_list = list(value_df.index)
value_list_1 = [(1- fee_rate - tax_rate) * price_df.loc[price_df.index[0], 'close']]
value_list_2 = [price_df.loc[price_df.index[0], 'open']]
a = 1
value_list = [value_list_1[0] + value_list_2[0]]
for date in date_list[1:]:
location = date_list.index(date)
last_date = date_list[location - 1]
if a == 1:
value_1 = value_list_1[location - 1] * price_df.loc[date, 'close'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * price_df.loc[date, 'close'] * (1 - fee_rate) / price_df.loc[date, 'open']
value_list_2.append(value_2)
a = 0
else:
value_1 = value_list_1[location - 1] * price_df.loc[date, 'close'] * (1 - fee_rate) / price_df.loc[date, 'open']
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * price_df.loc[date, 'close'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_2.append(value_2)
a = 1
value_list.append(value_1 + value_2)
# value_df['value_1'] = value_list_1
# value_df['value_1'] = value_df['value_1'] / value_list_1[0]
# value_df['value_2'] = value_list_2
# value_df['value_2'] = value_df['value_2'] / value_list_2[0]
value_df['value'] = value_list
value_df['value'] = value_df['value'] / value_list[0]
return value_df
#上證50指數等效組合價值曲線(基礎)
SZ50_value_basic = basic_value_cal('000016.XSHG', trade_day_list)
SZ50_value_basic['zero_cost'] = basic_value_cal('000016.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
SZ50_value_basic['fee'] = basic_value_cal('000016.XSHG', trade_day_list, tax_rate = 0)['value']
SZ50_value_basic['tax'] = basic_value_cal('000016.XSHG', trade_day_list, fee_rate = 0)['value']
SZ50_value_basic.plot(figsize = (20,12))
#滬深300指數等效組合價值曲線(基礎)
HS300_value_basic = basic_value_cal('000300.XSHG', trade_day_list)
HS300_value_basic['zero_cost'] = basic_value_cal('000300.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
HS300_value_basic['fee'] = basic_value_cal('000300.XSHG', trade_day_list, tax_rate = 0)['value']
HS300_value_basic['tax'] = basic_value_cal('000300.XSHG', trade_day_list, fee_rate = 0)['value']
HS300_value_basic.plot(figsize = (20,12))
#中證500指數等效組合價值曲線(基礎)
ZZ500_value_basic = basic_value_cal('000905.XSHG', trade_day_list)
ZZ500_value_basic['zero_cost'] = basic_value_cal('000905.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
ZZ500_value_basic['fee'] = basic_value_cal('000905.XSHG', trade_day_list, tax_rate = 0)['value']
ZZ500_value_basic['tax'] = basic_value_cal('000905.XSHG', trade_day_list, fee_rate = 0)['value']
ZZ500_value_basic.plot(figsize = (20,12))
半倉t+0——跳過周四¶
通過基礎半倉t+0策略的構建過程我們可以發現其獲取的是每一天的日內收益,但由於我們的研究發現,周四的日內收益顯著為負,因此對於基礎的半倉t+0策略,我們增加的改進方式如下:
【1】在策略執行到周四時不進行買賣操作。
【2】在周三開盤時買入的那部分股票在周五收盤時賣出。
【3】在周三收盤時賣出的資金在周五開盤時重新買入。
在這樣改進後觀察圖表,我們可以發現盡管高昂的手續費依然會抹平日內收益,但修正後的價值曲線相對於基礎價值曲線有了顯著的整體上移,特別地,在中證500的情況下策略曲線與基準幾乎重合,大幅度地提高了半倉t+0策略的績效。
#半倉t+0策略獲取價值數據且直接跳過周四
#(買入時萬分之三,賣出時萬分之三加千分之一印花稅)
def value_cal(security, trade_day_list, fee_rate = 0.0003, tax_rate = 0.001):
value_df = pd.DataFrame()
price_df = get_price(security, start_date = trade_day_list[0], end_date = trade_day_list[-1], fields = ['open', 'close'])
price_df = price_df.dropna()
value_df['benchmark'] = price_df['close'] / price_df.loc[price_df.index[0], 'close']
date_list = list(value_df.index)
value_list_1 = [(1- fee_rate) * price_df.loc[price_df.index[0], 'close']]
value_list_2 = [price_df.loc[price_df.index[0], 'open']]
a = 1
value_list = [value_list_1[0] + value_list_2[0]]
for date in date_list[1:]:
location = date_list.index(date)
last_date = date_list[location - 1]
weekday = date.weekday()
if weekday == 3:
date_list.remove(date)
value_df = value_df.drop([date])
continue
if a == 1:
value_1 = value_list_1[location - 1] * price_df.loc[date, 'close'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * price_df.loc[date, 'close'] * (1 - fee_rate) / price_df.loc[date, 'open']
value_list_2.append(value_2)
a = 0
else:
value_1 = value_list_1[location - 1] * price_df.loc[date, 'close'] * (1 - fee_rate) / price_df.loc[date, 'open']
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * price_df.loc[date, 'close'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_2.append(value_2)
a = 1
value_list.append(value_1 + value_2)
# value_df['value_1'] = value_list_1
# value_df['value_1'] = value_df['value_1'] / value_list_1[0]
# value_df['value_2'] = value_list_2
# value_df['value_2'] = value_df['value_2'] / value_list_2[0]
value_df['value'] = value_list
value_df['value'] = value_df['value'] / value_list[0]
return value_df
#上證50指數等效組合價值曲線
SZ50_value = value_cal('000016.XSHG', trade_day_list)
SZ50_value['zero_cost'] = value_cal('000016.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
SZ50_value['fee'] = value_cal('000016.XSHG', trade_day_list, tax_rate = 0)['value']
SZ50_value['tax'] = value_cal('000016.XSHG', trade_day_list, fee_rate = 0)['value']
SZ50_value.plot(figsize = (20,12))
#滬深300指數等效組合價值曲線
HS300_value = value_cal('000300.XSHG', trade_day_list)
HS300_value['zero_cost'] = value_cal('000300.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
HS300_value['fee'] = value_cal('000300.XSHG', trade_day_list, tax_rate = 0)['value']
HS300_value['tax'] = value_cal('000300.XSHG', trade_day_list, fee_rate = 0)['value']
HS300_value.plot(figsize = (20,12))
#中證500指數等效組合價值曲線
ZZ500_value = value_cal('000905.XSHG', trade_day_list)
ZZ500_value['zero_cost'] = value_cal('000905.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
ZZ500_value['fee'] = value_cal('000905.XSHG', trade_day_list, tax_rate = 0)['value']
ZZ500_value['tax'] = value_cal('000905.XSHG', trade_day_list, fee_rate = 0)['value']
ZZ500_value.plot(figsize = (20,12))
半倉t+0——調整周四¶
與上文相似地,對於周四顯著為負的日內收益我們還有另一種調整方式,即對於每個周四的買賣方式進行改變:
【1】對於周三收盤時賣出的資金,在周四收盤時進行買入。
【2】對於周三在開盤時買入的股票,在周四開盤時進行賣出。
這種改進方式可以在不影響周三以及周五倉位的情況下規避掉周四的負日內收益,觀察圖表可以發現,這一操作帶來的效果與上一種操作程度上相似,也可以顯著地提高半倉t+0策略的績效。
綜上所著,周內異象可以應用於多種形式的A股組合收益的提升,且由於我國股市高昂的交易成本,僅以獲得日內收益為目標的t+0策略表現並沒有交易頻率較低的基礎策略好。交易成本與超額收益的均衡在我國A股市場的投資中至關重要。
#半倉t+0策略獲取價值數據且周四時早上賣出半倉晚上買入半倉
#(買入時萬分之三,賣出時萬分之三加千分之一印花稅)
def thu_value_cal(security, trade_day_list, fee_rate = 0.0003, tax_rate = 0.001):
value_df = pd.DataFrame()
price_df = get_price(security, start_date = trade_day_list[0], end_date = trade_day_list[-1], fields = ['open', 'close'])
price_df = price_df.dropna()
value_df['benchmark'] = price_df['close'] / price_df.loc[price_df.index[0], 'close']
date_list = list(value_df.index)
value_list_1 = [(1- fee_rate) * price_df.loc[price_df.index[0], 'close']]
value_list_2 = [price_df.loc[price_df.index[0], 'open']]
a = 1
value_list = [value_list_1[0] + value_list_2[0]]
for date in date_list[1:]:
location = date_list.index(date)
last_date = date_list[location - 1]
weekday = date.weekday()
if weekday == 3:
if a == 1:
value_1 = value_list_1[location - 1] * price_df.loc[date, 'open'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * (1 - fee_rate)
value_list_2.append(value_2)
a = 0
else:
value_1 = value_list_1[location - 1] * (1 - fee_rate)
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * price_df.loc[date, 'open'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_2.append(value_2)
a = 1
else:
if a == 1:
value_1 = value_list_1[location - 1] * price_df.loc[date, 'close'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * price_df.loc[date, 'close'] * (1 - fee_rate) / price_df.loc[date, 'open']
value_list_2.append(value_2)
a = 0
else:
value_1 = value_list_1[location - 1] * price_df.loc[date, 'close'] * (1 - fee_rate) / price_df.loc[date, 'open']
value_list_1.append(value_1)
value_2 = value_list_2[location - 1] * price_df.loc[date, 'close'] * (1 - tax_rate - fee_rate) / price_df.loc[last_date, 'close']
value_list_2.append(value_2)
a = 1
value_list.append(value_1 + value_2)
# value_df['value_1'] = value_list_1
# value_df['value_1'] = value_df['value_1'] / value_list_1[0]
# value_df['value_2'] = value_list_2
# value_df['value_2'] = value_df['value_2'] / value_list_2[0]
value_df['value'] = value_list
value_df['value'] = value_df['value'] / value_list[0]
return value_df
#上證50指數等效組合價值曲線(含周四)
SZ50_value_thu = thu_value_cal('000016.XSHG', trade_day_list)
SZ50_value_thu['zero_cost'] = thu_value_cal('000016.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
SZ50_value_thu['fee'] = thu_value_cal('000016.XSHG', trade_day_list, tax_rate = 0)['value']
SZ50_value_thu['tax'] = thu_value_cal('000016.XSHG', trade_day_list, fee_rate = 0)['value']
SZ50_value_thu.plot(figsize = (20,12))
#滬深300指數等效組合價值曲線(含周四)
HS300_value_thu = thu_value_cal('000300.XSHG', trade_day_list)
HS300_value_thu['zero_cost'] = thu_value_cal('000300.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
HS300_value_thu['fee'] = thu_value_cal('000300.XSHG', trade_day_list, tax_rate = 0)['value']
HS300_value_thu['tax'] = thu_value_cal('000300.XSHG', trade_day_list, fee_rate = 0)['value']
HS300_value_thu.plot(figsize = (20,12))
#中證500指數等效組合價值曲線(含周四)
ZZ500_value_thu = thu_value_cal('000905.XSHG', trade_day_list)
ZZ500_value_thu['zero_cost'] = thu_value_cal('000905.XSHG', trade_day_list, fee_rate = 0, tax_rate = 0)['value']
ZZ500_value_thu['fee'] = thu_value_cal('000905.XSHG', trade_day_list, tax_rate = 0)['value']
ZZ500_value_thu['tax'] = thu_value_cal('000905.XSHG', trade_day_list, fee_rate = 0)['value']
ZZ500_value_thu.plot(figsize = (20,12))