主頁 > 後端開發 > 研報復現系列(一):【方正證券】跟蹤聰明錢:從分鐘行情資料到選股因子

研報復現系列(一):【方正證券】跟蹤聰明錢:從分鐘行情資料到選股因子

2021-04-13 11:16:40 後端開發

1.研報概述

本文是研報復現系列的第一篇,文本復現了【方正證券】的研報【跟蹤聰明錢:從分鐘行情資料到選股因子】,

該研報嘗試從分鐘行情資料中挖掘出那些聰明人(即機構)所做的交易,稱為“聰明錢”,并量化這些聰明錢對后市看漲、看跌的情緒觀點,進而跟隨聰明錢進行選股,

2.研究環境

JointQuant

import datetime
import math
import matplotlib.pyplot as plt
from jqdata import *
import pandas as pd
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False 

3.研報復現

3.1 資料獲取

本文資料來源于聚寬JoinQuant,用到的介面名和功能如下表所示,具體引數請查閱聚寬的API檔案,

介面名功能
get_bars獲取行情資料
get_price獲取獲取資料、是否停牌、漲跌停價
get_trade_days獲取一段時間內的交易日串列
get_extras查詢特定股票是否是ST股
get_all_securities獲取股票資料,包括上市時間
get_index_stocks獲取指數成分股

3.2 劃分聰明錢

聰明錢在交易程序中往往呈現“單筆訂單數量更大、訂單報價更為激進”

因此,某分鐘的交易的聰明度指標S定義為:S= ∣ R t ∣ \left| Rt \right| Rt / / / V t \sqrt{Vt} Vt ?

Rt為第t分鐘的漲跌幅,Vt為第t分鐘的成交量,S的值越大,則說明此交易越聰明,

我們封裝一個獲取分鐘行情資料以及S值的函式,方便后面的研究使用,

def get_minute_data(
    security,#股票代碼,可以是list或者字串
    count,#獲取多少條資料
    end_dt=datetime.datetime.now()
):
df = get_bars(
        security,
count = count,
include_now=True,
unit = '1m',
fields = ['date','open','close','volume'],
df = True,
end_dt = end_dt
    )
    
df['R'] = (df['close'] - df['open'])/df['open']#漲跌幅
df['S'] = abs(df['R'])/df['volume']**0.5*10000 #S值,乘10000為了方便畫圖
df['volume'] = df['volume']/10000#volume除以10000方便畫圖
return df

原研報利用成交量與計算出的S值來劃分聰明錢交易,

劃分演算法為:
step1:對于特定股票,特定時間段的分鐘級交易資料,計算得到每分鐘的S值,并將資料按照S值由大到小排序,
step2:求排序后的分鐘資料的交易量累加和,將交易量累加和占該該時間段總成交量前20%的分鐘交易視為由聰明錢進行的交易,

其中,閾值選取20%是因為在A股市場中機構交易者只貢獻了少量的交易量,原研報同時測驗了閾值選取其他數值時效果,其中20%是最優的.

我們以603198.XSHG在2021年4月8日下午14:00到14:30的分鐘資料為例,給出劃分聰明錢的程式并將結果可視化,

'''
功能:
    尋找聰明錢的交易并可視化
演算法:
     對于特定的股票,特定時間段的分鐘級交易資料,計算得到每分鐘的S值,并將資料按照S值由大到小排序,
     求排序后的分鐘資料的交易量累加,將交易量累加和占比前20%的分鐘視為由聰明錢交易的時刻,
     其中,閾值選取20%是因為在A股市場中機構交易者只貢獻了少量的交易量,原研報同時測驗了閾值選取其他數值時效果,其中20%是最優的,
資料:
     以603198.XSHG在2021年4月8日下午14:00到14:30的分鐘資料為例,
'''
#獲取資料并且個每個分鐘編號
df = get_minute_data('603198.XSHG',30,end_dt='2021-04-08 14:30:00')
df['bar_no'] = [_ for _ in range(1,31)]#每根bar的序號,方便x軸的顯示

