[TOC]

주식 시장의 다중 요인 모델에 대한 수많은 연구 보고서는 풍부한 이론과 실제를 담고 있습니다. 디지털 통화 시장은 통화 수, 총 시장 가치, 거래량, 파생상품 시장 측면에서 요인 연구에 충분합니다. 이 글은 주로 양적 전략 초보자를 대상으로 하며 복잡한 수학적 원리와 통계적 분석은 포함하지 않습니다. 선물 시장을 데이터 소스로 사용하여 요인 지표의 평가를 용이하게 하기 위해 간단한 요인 연구 프레임워크를 구축했습니다.
요인은 지표로 볼 수 있으며 표현식으로 작성할 수 있습니다. 요인은 지속적으로 변화하며 미래 수익 정보를 반영합니다. 일반적으로 요인은 투자 논리를 나타냅니다.
예를 들어, 종가 요인은 주가가 미래 수익을 예측할 수 있다는 가정에 근거합니다. 주가가 높을수록 미래 수익이 높아집니다(또는 수익이 낮아집니다). 이 요인에 기반한 포트폴리오를 구축하는 것은 실제로 투자입니다. 고가 주식을 매수하기 위해 정기적으로 포지션을 회전하는 모델/전략. 일반적으로 지속적으로 초과수익을 창출할 수 있는 요인을 알파라고 부릅니다. 예를 들어, 시가총액 요인과 모멘텀 요인은 효과적인 요인임이 학계와 투자계에서 검증되었습니다.
주식 시장이든 디지털 통화 시장이든 복잡한 시스템입니다. 어떤 요인도 미래 수익을 완벽하게 예측할 수는 없지만, 여전히 어느 정도 예측 가능합니다. 효과적인 알파(투자 모델)는 점점 더 많은 자금이 투자됨에 따라 효과가 없어집니다. 하지만 이 과정을 통해 시장에 다른 모델이 생겨나 새로운 알파가 탄생하게 됩니다. 시가총액 요인은 한때 A주 시장에서 매우 효과적인 전략이었습니다. 시가총액이 가장 낮은 주식 10개를 사서 하루에 한 번씩 조정하면 됩니다. 2007년의 10년 백테스트는 수익률의 400배 이상을 벌어들일 것입니다. 전체 시장을 능가합니다. 그러나 2017년의 대형주 시장은 소규모 시가총액 요소의 비효과성을 반영했고, 대신 가치 요소가 인기를 얻었습니다. 따라서 알파의 검증과 활용 사이에서 끊임없이 균형과 실험을 하는 것이 필요합니다.
우리가 찾는 요인은 전략을 수립하는 기초입니다. 더 나은 전략은 여러 개의 관련 없는 효과적인 요인을 결합하여 구성할 수 있습니다.
import requests
from datetime import date,datetime
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import requests, zipfile, io
%matplotlib inline
현재, 2022년 초부터 현재까지 바이낸스 USDT 영구 선물의 시간당 K-라인 데이터는 150개 통화를 넘어섰습니다. 앞서 언급했듯이, 요인 모델은 특정 통화만이 아닌 모든 통화를 타겟으로 하는 통화 선택 모델입니다. K-라인 데이터는 최고 개장가와 최저 종가, 거래량, 거래 건수, 활발한 매수량 등의 데이터를 포함합니다. 이러한 데이터는 미국 주가 지수, 금리 인상 예상 등 모든 요소의 출처가 아닙니다. , 수익성, 온체인 데이터, 소셜 미디어 관심 등 덜 인기 있는 데이터 소스에서도 효과적인 알파가 나타날 수 있지만, 기본적인 거래량과 가격 데이터도 충분합니다.
## 当前交易对
Info = requests.get('https://fapi.binance.com/fapi/v1/exchangeInfo')
symbols = [s['symbol'] for s in Info.json()['symbols']]
symbols = list(filter(lambda x: x[-4:] == 'USDT', [s.split('_')[0] for s in symbols]))
print(symbols)
Out:
['BTCUSDT', 'ETHUSDT', 'BCHUSDT', 'XRPUSDT', 'EOSUSDT', 'LTCUSDT', 'TRXUSDT', 'ETCUSDT', 'LINKUSDT',
'XLMUSDT', 'ADAUSDT', 'XMRUSDT', 'DASHUSDT', 'ZECUSDT', 'XTZUSDT', 'BNBUSDT', 'ATOMUSDT', 'ONTUSDT',
'IOTAUSDT', 'BATUSDT', 'VETUSDT', 'NEOUSDT', 'QTUMUSDT', 'IOSTUSDT', 'THETAUSDT', 'ALGOUSDT', 'ZILUSDT',
'KNCUSDT', 'ZRXUSDT', 'COMPUSDT', 'OMGUSDT', 'DOGEUSDT', 'SXPUSDT', 'KAVAUSDT', 'BANDUSDT', 'RLCUSDT',
'WAVESUSDT', 'MKRUSDT', 'SNXUSDT', 'DOTUSDT', 'DEFIUSDT', 'YFIUSDT', 'BALUSDT', 'CRVUSDT', 'TRBUSDT',
'RUNEUSDT', 'SUSHIUSDT', 'SRMUSDT', 'EGLDUSDT', 'SOLUSDT', 'ICXUSDT', 'STORJUSDT', 'BLZUSDT', 'UNIUSDT',
'AVAXUSDT', 'FTMUSDT', 'HNTUSDT', 'ENJUSDT', 'FLMUSDT', 'TOMOUSDT', 'RENUSDT', 'KSMUSDT', 'NEARUSDT',
'AAVEUSDT', 'FILUSDT', 'RSRUSDT', 'LRCUSDT', 'MATICUSDT', 'OCEANUSDT', 'CVCUSDT', 'BELUSDT', 'CTKUSDT',
'AXSUSDT', 'ALPHAUSDT', 'ZENUSDT', 'SKLUSDT', 'GRTUSDT', '1INCHUSDT', 'CHZUSDT', 'SANDUSDT', 'ANKRUSDT',
'BTSUSDT', 'LITUSDT', 'UNFIUSDT', 'REEFUSDT', 'RVNUSDT', 'SFPUSDT', 'XEMUSDT', 'BTCSTUSDT', 'COTIUSDT',
'CHRUSDT', 'MANAUSDT', 'ALICEUSDT', 'HBARUSDT', 'ONEUSDT', 'LINAUSDT', 'STMXUSDT', 'DENTUSDT', 'CELRUSDT',
'HOTUSDT', 'MTLUSDT', 'OGNUSDT', 'NKNUSDT', 'SCUSDT', 'DGBUSDT', '1000SHIBUSDT', 'ICPUSDT', 'BAKEUSDT',
'GTCUSDT', 'BTCDOMUSDT', 'TLMUSDT', 'IOTXUSDT', 'AUDIOUSDT', 'RAYUSDT', 'C98USDT', 'MASKUSDT', 'ATAUSDT',
'DYDXUSDT', '1000XECUSDT', 'GALAUSDT', 'CELOUSDT', 'ARUSDT', 'KLAYUSDT', 'ARPAUSDT', 'CTSIUSDT', 'LPTUSDT',
'ENSUSDT', 'PEOPLEUSDT', 'ANTUSDT', 'ROSEUSDT', 'DUSKUSDT', 'FLOWUSDT', 'IMXUSDT', 'API3USDT', 'GMTUSDT',
'APEUSDT', 'BNXUSDT', 'WOOUSDT', 'FTTUSDT', 'JASMYUSDT', 'DARUSDT', 'GALUSDT', 'OPUSDT', 'BTCUSDT',
'ETHUSDT', 'INJUSDT', 'STGUSDT', 'FOOTBALLUSDT', 'SPELLUSDT', '1000LUNCUSDT', 'LUNA2USDT', 'LDOUSDT',
'CVXUSDT']
print(len(symbols))
Out:
153
#获取任意周期K线的函数
def GetKlines(symbol='BTCUSDT',start='2020-8-10',end='2021-8-10',period='1h',base='fapi',v = 'v1'):
Klines = []
start_time = int(time.mktime(datetime.strptime(start, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000
end_time = min(int(time.mktime(datetime.strptime(end, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000,time.time()*1000)
intervel_map = {'m':60*1000,'h':60*60*1000,'d':24*60*60*1000}
while start_time < end_time:
mid_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]
url = 'https://'+base+'.binance.com/'+base+'/'+v+'/klines?symbol=%s&interval=%s&startTime=%s&endTime=%s&limit=1000'%(symbol,period,start_time,mid_time)
res = requests.get(url)
res_list = res.json()
if type(res_list) == list and len(res_list) > 0:
start_time = res_list[-1][0]+int(period[:-1])*intervel_map[period[-1]]
Klines += res_list
if type(res_list) == list and len(res_list) == 0:
start_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]
if mid_time >= end_time:
break
df = pd.DataFrame(Klines,columns=['time','open','high','low','close','amount','end_time','volume','count','buy_amount','buy_volume','null']).astype('float')
df.index = pd.to_datetime(df.time,unit='ms')
return df
start_date = '2022-1-1'
end_date = '2022-09-14'
period = '1h'
df_dict = {}
for symbol in symbols:
df_s = GetKlines(symbol=symbol,start=start_date,end=end_date,period=period,base='fapi',v='v1')
if not df_s.empty:
df_dict[symbol] = df_s
symbols = list(df_dict.keys())
print(df_s.columns)
Out:
Index(['time', 'open', 'high', 'low', 'close', 'amount', 'end_time', 'volume',
'count', 'buy_amount', 'buy_volume', 'null'],
dtype='object')
먼저 K-라인 데이터에서 관심 있는 데이터(종가, 시가, 거래량, 거래건수, 활성 매수율)를 추출하고, 이 데이터를 기초로 필요한 요소를 처리합니다.
df_close = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())
df_open = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())
df_volume = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())
df_buy_ratio = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())
df_count = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())
for symbol in df_dict.keys():
df_s = df_dict[symbol]
df_close[symbol] = df_s.close
df_open[symbol] = df_s.open
df_volume[symbol] = df_s.volume
df_count[symbol] = df_s['count']
df_buy_ratio[symbol] = df_s.buy_amount/df_s.amount
df_close = df_close.dropna(how='all')
df_open = df_open.dropna(how='all')
df_volume = df_volume.dropna(how='all')
df_count = df_count.dropna(how='all')
df_buy_ratio = df_buy_ratio.dropna(how='all')
시장 지수 실적을 살펴보면, 연초 이래 60% 하락해 매우 암울하다고 할 수 있습니다.
df_norm = df_close/df_close.fillna(method='bfill').iloc[0] #归一化
df_norm.mean(axis=1).plot(figsize=(15,6),grid=True);
#最终指数收益图

