ファクター理論 === ファクター理論の考え方 CAPMを拡張したファーマフレンチのファクターモデルに基づく - わかりやすい解説 https://note.com/moto12345/n/n6dceb5fc7571 ETHのリターン=マーケットベータ*ベータ感応度+ (LSのファクター値)*a + (ボラティリティのファクター値)*b + スペシフィックリターン # ファクター分析 ## LS ### ある日(9/22)のLSと価格の関係 ![](https://i.imgur.com/FP7a5dy.png) - 相関係数 ``` ls dif ls 1.00000 0.06006 dif 0.06006 1.00000 ``` - ラグなしリターンとの相関係数だとさらに成績が改善 ``` ls dif0 ls 1.000000 -0.098875 dif0 -0.098875 1.000000 ``` ### LSとリターンの相関係数の累積和 - 1日ごとに計算 ![](https://i.imgur.com/txqAwFk.png) ### LSとリターン(ラグなし)の相関係数の累積和 - 訂正的な説明:価格が上がった銘柄はその時LSが低い ![](https://i.imgur.com/BJfUEsM.png) ## ~~ボラティリティ~~ highlowが正しくなかった - 方向ボラティリティ ``` 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"]) ``` ### ボラティリティとリターンの相関係数の累積和 - かなり顕著な傾向が見られた - 前日上昇したハイボラティリティ銘柄は次の日下がる - 前日下落したハイボラティリティ銘柄は次の日上がる - **ハイボラティリティ銘柄はリバーサルの現象が起きやすい** - **下落相場だからこの特性が表れている気もする** ![](https://i.imgur.com/qe1SWJe.png) #### ある日のボラティリティとリターンの相関関係 - 正規分布じゃない ![](https://i.imgur.com/XkjRMPt.png) - バックテスト - 試しに方向性atr単体ファクターでバックテストしてみたら、かなり勝率高そうだった ![](https://i.imgur.com/TeJMGpy.png) #### 方向を加味しない純粋なATRを指標として用いた場合 ``` i["atr"]=(i["high"]-i["low"])/i["close"]*1 i["atr"]=i["atr"].rolling(7).mean() ``` ![](https://i.imgur.com/MPbm3cG.png) #### 方向を加味しない純粋な(前日のみの)ATRを指標として用いた場合 ![](https://i.imgur.com/auVy23z.png) #### 前日のみの方向性ATRを用いた場合 ![](https://i.imgur.com/jyDeuUS.png) #### 前日騰落率をファクター値とした場合 ![](https://i.imgur.com/P0Dqthp.png) ## high lowが正しくない問題への対処 - 5月中旬ごろから1時間足になっている ![](https://i.imgur.com/3Ey8Fah.png) ``` 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]) ``` これで改善した ## ボラティリティ - 全期間の全銘柄の散布図と、累積相関係数 - 前日が上がった入ボラ銘柄は売られる。逆もしかり ![](https://i.imgur.com/RErXeTT.png) ![](https://i.imgur.com/aaYqSed.png) ### 方向性を考慮しないただのボラティリティ 7day - 入ボラ銘柄のほうが買われる ![](https://i.imgur.com/lgKy5PW.png) ![](https://i.imgur.com/5Okhc6j.png) ### 方向性なし。ATRは前日のhighlow差分のみ - 前日にはいぼらだと売られる ![](https://i.imgur.com/2lRs0iN.png) ### 方向性あり。ATRは前日のhighlow差分のみ ![](https://i.imgur.com/b14Kbvp.png) ## モメンタム  - 前日騰落率とリターン - 見た感じ当然だけど方向性ATRと相関してるから一緒に使っちゃダメ ![](https://i.imgur.com/WYL3k6I.png) LSで単回帰して予測値を算出。 予測値を前日騰落率で回帰。残さをあらたな予測値とする。(直行化) その予測値を、さらにマーケットベータ感応度(直近7日とかで算出する)で回帰して残さをあらたな予測値とする。 さらにいろんなファクターで、、、 前日の確定リターンに対しても複数ファクターで直行化して残さ(スペシフィックリターン)を算出する。スペシフィックリターンリバーサルの特性が観測されるから、それを用いてさらに当日の予測値を回帰して残さをあらたな予測値とする。 *ファクター値とスペシフィックリターンは相関しないから、前日騰落率とスペシフィックリターン(両社ともリバーサル特性)のような一見相関しそうな指標同士は一緒に使って大丈夫。 上記のように、複数ファクターで順番に控除していくのか、それとも重回帰で一気に控除していくのかむずかしい。UKIさんのツイートでFNって調べると、これが参考になった。 https://twitter.com/aru_crypto/status/1544550010930401281?s=46&t=46DZcWB463uem8ZWqJRbyw # FN実践 ``` 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)) #ラベルを右に ``` ![](https://i.imgur.com/FyhXV1E.png) ### 仮想通貨データで試してみる - 回転させてるように見える ![](https://i.imgur.com/3Lebfvz.png) - これ結構わかりやすい - y軸:LS - x軸:LS7日平均 - ”LS7日平均”の”LS”への相関が高い成分だけいい感じに消えてる - ![](https://i.imgur.com/kxBQ0qD.png) ### 前処理の大切さ - FNは回帰分析をつかってるから、正規分布を仮定している。 - そのため、価格系列のデータを使うときは対数変換&異常値排除 - また、回転を行っているので、平均0。 - 重回帰分析もおいおいするならスケーリングもしておいたほうがいいから、標準化しておいたほうがいい。 **まとめると対数変換+異常値+標準化が必要** - 変換時に-infとかができるからreplace,fillnaする - 前処理無し ![](https://i.imgur.com/xcAybW8.png) ![](https://i.imgur.com/Hdc5sp2.png) - 前処理あり ![](https://i.imgur.com/NY6ZGZh.png) ![](https://i.imgur.com/LSJw53i.png) # 前処理の手順 1. 全値を正の数にする 3. 対数変換 4. inf,nan除去 5. 異常値排除 6. 標準化 7. FN # 重回帰実践(事前FN) #### テストデータ=学習データの場合 インサンプルだとFNしないほうがいい。 これはしょうがない 特徴量はls,dif0,trend7 - FNあり - [-7.71111975e-08 2.60022886e-07 -8.52166213e-07] ![](https://i.imgur.com/k932IgO.png) - FNなし - [-0.06049714 -0.07953951 -0.01355275] ![](https://i.imgur.com/SmWAfxO.png) #### 各特徴量について FNしないと結構極端に相関関係をとらえてしまう →アウトオブサンプルで予測した際に、特徴量とターゲットとの相関関係が変化すると性能が劣化する。 複数指標を使っていても、学習データでターゲットyとの相関が特に強い特徴量の線形な関係性が極端に用いられてしまうから、その特徴量が効かない相場になった途端にワークしない。 FNして、特徴量とターゲットとの線形成分を除去したのはいいけど、完全に直交化したら、そもそもその特徴量はターゲットに対して何の説明力も持たないのでは? - FNなし dif0と予測値の散布図 ![](https://i.imgur.com/hgWbzLo.png) - FNあり dif0と予測値の散布図 ![](https://i.imgur.com/8BR9AhG.png) - dif0と実際のyの散布図 ![](https://i.imgur.com/Hi1nFNI.png) # 事後FN UKIさんのツイート見たら事後的にFN(予測値に対してのFN)が効いてるって言ってたから、そちらも試してみる - FNなし ![](https://i.imgur.com/sIWc2Z3.png) - LSで事後FN ![](https://i.imgur.com/gzimXWU.png) - さらにdif0でFN ![](https://i.imgur.com/4YuK5vY.png) - さらにtrend7 ![](https://i.imgur.com/WkG8LR3.png) # 特徴量をクロスセクションで相対化する - 時系列でラグ特徴量を作成したのちに、クロスセクションで相対化 ### ヒストグラム確認 - 相対化した特徴量のヒストグラムを確認 - これによって、タイムシリーズで定常な分布となる。 ![](https://i.imgur.com/lWH1zHG.png) - 時系列結合でも見てみる ![](https://i.imgur.com/07kXcSU.png) - atrはさすがに対数分布っぽい。他は歪度が高い正規分布っぽい ![](https://i.imgur.com/iNEw3GO.png) - **対数分布でも結局分布を定常にしたいだけだから対数変換しなくていい?** - 対数変換せずに標準化した結果 ![](https://i.imgur.com/mehOs7Q.png) ### 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 ``` - high-lowの計算が間違ってた ``` #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) ``` # 特徴量の相関係数のヒートマップ - atrどうしや、trend特徴量の相関が高い→直交化 ![](https://i.imgur.com/eCtiMPC.png) ### 直交化して再度ヒートマップ ![](https://i.imgur.com/ux736Os.png) ``` #特徴量直交化 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()) ``` - atr7をatrに対して直交化した - fillna(0)してるから気持ち悪いけど ![](https://i.imgur.com/wCN2yXg.png) ### trendも直交化 - trend30は二回直交化 - diflsも一応相関あるからFN ``` 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"]) ``` ![](https://i.imgur.com/ivhrFFy.png) ![](https://i.imgur.com/21LivvX.png) # いったんバックテストしてみる - 40銘柄21時 pred_y 重回帰分析 - ![](https://i.imgur.com/UHYmQMz.png) - LGBMreggressor - 全く汎か性能ない(笑) - ![](https://i.imgur.com/lthEeXO.png) ### スペシフィックリターンを算出してみる - code ``` 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 ``` - 50銘柄 - なんか強そう - ![](https://i.imgur.com/t4wBLtH.png) - 5銘柄 - ![](https://i.imgur.com/JdwwiIh.png) ### LSとスペシフィックリターン - 40銘柄 - ![](https://i.imgur.com/5XLGOTl.png) - 30銘柄 - ![](https://i.imgur.com/iAMOBKM.png) - specific retrunとは - https://qiita.com/blog_UKI/items/c718334247a083f250c7 ``` 最後にスペシフィックリターンであるが、上記までに部分的な機械学習モデルの出力を含めて全ての特徴量が出揃ったら、それら全てを用いてリターンを直交化して独自のスペシフィックリターンを算出する。スペシフィックリターンは強いリバーサルの特性(ファクターリターンが負方向に推移する)を持つ場合が多く、原理的には情報を控除すればするほどその性能が向上する(はずである)。このため、控除対象として機械学習を用いたクラスタや非線形の情報を使うことは、この性能を向上する上で非常に重要である。下図は控除する情報を増やした場合、スペシフィックリターンの持つ予測性能がどのように向上していくか示している。 ``` ### 訓練期間を変えて試してみる - code ``` # 学習データとテストデータに分ける 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] ``` - ~5/1 ``` 損益計算 li coe 0 ls -0.148759 1 sp -0.118043 回帰係数 [-0.14875944 -0.11804299] 決定係数(r2):0.00939 平均誤差(MAE):0.048 RMSE:0.07 ``` ![](https://i.imgur.com/n8ElVMD.png) - ~2/1 ``` li coe 0 ls -0.125903 1 sp -0.149567 回帰係数 [-0.12590263 -0.14956725] 決定係数(r2):0.00719 平均誤差(MAE):0.048 RMSE:0.07 ``` ![](https://i.imgur.com/zizVkt8.png) # 一旦、ls spモデルを実装&実稼働 - 実稼働 2022/10/4~ # 特徴量の定常性に関して研究 ### 月ごとのlsのヒストグラムを確認 ![](https://i.imgur.com/1fzHl1h.png) - 各月ごと ![](https://i.imgur.com/0rdz6nx.png) - 見づらいので8.9月のみ ![](https://i.imgur.com/GRqjcck.png) - 相対化しない場合 - 下のように月によってぶれが生じる ![](https://i.imgur.com/pIh5o0I.png) ### difのヒストグラム - difは定常性がない ![](https://i.imgur.com/hx0CIA0.png) # 重大なミス発覚 - 時間ごとに特徴量を相対化ができていなかった - 最後のconcat(vol)を書き忘れてた - **描画が大事** ``` #時間ごとに特徴量を相対化 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) ``` - 特徴量の相対化をやった後のバックテスト結果 25銘柄 ![](https://i.imgur.com/EhK7rfE.png) # 学習期間を変えてみて、バックテスト結果と回帰係数を確認してみる - **結局LSとSPの回帰係数の比によって結果が変わる** - 3月まではLSがSPに対して有効だったから、LSを重視しすぎている - 8月までにするとLSの予測力が低くなるため、SPの重要度が相対的に高い - 3月 ``` li coe 0 ls -0.023936 1 sp -0.004565 ``` ![](https://i.imgur.com/bkA1gxt.png) - 5月 ``` li coe 0 ls -0.031033 1 sp -0.019716 ``` ![](https://i.imgur.com/7uT41Me.png) - 8月 ``` li coe 0 ls -0.019592 1 sp -0.030720 ``` ![](https://i.imgur.com/IcINrGu.png) - trend30も入れてみる - 過剰適合している - **必ず回帰係数を確認しないといけない** ``` li coe 0 trend30 -3672.314702 1 trend7 -53.421259 2 sp -0.079352 ``` ![](https://i.imgur.com/Uwlp0vp.png) # 重回帰の過剰適合の原因を探る ### エラー修正 - この分布が原因そう - fillna(0)がだめ ![](https://i.imgur.com/WYBDMqw.png) - lsは分布がきれい ![](https://i.imgur.com/bxE6t0r.png) #### nanの数を確認 - trend30が多すぎる ``` 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 ``` - 相対化するときに標準化にnanが含まれてた ``` #時間ごとに特徴量を相対化 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]) ``` ### 直交化する前と後のLSのヒストグラムを確認 - diflsで直行化する前と後 - ほぼほぼ変わらない - 異常値が真ん中に寄る - バックテスト結果もほぼ変わらない ![](https://i.imgur.com/KVvczsE.png) # LSの値を予測する - 指標を作成&相関を確認 ![](https://i.imgur.com/xi6zGpA.png) ``` 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 ``` ### 各指標のヒストグラムを確認 ![](https://i.imgur.com/sOjByMJ.png) - ma系が左に歪みすぎてる - **これは交互作用とか次第で有益な指標かも** ![](https://i.imgur.com/AaWayKq.png) - 生値を確認したら違った ![](https://i.imgur.com/CvkWmsc.png) - 各時間でlsma7のヒストグラムを確認した ![](https://i.imgur.com/TOQHEeN.png) - 銘柄を確認してみる - **その時々で(他の銘柄と比べて相対的に)長期間LSが小さい銘柄が発掘できた** - **これらの銘柄は特別な挙動を示すかもしれない** ``` print(i[i[a]==i[a].min()][["sc",a]]) ``` ![](https://i.imgur.com/eCf5tpx.png) ### とりあえずlsmaとdevは正規分布じゃないから、それらを覗いて直交化 ``` #とりあえず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]) ``` - 分布を確認した感じマシになった ![](https://i.imgur.com/Bt1QJc0.png) ### 再度特徴量同士の相関を確認 - 下と右は目的変数&FNしていない特徴量 ![](https://i.imgur.com/wzJMdkM.png) ## 学習 ### 翌日までのLSの変化量を予測する - 予測実測プロットの作成 - **以下二つの特徴量がLSのリターン予測において説明力がある** - 移動平均乖離率→1週間平均よりも下がりすぎるとリバウンドする - 前日のLS変化量→リバーサル ``` 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 ``` ![](https://i.imgur.com/yknykvP.png) - lsnextを予測してみる - lsが効きすぎるし、前のデータをそのまま予測しちゃってるような気がするから微妙そう - ``` 相関係数 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 ``` ## pred_yとlsretのヒストグラムを確認 - 予測値のほうが控えめな値を出す ![](https://i.imgur.com/u7150ct.png) # LSretの予測値をさらに新たな特徴量として加えてみる - 現在特徴量として強力にワークしているLSとlsretpredが相関していないか確認 - 割と大丈夫そう ![](https://i.imgur.com/A5wHbmB.png) - difとの相関を確認 ``` fig=plt.figure(figsize=(6,6)) df[["dif","trend1","ls","pred_y","lsdev7","difls"]].corr()["dif"].iloc[1:].plot.bar() ``` ![](https://i.imgur.com/pwEgstD.png) ## 実装 - predlsretを出力 ![](https://i.imgur.com/obyRiiW.png) - 直交化 ``` df["plsret"]=neutralize_series(df["plsret"],df["trend1"]) df["plsret"]=neutralize_series(df["plsret"],df["ls"]) ``` ### 結論:劣化した - 原因 - plsretがリターンに対して時系列で安定した特性を示さない - 相関係数はちゃんとある ``` li coe 0 ls -0.035179 1 sp -0.031559 2 plsret 0.031689 ``` - 特徴量にplsretを入れる前 ![](https://i.imgur.com/MqWSVS7.png) - 入れた後 ![](https://i.imgur.com/MeJusbx.png) ``` 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 ``` # XGBoostでLSを予測してみる - 参照 - uki github - https://github.com/UKI000/JQuants-Forum/blob/main/jquants02_news_uki_predictor.py - install ``` (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) ``` - 特徴量重要度 ![](https://i.imgur.com/pWJym5V.png) ## 事後FNによって線形成分を取り除く - FNなし XGB 5月まで訓練 ![](https://i.imgur.com/eH0WMRF.png) - FNあり XGB ``` pred_y=neutralize_series(pd.Series(pred_y),df["ls"]) pred_y=neutralize_series(pd.Series(pred_y),df["difls"]) ``` ![](https://i.imgur.com/SpLKrLR.png) - 5月まで - ちょっと成果出そうだけど、むずいなあ ``` 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]) ``` ![](https://i.imgur.com/hmSgWnR.png) # 定常性に関して再検討 ## dif - 各時間で標準化(クロスセクションで相対化)したものを月ごとに表示 - 割と定常 - ファットテール(トレンドフォローが効く可能性示唆) ``` #定常性について検討 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)) #ラベルを右に ``` ![](https://i.imgur.com/ETa4Obz.png) - 曜日 ![](https://i.imgur.com/27DCgsI.png) - 平日と土日でも変化なし ![](https://i.imgur.com/PAP8pSF.png) ## atr - atrに関して ![](https://i.imgur.com/lZy5I6D.png) - atrretを予測 ``` 相関係数 0.47710374303751435 li coe 0 ls -0.029137 1 sp 0.081840 2 atr 0.715933 3 atr7 -0.384286 ``` ![](https://i.imgur.com/VUr1xaM.png) ## ls - ls ![](https://i.imgur.com/NFzeFzo.png) - lsma30 ![](https://i.imgur.com/DvspbLq.png) - lsmaだけ分布が汚い - 元の分布を表示させてみる ![](https://i.imgur.com/f902wAt.png) ## fr ![](https://i.imgur.com/h4sx67Z.png) - 対数変換がうまくいかない ![](https://i.imgur.com/yC7yVIX.png) - もとの分布 ![](https://i.imgur.com/TFkmFIl.png) #### frに適当なランダム数字を割り振る - 適当なランダム数字を割り振る - 標準偏差1以内に収まるようにいい感じに散らす - 0.0001を中心にする - いい感じになった! ``` 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 ])) ``` ![](https://i.imgur.com/JOOdyMn.png) - 月ごとの分布 ![](https://i.imgur.com/HhlKMN6.png) - midを0にした ![](https://i.imgur.com/QhXU7pp.png) # fr込みでバックテスト - frを散らしすぎたせいか成果が出ない ``` シャープレシオ 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は相対値よりも絶対値が大事=リターンとの線形的な関係性を持つ - fr標準化なし&分散あり - バックテストはFR単体 ![](https://i.imgur.com/nhIp8Mp.png) ![](https://i.imgur.com/6sAtdsT.png) - fr標準化あり&分散あり ![](https://i.imgur.com/c65R6GG.png) ![](https://i.imgur.com/RIXYKrW.png) ### quantile ![](https://i.imgur.com/fSmNw22.png) ### 最終バックテスト li=["sp","ls","fr"] ![](https://i.imgur.com/xGYeWa2.png) ``` 損益計算 シャープレシオ 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 ``` # ~~目的関数の代替関数を作る~~ 全部間違ってる ### 分類タスク ##### 2分類 ``` #目的関数 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 ``` ![](https://i.imgur.com/DRwgq7B.png) ![](https://i.imgur.com/xvcCooR.png) ##### 5分類 ![](https://i.imgur.com/ZXVvkQH.png) ![](https://i.imgur.com/W7UPIeD.png) ### 特徴量を大量に入れてみる ![](https://i.imgur.com/DojRi0Z.png) ![](https://i.imgur.com/TNFXCpp.png) - 二値分類が結構いい ![](https://i.imgur.com/9gAdxxz.png) ``` 損益計算 シャープレシオ 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 ``` # マーケット特徴量,ranking特徴量 - よしそAMAやtwitterを参考に入れてみる - 交互作用が強いらしい(マーケット) ``` #時間ごとに特徴量を相対化 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 ``` #### LGBMにマーケット特徴量入れてみる - 損失関数が減った!!!!!!!!!!!!!!!! ``` 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 ``` ![](https://i.imgur.com/f65rWts.png) ![](https://i.imgur.com/NsV7V0V.png) #### 回帰で解いてみる - だめ ![](https://i.imgur.com/ijFyTba.png) # GBDTについて深堀 ### ツリーを可視化してみる - 簡単な決定木だと可視化できる - https://qiita.com/perico_v1/items/fbbb18681ecc362a4f9e ``` 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() ``` ![](https://i.imgur.com/QV8oepO.png) ![](https://i.imgur.com/v484J5u.png) ![](https://i.imgur.com/QH67xWu.png) ### SHAP ``` #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の絶対値 ![](https://i.imgur.com/B7xbaDt.png) - shap ``` # 全サンプルについて、各特徴量のSHAP値の分布を可視化したもの # 上記の棒グラフ同様、予測に対する寄与度が大きい順にソートされて表示される # 色は特徴量の値の大小を表している(赤が大きくて青が小さい) # LSTATの値が小さいほど予測値(住宅価格)は大きくなることがわかる ``` ![](https://i.imgur.com/KmfSwFr.png) - shap 1 ``` # ある1つの特徴量(今回はRM)について、訓練データ全体でのその特徴量(RM)の値と、対応するSHAP値の散布図を作成 # 横軸が特徴量の値、縦軸が対応するSHAP値 # SHAP値は予測値に対する特徴量の寄与を示すので、この図から、RMが変化したら予測値がどう変化するか読み取れる # RMが大きいほどSHAP値は大きい、つまり、home priceが大きくなることを示している # また、このグラフでは選択した特徴量(RM)と他の特徴量との相互作用も確認できるようになっていて # color引数にshap値の変数を指定すると、自動的に適した変数が選択される # 今回はRADが選ばれていて、RADの大小によって、同じRMでもSHAP値が異なることが確認できる ``` ![](https://i.imgur.com/644var9.png) ### shapの値から特徴量の特性を判断する ##### market - 前日marketが下落した日はtrend1が小さい(前日市場平均よりももっと下がった)ものが買われる - 逆に市場平均より耐えたものは翌日売られる - 前日マーケットが上昇すると、市場平均をアウトパフォームしたものはもっと買われる。 - アンダーパフォームしたものは次の日売られる ![](https://i.imgur.com/m67pLH2.png) ##### atr atr7 - atrは逆張り、atr7は順張りの傾向がある ![](https://i.imgur.com/QllSlbK.png) ![](https://i.imgur.com/PAg96wg.png) ##### ls lsma7 - やっぱり逆張り ![](https://i.imgur.com/NJhtTXi.png) ![](https://i.imgur.com/7sU3hJJ.png) - lsが低くてボラが低いと上がる? - lsが高いと下がる ![](https://i.imgur.com/XwtIXlC.png) ![](https://i.imgur.com/DLtsDFH.png) ### 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"] ``` ![](https://i.imgur.com/0QEhNEB.png) #### バックテスト - sp,lsだけ ![](https://i.imgur.com/UxVYY1H.png) - いろいろ使った - li=["sp","ls","trend1_market","atr_market","atr7_market"] ![](https://i.imgur.com/9NCSa0c.png) # ベータ感応度 ``` #ベータ感応度 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() #ラベルを右に ``` ![](https://i.imgur.com/44lMN09.png) ### 特徴量の相関 - ちょっとあやしい ![](https://i.imgur.com/RF4hDnd.png) ![](https://i.imgur.com/fkg67Sk.png) ### ヒストグラム 定常性 ![](https://i.imgur.com/6buwR75.png) ![](https://i.imgur.com/HdCSbwa.png) ![](https://i.imgur.com/jjRhCXL.png) # クラスタリング ``` 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() ``` - 過去30日rollingdataで検証したクラスターの平均値 - こうやってやるしかない気がする ![](https://i.imgur.com/HYXY2CP.png) ![](https://i.imgur.com/g4i5pxJ.png) ![](https://i.imgur.com/n68I867.png) # 銘柄ごとにファクターへの感応度を調べる - 銘柄ごとのlsとdifの相関係数 - クロスセクション標準化済み ![](https://i.imgur.com/rjyyutI.png) ![](https://i.imgur.com/Hxftfdt.png) - 標準化なし ![](https://i.imgur.com/gqYpx8E.png) ##### 異常値排除大事かも - unfiの散布図 ``` 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 ``` ![](https://i.imgur.com/hp5OCk5.png) # lsで説明できないリターンのファクターリバーサル 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"] ![](https://i.imgur.com/TtHtJmI.png) ##### 全体で回帰ver ``` #回帰 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の成分 ``` ![](https://i.imgur.com/zwA0A7j.png) ![](https://i.imgur.com/P8EG1jj.png) # ユニバース選定 li=["sp","ls"] ## 週1ポジション(1週間持ち続ける) ##### 月 ![](https://i.imgur.com/EInampX.png) ##### 火 ![](https://i.imgur.com/HATkKra.png) ##### 水 ![](https://i.imgur.com/AcIGcCC.png) ##### 木 ![](https://i.imgur.com/uVWETmX.png) ##### 金 ![](https://i.imgur.com/s6GREiB.png) ##### 土 ![](https://i.imgur.com/oryjYcW.png) ##### 日 ![](https://i.imgur.com/G0yoePP.png) ## 週1ポジション(1日だけ) ##### 月 ![](https://i.imgur.com/Klr5bJm.png) ##### 火 ![](https://i.imgur.com/clOWYdt.png) ##### 水 ![](https://i.imgur.com/jTQ3nin.png) ##### 木 ![](https://i.imgur.com/J7Bpurv.png) ##### 金 ![](https://i.imgur.com/K4Pffrr.png) ##### 土 ![](https://i.imgur.com/sVFcXuT.png) ##### 日 ![](https://i.imgur.com/kvVQs6L.png) ## market<0 5,10,30 ![](https://i.imgur.com/M4Z3gul.png) ## market>0 ![](https://i.imgur.com/wCj5uN0.png) ## atrは大きい銘柄のほうがいい - atrが小さい銘柄でやるときれいな右肩下がり ![](https://i.imgur.com/aDxngqM.png) ##### atrの分布 - めちゃくちゃポジティブスキュー ![](https://i.imgur.com/NHugzOO.png) ##### trend1の分布 ![](https://i.imgur.com/NoS2aHZ.png) ## lsは大きい銘柄のほうがいい # beta感応度をちゃんと実装&スペシフィックリターンリバーサル検証 ``` 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 ``` ![](https://i.imgur.com/WzhACuY.png) - trend1とaplhaの比較 ![](https://i.imgur.com/NF7K4Ib.png) - 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() ``` ![](https://i.imgur.com/D5yBosR.png) ``` -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 ``` - 分布 ![](https://i.imgur.com/eGvVRlN.png) # 上昇銘柄と下落銘柄のパフォーマンスの違い ### 市場平均より下落した銘柄 - lsは順相関 - LSが高い銘柄が落ちずらい - LSが低い銘柄が大きく落ちる ``` df=df[df["dif"]<0] ``` ![](https://i.imgur.com/RIZVt8N.png) ![](https://i.imgur.com/tmBehqF.png) ![](https://i.imgur.com/wcBlQbh.png) ### 市場平均より上昇した銘柄 ![](https://i.imgur.com/Q3Be1zC.png) ![](https://i.imgur.com/GwxcUPh.png) # 課題 特徴量同士の相関のケア predyに対して特徴量で直交化  線形な重回帰でもつかえるの? 特徴量を作成するときに、クロスセクションでの相対値にしなくていいの? マーケットベータを算出する 重回帰でも分割検証 21時以外 **非線形な特徴量でスペシフィックリターンを控除** 代替の指標を予測する セクターごとに指標を正規化する 分布の定常性を高める工夫をする LSが下がっても折込が甘い銘柄をロング LSの分散とか移動平均とかを考慮 LS移動平均乖離、LS前日騰落率を特徴量にしたらいいのでは lsretの予測値を新たな特徴量にする lsma7を使ってみる ボラを予測する 高値安値を予測 lsの分散とかを見る **LSにypredを足して予測してみる** 目的関数をリニア、分類などいろいろなものにしてみて、それらの結果をアンサンブルする 特徴量重要度出してみる 複数の特徴量を使ったほうがロバストにはなるのか? 期待値は変わらず、SRは若干劣化していても、指標が2個より5個のほうがロバストになるのか 銘柄ごとにファクターから影響の受けやすさを定量化する 1 銘柄ごとにタイムシリーズ回帰 2 クロスセクション標準化+ファクターで全体回帰して、残さのでかさを算出   その平均値を銘柄ごとに算出 LSで線形回帰した残さ=LSで説明できない部分 これが特徴量になったりしないか y=ax+b のb 指標同士の直交化を深堀 周1回ポジる  曜日ごとで挙動変化チェック # よしそAMA ビッグデータのほうがロバストな傾向がある 現在の潮流として データ数数百万とか 目的関数にエッジがある(よしそ) リターンが正規分布じゃないから線形回帰はだめ だからクラシフィケーションなどのバイナリーデータでの予測のほうがいい(たぶん分類問題) 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 意図してレジーム特徴量を入れる よ:相場全体から集約した特徴量 全銘柄のうちで何%が上がってるかとか そういう指標が効く! # FNの説明 https://hackmd.io/@taku5595/B1IYhYVMs