#繪圖
fig = plt.figure(figsize=(14,10))
#S值與成交量圖
ax = fig.add_subplot(211)
h1 = ax.bar(df.index,df['volume'],label='成交量',width=0.5)#成交量
ax2 = ax.twinx()
h2 = ax2.scatter(df.index, df['S'],label='S因子',marker='o',color='r')#S值
plt.legend([h1,h2], [h1.get_label(),h2.get_label()], loc='best')
ax.grid()#網格
ax.set_ylabel(u"成交量(萬)")
ax.set_title(u"成交量與S因子值")
ax2.set_ylabel(u"S值(10e-4)")
ax.set_xlabel(u"時間序號")
plt.xticks(df.index.values, df['bar_no'])

#排序后的圖
ax = fig.add_subplot(212)
df = df.sort_values(by='S',ascending=False).reset_index(drop=True)#按S值排序
df['acc_volume_pct'] = df['volume'].cumsum()/df['volume'].sum()#計算累計成交量
df1 = df[df['acc_volume_pct']<=0.2]#累計成交量前20%
df2 = df[df['acc_volume_pct']>0.2]
ax.bar(df1.index,df1['volume'],color='r',width=0.5)#繪制累計成交量前20%的柱狀圖
h1 = ax.bar(df2.index,df2['volume'],label='成交量',width=0.5)#繪制余下的
ax2 = ax.twinx()
h2 = ax2.plot(df.index, df['acc_volume_pct'],label='累計成交量占比',marker='o',color='g')#累計成交量
plt.legend([h1,h2[0]], [h1.get_label(),h2[0].get_label()], loc='best')
ax.grid()
ax.set_ylabel(u"成交量(萬)")
ax.set_title(u"選擇聰明錢")
ax2.set_ylabel(u"累計成交量")
ax.set_xlabel(u"時間序號")
plt.xticks(df.index.values, df['bar_no'])

圖片

3.3 量化聰明錢對股票后市的情緒

我們劃分出了聰明錢的交易后,如何判斷聰明錢是對這只股票看多還是看空呢

對于特定股票、特定時段的分鐘行情資料,按照上述方法劃分出聰明錢的交易之后,我們可以構造聰明錢的情緒因子Q

Q = vwap_smart/vwap_all

vwap_smart和vwap_all分別是聰明錢的成交量加權平均價和該段時間內所有分鐘的成交量加權平均價

因子 Q 實際上反映了在該時間段中聰明錢參與交易的相對價位,之所以將其稱為聰明錢的情緒因子,是因為因子 Q 的值越大,表明聰明錢的交易越傾向于出現在價格較高處,這是逢高出貨的表現,反映了聰明錢的看空,消極情緒;因子Q的值越小,則表明聰明錢的交易多出現在價格較低處,這是逢低吸籌的表現,看多,積極情緒,

我們將情緒因子Q的計算封裝成一個函式

def calcQ(data_bar):
    '''
    功能:
        計算Q值
    引數:
        DataFrame型別,特定股票一定時間段內的分鐘資料(close,volume和S)
    回傳:
        浮點數,即Q值
    '''
    data_bar = data_bar.sort_values(by='S',ascending=False).reset_index(drop=True)#按S值排序
    data_bar['acc_volume_pct'] = data_bar['volume'].cumsum()/data_bar['volume'].sum()#計算累計成交量
    smart_bar = data_bar[data_bar['acc_volume_pct']<=0.2]#聰明錢交易,累計成交量前20%
    #vwmap:volume weighted average price,成交量加權平均價
    vwap_smart = (smart_bar['close']*smart_bar['volume']).sum()/smart_bar['volume'].sum()
    vwap_all = (data_bar['close']*data_bar['volume']).sum()/data_bar['volume'].sum()
    return vwap_smart/vwap_all

3.4因子選股能力驗證

本小節對上一小節量化出的聰明錢情緒因子的選股能力進行驗證,

樣本空間:滬深300成分股,剔除 ST 股和上市未滿 60 日的新股,以及漲停、停牌、跌停(原研報為全部A股,但資料量過多,受硬體限制本文縮小了樣本范圍,做了簡化)

測驗時間:2013-04-30 到 2016-05-31

