[toc]
# Binance全銘柄のPOCロジック
## データ整理
* 以下の画像のように全銘柄のPrice Of Controll (以下POC)を計算したい。
* 元データの`test3.pkl`は5分足でバイナンス全銘柄、2021年8月から2022年11月現在まで、約3000万行のデータ。サイズは3GBくらい。

* 書いたコード
```python=
import requests
from datetime import datetime
from datetime import timedelta
import time
import numpy as np
import pandas as pd
from tqdm import tqdm
from datetime import timedelta
import aiohttp
import asyncio
import itertools
import traceback
import gc
import matplotlib.pyplot as plt
from matplotlib import mlab
import glob
import sys
class anomary:
def MakeFeatures(self):
df = pd.read_pickle("test3.pkl")
#df = df.tail(1500)#少なめのデータでテスト
def CalcPOC(x, df):
#rollingの区間でのtimestamp、close、volumeのDataFrameを再生成
df_i = pd.DataFrame()
df_i["timestamp"] = x
df_i["close"] = df.loc[x.index, "close"]
df_i["volume"] = df.loc[x.index, "volume"]
#vwapからprice of controlを計算
df_vwap = pd.pivot_table(df_i, values="volume", index="close", columns="timestamp")
df_vwap["sum_volume"] = df_vwap.sum(axis=1)
poc = df_vwap[ df_vwap["sum_volume"] == df_vwap["sum_volume"].max() ].index
#print(df_i)
#print(df_vwap)
#print(poc[0])
#sys.exit()
return poc[0]
VwapDataNum = 2 #binanceでは1500ずつデータを呼び出せるから時間足1500本ずつvwap、pocを出して行きたい
def AllSymbolFeatures(df):
#複数列のrollingの方法
#https://qiita.com/matsxxx/items/49068d2cf0c3311819dd
df[f"poc_{VwapDataNum}"] = df["timestamp"].rolling(VwapDataNum).apply(CalcPOC, args=(df,), raw=False)
#print(df)
#sys.exit()
return df
#df = df.groupby("symbol").apply(AllSymbolFeatures) #DataFrameを銘柄ごとに分割
df = df.groupby("symbol").progress_apply(AllSymbolFeatures) #tqdmとpandasの連携、進捗バーを表示する
df.to_pickle("test4.pkl")
return
if __name__ == "__main__":
an = anomary()
an.MakeFeatures()
```
**まじで処理が一生終わらない。**
### Pandasの高速化について調べる
#### 参考記事
* [pandasの書き方1](https://shinyorke.hatenablog.com/entry/pandas-tips)
* [pandasの書き方2](https://naotaka1128.hatenadiary.jp/entry/2021/12/07/083000)
* [Cython入門](https://qiita.com/en3/items/1f1a609c4d7c8f3066a7)
* [Numba入門](https://yutori-datascience.hatenablog.com/entry/2014/12/09/235628)
* [Numpyのrolling](https://zenn.dev/taku227/articles/833455ace8a3aa)
#### 参考記事を読んだ所感
* CythonとかNumbaとかが最速っぽい、けど学習コスト高い
* Pandasのfor、iterrowsは当然論外として、applyもかなり遅い
* なるべくNumpyを使う意識をつけるだけでもかなり改善されそう
* groupbyを使うときは、型をdtypeからcategoryに変換することが大事
とりあえずCython & Numbaは置いといてNumpyを使う意識をつける
いま何とかできるコードを書いてる途中
#### コードの改善案
* applyをなるべく使わないようにするとしても、そもそも上のコードは馬鹿正直に価格帯別出来高を求めてからpocを計算してて、**遠回りすぎる**
* 普通にvolume * closeの最大を抽出すればいいだけなのでは?(しかもbinanceはもともとvolumeusdが用意されてる)
* あほやん
##### Numpyテスト
* maxとかいろいろ
```python=
import numpy as np
import pandas as pd
lista = [
[1,2],
[2,3],
[10,100],
[3,4],
[4,5],
[99,6],
]
df = pd.DataFrame(lista,columns=["A","B"])
a = df[["A","B"]].values
print(a)
print(f"要素が最大となるインデックス{ np.unravel_index(a.argmax(), a.shape)}")
print(f"a[i][0]の最大インデックス:{np.argmax(a, axis=0)[0]}")
print(f"a[i][1]の最大インデックス:{np.argmax(a, axis=0)[1]}")
print(f"a[i][1]が最大である時のa[i][0]:{ a[ np.argmax(a, axis=0)[1] ][0] }")
```
===
```
[[ 1 2]
[ 2 3]
[ 10 100]
[ 3 4]
[ 4 5]
[ 99 6]]
要素が最大となるインデックス(2, 1)
a[0]の最大インデックス:5
a[1]の最大インデックス:2
a[1]が最大である時のa[0]:10
```
* itertoolsのgroupbyとか
```python=
import numpy as np
import pandas as pd
import itertools
lista = [
["a",1,2],
["b",2,3],
["b",10,100],
["a",3,4],
["b",4,5],
["a",99,6],
]
df = pd.DataFrame(lista,columns=["category","A","B"])
print(df)
[ print(_, list(x)) for _, x in itertools.groupby(df.to_numpy(), key=lambda x: x[0]) ]
print("========")
df.sort_values(["category"],inplace=True)
print("========")
[ print(_, list(x)) for _, x in itertools.groupby(df.to_numpy(), key=lambda x: x[0]) ]
```
```
category A B
0 a 1 2
1 b 2 3
2 b 10 100
3 a 3 4
4 b 4 5
5 a 99 6
a [array(['a', 1, 2], dtype=object)]
b [array(['b', 2, 3], dtype=object), array(['b', 10, 100], dtype=object)]
a [array(['a', 3, 4], dtype=object)]
b [array(['b', 4, 5], dtype=object)]
a [array(['a', 99, 6], dtype=object)]
========
========
a [array(['a', 1, 2], dtype=object), array(['a', 3, 4], dtype=object), array(['a', 99, 6], dtype=object)]
b [array(['b', 2, 3], dtype=object), array(['b', 10, 100], dtype=object), array(['b', 4, 5], dtype=object)]
```
* rollingの代わり、np.lib.stride_tricks.sliding_window_viewについて(numpy-1.19.2を僕は今まで使ってたけどそれは動かなかった、numpy-1.23.4にアップグレード)
```python=
import numpy as np
import pandas as pd
import itertools
lista = [
["a",1,2],
["b",2,3],
["b",10,100],
["a",3,4],
["b",4,5],
["a",99,6],
["a",15,2],
["b",30,5],
]
df = pd.DataFrame(lista,columns=["category","A","B"])
df.sort_values(["category"],inplace=True)
print(df)
windowsize = 2
print(f"windowsize : {windowsize}")
def rollingtest(x,windowsize):
a = np.array( list(x) )
print("----")
print( a )
a = a[:,1:]
print("====")
print( a )
rolled = np.lib.stride_tricks.sliding_window_view(a, windowsize, axis=0)
print("xxxxx")
print( np.array( list(rolled) ))
x = rolled.max(axis=2)
print("****")
print(x)
return
[ rollingtest(x,windowsize) for _, x in itertools.groupby(df.to_numpy(), key=lambda x: x[0]) ]
```
```
category A B
0 a 1 2
3 a 3 4
5 a 99 6
6 a 15 2
1 b 2 3
2 b 10 100
4 b 4 5
7 b 30 5
windowsize : 2
----
[['a' 1 2]
['a' 3 4]
['a' 99 6]
['a' 15 2]]
====
[[1 2]
[3 4]
[99 6]
[15 2]]
xxxxx
[[[1 3]
[2 4]]
[[3 99]
[4 6]]
[[99 15]
[6 2]]]
****
[[3 4]
[99 6]
[99 6]]
----
[['b' 2 3]
['b' 10 100]
['b' 4 5]
['b' 30 5]]
====
[[2 3]
[10 100]
[4 5]
[30 5]]
xxxxx
[[[2 10]
[3 100]]
[[10 4]
[100 5]]
[[4 30]
[5 5]]]
****
[[10 100]
[10 100]
[30 5]]
```
### POC計算最終コード
* 上のnumpyテストで色々試した書き方でPOC計算の高速化(というかなるべくpandasを使わず、numpy計算するように心がける)したコード
```python=
#df = pd.read_pickle("test3.pkl")
df = pd.read_pickle("btctest.pkl")
df.sort_values(["symbol","timestamp"],inplace=True)
print(df)
windowsize = 4
def rollingtest(x,windowsize):
a = np.array( list(x) )
#symbolを除いた[ [close_0, volumeusd_0], [close_1, volumeusd_1], ... ]の長さ2の2次元行列
a = a[:,1:]
#0軸(close方向、volumeusd方向)のrolling行列rolledを生成する、[ [ [close_0, close_2, ... close_win] [vol_0, vol_1, ... vol_win] ], ... ]の3次元行列になる
rolled = np.lib.stride_tricks.sliding_window_view(a, windowsize, axis=0)
rolled = np.array(list(rolled))
#3次元行列のうち一番高次元の2(close方向、vol方向)のmaxのインデックス番号を出す, [ [max close index, max vol index], ... ]の2次元行列になる
x = rolled.argmax(axis=2)
#xを各max vol indexのみの1次元配列にする [ vol_idx, vol_idx, ... ]
x = x[:,1]
#各rolledからx[i]のcloseを抽出
pocs = [ roll_i[0][x_i] for roll_i, x_i in zip(rolled, x) ]
#格納するためのnan1次元配列生成
results = np.full(len(a),np.nan)
#例えばrolling=2だったら、1はnanだからそれ以降に結果を格納
results[windowsize-1 : ] = pocs
return results
results_numpy = [
rollingtest(x,windowsize) for _, x in itertools.groupby(df[["symbol","close","VolumeUsd"]].to_numpy(), key=lambda x: x[0])
]
#結果は[ [銘柄のpoc], [銘柄のpoc], ... ]の2次元だから1次元に平坦化
results_numpy = list( itertools.chain.from_iterable(results_numpy) )
df["poc"] = results_numpy
print(df[df["symbol"]=="BTCUSDT"])
```
```
symbol timestamp LS OI OIUsd open high low close volume VolumeUsd BuyVolume BuyVolumeUsd
33007742 1000LUNCUSDT 1.667651e+12 0.7610 113091373.0 2.853295e+07 0.2523 0.2525 0.2500 0.2509 7863409.0 1.974864e+06 2814372.0 707081.42830
33007892 1000LUNCUSDT 1.667652e+12 0.7623 112521480.0 2.822990e+07 0.2509 0.2519 0.2507 0.2516 1851985.0 4.657780e+05 996650.0 250635.16430
33008042 1000LUNCUSDT 1.667652e+12 0.7622 112456497.0 2.829540e+07 0.2517 0.2523 0.2516 0.2521 2154617.0 5.428617e+05 1140706.0 287421.50030
33008192 1000LUNCUSDT 1.667652e+12 0.7613 112406711.0 2.833422e+07 0.2521 0.2523 0.2510 0.2520 2189235.0 5.511149e+05 1239507.0 312100.88790
33008342 1000LUNCUSDT 1.667653e+12 0.7619 112294564.0 2.830946e+07 0.2521 0.2525 0.2510 0.2517 2005778.0 5.051170e+05 920877.0 231949.99060
... ... ... ... ... ... ... ... ... ... ... ... ... ...
33008041 ZRXUSDT 1.667652e+12 0.6765 16752614.6 4.683203e+06 0.2796 0.2800 0.2793 0.2794 56462.6 1.579166e+04 17328.1 4847.22619
33008191 ZRXUSDT 1.667652e+12 0.6764 16760636.1 4.683244e+06 0.2794 0.2794 0.2792 0.2794 28437.5 7.944568e+03 19215.7 5368.59997
33008341 ZRXUSDT 1.667652e+12 0.6772 16753661.4 4.680973e+06 0.2794 0.2797 0.2791 0.2795 79323.3 2.215720e+04 53845.2 15041.30368
33008491 ZRXUSDT 1.667653e+12 0.6770 16750298.5 4.683376e+06 0.2795 0.2799 0.2788 0.2788 137192.4 3.835039e+04 76108.7 21287.21869
33008641 ZRXUSDT 1.667653e+12 0.6745 16774160.2 4.681995e+06 0.2789 0.2790 0.2781 0.2782 84156.9 2.342662e+04 36460.0 10152.09095
[1000 rows x 13 columns]
symbol timestamp LS OI OIUsd open high low close volume VolumeUsd BuyVolume BuyVolumeUsd poc
33007774 BTCUSDT 1.667651e+12 0.4163 149497.820 3.184981e+09 21304.2 21315.0 21283.5 21297.1 2299.624 4.897305e+07 1088.481 2.318041e+07 NaN
33007924 BTCUSDT 1.667652e+12 0.4157 149401.439 3.182101e+09 21297.0 21316.0 21292.9 21296.9 924.741 1.969980e+07 572.240 1.219043e+07 21297.1
33008074 BTCUSDT 1.667652e+12 0.4170 149401.930 3.181936e+09 21296.9 21314.4 21296.9 21314.2 814.244 1.735097e+07 625.065 1.331957e+07 21314.2
33008224 BTCUSDT 1.667652e+12 0.4180 149495.241 3.186440e+09 21314.2 21335.2 21306.4 21329.2 917.112 1.955567e+07 640.528 1.365788e+07 21329.2
33008374 BTCUSDT 1.667653e+12 0.4201 149448.295 3.187680e+09 21329.1 21344.4 21305.9 21313.1 1133.483 2.417168e+07 549.409 1.171727e+07 21329.2
33008524 BTCUSDT 1.667653e+12 0.4204 149579.740 3.188023e+09 21313.2 21313.9 21300.0 21304.0 509.563 1.085710e+07 222.077 4.731629e+06 21313.1
```
## 特徴量その他計算
```python=
df = pd.read_pickle("test4.pkl")
df["date"] = df["timestamp"].progress_apply(lambda x: pd.Timestamp(x, unit="ms"))
def Features(x):
x = np.array(list(x))
close = x[:,1]
pct_change = np.full(len(close), np.nan)
pct_change[:-1] = np.diff(close) / close[1:]
return pct_change
print("___銘柄ごと特徴量計算___")
results_numpy = [
Features(x) for _, x in tqdm(itertools.groupby(df[["symbol","close"]].to_numpy(), key=lambda x: x[0]))
]
results_numpy = list( itertools.chain.from_iterable(results_numpy) )
df["ret"] = results_numpy
print(df[df["symbol"]=="BTCUSDT"])
df.to_pickle("test4.pkl")
```
## データ分析1
* 直近30分 価格変化率正の銘柄数 ÷ 全銘柄数 = UpSymbolsRatio と定義
- UpSymbolsRatio > 0.8 の時、相場全体が上がってるとして、ラベル"up"
- UpSymbolsRatio < 0.2 の時、相場全体が下がってるとして、ラベル"down"
- その他のとき、ラベル"none"
* 全銘柄のうち、POCに対して上乖離している上位5銘柄を抽出 **(上がってる5銘柄)**
* 直近30分変化率 と 30分後リターン、 60分後リターン との相関係数を出す
```
up 30min ret corr : 0.05786 up 60min ret corr : -0.006381
down 30min ret corr : -0.04328 down 60min ret corr : -0.01107
none 30min ret corr : 0.06684 none 60min ret corr : 0.1314
```

拡大、noneだけ見てみる

* 考察:
* 相場全体がヨコヨコのときに、急騰した銘柄は次も継続して上がる?
* 下落相場、上昇相場では急騰した銘柄の次の値動きは不明?
* 直観的には、チャートみててよくある「突然何かのアルトが吹き上げて、そのまましばらく上がる」現象が見えてきているような気もする。
___
* 全銘柄のうち、POCに対して下乖離している上位5銘柄を抽出 **(下がってる5銘柄)**
* 直近30分変化率 と 30分後リターン、60分後リターン との相関係数を出す
```
up 30min ret corr : 0.3355 up 60min ret corr : 0.2812
down 30min ret corr : -0.1113 down 60min ret corr : -0.1443
none 30min ret corr : 0.03544 none 60min ret corr : -0.0175
```

相関係数的にupとdownだけ見てみる


* 考察:
* 上昇、下落相場どちらでも相関係数の絶対値がかなり大きい。
* ヨコヨコでは比較的小さい。
* 相関係数だけから解釈すると、「下げてる銘柄は下げ相場の時はリバる、上げ相場の時にはそのまま下げ続ける」と言えそう。
* ただ、プロットを見てみると、外れ値の影響がかなり大きそうで、相関係数の解釈は眉唾?
___
* 上の分析のうち、noneの黒い点がプロットされてるやつ
* 0.2 < UpSymbolsRatio < 0.8 の 相場全体がnoneの時
* 直近30分変化率 と 60分後リターン の相関係数 0.1314
* のやつが気になったからもう少し深堀りする。
__
* 全銘柄のうち、POCに対して上乖離している上位5銘柄を抽出 **(上がってる5銘柄)**
* 0.2 < UpSymbolsRatio <= 0.5 の とき、ラベル = none_down
* 0.5 < UpSymbolsRatio < 0.8 の とき、ラベル = none_up
* 直近30分と 30分後、60分後リターン との相関係数を出す
```
none_up 30min ret corr : 0.1236 none_up 60min ret corr : 0.2061
none_down 30min ret corr : -0.01975 none_down 60min ret corr : 0.02609
```

拡大

* 考察:
* なんとも言えない。結局この相関係数も外れ値に引っ張られてるだけ?
* 外れ値の入ってないnone_downの方もある程度相関係数があったら嬉しかったのに…
___
## データ分析1の総括
相関係数に対する外れ値の影響が大きすぎてなんとも言えない。
そもそも今回の検証をしてる動機は、「突然急騰したアルトとかを良い感じに拾えないか?」というもの。
"急騰"の定義を考えてみる。
## データ分析2
* 急騰の定義:
* 直近30分での価格変化率が5%以上
* 12時間でのPOCと現在価格の乖離が5%以上
- UpSymbolsRatio > 0.8 の時、相場全体が上がってるとして、ラベル"up"
- UpSymbolsRatio < 0.2 の時、相場全体が下がってるとして、ラベル"down"
- その他のとき、ラベル"none"
* 上を満たす銘柄の未来のリターンと現在の変化の相関を出す。
* 急騰した銘柄に限定して、直近30分と 30分後、60分後リターン との相関係数を出す
```
up 30min ret corr : 0.08696 up 60min ret corr : -0.06872
down 30min ret corr : -0.1799 down 60min ret corr : 0.04066
none 30min ret corr : 0.2643 none 60min ret corr : 0.5343
```
60分後リターンのプロット

60分後リターンのプロット noneだけ 拡大

30分後リターンのプロット

30分後リターンのプロット noneだけ 拡大

30分後リターンのプロット downだけ 拡大

___
* 急騰銘柄に対して
* 0.2 < UpSymbolsRatio <= 0.5 の とき、ラベル = none_down
* 0.5 < UpSymbolsRatio < 0.8 の とき、ラベル = none_up
* 直近30分と 30分後、60分後リターン との相関係数を出す
```
none_up 30min ret corr : 0.4198 none_up 60min ret corr : 0.7159
none_down 30min ret corr : -0.3485 none_down 60min ret corr : -0.146
```
30分リターンのプロット

30分リターンのプロット拡大

* 考察:
* none_up、none_downの相関係数を見ると
* 上げ相場よりの時、急騰銘柄はそのまま上がる
* 下げ相場よりの時、急騰銘柄は反転落ちる
* みたいな性質があると言えそう?
---
* さっきの急騰の定義の正負を逆にして急落としてやってみる
* 急落の定義:
* 直近30分での価格変化率が-3%以下
* 12時間でのPOCと現在価格の乖離が-3%以下
- UpSymbolsRatio > 0.8 の時、相場全体が上がってるとして、ラベル"up"
- UpSymbolsRatio < 0.2 の時、相場全体が下がってるとして、ラベル"down"
- その他のとき、ラベル"none"
* 急落した銘柄に限定して、直近30分と 30分後、60分後リターン との相関係数を出す
```
up 30min ret corr : 0.301 up 60min ret corr : 0.4852
down 30min ret corr : -0.1765 down 60min ret corr : -0.1834
none 30min ret corr : 0.3622 none 60min ret corr : -0.009148
```
30分後リターンのプロット

upだけ

noneだけ

downだけ

___
* 急落銘柄に対して
* 0.2 < UpSymbolsRatio <= 0.5 の とき、ラベル = none_down
* 0.5 < UpSymbolsRatio < 0.8 の とき、ラベル = none_up
* 直近30分と 30分後、60分後リターン との相関係数を出す
```
none_up 30min ret corr : 0.4621 none_up 60min ret corr : -0.355
none_down 30min ret corr : 0.1578 none_down 60min ret corr : 0.1287
```

* 考察:
* 何だか急騰銘柄の時と違った性質が見えてきてる気がする。
* 急騰銘柄の時は、
* 上げ相場よりの時、急騰銘柄はそのまま上がる
* 下げ相場よりの時、急騰銘柄は反転落ちる
* だったのに対して、今回の急落銘柄は
* none_up、none_downの30分後リターンの相関係数がどちらも正、つまり相場が上げよりだろうが下げよりだろうが基本落ちていく
* ただし、ほぼ全銘柄が落ちていくような相場の時は、リバる性質もある
* と言えそう?
## データ分析1,2の総括
* 上の文章に乗せたパラメータはあくまでも一例で、その後もUpsymbolratio、急騰、急落銘柄の閾値とかを色々変えてやってみた
* 繰り返しになるけど、急騰銘柄は
* 全体的に上げ相場よりの時に続伸
* 全体的に下げ相場よりの時には反転落ちる
* 急落銘柄は
* 相場の上下に関わらず落ちやすい?
* という定性的な説明ができるかもしれない。
* また、急騰急落の定義を、POCの乖離だけ、もしくは直近変化率だけの相関係数と散布図もみてみた。
* すると、上げ下げ関係なく大まかにリターンと直近変化の相関係数が-0.1~0.1の間に収まることが多かった。
* POC乖離と直近変化は両方を組み合わせて意味があると言えそう?
* 今までの結果の閾値を、そのままロジックの閾値としてバックテストして、損益グラフ、シャープレシオ、ドローダウンなど求めてみる。
## バックテスト
* UpSymbolRatio = 直近30分 価格変化率正の銘柄数 ÷ 全銘柄数、
* Condition : 直近30分価格変化 5% 以上かつ pocからの価格乖離5%以上 (急騰銘柄)
* 0.5 < UpSymbolRatio かつ Condition == True を満たす全銘柄のsum ロング
* UpSymbolRatio < 0.5 かつ Condition == True を満たす全銘柄のsum ショート
* 30分リターン
