ファクター理論の考え方
CAPMを拡張したファーマフレンチのファクターモデルに基づく
ETHのリターン=マーケットベータ*ベータ感応度+
(LSのファクター値)*a + (ボラティリティのファクター値)*b + スペシフィックリターン
ls dif
ls 1.00000 0.06006
dif 0.06006 1.00000
ls dif0
ls 1.000000 -0.098875
dif0 -0.098875 1.000000
i["trend1"]=i["close"].pct_change(1)
i["atr"]=(i["high"]-i["low"])/i["close"]*1
i["atr"]=i["atr"].rolling(7).mean()
df["atr"]=df["atr"].where(df["trend1"]>0 , -df["atr"])
正規分布じゃない
バックテスト
i["atr"]=(i["high"]-i["low"])/i["close"]*1
i["atr"]=i["atr"].rolling(7).mean()
1270 2022-05-13 01:00:00
1271 2022-05-13 05:00:00
1272 2022-05-13 09:00:00
1273 2022-05-13 13:00:00
1274 2022-05-13 17:00:00
1275 2022-05-13 21:00:00
1276 2022-05-14 01:00:00
1277 2022-05-14 05:00:00
1278 2022-05-14 09:00:00
1279 2022-05-14 13:00:00
1280 2022-05-14 16:00:00
1281 2022-05-14 17:00:00
1282 2022-05-14 18:00:00
1283 2022-05-14 19:00:00
1284 2022-05-14 20:00:00
1285 2022-05-14 21:00:00
1286 2022-05-14 22:00:00
1287 2022-05-14 23:00:00
1288 2022-05-15 00:00:00
1289 2022-05-15 01:00:00
dfa=df[df["ti"]<=datetime(2022,5,14,13)]
dfb=df[df["ti"]>=datetime(2022,5,14,16)]
dfa["high"]=dfa["high"].rolling(6).max()
dfa["low"]=dfa["low"].rolling(6).min()
dfb["high"]=dfa["high"].rolling(24).max()
dfb["low"]=dfa["low"].rolling(24).min()
df=pd.concat([dfa,dfb])
これで改善した
LSで単回帰して予測値を算出。
予測値を前日騰落率で回帰。残さをあらたな予測値とする。(直行化)
その予測値を、さらにマーケットベータ感応度(直近7日とかで算出する)で回帰して残さをあらたな予測値とする。
さらにいろんなファクターで、、、
前日の確定リターンに対しても複数ファクターで直行化して残さ(スペシフィックリターン)を算出する。スペシフィックリターンリバーサルの特性が観測されるから、それを用いてさらに当日の予測値を回帰して残さをあらたな予測値とする。
*ファクター値とスペシフィックリターンは相関しないから、前日騰落率とスペシフィックリターン(両社ともリバーサル特性)のような一見相関しそうな指標同士は一緒に使って大丈夫。
上記のように、複数ファクターで順番に控除していくのか、それとも重回帰で一気に控除していくのかむずかしい。UKIさんのツイートでFNって調べると、これが参考になった。
https://twitter.com/aru_crypto/status/1544550010930401281?s=46&t=46DZcWB463uem8ZWqJRbyw
import pandas as pd
import numpy as np
import requests
from datetime import datetime
import time
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
#import ccxt
import gc
from tqdm import tqdm
from scipy import stats
import json
import sys
import matplotlib.pyplot as plt
import re
import datetime as dtx
import os
def neutralize_series(series, by, proportion=1.0):
#1次元の縦行列にする
#scoreが目的関数、exposuresが説明関数
scores = series.values.reshape(-1, 1)
exposures = by.values.reshape(-1, 1)
print(scores)
# this line makes series neutral to a constant column so that it's centered and for sure gets corr 0 with exposures
exposures = np.hstack(
(exposures, np.array([np.mean(series)] * len(exposures)).reshape(-1, 1)))
print(exposures)
#線形回帰をNPでやってる
#dotは内積
correction = proportion * (exposures.dot(
np.linalg.lstsq(exposures, scores)[0]))
corrected_scores = scores - correction
#ravel=多次元を1次元に
neutralized = pd.Series(corrected_scores.ravel(), index=series.index)
return neutralized
#df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df=pd.read_csv("testdata.csv")
print(df)
b=neutralize_series(df["A"],df["fn"])
print(b)
#f["fn"]=b
#df.to_csv("testdata.csv")
fig=plt.figure(dpi=120)
plt.xticks(rotation =45)
ax1 = fig.add_subplot(1, 1, 1)
ax1.grid(which = "major", axis = "x", color = "blue", alpha = 0.5,linestyle = "--", linewidth = 1)
ax1.grid(which = "major", axis = "y", color = "green", alpha = 0.5,linestyle = "--", linewidth = 1)
ax1.scatter(df["A"],df["fn"],label="beforeFN")
ax1.scatter(df["A"],b,color="orange",label="afterFN")
#ax1.set(title="pnl",xlabel='time', ylabel="pnl(%)")
ax1.legend(bbox_to_anchor=(1.0, 1)) #ラベルを右に
回転させてるように見える
これ結構わかりやすい
まとめると対数変換+異常値+標準化が必要
変換時に-infとかができるからreplace,fillnaする
前処理無し
前処理あり
インサンプルだとFNしないほうがいい。
これはしょうがない
特徴量はls,dif0,trend7
FNしないと結構極端に相関関係をとらえてしまう
→アウトオブサンプルで予測した際に、特徴量とターゲットとの相関関係が変化すると性能が劣化する。
複数指標を使っていても、学習データでターゲットyとの相関が特に強い特徴量の線形な関係性が極端に用いられてしまうから、その特徴量が効かない相場になった途端にワークしない。
FNして、特徴量とターゲットとの線形成分を除去したのはいいけど、完全に直交化したら、そもそもその特徴量はターゲットに対して何の説明力も持たないのでは?
UKIさんのツイート見たら事後的にFN(予測値に対してのFN)が効いてるって言ってたから、そちらも試してみる
相対化した特徴量のヒストグラムを確認
時系列結合でも見てみる
atrはさすがに対数分布っぽい。他は歪度が高い正規分布っぽい
対数分布でも結局分布を定常にしたいだけだから対数変換しなくていい?
sc atr high low close
464 AAVEUSDT 12.521418 3914.670 0.09910 312.630000
805 ADAUSDT 77.132485 167.716 0.32308 2.170200
1146 ALGOUSDT 190.841108 351.720 0.38152 1.841000
3366 ATOMUSDT 117.294028 3914.670 0.09910 33.374000
4004 AVAXUSDT 1.005245 58.420 0.17110 57.945000
4345 AXSUSDT 3.747271 471.230 0.23550 125.690000
5528 BCHUSDT 1.007300 625.280 0.38152 620.370000
6068 BNBUSDT 1.006319 471.230 0.32308 467.950000
6573 BTCUSDT 1.008627 61547.560 0.05170 61021.050000
8134 CHZUSDT 189277.941692 61547.560 0.05170 0.325170
#highlowの計算
sc=df.groupby("sc")
vol=[]
for ind,i in sc:
ia=i[i["ti"]<=datetime(2022,5,14,13)]
ib=i[i["ti"]>=datetime(2022,5,14,16)]
ia["high"]=ia["high"].rolling(6).max()
ia["low"]=ia["low"].rolling(6).min()
ib["high"]=ib["high"].rolling(24).max()
ib["low"]=ib["low"].rolling(24).min()
i=pd.concat([ia,ib])
vol.append(i)
df=pd.concat(vol)
#指標を作成
def indi(df):
#銘柄ごとに指標のラグ特徴量を作成
sc=df.groupby("sc")
vol=[]
for ind,i in sc:
#LS
i["difls"]=i["ls"].pct_change(1)
#価格
#i["predif"]=i["close"].pct_change()
i["dif"]=i["close"].pct_change().shift(-1)
i["trend30"]=i["close"].pct_change(30)
i["trend7"]=i["close"].pct_change(7)
i["trend1"]=i["close"].pct_change(1)
#ボラ
i["atr"]=(i["high"]-i["low"])/i["close"]*1
i["atr7"]=i["atr"].rolling(7).mean()
#i["hige"]=(i["high"]-i["close"])/i["close"]
vol.append(i)
#データ結合
df=pd.concat(vol)
#df["atr"]=df["atr"].where(df["trend1"]>0 , -df["atr"])
#時間ごとに特徴量を相対化
vol=[]
for ind,i in df.groupby("ti"):
#print(ind)
for a in ["ls","difls","dif","trend30","trend7","trend1","atr","atr7"]:
i[a]=scipy.stats.zscore(i[a])
print(i[a])
i[a]=i[a].fillna(0)
fig=plt.figure(dpi=120)
plt.xticks(rotation =45)
ax1 = fig.add_subplot(1, 1, 1)
ax1.grid(which = "major", axis = "x", color = "blue", alpha = 0.5,linestyle = "--", linewidth = 1)
ax1.grid(which = "major", axis = "y", color = "green", alpha = 0.5,linestyle = "--", linewidth = 1)
#ax1.scatter(df[s],df[m],label="beforeFN",alpha=0.1)
ax1.hist(i[a],bins=100)
ax1.set(title=f"{a}",xlabel='time', ylabel="pnl(%)")
vol.append(i)
#特徴量直交化
df=df.fillna(0)
df["atr7"]=neutralize_series(df["atr7"],df["atr"])
#ヒートマップ
a=["ls","difls","dif","trend30","trend7","trend1","atr","atr7"]
print(df[a].corr())
sns.heatmap(df[a].corr())
df["atr7"]=neutralize_series(df["atr7"],df["atr"])
df["trend7"]=neutralize_series(df["trend7"],df["trend1"])
df["trend30"]=neutralize_series(df["trend30"],df["trend1"])
df["trend30"]=neutralize_series(df["trend30"],df["trend7"])
df["difls"]=neutralize_series(df["difls"],df["ls"])
40銘柄21時 pred_y 重回帰分析
LGBMreggressor
df["sp"]=df["trend1"]
for i in ["ls","difls","trend30","trend7","atr","atr7"]:
df["sp"]=neutralize_series(df["sp"],df[i])
li coe
0 ls -0.112939
1 sp -0.096485
40銘柄
30銘柄
specific retrunとは
最後にスペシフィックリターンであるが、上記までに部分的な機械学習モデルの出力を含めて全ての特徴量が出揃ったら、それら全てを用いてリターンを直交化して独自のスペシフィックリターンを算出する。スペシフィックリターンは強いリバーサルの特性(ファクターリターンが負方向に推移する)を持つ場合が多く、原理的には情報を控除すればするほどその性能が向上する(はずである)。このため、控除対象として機械学習を用いたクラスタや非線形の情報を使うことは、この性能を向上する上で非常に重要である。下図は控除する情報を増やした場合、スペシフィックリターンの持つ予測性能がどのように向上していくか示している。
# 学習データとテストデータに分ける
tii=datetime(2022,5,1)
df["ti"]=pd.to_datetime(df["ti"])
train=df[df["ti"]<=tii]
test=df[df["ti"]>=tii]
train_X=train[li]
train_y=train["dif"]
test_X=test[li]
test_y=test["dif"]
df_X=df[li]
損益計算
li coe
0 ls -0.148759
1 sp -0.118043
回帰係数 [-0.14875944 -0.11804299]
決定係数(r2):0.00939
平均誤差(MAE):0.048
RMSE:0.07
li coe
0 ls -0.125903
1 sp -0.149567
回帰係数 [-0.12590263 -0.14956725]
決定係数(r2):0.00719
平均誤差(MAE):0.048
RMSE:0.07
#時間ごとに特徴量を相対化
vol=[]
for ind,i in df.groupby("ti"):
#print(ind)
#print(i[["sc","atr","high","low","close"]])
for a in ["ls","difls","dif","trend30","trend7","trend1","atr","atr7"]:
i[a]=scipy.stats.zscore(i[a])
#print(i[a])
#ヒストグラム
"""
#i[a]=i[a].fillna(0)
#if i[a].isnull().all()==False and a=="atr":
if i[a].isnull().all()==False:
fig=plt.figure(dpi=120)
plt.xticks(rotation =45)
ax1 = fig.add_subplot(1, 1, 1)
ax1.grid(which = "major", axis = "x", color = "blue", alpha = 0.5,linestyle = "--", linewidth = 1)
ax1.grid(which = "major", axis = "y", color = "green", alpha = 0.5,linestyle = "--", linewidth = 1)
#ax1.scatter(df[s],df[m],label="beforeFN",alpha=0.1)
ax1.hist(i[a],bins=100)
ax1.set(title=f"{a}",xlabel='time', ylabel="pnl(%)")
"""
vol.append(i)
df=pd.concat(vol)
結局LSとSPの回帰係数の比によって結果が変わる
3月
li coe
0 ls -0.023936
1 sp -0.004565
li coe
0 ls -0.031033
1 sp -0.019716
li coe
0 ls -0.019592
1 sp -0.030720
li coe
0 trend30 -3672.314702
1 trend7 -53.421259
2 sp -0.079352
sc 0
ls 0
sell_ratio 18956
timestamp 18956
open_interest 18970
period 18956
start_at 18956
open 18956
high 262
low 262
close 0
ti 0
volume 18334
buyvol 18334
oi 37276
difls 5902
dif 142
trend30 37014
trend7 24872
trend1 5902
atr 5383
atr7 24618
ret 142
dtype: int64
37290
#時間ごとに特徴量を相対化
vol=[]
for ind,i in df.groupby("ti"):
#print(ind)
#print(i[["sc","atr","high","low","close"]])
for a in ["ls","difls","dif","trend30","trend7","trend1","atr","atr7"]:
i[a]=i[a].fillna(0)
i[a]=scipy.stats.zscore(i[a])
li coe
0 ls 0.889976
1 difls 0.183629
2 difls7 -0.010343
3 difls30 -0.020703
4 lsdev7 -0.087950
5 lsma7 0.023583
ma系が左に歪みすぎてる
生値を確認したら違った
各時間でlsma7のヒストグラムを確認した
銘柄を確認してみる
print(i[i[a]==i[a].min()][["sc",a]])
#とりあえずlsmaとdevは正規分布じゃないから、それらを覗いて直交化
for i in ["ls","difls","difls7","difls30","lsdev7","lsdev30","trend1","trend7","trend30"]:
print(i)
for k in ["ls","difls","difls7","difls30","lsdev7","lsdev30","trend1","trend7","trend30"]:
if i != k:
df[i]=neutralize_series(df[i],df[k])
li coe
0 ls 0.000447
1 difls -0.190711
2 difls7 -0.036090
3 difls30 0.079195
4 lsdev7 -0.371127
5 lsdev30 -0.310811
6 trend1 0.067384
7 trend7 0.098877
8 trend30 0.074465
9 dev7 -0.002332
10 dev30 0.006336
11 lsma7 0.030261
12 lsma30 -0.001242
相関係数 0.40087785237434065
相関係数 0.8484678175039243
損益計算
損益計算
li coe
0 ls 0.612766
1 difls 0.139410
2 difls7 0.144509
3 difls30 0.197832
4 lsdev7 0.287352
5 lsdev30 0.327357
6 trend1 -0.152228
7 trend7 -0.286963
8 trend30 -0.290511
9 dev7 -0.002200
10 dev30 0.003584
11 lsma7 0.018805
12 lsma30 0.001803
現在特徴量として強力にワークしているLSとlsretpredが相関していないか確認
difとの相関を確認
fig=plt.figure(figsize=(6,6))
df[["dif","trend1","ls","pred_y","lsdev7","difls"]].corr()["dif"].iloc[1:].plot.bar()
predlsretを出力
直交化
df["plsret"]=neutralize_series(df["plsret"],df["trend1"])
df["plsret"]=neutralize_series(df["plsret"],df["ls"])
li coe
0 ls -0.035179
1 sp -0.031559
2 plsret 0.031689
df=pd.read_csv("test.csv")
lsret=pd.read_csv("03pred_lsret.csv")[["sc","ti","pred_y"]]
df=pd.merge(df,lsret,on=["sc","ti"])
print(df.columns)
print(df.isna().sum())
======================
Index(['Unnamed: 0', 'Unnamed: 0.1', 'sc', 'ls', 'sell_ratio', 'timestamp',
'open_interest', 'period', 'start_at', 'open', 'high', 'low', 'close',
'ti', 'volume', 'buyvol', 'oi', 'pred_y'],
dtype='object')
Unnamed: 0 0
Unnamed: 0.1 0
sc 0
ls 0
sell_ratio 18956
timestamp 18956
open_interest 18970
period 18956
start_at 18956
open 18956
high 262
low 262
close 0
ti 0
volume 18334
buyvol 18334
oi 37276
pred_y 0
dtype: int64
(base) C:\Users\taku2>pip install xgboost
Collecting xgboost
Downloading xgboost-1.6.2-py3-none-win_amd64.whl (125.4 MB)
|████████████████████████████████| 125.4 MB 3.3 MB/s
Requirement already satisfied: scipy in c:\users\taku2\anaconda3\lib\site-packages (from xgboost) (1.6.2)
Requirement already satisfied: numpy in c:\users\taku2\anaconda3\lib\site-packages (from xgboost) (1.20.1)
Installing collected packages: xgboost
Successfully installed xgboost-1.6.2
model = XGBRegressor(max_depth=6, learning_rate=0.01, n_estimators=3000, n_jobs=-1, colsample_bytree=0.1, random_state=0)
model.fit(train_X, train_y)
pred_y = model.predict(df_X)
pred_y=neutralize_series(pd.Series(pred_y),df["ls"])
pred_y=neutralize_series(pd.Series(pred_y),df["difls"])
li=["ls","difls","trend1"]
model = XGBRegressor(max_depth=6, learning_rate=0.01, n_estimators=300, n_jobs=-1, colsample_bytree=0.1, random_state=0)
#中立化
for i in li:
pred_y=neutralize_series(pd.Series(pred_y),df[i])
#定常性について検討
df["mon"]=df["ti"].dt.month
df=df[df["ti"]>datetime(2022,5,1)]
print(df)
fig=plt.figure(dpi=120)
for ind,i in df.groupby("mon"):
a="dif"
q=0.025
i[i[a]>i[a].quantile(1-q)]=i[a].quantile(1-q)
i[i[a]<i[a].quantile(q)]=i[a].quantile(q)
plt.xticks(rotation =45)
ax1 = fig.add_subplot(1, 1, 1)
ax1.grid(which = "major", axis = "x", color = "blue", alpha = 0.5,linestyle = "--", linewidth = 1)
ax1.grid(which = "major", axis = "y", color = "green", alpha = 0.5,linestyle = "--", linewidth = 1)
#ax1.scatter(df[s],df[m],label="beforeFN",alpha=0.1)
ax1.hist(i[a],bins=100,alpha=0.4,label=f"month{ind}")
ax1.set(title=f"month {a}",xlabel='', ylabel="")
ax1.legend(bbox_to_anchor=(1.0, 1)) #ラベルを右に
相関係数 0.47710374303751435
li coe
0 ls -0.029137
1 sp 0.081840
2 atr 0.715933
3 atr7 -0.384286
if a=="fr":
se=np.arange(0.0001-i[i[a]!=0.0001][a].std() ,0.0001+i[i[a]!=0.0001][a].std(),0.00001 )
odf=i[i[a]==0.0001 ]
odf[a]=np.random.choice(se, len(i[i[a]==0.0001 ]))
月ごとの分布
midを0にした
シャープレシオ 0.25146979789109875
相関係数 0.021719312672249386
li coe
0 sp -0.001031
1 ls -0.001948
2 fr 0.000099
if a=="fr":
#対数変換
i[a]=i[a]
i[a]=i[a]-i[a].min()
i[a]=i[a].apply(np.log)
i = i.replace([np.inf, -np.inf], np.nan)
i=i.fillna(0)
#対数変換
"""
a="fr"
df[a]=-df[a]
#df[a]=df[a]-df[a].min()
#df[a]=df[a]-0.5
#df[a]=df[a].apply(np.log)
#df[a]=df[a].apply(np.log)
df = df.replace([np.inf, -np.inf], np.nan)
df=df.fillna(0)
"""
標準化はしないほうがいい
→FRは相対値よりも絶対値が大事=リターンとの線形的な関係性を持つ
li=[“sp”,“ls”,“fr”]
損益計算
シャープレシオ 0.21985590811969388
相関係数 0.02629230276413826
li coe
0 sp -0.001072
1 ls -0.001887
2 fr -0.001426
回帰係数 [-0.00107201 -0.00188671 -0.00142605]
決定係数(r2):0.00058
平均誤差(MAE):0.049
RMSE:0.071
#目的関数
i["retclass2"]= pd.qcut(i["dif"], 2, labels=[0,1])
i["retclass3"]= pd.qcut(i["dif"], 3, labels=[0,1,2])
i["retclass5"]= pd.qcut(i["dif"], 5, labels=[0,1,2,3,4])
損益計算
シャープレシオ 0.1272594684756887
相関係数 0.022215797157039533
li coe
0 sp -0.004142
1 ls -0.000320
回帰係数 [-0.004142 -0.00032034]
決定係数(r2):-0.00257
平均誤差(MAE):0.5
RMSE:0.501
損益計算
シャープレシオ 0.20666805329125426
相関係数 0.026042783964642532
li coe
0 ls 0.010463
1 difls -0.002639
2 stdls 0.006532
3 difls_std 0.006172
4 ls_lsma30 0.001901
5 difls7 -0.008448
6 difls30 0.005655
7 lsdev7 -0.001172
8 lsdev30 -0.009438
9 lsma7 -0.012735
10 lsma30 0.006552
11 trend1 -0.002693
12 trend7 -0.005050
13 trend30 0.004113
14 dev7 -0.005439
15 dev30 -0.004832
16 atr -0.007551
17 atr7 0.003664
18 fr -0.014193
19 sp -0.003510
回帰係数 [ 0.01046285 -0.00263948 0.00653189 0.00617152 0.00190053 -0.00844805
0.00565455 -0.00117188 -0.0094376 -0.01273536 0.00655189 -0.00269292
-0.00505044 0.00411324 -0.00543941 -0.00483228 -0.0075507 0.00366355
-0.01419305 -0.00350971]
決定係数(r2):-0.00207
平均誤差(MAE):0.499
RMSE:0.501
16 atr -0.008109
17 atr7 0.003607
18 fr -0.002242
19 sp 0.005306
20 trend1_c2 -0.049447
21 trend7_c2 -0.049447
#時間ごとに特徴量を相対化
aaa=True
if aaa:
print("#時間ごとに特徴量を相対化")
vol=[]
for ind,i in df.groupby("ti"):
#指標
i=i.dropna(subset=["dif","trend1","trend7"])
if len(i)==0:
continue
#ランキング
i["retclass2"]= pd.qcut(i["dif"], 2, labels=[0,1])
i["retclass3"]= pd.qcut(i["dif"], 3, labels=[0,1,2])
i["retclass5"]= pd.qcut(i["dif"], 5, labels=[0,1,2,3,4])
i["trend1_c2"]= pd.qcut(i["trend1"], 2, labels=range(2) )
i["trend1_c3"]= pd.qcut(i["trend1"], 3, labels=range(3) )
i["trend7_c2"]= pd.qcut(i["trend1"], 2, labels=range(2) )
i["trend7_c3"]= pd.qcut(i["trend1"], 3, labels=range(3) )
#市場全体 指標
i["market"]=[i["trend1"].mean()]*len(i)
i["bull"]=[ i[i["trend1"]>0]["trend1"].count()/len(i) ]*len(i)
bullbear=i[i["trend1"]>i["trend1"].mean() ]["trend1"].mean()-i[i["trend1"]<i["trend1"].mean() ]["trend1"].mean()
i["bullbear"]=[bullbear]*len(i)
18 fr -0.005985
19 sp -0.023962
20 market -0.018494
21 bull 0.001064
22 bullbear -0.002191
li=["trend1","ls","difls","market","bull","bullbear"]
if sw==2:
params = {
'task': 'train', # タスクを訓練に設定
'boosting_type': 'gbdt', # GBDTを指定
'objective': 'binary', # 分類
'metric': 'binary_logloss', # 評価関数
'learning_rate': 0.01, # 学習率
#"num_leaves":30,
"max_depth":4
}
損益計算
シャープレシオ 0.25876462875724626
相関係数全体 0.1132148772929254
相関係数訓練 0.14743100154517633
相関係数テスト 0.08542211486046501
import lightgbm as lgb
from sklearn.tree import plot_tree
from sklearn.tree import DecisionTreeRegressor
# create model
model = DecisionTreeRegressor(max_depth=2) # シンプルな木にするため2階層に指定
model.fit(X_train, y_train)
plt.figure(figsize=(16, 6))
plot_tree(model, feature_names=X_train.columns, filled=True, proportion=True, fontsize=10)
plt.show()
#shap
plt.figure(figsize=(6,6))
explainer = shap.Explainer(model, X_train)
shap_values = explainer(X_train)
shap.plots.bar(shap_values, max_display=20) # max_displayで表示する変数の最大数を指定
shap.plots.beeswarm(shap_values, max_display=20) # max_displayで表示する変数の最大数を指定
shap.plots.scatter(shap_values[:,'trend1'], color=shap_values)
shap.plots.scatter(shap_values[:,'market'], color=shap_values)
plt.show()
SHAPの絶対値
shap
# 全サンプルについて、各特徴量のSHAP値の分布を可視化したもの
# 上記の棒グラフ同様、予測に対する寄与度が大きい順にソートされて表示される
# 色は特徴量の値の大小を表している(赤が大きくて青が小さい)
# LSTATの値が小さいほど予測値(住宅価格)は大きくなることがわかる
# ある1つの特徴量(今回はRM)について、訓練データ全体でのその特徴量(RM)の値と、対応するSHAP値の散布図を作成
# 横軸が特徴量の値、縦軸が対応するSHAP値
# SHAP値は予測値に対する特徴量の寄与を示すので、この図から、RMが変化したら予測値がどう変化するか読み取れる
# RMが大きいほどSHAP値は大きい、つまり、home priceが大きくなることを示している
# また、このグラフでは選択した特徴量(RM)と他の特徴量との相互作用も確認できるようになっていて
# color引数にshap値の変数を指定すると、自動的に適した変数が選択される
# 今回はRADが選ばれていて、RADの大小によって、同じRMでもSHAP値が異なることが確認できる
#合成指標
i["trend1_market"]=i["trend1"]/i["market"]
i["atr_market"]=i["atr"]/i["market"]
i["atr7_market"]=i["atr7"]/i["market"]
i["ls_atr"]=i["ls"]/i["atr"]
#ベータ感応度
print("ベータ感応度")
vol=[]
market=df[df["sc"]=="BTCUSDT"]
for ind,i in df.groupby("sc"):
i=i.sort_values("ti")
#結合
ii=pd.merge(i,market,on="ti",suffixes=["","_market"])
#fillna
ii["trend1"]=ii["trend1"].fillna(0)
ii["trend1_market"]=ii["trend1_market"].fillna(0)
#回帰
model = LinearRegression()
model.fit(ii[["trend1_market"]], ii["trend1"])
pred_y = model.predict(ii[["close"]])
ii["pred_y"]=pred_y
#print(pred_y)
#print(ii[["sc","ti","sc_market","trend1","trend1_market"]])
print(model.coef_)
fig=plt.figure(dpi=120)
plt.xticks(rotation =45)
ax1 = fig.add_subplot(1, 1, 1)
ax1.scatter(ii["trend1_market"],ii["trend1"],label=f"{ind} {model.coef_} {ii[['trend1','trend1_market']].corr().iloc[1,0]}",alpha=0.25)
ax1.legend() #ラベルを右に
def kmeans(df):
from sklearn.cluster import KMeans
df=df[df["ti"]>datetime(2022,9,1)]
#クラスタリング クロスセクション
vol=[]
for ind,i in df.groupby("ti"):
i=i.fillna(0)
kmeans = KMeans(n_clusters=2, max_iter=30, init="random", n_jobs=-1)
cluster = kmeans.fit_predict(i[["ls","trend7"]].values)
i["cluster"]=cluster
vol.append(i)
df=pd.concat(vol)
#銘柄ごとのクラスター平均値
kf=df.groupby("sc").mean()
#グラフ
fig=plt.figure(dpi=150)
ax=sns.scatterplot(data=kf,x="ls",y="trend7",hue = "cluster",)
#ラベル
kf["sc"]=kf.index
kf["sc"]=kf["sc"].apply(lambda x :x[:-4])
for ind,i in kf.iterrows():
ax.text(i["ls"],i["trend7"],i["sc"], fontsize="xx-small")
print("fin")
sys.exit()
銘柄ごとのlsとdifの相関係数
標準化なし
sc co
56 FLOWUSDT -0.308259
140 ZILUSDT -0.269387
99 OPUSDT -0.259461
127 UNFIUSDT -0.244993
100 PEOPLEUSDT -0.221613
.. ... ...
134 XMRUSDT 0.068429
132 XEMUSDT 0.076617
115 SPELLUSDT 0.141948
86 LUNA2USDT 0.151788
141 ZRXUSDT 0.178938
LSで線形回帰した残さ=LSで説明できない部分
これが特徴量になったりしないか
y=ax+b のb
def ls_sen(df):
vol=[]
for ind,i in df.groupby("ti"):
#回帰
model = LinearRegression()
model.fit(i[["lslast"]], i["trend1"])
pred_y = model.predict(i[["ls"]])
i["beta"]=pred_y
#i["beta_sen"]=[model.coef_[0]]*len(ii)
i["trend1_ls"]=i["trend1"]-i["beta"] #lsで予測できなかったtrend1の成分
vol.append(i)
df=pd.concat(vol)
li=[“trend1_ls”]
#回帰
model = LinearRegression()
model.fit(df[["lslast"]], df["trend1"])
pred_y = model.predict(df[["ls"]])
df["beta"]=pred_y
#i["beta_sen"]=[model.coef_[0]]*len(ii)
df["trend1_ls"]=df["trend1"]-df["beta"] #lsで予測できなかったtrend1の成分
li=[“sp”,“ls”]
5,10,30
def get_beta(df):
#ベータ感応度
print("\nベータ感応度")
vol=[]
market=df[df["sc"]=="BTCUSDT"]
for ind,i in tqdm(df.groupby("sc")):
i=i.sort_values("ti")
#結合
ii=pd.merge(i,market[["ti","trend1"]],on="ti",suffixes=["","_market"])
#fillna
ii["trend1"]=ii["trend1"].fillna(0)
ii["trend1_market"]=ii["trend1_market"].fillna(0)
#ベータ一部期間
vol2=[]
for n in range(len(ii)):
i3=ii.iloc[n-7:n,:].copy() #https://qiita.com/miler0528/items/32a5d338b4b313fb8898
#print(i3)
#回帰
if len(i3) ==7:
model = LinearRegression()
model.fit(i3[["trend1_market"]], i3["trend1"])
pred_y = model.predict(i3[["trend1_market"]])
i3["beta"]=pred_y
beta_sen=model.coef_[0]
alpha=i3["trend1"].tail(1).to_list()[0]-i3["beta"].tail(1).to_list()[0]
else:
beta_sen=np.nan
alpha=np.nan
vol2.append({"beta_sen":beta_sen,"alpha":alpha})
#print(i3["sc"].unique(),beta_sen)
ab=pd.DataFrame(vol2)
ii["beta_sen2"]=ab["beta_sen"]
ii["alpha2"]=ab["alpha"]
vol.append(ii)
#データ結合
df=pd.concat(vol)
return df
trend1とaplhaの比較
alphaの自己相関
for ind,i in df.groupby("sc"):
i["alpha_ret"]=i["alpha2"].shift(-1)
fig=plt.figure(dpi=120)
plt.xticks(rotation =45)
ax1 = fig.add_subplot(1, 1, 1)
#ax1.scatter(ii["trend1_market"],ii["trend1"],label=f"{ind} {model.coef_} {ii[['trend1','trend1_market']].corr().iloc[1,0]}",alpha=0.25)
ax1.scatter(i["alpha2"],i["alpha_ret"],label=f"{ind}")
#ax2=ax1.twinx()
#ax1.bar(i["ti"],i["trend1"],color="orange",alpha=0.5)
ax1.legend() #ラベルを右に
print(i[["alpha2","alpha_ret"]].corr().iloc[0,1] )
sys.exit()
-0.0920876787295905
-0.03380395658027237
-0.12990771143143728
0.07140482392801528
-0.05258264312763598
-0.1863481902578635
-0.226322438834671
-0.19133725990052589
-0.11260738036265244
-0.05073073953704172
-0.15768125726676127
-0.0796899234696409
-0.12136034491483873
0.059604627741644545
-0.07709724447456101
-0.06410029679818585
-0.04705921875703488
-0.023609913100188385
0.004221702123679208
-0.20282087488102007
0.07090753511310113
0.14021162284229283
-0.17520639102369343
df=df[df["dif"]<0]
特徴量同士の相関のケア
predyに対して特徴量で直交化
線形な重回帰でもつかえるの?
特徴量を作成するときに、クロスセクションでの相対値にしなくていいの?
マーケットベータを算出する
重回帰でも分割検証
21時以外
非線形な特徴量でスペシフィックリターンを控除
代替の指標を予測する
セクターごとに指標を正規化する
分布の定常性を高める工夫をする
LSが下がっても折込が甘い銘柄をロング
LSの分散とか移動平均とかを考慮
LS移動平均乖離、LS前日騰落率を特徴量にしたらいいのでは
lsretの予測値を新たな特徴量にする
lsma7を使ってみる
ボラを予測する
高値安値を予測
lsの分散とかを見る
LSにypredを足して予測してみる
目的関数をリニア、分類などいろいろなものにしてみて、それらの結果をアンサンブルする
特徴量重要度出してみる
複数の特徴量を使ったほうがロバストにはなるのか?
期待値は変わらず、SRは若干劣化していても、指標が2個より5個のほうがロバストになるのか
銘柄ごとにファクターから影響の受けやすさを定量化する
1 銘柄ごとにタイムシリーズ回帰
2 クロスセクション標準化+ファクターで全体回帰して、残さのでかさを算出
その平均値を銘柄ごとに算出
LSで線形回帰した残さ=LSで説明できない部分
これが特徴量になったりしないか
y=ax+b のb
指標同士の直交化を深堀
周1回ポジる
曜日ごとで挙動変化チェック
ビッグデータのほうがロバストな傾向がある 現在の潮流として
データ数数百万とか
目的関数にエッジがある(よしそ)
リターンが正規分布じゃないから線形回帰はだめ
だからクラシフィケーションなどのバイナリーデータでの予測のほうがいい(たぶん分類問題)
UKI:クロスエントロピードスを損失関数にしてる?
残さリターンはある程度正規性を仮定できることがある
回帰でも分類でも遜色ない
1日目に中間だった値が次の日にランク1位になったときとかを考えると、分布の繊維に対して正規分布を仮定するのは難しい
変換しても分布のもとの性質はあんまり変わらない
応用としてトリプルバリアとかがある
サンプリング
IIDは独立分布を仮定
逐次ブートストラップで学習することでIIDらしい分布からの学習っぽくできる
サンプル数が超長期のデータの場合
サンプルを確保しつつIID性を保つ
RICHWOMAN:サンプリング反対
リサンプリング使う? 使わない(よ)
よ:ディープラーニングで優位性もとめてるからリサンプリングしない
逆にレアなイベントとか狙うならいいかも
過去にやったこと
2018では15分間の移動平均から下振れてた銘柄を買えば無限に儲かったのが2,3週間あった
pumpdumpがはやったときに、事前に予知する戦略
週で2,3銘柄に仕込んで売る 週次10% 1か月くらい
・株
numeraiのTCでは、numeraiに対する貢献度が高いほど評価される
メタモデルと逆相関するモデルだけど、モデル自体は、、、わからん
執行戦略の工夫
エッジにかかわりそう(UKI
予測力>執行力
全部テイクオーダーでぶん投げてる
UKI:テイクで利益出せるのが信じられない (UKIめちゃびびる)
マーケットが変わると
ある程度の分布のずれは再学習で何とかなる
完全に変わるとモデル作り直し
分投げどれくらい?
ロット数は板次第
ベストだけ?2,3枚?(UKI
マーケット全体の1%とか?
DLで好みのネットワーク構造は?
トランスフォーマー、S4
だんとつCNN
計算コストと、処理できる~の依存関係がキャッチできる
RNN LSTMとかは学習に時間がかかるのが問題
CNNはトラんけーとされたRNNと解釈できる
RNNだからと言って過去の依存関係をめっちゃ見れるわけではなく、CNNで十分
TCNとか時系列特化のやつがあるが、生の単純なCNNでいい
4次元データでやったらどうみたいな話がある(UKI
画像的な意味合いもある?UKI
2次元で画像的な意味合いをキャプチャーしたい
3次元だったらそこにアルファーがある
UKI トランスフォーマーであんまよくならなかった
目に見張るような改善はなかった
シンプルにデータが足りん
s4がシンプルでそれなりの結果
状態空間モデルをベース
内部状態を推論しながら未来を予測
RNN系とs4はちがう?UKI
今までのモデルで解けなかったものが解けるようになっていることがすごい(YO
グラフラーニング使ってる?GNN
試したいが試してない
モデル戦略
一つのモデルを強くするのか、アセットを分散するのか?
YO:一つのモデルを強くする
numeraiは150個モデルを回してて、10個ライブで選んでる
問題設定次第、安定運用志向
特徴量を増やすときにOHLCVをこねくり回してふやしていい?
条件によるが基本的に有効ではない
OHLCVからだったら移動平均乖離と標準偏差、volumeだけ
とくちょうりょうを増やすならデータソースを増やす
特徴量にノイズを与えたりしてる?
試したがかいぜんしない UKI&よしそ
どういったスコア付けを行ってるか
トレンドとレンジの変化をどう落とし込むか
将来リターンをラベルとして使う
ある程度期待値を犠牲にする
市場変化、DLモデルでそれをどうケアしてるか
為替だとファンダメンタルがないから、そういう特徴量でケアするしかない
大きいタイムフレームでのパターンがないとか!
DLうんぬんより十分に特徴量が市場環境を表現できているか
相場ごとにモデルを変えるか?
ひとつでカバー:よしそ
UKIも
レジームに対応するような特徴量を入れていけばいい:よしそ
いいレジーム特徴量は?UKI
意図してレジーム特徴量を入れる
よ:相場全体から集約した特徴量
全銘柄のうちで何%が上がってるかとか
そういう指標が効く!