測驗方法:每個月末時,對于選股空間的所有股票,計算其之前10天的分鐘級交易資料的Q值,和該股票次月的收益率,這樣每個月可以得到兩組序列,分別為每只股票的Q值和次月的收益率,計算其RankIC秩相關系數,可視化測驗時間周期內所有月份的兩組序列的相關系數,統計顯著相關情況,

概念解釋

RankIC秩相關系數為兩組序列中的元素在序列中的相對位置(即大小順序)的相關系數,

在因子模型中,因子與收益的相關系數大于0.03即認為是有效的因子,

為了實作對因子的驗證,我們封裝了兩個簡單的函式,分別用于

1.獲取特定日期滿足交易條件的股票(即在選股樣本中剔除ST股、漲停、跌停等)

2.獲取特定日期所在月的最后一日,用于獲取次月的行情資料

def get_stocks(date,index=None):
    '''
    功能:
        根據日期,獲取該日滿足交易要求的股票相關資料,即剔除ST股、上市未滿60天、停牌、跌漲停股
    引數:
        date,日期
        index,指數代碼,在特定指數的成分股中選股,預設時選股空間為全部A股
    回傳:
        DataFrame型別,索引為股票代碼,同時包含了價格資料,方便后續使用
    '''
    stocks = get_all_securities(
        types=['stock'],
        date=date
    )#該日正在上市的股票
    
    if index:#特定成分股
        stock_codes = get_index_stocks(index,date=date)#成分股
        stocks = stocks[stocks.index.isin(stock_codes)]
        
    #上市日期大于60個自然日
    #date = datetime.datetime.strptime(date,'%Y-%m-%d').date()
    stocks['datedelta'] = date - stocks['start_date']
    stocks = stocks[stocks['datedelta'] > datetime.timedelta(days=60)]
    
    #是否是ST股
    stocks['is_st'] = get_extras(
        info='is_st',
        security_list=list(stocks.index),
        count=1, 
        end_date=date
    ).T
    
    #漲停、跌停、停牌
    stocks_info = get_price(
        security = list(stocks.index),
        fields=['close','high','low','high_limit','low_limit','paused'],
        count=1,
        end_date=date,
        panel=False
    ).set_index('code').drop('time',axis=1)
    
    stocks['price'] = stocks_info['close']#順便保存價格,方便后續運算
    stocks['paused'] = stocks_info['paused'] == 1#是否停牌
    stocks['high_stop'] = stocks_info['high'] >= stocks_info['high_limit']#漲停
    stocks['low_stop'] = stocks_info['low'] <= stocks_info['low_limit']#跌停
    stocks = stocks[~(stocks['is_st'] | stocks['paused'] | stocks['high_stop'] | stocks['low_stop'])]   
    return stocks
def last_day_of_month(any_day):
    #獲取某個日期所在月份的最后一天,方便在計算次月收益時獲取次月的時間范圍
    next_month = any_day.replace(day=28) + datetime.timedelta(days=4)  # this will never fail
    return next_month - datetime.timedelta(days=next_month.day)

萬事俱備,我們現在統計每個月份每只股票的Q值和次月收益率,并計算秩相關系數、可視化,

