設定

ファクター理論

ファクター理論の考え方
CAPMを拡張したファーマフレンチのファクターモデルに基づく

ETHのリターン=マーケットベータ*ベータ感応度+
(LSのファクター値)*a + (ボラティリティのファクター値)*b + スペシフィックリターン

ファクター分析

LS

ある日(9/22)のLSと価格の関係

          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とリターンの相関係数の累積和

LSとリターン(ラグなし)の相関係数の累積和

ボラティリティ 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"])

ボラティリティとリターンの相関係数の累積和

ある日のボラティリティとリターンの相関関係

方向を加味しない純粋なATRを指標として用いた場合

i["atr"]=(i["high"]-i["low"])/i["close"]*1
i["atr"]=i["atr"].rolling(7).mean()

方向を加味しない純粋な(前日のみの)ATRを指標として用いた場合

前日のみの方向性ATRを用いた場合

前日騰落率をファクター値とした場合

high lowが正しくない問題への対処

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])

これで改善した

ボラティリティ

方向性を考慮しないただのボラティリティ 7day

方向性なし。ATRは前日のhighlow差分のみ

方向性あり。ATRは前日のhighlow差分のみ

モメンタム

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))   #ラベルを右に

仮想通貨データで試してみる

前処理の大切さ

まとめると対数変換+異常値+標準化が必要

前処理の手順

  1. 全値を正の数にする
  2. 対数変換
  3. inf,nan除去
  4. 異常値排除
  5. 標準化
  6. FN

重回帰実践(事前FN)

テストデータ=学習データの場合

インサンプルだとFNしないほうがいい。
これはしょうがない
特徴量はls,dif0,trend7

各特徴量について

FNしないと結構極端に相関関係をとらえてしまう
→アウトオブサンプルで予測した際に、特徴量とターゲットとの相関関係が変化すると性能が劣化する。
複数指標を使っていても、学習データでターゲットyとの相関が特に強い特徴量の線形な関係性が極端に用いられてしまうから、その特徴量が効かない相場になった途端にワークしない。
FNして、特徴量とターゲットとの線形成分を除去したのはいいけど、完全に直交化したら、そもそもその特徴量はターゲットに対して何の説明力も持たないのでは?

事後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())

trendも直交化

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"])


いったんバックテストしてみる

スペシフィックリターンを算出してみる

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

LSとスペシフィックリターン

最後にスペシフィックリターンであるが、上記までに部分的な機械学習モデルの出力を含めて全ての特徴量が出揃ったら、それら全てを用いてリターンを直交化して独自のスペシフィックリターンを算出する。スペシフィックリターンは強いリバーサルの特性(ファクターリターンが負方向に推移する)を持つ場合が多く、原理的には情報を控除すればするほどその性能が向上する(はずである)。このため、控除対象として機械学習を用いたクラスタや非線形の情報を使うことは、この性能を向上する上で非常に重要である。下図は控除する情報を増やした場合、スペシフィックリターンの持つ予測性能がどのように向上していくか示している。

訓練期間を変えて試してみる

# 学習データとテストデータに分ける
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

一旦、ls spモデルを実装&実稼働

特徴量の定常性に関して研究

月ごとのlsのヒストグラムを確認

difのヒストグラム

重大なミス発覚

    #時間ごとに特徴量を相対化
    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)

学習期間を変えてみて、バックテスト結果と回帰係数を確認してみる

   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

重回帰の過剰適合の原因を探る

エラー修正

nanの数を確認


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])

直交化する前と後のLSのヒストグラムを確認

LSの値を予測する

        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

各指標のヒストグラムを確認

print(i[i[a]==i[a].min()][["sc",a]])

とりあえず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])

再度特徴量同士の相関を確認

学習

翌日までの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

相関係数 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のヒストグラムを確認

LSretの予測値をさらに新たな特徴量として加えてみる

fig=plt.figure(figsize=(6,6))
df[["dif","trend1","ls","pred_y","lsdev7","difls"]].corr()["dif"].iloc[1:].plot.bar()

実装

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             

XGBoostでLSを予測してみる

(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)

事後FNによって線形成分を取り除く

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])

定常性に関して再検討

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))   #ラベルを右に

atr

相関係数 0.47710374303751435
     li       coe
0    ls -0.029137
1    sp  0.081840
2   atr  0.715933
3  atr7 -0.384286

ls

fr

frに適当なランダム数字を割り振る

                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 ]))

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は相対値よりも絶対値が大事=リターンとの線形的な関係性を持つ

quantile

最終バックテスト

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

目的関数の代替関数を作る 全部間違ってる

分類タスク

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


5分類


特徴量を大量に入れてみる


損益計算
シャープレシオ 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特徴量

    #時間ごとに特徴量を相対化
    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


回帰で解いてみる

GBDTについて深堀

ツリーを可視化してみる

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

#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値の分布を可視化したもの
# 上記の棒グラフ同様、予測に対する寄与度が大きい順にソートされて表示される
# 色は特徴量の値の大小を表している(赤が大きくて青が小さい)
# LSTATの値が小さいほど予測値(住宅価格)は大きくなることがわかる

# ある1つの特徴量(今回はRM)について、訓練データ全体でのその特徴量(RM)の値と、対応するSHAP値の散布図を作成
# 横軸が特徴量の値、縦軸が対応するSHAP値
# SHAP値は予測値に対する特徴量の寄与を示すので、この図から、RMが変化したら予測値がどう変化するか読み取れる
# RMが大きいほどSHAP値は大きい、つまり、home priceが大きくなることを示している

# また、このグラフでは選択した特徴量(RM)と他の特徴量との相互作用も確認できるようになっていて
# color引数にshap値の変数を指定すると、自動的に適した変数が選択される
# 今回はRADが選ばれていて、RADの大小によって、同じRMでもSHAP値が異なることが確認できる

shapの値から特徴量の特性を判断する

market
atr atr7
ls lsma7

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()

銘柄ごとにファクターへの感応度を調べる

異常値排除大事かも
             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で線形回帰した残さ=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”]

全体で回帰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の成分


ユニバース選定

li=[“sp”,“ls”]

週1ポジション(1週間持ち続ける)

週1ポジション(1日だけ)

market<0

5,10,30

market>0

atrは大きい銘柄のほうがいい

atrの分布
trend1の分布

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

    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回ポジる
 曜日ごとで挙動変化チェック

よしそ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

全部見るトップへ戻る底へ移る