회귀 분석 방법 다음 기간의 수익률을 종속변수로 취하고, 검정할 요인을 독립변수로 취하고, 회귀분석을 통해 얻은 계수를 해당 요인의 수익률로 정합니다. 회귀 방정식을 구성한 후 일반적으로 계수 t 값의 절대 평균, 계수 t 값의 절대 값 시퀀스가 2보다 큰 비율, 연간화된 요인 수익률, 연간화된 요인 수익률 변동성, 샤프 비율을 참조합니다. 팩터 수익률과 다른 매개변수. 팩터 효과성과 변동성. 한 번에 여러 요소를 회귀시킬 수 있습니다. 자세한 내용은 barra 설명서를 참조하세요.
IC, IR 및 기타 지표 소위 IC는 요인과 다음 기간의 수익률 간의 상관 계수입니다. RANK_IC는 현재 일반적으로 사용되는데, 이는 요인 순위와 다음 기간의 주식 수익률 간의 상관 계수입니다. IR은 일반적으로 IC 시퀀스의 평균/IC 시퀀스의 표준 편차입니다.
계층적 회귀 이 글에서는 테스트할 요인을 정렬하고, 그룹 백테스팅을 위해 통화를 N개 그룹으로 나누고, 고정 기간을 사용하여 포지션을 조정하는 방법을 사용할 것입니다. 상황이 이상적이라면 N개 통화 그룹의 수익률은 좋은 단조성을 보이며 단조적으로 증가하거나 감소하며 각 그룹 간의 수익률 격차는 커질 것입니다. 이런 요소는 더 나은 판별력으로 반영됩니다. 첫 번째 그룹이 가장 높은 수익률을 가지고 마지막 그룹이 가장 낮은 수익률을 가지고 있다면, 첫 번째 그룹에서 롱 포지션을 취하고 마지막 그룹에서 숏 포지션을 취합니다. 최종 수익률은 샤프 비율의 기준 지표입니다.
요인에 따라 선정된 통화는 작은 것부터 큰 것 순으로 3개 그룹으로 나뉜다. 각 통화 그룹은 약 1/3을 차지한다. 요인이 효과적이라면 각 그룹의 비중이 작을수록 더 높다. 수익률이지만 또한 각 통화에 할당된 자금이 비교적 크다는 것을 의미합니다. 롱 포지션과 숏 포지션이 각각 1배 레버리지이고 첫 번째와 마지막 그룹이 각각 10개 통화인 경우 각각 10%를 차지합니다. 공매도가 상승하면, 투자금액이 2배로 늘어나면, 반등폭은 20%가 됩니다. 이에 따라, 그룹수가 50개이면, 반등폭은 4%가 됩니다. 통화를 다양화하면 블랙스완 위험을 줄일 수 있습니다. 첫 번째 그룹(가장 작은 팩터 값)에 롱 포지션을 취하고, 세 번째 그룹에서 숏 포지션을 취하세요. 요인이 클수록 수익률도 높아지므로 롱 포지션과 숏 포지션을 반전시키거나 간단히 요인을 음수나 역수로 만들 수 있습니다.
요인의 예측 능력은 일반적으로 최종 백테스트 수익률과 샤프 비율을 기준으로 대략적으로 평가할 수 있습니다. 또한, 요인 표현이 간단한지, 그룹화 크기에 둔감한지, 포지션 조정 간격에 둔감한지, 백테스트의 초기 시간에 둔감한지 등도 참고할 필요가 있습니다.
포지션 조정 빈도에 있어서 주식시장은 대체로 5일, 10일, 1개월 주기를 갖는 반면, 디지털 화폐시장은 그러한 주기가 너무 길다는 것은 의심할 여지가 없으며, 실제 시장에서의 시장 상황은 다음과 같이 모니터링됩니다. 실시간이므로 특정 사이클을 고수하기 어렵습니다. 포지션을 다시 조정할 필요가 없으므로 실제 거래에서는 실시간으로 또는 짧은 시간 간격으로 포지션을 조정합니다.
포지션을 닫는 방법과 관련하여, 전통적인 방법에 따르면 다음 정렬 시 그룹에 포함되지 않은 포지션을 닫을 수 있습니다. 하지만 실시간 포지션 조정의 경우 일부 통화가 경계선에 도달하여 포지션이 앞뒤로 닫힐 수 있습니다. 따라서 이 전략은 그룹 변경을 기다리고 반대 방향으로 포지션을 열어야 할 때 포지션을 닫는 접근 방식을 채택합니다. 예를 들어, 첫 번째 그룹에서 롱 포지션을 취하는 경우 롱 포지션의 통화가 세 번째 그룹은 포지션을 종료하고 단기 포지션을 취할 수 있습니다. 매일이나 8시간마다 등 일정 주기로 포지션을 청산하면, 그룹에 속하지 않아도 포지션을 청산할 수 있습니다. 더 많이 시도해 볼 수 있습니다.
#回测引擎
class Exchange:
def __init__(self, trade_symbols, fee=0.0004, initial_balance=10000):
self.initial_balance = initial_balance #初始的资产
self.fee = fee
self.trade_symbols = trade_symbols
self.account = {'USDT':{'realised_profit':0, 'unrealised_profit':0, 'total':initial_balance, 'fee':0, 'leverage':0, 'hold':0}}
for symbol in trade_symbols:
self.account[symbol] = {'amount':0, 'hold_price':0, 'value':0, 'price':0, 'realised_profit':0,'unrealised_profit':0,'fee':0}
def Trade(self, symbol, direction, price, amount):
cover_amount = 0 if direction*self.account[symbol]['amount'] >=0 else min(abs(self.account[symbol]['amount']), amount)
open_amount = amount - cover_amount
self.account['USDT']['realised_profit'] -= price*amount*self.fee #扣除手续费
self.account['USDT']['fee'] += price*amount*self.fee
self.account[symbol]['fee'] += price*amount*self.fee
if cover_amount > 0: #先平仓
self.account['USDT']['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount #利润
self.account[symbol]['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount
self.account[symbol]['amount'] -= -direction*cover_amount
self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
if open_amount > 0:
total_cost = self.account[symbol]['hold_price']*direction*self.account[symbol]['amount'] + price*open_amount
total_amount = direction*self.account[symbol]['amount']+open_amount
self.account[symbol]['hold_price'] = total_cost/total_amount
self.account[symbol]['amount'] += direction*open_amount
def Buy(self, symbol, price, amount):
self.Trade(symbol, 1, price, amount)
def Sell(self, symbol, price, amount):
self.Trade(symbol, -1, price, amount)
def Update(self, close_price): #对资产进行更新
self.account['USDT']['unrealised_profit'] = 0
self.account['USDT']['hold'] = 0
for symbol in self.trade_symbols:
if not np.isnan(close_price[symbol]):
self.account[symbol]['unrealised_profit'] = (close_price[symbol] - self.account[symbol]['hold_price'])*self.account[symbol]['amount']
self.account[symbol]['price'] = close_price[symbol]
self.account[symbol]['value'] = abs(self.account[symbol]['amount'])*close_price[symbol]
self.account['USDT']['hold'] += self.account[symbol]['value']
self.account['USDT']['unrealised_profit'] += self.account[symbol]['unrealised_profit']
self.account['USDT']['total'] = round(self.account['USDT']['realised_profit'] + self.initial_balance + self.account['USDT']['unrealised_profit'],6)
self.account['USDT']['leverage'] = round(self.account['USDT']['hold']/self.account['USDT']['total'],3)
#测试因子的函数
def Test(factor, symbols, period=1, N=40, value=300):
e = Exchange(symbols, fee=0.0002, initial_balance=10000)
res_list = []
index_list = []
factor = factor.dropna(how='all')
for idx, row in factor.iterrows():
if idx.hour % period == 0:
buy_symbols = row.sort_values().dropna()[0:N].index
sell_symbols = row.sort_values().dropna()[-N:].index
prices = df_close.loc[idx,]
index_list.append(idx)
for symbol in symbols:
if symbol in buy_symbols and e.account[symbol]['amount'] <= 0:
e.Buy(symbol,prices[symbol],value/prices[symbol]-e.account[symbol]['amount'])
if symbol in sell_symbols and e.account[symbol]['amount'] >= 0:
e.Sell(symbol,prices[symbol], value/prices[symbol]+e.account[symbol]['amount'])
e.Update(prices)
res_list.append([e.account['USDT']['total'],e.account['USDT']['hold']])
return pd.DataFrame(data=res_list, columns=['total','hold'],index = index_list)
거래량 요인: 거래량이 낮은 코인은 롱 포지션을 취하고, 거래량이 높은 코인은 숏 포지션을 취하는 것만으로도 매우 좋은 성과를 거두는데, 이는 인기 있는 코인일수록 하락할 가능성이 더 높다는 것을 보여줍니다.
거래가격 요인: 가격이 낮은 통화는 매수하고, 가격이 높은 통화는 매도하면 효과는 평균입니다.
거래 수 요인: 성과는 거래량과 매우 유사합니다. 거래량 요인과 거래 수 요인 간의 상관관계가 매우 높다는 것은 분명합니다. 사실, 서로 다른 통화에서 두 요인 간의 평균 상관관계는 0.97로, 이는 이 두 요인이 매우 유사하다는 것을 보여줍니다. 이 요인은 취해야 합니다. 고려합니다.
3h 모멘텀 요인: (df_close - df_close.shift(3))/df_close.shift(3). 즉, 요인의 3시간 증가입니다. 백테스트 결과는 3시간 증가가 명백한 회귀 특성을 가지고 있음을 보여줍니다. 즉, 증가가 다음 기간에 떨어질 가능성이 더 큽니다. 전반적인 성과는 좋지만, 하락과 진동이 길어지는 기간도 있습니다.
24시간 모멘텀 요인: 24시간 리밸런싱 주기의 결과는 꽤 좋았으며, 수익률은 3시간 모멘텀과 유사하고 하락폭은 작았습니다.
매출 변화 계수: df_volume.rolling(24).mean() / df_volume.rolling(96).mean()은 최근 하루 매출과 최근 3일 매출의 비율입니다. 포지션 8시간마다 조정됩니다. 백테스트 결과는 비교적 좋고, 반등도 비교적 낮아, 거래량이 활발한 주식이 하락할 가능성이 더 크다는 것을 보여줍니다.
거래 번호 변경 계수: df_count.rolling(24).mean() / df_count.rolling(96).mean()은 지난 하루 거래 수를 지난 3일 동안 거래 수로 나눈 비율입니다. . 위치는 8시간마다 조정됩니다. 백테스트 결과는 비교적 좋고 되돌림도 비교적 낮은데, 이는 거래량이 늘어날수록 시장이 더 급격하게 하락하는 경향이 있음을 보여줍니다.
단일 거래 가치 변경 요인: -(df_volume.rolling(24).mean()/df_count.rolling(24).mean())/(df_volume.rolling(24).mean()/df_count.rolling(96).mean()) , 이는 최근 하루의 거래 가치를 최근 3일간의 거래 가치로 나눈 비율이며, 포지션은 8시간마다 조정됩니다. 이 요인은 또한 양적 요인과 높은 상관관계를 갖습니다.
활성 거래 비율 변화 계수: df_buy_ratio.rolling(24).mean()/df_buy_ratio.rolling(96).mean(), 즉 지난 하루 동안의 활성 매수량과 전체 거래량의 비율입니다. 지난 3일간의 값을 기준으로 8시간마다 위치를 조정합니다. 이 요인은 좋은 성과를 보이며 볼륨 요인과 상관 관계가 거의 없습니다.
변동성 요소: (df_close/df_open).rolling(24).std()는 변동성이 낮은 통화에 롱 포지션을 취할 때 특정 효과가 있습니다.
거래량과 종가의 상관관계 인자: df_close.rolling(96).corr(df_volume), 지난 4일간 종가와 거래량 간의 상관관계 인자, 전반적인 성과가 좋습니다.
여기에 나열된 것은 수량과 가격에 따른 요인 중 일부에 불과합니다. 사실, 요인 공식의 조합은 매우 복잡할 수 있으며 명확한 논리가 없을 수도 있습니다. 유명한 ALPHA101 요인 구성 방법을 참조할 수 있습니다: https://github.com/STHSF/alpha101.
#成交量
factor_volume = df_volume
factor_volume_res = Test(factor_volume, symbols, period=4)
factor_volume_res.total.plot(figsize=(15,6),grid=True);

#成交价
factor_close = df_close
factor_close_res = Test(factor_close, symbols, period=8)
factor_close_res.total.plot(figsize=(15,6),grid=True);

#成交笔数
factor_count = df_count
factor_count_res = Test(factor_count, symbols, period=8)
factor_count_res.total.plot(figsize=(15,6),grid=True);

print(df_count.corrwith(df_volume).mean())
0.9671246744996017
#3小时动量因子
factor_1 = (df_close - df_close.shift(3))/df_close.shift(3)
factor_1_res = Test(factor_1,symbols,period=1)
factor_1_res.total.plot(figsize=(15,6),grid=True);

#24小时动量因子
factor_2 = (df_close - df_close.shift(24))/df_close.shift(24)
factor_2_res = Test(factor_2,symbols,period=24)
tamenxuanfactor_2_res.total.plot(figsize=(15,6),grid=True);

#成交量因子
factor_3 = df_volume.rolling(24).mean()/df_volume.rolling(96).mean()
factor_3_res = Test(factor_3, symbols, period=8)
factor_3_res.total.plot(figsize=(15,6),grid=True);

#成交笔数因子
factor_4 = df_count.rolling(24).mean()/df_count.rolling(96).mean()
factor_4_res = Test(factor_4, symbols, period=8)
factor_4_res.total.plot(figsize=(15,6),grid=True);

#因子相关性
print(factor_4.corrwith(factor_3).mean())
0.9707239580854841
#单笔成交价值因子
factor_5 = -(df_volume.rolling(24).mean()/df_count.rolling(24).mean())/(df_volume.rolling(24).mean()/df_count.rolling(96).mean())
factor_5_res = Test(factor_5, symbols, period=8)
factor_5_res.total.plot(figsize=(15,6),grid=True);

print(factor_4.corrwith(factor_5).mean())
0.861206620552479
#主动成交比例因子
factor_6 = df_buy_ratio.rolling(24).mean()/df_buy_ratio.rolling(96).mean()
factor_6_res = Test(factor_6, symbols, period=4)
factor_6_res.total.plot(figsize=(15,6),grid=True);

print(factor_3.corrwith(factor_6).mean())
0.1534572192503726
#波动率因子
factor_7 = (df_close/df_open).rolling(24).std()
factor_7_res = Test(factor_7, symbols, period=2)
factor_7_res.total.plot(figsize=(15,6),grid=True);

#成交量和收盘价相关性因子
factor_8 = df_close.rolling(96).corr(df_volume)
factor_8_res = Test(factor_8, symbols, period=4)
factor_8_res.total.plot(figsize=(15,6),grid=True);

끊임없이 새로운 효과적인 요소를 발견하는 것은 확실히 전략 구축 과정에서 가장 중요한 부분이지만, 좋은 요소 합성 방법 없이는 뛰어난 단일 알파 요소만으로는 최대 역할을 수행할 수 없습니다. 일반적인 다중 요인 합성 방법은 다음과 같습니다.
동일 가중치 방법: 합성할 모든 요소를 동일한 가중치로 추가하여 새로운 합성 요소를 얻습니다.
과거 팩터 수익률의 가중법: 합성할 모든 팩터를 가장 최근 기간의 과거 팩터 수익률의 산술 평균에 따라 가중치로 합산하여 새로운 합성 팩터를 얻습니다. 이 방법은 성과가 좋은 요인에 더 높은 가중치를 둡니다.
IC_IR 가중 방법 극대화: 과거 기간 동안 복합 요인의 평균 IC 값을 다음 기간의 복합 요인 IC 값 추정치로 사용하고 과거 IC 값의 공분산 행렬을 추정치로 사용합니다. 다음 기간의 복합 요인 변동성의. IC의 기대값을 IC의 표준편차로 나눈 값과 같으며, 복합 요인 IC_IR을 최대화하는 최적의 가중치 솔루션을 얻을 수 있습니다.
주성분 분석(PCA) 방법: PCA는 데이터 차원 감소에 일반적으로 사용되는 방법입니다. 요인 간의 상관관계는 비교적 높을 수 있으며, 차원 감소 후의 주성분은 합성된 요인으로 사용됩니다.
이 문서에서는 요인 타당성 가중치를 수동으로 참조합니다. 위에 설명된 방법은 다음을 참조할 수 있습니다.ae933a8c-5a94-4d92-8f33-d92b70c36119.pdf
단일 요인을 검정할 때는 순서가 고정되어 있지만, 다중 요인 합성은 완전히 다른 데이터를 병합해야 하므로 모든 요인을 표준화해야 하며, 일반적으로 극단적인 값과 누락된 값은 제거해야 합니다. 여기서는 합성을 위해 df_volume\factor_1\factor_7\factor_6\factor_8을 사용합니다.
#标准化函数,去除缺失值和极值,并且进行标准化处理
def norm_factor(factor):
factor = factor.dropna(how='all')
factor_clip = factor.apply(lambda x:x.clip(x.quantile(0.2), x.quantile(0.8)),axis=1)
factor_norm = factor_clip.add(-factor_clip.mean(axis=1),axis ='index').div(factor_clip.std(axis=1),axis ='index')
return factor_norm
df_volume_norm = norm_factor(df_volume)
factor_1_norm = norm_factor(factor_1)
factor_6_norm = norm_factor(factor_6)
factor_7_norm = norm_factor(factor_7)
factor_8_norm = norm_factor(factor_8)
factor_total = 0.6*df_volume_norm + 0.4*factor_1_norm + 0.2*factor_6_norm + 0.3*factor_7_norm + 0.4*factor_8_norm
factor_total_res = Test(factor_total, symbols, period=8)
factor_total_res.total.plot(figsize=(15,6),grid=True);

이 글은 단일 요인 검정 방법을 소개하고 일반적인 단일 요인을 검정하며, 다중 요인 합성 방법을 예비적으로 소개합니다. 그러나 다중 요인 연구의 내용은 매우 풍부합니다. 글에서 언급된 각 요점은 심층적으로 확장될 수 있습니다. . 이러한 전략 연구를 알파 팩터의 발견으로 전환하는 것은 실현 가능한 접근 방식입니다. 팩터 방법론을 사용하면 거래 아이디어의 검증을 크게 가속화할 수 있으며, 사용 가능한 참고 자료가 많이 있습니다.