#獲取交易日序列
trade_days = get_trade_days(start_date='2013-04-30', end_date='2016-05-31')
#存盤結果
result={
    'months':[],#月份序列
    'stocks':[],#股票序列
    'Q':[],#Q值序列
    'return':[]#次月收益序列
}
days_count = len(trade_days)
for i,date in enumerate(trade_days):#遍歷交易日
    if i == days_count-1:#如果是串列最后一個日期
        #可交易的股票
        stocks = get_stocks(trade_days[i],index='000300.XSHG')
        stocks_list = list(stocks.index)
        
        #每只股票下個月的價格
        next_month_bar = get_bars(
            stocks_list, 
            count = 1, 
            unit = '1M',
            fields = ['open','close'],
            df = True,
            include_now=True,
            end_dt = '2016-06-30'
        )
              
        '''
        獲取分鐘資料,用來計算Q
        
        這里一次性獲取所有股票的資料,來減少資料請求的次數,加速程式運行
        '''
        data = get_minute_data(
            stocks_list,
            2400,
            end_dt=trade_days[i]
        ).reset_index(level=0). rename(columns={'level_0':'code'})
        
        data_groups = data.groupby('code')#分鐘資料根據股票代碼分組
        
        result['stocks']+=stocks_list
        result['months']+=[trade_days[i].strftime('%Y-%m')]*stocks.shape[0]
        #依次計算每一只股票的Q值
        result['Q']+=[calcQ(data_bar) for name,data_bar in data_groups]
        result['return']+=list((next_month_bar['close']-next_month_bar['open'])/next_month_bar['open'])       
        break        
    if trade_days[i+1].month != trade_days[i].month:#每月的最后一個交易日
        stocks = get_stocks(trade_days[i],index='000300.XSHG')
        stocks_list = list(stocks.index)
        
        next_month_bar = get_bars(
            stocks_list, 
            count = 1, 
            unit = '1M',
            fields = ['open','close'],
            df = True,
            include_now = True,
            end_dt = last_day_of_month(trade_days[i+1])
        )
        
        data = get_minute_data(
            stocks_list,
            2400,
            end_dt=date
        ).reset_index(level=0).rename(columns={'level_0':'code'})
        data_groups = data.groupby('code')
        
        result['stocks']+=stocks_list
        result['months']+=[date.strftime('%Y-%m')]*stocks.shape[0]
        result['Q']+=[calcQ(data_bar) for name,data_bar in data_groups]
        result['return']+=list((next_month_bar['close']-next_month_bar['open'])/next_month_bar['open'])

df = pd.DataFrame(result)
'''
因為該段程式需要運行的時間較長,所以將運行出的結果存盤到檔案中
之后的研究可以直接讀取檔案
'''
df.to_excel('result.xlsx')
result = pd.read_excel('result.xlsx').drop('stocks',axis=1)
result_group = result.groupby('months')
#存盤月份和每月兩個序列的秩相關系數
result_df = {
    'month':[],
    'RankIC':[]
}
for month,month_result in result_group:
    result_df['month'].append(month)
    result_df['RankIC'].append(month_result.corr(method='spearman')['Q']['return'])
#以0.03作為閾值來劃分顯著和不顯著相關    
result_df = pd.DataFrame(result_df)
ic_plus = result_df[result_df['RankIC']>0.03]
ic_minus = result_df[result_df['RankIC']<-0.03]
ic_other = result_df[(result_df['RankIC']>=-0.03) &(result_df['RankIC']<=0.03)]
print('顯著為負: ',ic_minus.shape[0])
print('顯著為正: ',ic_plus.shape[0])
print('不顯著:',ic_other.shape[0])

#可視化
fig = plt.figure(figsize=(14,5))
ax1 = fig.add_subplot(111)
ax1.bar(ic_other.index, ic_other['RankIC'], align='center', width=0.5, color='pink')
ax1.bar(ic_plus.index, ic_plus['RankIC'], align='center', width=0.5, color='r')
ax1.bar(ic_minus.index, ic_minus['RankIC'], align='center', width=0.5, color='b')
ax1.set_ylabel(u"Rank IC")
ax1.set_title(u"因子 RankIC 的月度序列")
ax1.set_xlabel(u"時間" )
ax1.grid(axis='y')
plt.xticks(result_df.index, result_df['month'],ro

顯著為負: 22

顯著為正: 7

不顯著: 8

圖片

由結果來看,股票Q值與次月收益率在大部分時間顯著為負,這也許上文在定義Q時的分析一致,Q值越大,聰明錢對該只股票看跌,聰明錢在高位拋售該股,該股次月的收益率下降,

3.5 選股策略的構建與回測

研究方法:每月月底將股票前10日分數資料的Q值排序、根據Q值由小到大等分為5類,每一類分別建倉,可視化每一類的收益情況,

研究結果:在策略運行的時間周期內,5類持倉的收益率具有單調性,即Q值小的組收益率大,這也可以由之前的分析解釋,因為Q值小意味著聰明錢在低價吸入,聰明錢具有看多的情緒,

策略構建:基于研究結果,可構建兩種策略,

一種是多空對沖策略,即做多第一組的股票,做空第五組的股票,進行對沖,該對沖策略在本文的回測中的收益率高于五組分別做多的收益,且最大回撤更低,在原研報的所有A股的樣本中,對沖策略的收益率位于第一組和第三組之間,且最大回撤更低,

另一種是做多第一組的股票,但是要剔除前期漲幅過高的五分之一的樣本,原研報將這種選股組合稱為SMART組合,

回測規則:每月月底計算Q值、選股、調倉,等權重配置股票,即讓每只股票的(價格*持有數)占總資產的比例相同,初始資金100萬,交易費率0.003,選股空間、與上文相同,

為了方便回測,我們新建了一個倉位類,來對應每一組的賬戶,該類具有當前持有的股票串列、對應的價格、每只股票持有數量、現金、賬戶凈值這些屬性提供了更新價格、調整倉位的功能,并可以維護現金和賬戶凈值,

class Position():
    '''
    為了方便回測,我們新建了一個倉位類,來對應每一組的賬戶,
    該類具有當前持有的股票串列、對應的價格、每只股票持有數量、現金、賬戶凈值這些屬性
    提供了更新價格、調整倉位的功能,并可以維護現金和賬戶凈值
    '''
    def __init__(
        self,
        stocks=np.array([]),
        price=np.array([]),
        weight=np.array([]),
        cash=0
    ):
        self.stocks = stocks
        self.price = price
        self.weight = weight
        self.cash = cash
        if not self.stocks.size:
            self.net_value = self.cash
        else:
            self.net_value = (self.weight*self.price).sum()+self.cash
    def update_price(self,price):
        #傳入一個價格序列,對應持有股票的最新價格
        for i,p in enumerate(price):
            if not np.isnan(p):
                self.price[i] = p
        self.net_value = (self.weight*self.price).sum()+self.cash
    def change_position(self,new_stocks,price,commission):
        #更新倉位,傳入篩選出的股票串列和對應的價格
        
        for i,stock in enumerate(self.stocks):#原有股票不在新串列中,需要賣出
            if stock not in new_stocks:
                self.cash+=self.price[i]*self.weight[i]*(1-commission)
        
        #計算每一只股票應持有的數量
        new_weight = self.net_value*0.99/len(new_stocks)/price
        new_weight = [int(_) for _ in new_weight]
        
        for i,stock in enumerate(self.stocks):#原有股票在新串列中,需要調整數量
            if stock in new_stocks:
                weight_chg = new_weight[list(new_stocks).index(stock)] - self.weight[i]
                self.cash -= self.price[i]*weight_chg\
                              + abs(self.price[i]*weight_chg)*commission
                
        for i,stock in enumerate(new_stocks):#新增股票,需要買入
            if stock not in new_stocks:
                self.cash -= price[i]*new_weight[i]*(1+commission)
        self.stocks = new_stockps
        self.weight = new_weight
        self.price = price
        self.net_value = (self.weight*self.price).sum()+self.cash

多空對沖策略回測

capital_base = 1000000 #初始資金
commission = 0.003     #交易費率
#五組倉位
position_dict = {
    1:Position(cash=capital_base),
    2:Position(cash=capital_base),
    3:Position(cash=capital_base),
    4:Position(cash=capital_base),
    5:Position(cash=capital_base)
}
#凈值統計
net_value_dict = {
    'month':[],#時間
    1:[],#每一組每個月月末的凈值序列
    2:[],
    3:[],
    4:[],
    5:[]
}
trade_days = get_trade_days(start_date='2013-04-30', end_date='2016-05-31')
days_count = len(trade_days)
for i,date in enumerate(trade_days):#遍歷交易日
    if  i == days_count-1 or trade_days[i+1].month != trade_days[i].month:
        #print(trade_days[i])
        #可交易股票
        stocks = get_stocks(trade_days[i],index='000300.XSHG')
        stocks_list = list(stocks.index)
        
        #股票的前10天的分鐘資料,并根據股票分組
        data = get_minute_data(
            stocks_list,
            2400,
            end_dt=trade_days[i]
        ).reset_index(level=0).rename(columns={'level_0':'code'})
        data_groups = data.groupby('code')
        
        #計算每一只股票的Q值
        stocks['Q'] = [calcQ(data_bar) for name,data_bar in data_groups]
        #Q值排序
        stocks = stocks.sort_values(by='Q').reset_index()
        lens = stocks.shape[0]
        #根據Q值的大小等分為5組
        s_1 = stocks[stocks['Q'].isin(stocks['Q'][:int(0.2*lens)])].reset_index()
        s_2 = stocks[stocks['Q'].isin(stocks['Q'][int(0.2*lens):int(0.4*lens)])].reset_index()
        s_3 = stocks[stocks['Q'].isin(stocks['Q'][int(0.4*lens):int(0.6*lens)])].reset_index()
        s_4 = stocks[stocks['Q'].isin(stocks['Q'][int(0.6*lens):int(0.8*lens)])].reset_index()
        s_5 = stocks[stocks['Q'].isin(stocks['Q'][int(0.8*lens):])].reset_index()
        
        #如果有持倉資料
        if position_dict[1].stocks.size:
            #更新持倉價格
            for cls in range(1,6):
                    position_dict[cls].update_price(
                        price=get_price(                       
                            security = list(position_dict[cls].stocks),
                            count = 1,
                            end_date = trade_days[i],
                            panel = False
                        )['close']
                    )
        #調倉
        position_dict[1].change_position(new_stocks=s_1['index'],price = s_1['price'],commission=commission)            
        position_dict[2].change_position(new_stocks=s_2['index'],price = s_2['price'],commission=commission)
        position_dict[3].change_position(new_stocks=s_3['index'],price = s_3['price'],commission=commission)
        position_dict[4].change_position(new_stocks=s_4['index'],price = s_4['price'],commission=commission)
        position_dict[5].change_position(new_stocks=s_5['index'],price = s_5['price'],commission=commission)
        
        #添加該月的結果
        net_value_dict['month'].append(trade_days[i])
        for cls in range(1,6):
            net_value_dict[cls].append(position_dict[cls].net_value)
            continue
           
#可視化
net_value_df = pd.DataFrame(net_value_dict)
net_value_df.to_excel('net_value_df.xlsx')
net_value_df = pd.DataFrame(net_value_dict)
plt.figure(figsize=(14,6))
net_value_df[1] = (net_value_df[1]/1000000)
net_value_df[2] = (net_value_df[2]/1000000)
net_value_df[3] = (net_value_df[3]/1000000)
net_value_df[4] = (net_value_df[4]/1000000)
net_value_df[5] = (net_value_df[5]/1000000)
net_value_df['1與5對沖'] = net_value_df[1] - net_value_df[5]+1
index=get_bars(
            '000300.XSHG', 
            count = 37, 
            unit = '1M',
            fields = ['close'],
            df = True,
            include_now=True,
            end_dt = '2016-05-31'
        )['close']
index = index/index[0]
fig = plt.figure(figsize=(14,5))
ax = fig.add_subplot(111)
ax.plot(net_value_df['month'].index,net_value_df[1],label='1組')
ax.plot(net_value_df['month'].index,net_value_df[2],label='2組')
ax.plot(net_value_df['month'].index,net_value_df[3],label='3組')
ax.plot(net_value_df['month'].index,net_value_df[4],label='4組')
ax.plot(net_value_df['month'].index,net_value_df[5],label='5組')
ax.plot(net_value_df['month'].index,net_value_df['1與5對沖'],label='1組與5組對沖',linestyle='--',linewidth=3)
ax.plot(net_value_df['month'].index,index,linestyle='--',label='滬深300',color='black',linewidth=3)
ax.set_xlabel(u"時間")
ax.set_ylabel(u"凈值")
ax.set_title(u"分組表現與多空對沖凈值")
plt.xticks(net_value_df['month'].index,net_value_df['month'],rotation=90)
plt.legend()

圖片

SMART選股策略

capital_base = 1000000
position_dict_2 = {
    1:Position(cash=capital_base)
}
net_value_dict_2 = {
    'month':[],
    1:[]
}

trade_days = get_trade_days(start_date='2013-04-30', end_date='2016-05-31')
days_count = len(trade_days)
commission = 0.003
for i in range(days_count):
    if i == days_count-1 or trade_days[i+1].month != trade_days[i].month:
        stocks = get_stocks(trade_days[i],index='000300.XSHG')
        stocks_list = list(stocks.index)
        data = get_minute_data(
            stocks_list,
            2400,
            end_dt=trade_days[i]
        ).reset_index(level=0).rename(columns={'level_0':'code'})
        data_groups = data.groupby('code')
        stocks['Q'] = [calcQ(data_bar) for name,data_bar in data_groups]
        stocks = stocks.sort_values(by='Q').reset_index()
        lens = stocks.shape[0]
        s_1 = stocks[stocks['Q'].isin(stocks['Q'][:int(0.2*lens)])].reset_index(drop=True)
        month_bar = get_bars(
            list(s_1['index']), 
            count = 1, 
            unit = '1M',
            fields = ['open','close'],
            df = True,
            include_now=True,
            end_dt = trade_days[i]
        ).reset_index(drop=True)
        
        #剔除漲幅過高的20%的股票
        s_1['ratio'] = (month_bar['close'] - month_bar['open'])/month_bar['open']
        s_1 = s_1.sort_values(by='ratio').reset_index(drop=True)
        lens = s_1.shape[0]
        s_1 = s_1[s_1['ratio'].isin(s_1['ratio'][:int(0.8*lens)])]
        
        
        if position_dict_2[1].stocks.size:
                position_dict_2[1].update_price(
                    price=get_price(                       
                        security = list(position_dict_2[1].stocks),
                        count = 1,
                        end_date = trade_days[i],
                        panel = False
                    )['close']
                )
        position_dict_2[1].change_position(new_stocks=s_1['index'],price = s_1['price'],commission=commission)            
        net_value_dict_2['month'].append(trade_days[i])
        net_value_dict_2[1].append(position_dict_2[1].net_value)
        
        
#可視化
net_value_df_2 = pd.DataFrame(net_value_dict_2)
net_value_df_2[1] = (net_value_df_2[1]/1000000)
index=get_bars(
            '000300.XSHG', 
            count = 37, 
            unit = '1M',
            fields = ['close'],
            df = True,
            include_now=True,
            end_dt = '2016-05-31'
        )['close']
index = index/index[0]
fig=plt.figure(figsize=(14,5))
ax = fig.add_subplot(111)
ax.plot(net_value_df_2['month'].index,net_value_df_2[1],label='SMART選股組合')
ax.plot(net_value_df_2['month'].index,index,label='滬深300')
ax.set_xlabel(u"時間")
ax.set_ylabel(u"凈值")
ax.set_title(u"SMART選股組合")
plt.xticks(net_value_df_2['month'].index,net_value_df['month'],rotation=90)
plt.legend()

圖片

4.總結

本文基本復現了【方正證券-跟蹤聰明錢:從分鐘行情資料到選股因子】的研究內容,同時得到了與原研報相近的結果,

本文的不足:

1.因為軟硬體條件的限制,壓縮了原研報的樣本空間,

2.缺少原研報中因子風險特性的研究,

5.本文作者

蔡金航 哈爾濱工業大學威海校區 計算機科學與技術學院

舒意茗 哈爾濱工業大學威海校區 汽車工程學院

寫在最后

我們是國內普通高校的在校學生,同時也是量化投資的初學者,我們的學校不是清北復交,也沒有金融工程實驗室,同時地處三線小城,因此我們在校期間較難獲得量化實習機會,但我們期待與業界進行溝通、交流,

蔡金航同學是我們其中的一員,其在尋找暑期量化實習時,收到了幾家私募和券商金工組的筆試邀請,筆試內容皆為在給定時間內復現出一篇金工研報,蔡同學受到啟發,發覺復現金工研報是我們學習量化策略、鍛煉程式設計能力同時也是與業界交流的很好的途徑,

在蔡同學的建議下,我們開啟研報復現系列的創作,記錄我們的學習程序,并將我們的創作內容分享出來,與讀者們一起交流、學習、進步,

我們的水平有限,創作的內容難免會有錯誤或不嚴謹的內容,我們歡迎讀者的批評指正,

如果您對我們的內容感興趣,請聯系我們:cai_jinhang@foxmail.com

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/275484.html

標籤:python

上一篇:2021年最新合適學Python小白練習的24道題和答案決議《不看后悔,墻裂推薦》

下一篇:Python25道練習題及詳細答案決議,爆肝八小時總結

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more