【仮想通貨取引所】FTXのfunding rateなどについて
株含めてデリバティブ取引の経験がなかったので、Funding Rate(資金調達率)を含め、どのような特長があるのか備忘録とする。今回は、仮想通貨取引所のひとつFTXを取り扱う。
取り扱うもの
FTX
Funding Rate
Funding Rateとは?
ざっくりした理解は、定時ごと手数料が加算/減算されていく仕組み・・・
ただ、取引所ごとに徴収タイミングやレート計算が違うとのこと。
- 取引所ごとに手数料計算が異なる
- 取引所ごとに徴収タイミングが異なる
- 現物と先物の価格乖離を減らす方向に働く手数料
- ポジションによって受取り側になるか支払い側になるか変わる
FTXのFunding Rate(FR)は?
BTC-PERP(BTC無期限先物)
文字通り期限のない先物取引。 そのため、FR徴収タイミングが短時間で設定されている。
- 取引タイミング 1時間ごと
FR手数料支払い
FR正値 FR負値 ロングが支払い ショートが支払い FR計算
先物TWAPとインデックスTWAPの差分から手数料を求めている。
つまり、これは先物とインデックスの乖離を表している。
TWAPf - TWAPi > 0 (乖離プラス) TWAPf - TWAPi < 0 (乖離マイナス) ロングが支払い ショートが支払い
手数料 = size * (TWAPf - TWAPi) / 24 size : ポジションサイズ TWAPf : 先物の1時間TWAP TWAPi : インデックスの1時間TWAP
そもそも、TWAPって??
TWAPを計算してみる
検証するのは、 TWAPf - TWAPiの計算結果。
ポジション0.01を保持したまま放置し、そのときのFRと手数料のデータを取得する
先物とインデックの1分間隔のOHCLをFTXから取得する
これは、ドキュメントに1時間TWAPと記載されていたため、1分足くらいで良いかと判断したので。
TWAPf - TWAPiを比較する
OHCLから算出したTWAPから算出したものと手数料から逆算した結果を比較する。
結果をみる・・・
- 右から3列目:手数料から逆算したTWAP乖離値
- 右から2列目:1分足Closeで算出したTWAP乖離値
- 右から1列目:3列目と2列目の差分
最右列diff値が小さく、1分足CLOSEでTWAPを計算しても問題はなさそう。
完全に一致していない理由は、TWAP定義にあるように、期間内にあるすべての取引価格の単純平均をとっていないからだと考えられる。つまり、1分サンプリングでは、情報が欠けているので、近しい値にしかならない。
手数料の適用タイミングは?
1時間ごとに徴収されること、取引所UIには次回資金調達時刻がhh:mm:ss
表記となっていることから、hh:59:59
までの保持ポジションが徴収対象となると予想できる。
実際に、そのようになっているか検証してみる。
検証1
hh:59:ss
においてポジション保持するhh:00:ss
においてポジションを閉じるFR徴収されているか確認する
検証2
hh:58:ss
においてポジション保持しておくhh:59:ss
においてポジションを閉じるFR徴収されているか確認する
検証結果
検証1 | 検証2 |
---|---|
徴収対象 | 徴収対象でない |
参考
【参考書】クリーンアーキテクチャ
go modulesでgit forkとcloneしたライブラリの利用
github上にあるライブラリにバグがあったので、fork ⇛ clone後に修正して、自作パッケージからimportしようとした。すると、インポートエラーが発生して少しはまったので、備忘録とする。
取り扱うもの
go modules
git、github
どんなエラーか?
go.modで宣言しているパスと呼ばれてるパスが違うよと・・・
go: github.com/goroumaru/gocryptotrader@v0.0.0-20200731043922-68f65f5fa47e: parsing go.mod: module declares its path as: github.com/thrasher-corp/gocryptotrader but was required as: github.com/goroumaru/gocryptotrader
試したリポジトリ
今回はこれ。
fork元リポジトリ
github.com/thrasher-corp/gocryptotrader
forkしてローカルへcloneしたリポジトリ(つまり、これから修正する対象)
github.com/goroumaru/gocryptotrader
cloneしたリポジトリをインポートする自作レポジトリ
github.com/goroumaru/testRepository
ローカルファイル構成
- github.com - goroumaru - gocryptotrader (cloneしたリポジトリ) - testRepository (cloneリポジトリをインポートしたいリポジトリ)
対策
go.modへreplace文を追記すると、パスを読み替えてくれる。 勝手に読み替えてくれるので、import先ファイルで名称を手作業で変えてしまうと、またエラーとなるのでやらないこと。
cloneしたライブラリにある
go.mod
のmodule名称を変える[was]
module github.com/thrasher-corp/gocryptotrader
[is]
module github.com/goroumaru/gocryptotrader
自作レポジトリにある
go.mod
へ追加する[add]
github.com/thrasher-corp/gocryptotrader => ../gocryptotrader
参考
go.mod で利用している外部ライブラリをforkして修正したのを使う Go Modules でインターネット上のレポジトリにはないローカルパッケージを import する方法 ForkしてからPull Requestをするまでの流れ
【新しい枕(参考書)】効果検証入門、ヘッジファンドのアクティブ投資戦略
うちの辞書よりは薄い・・・
新しく参考書を購入したので、読み進めていきながら、備忘録を残していく予定。
購入したのは、この2冊。
「効果検証入門」は、3ヶ月以上買おうか悩んだ結果、買わない理由が金なら買えを実行・・・
「ヘッジファンドのアクティブ投資戦略」は、TwitterのTLに流れてきたので、即購入した。
Dipendency Injection(DI)について
詳細については、参考ページを参照するとして、ざっくりしたイメージを思い出せるような備忘録とする。
取り扱うもの
DI(Dipendency Injection)
※ DIコンテナについては、別の備忘録でまとめる予定なので、ここでは扱わない
依存しているとは?
機能Aと機能Bがあり、AはBを呼び出しているとする。 このとき、AはBに依存しているという。
DIとは?
DI(Dipendency Injection)とは、外部からオブジェクトを渡すという考え方・パターンのこと。 プログラミング言語に依らない考え方。
これは、機能Aから機能Bを呼ぶとき、外部からAとBへオブジェクトを渡すことへ置き換えて考える。
1. 外部 → B (外部からBに必要なオブジェクトを渡す) 2. 外部 → A (外部からAにBのオブジェクトを渡す) 3. 項1、2によってAでBを使用できる
ちなみに、Dipendency Injectionは直訳すると「依存注入」だけれど、これは 「依存(されているオブジェクトを)注入(=渡す)」 と捉える。
なぜDI(オブジェクトを渡す)とするのか?
目的は、*コード変更があったとき、その影響範囲を最小限にすること。
機能Aと機能Bが独立していることで、中身が別々に構築でき、ユニットテストも可能となる。
1. AからBを隠蔽する(抽象化する) AがBを呼び出すとき、AがBの内部を考えないようにする。 2. Aのユニットテストを可能とする Bが完成してなくても、Aのみでユニットテストできるようにする。
NGなパターン
AがBを呼び出したいとき、Aでオブジェクトを作成して、Bへ渡してしまうパターン。
1. A → B (Bに必要なオブジェクトをAで作成して渡す)
これだと、AとBを独立させていないので、Aのユニットテストを書けないし、Bが変更されるとAも変更しなければいけない。
OKなパターン(コンストラクタを渡すパターン)
サービス(機能A)からレポジトリ(機能B)を呼び出す構成のとき。
1. DIできるように受け皿をつくっておく * Bにおいて、Bのコンストラクタをつくる * Aにおいて、Aのコンストラクタをつくる 2. DIする(オブジェクトを渡すだけ) * 外部 → B (オブジェクトを渡す) * 外部 → A (オブジェクトを渡す) 3. AでBを使うなどの処理をする
ある「サービス」が「レポジトリ」を利用し、「レポジトリ」が「データベース」を利用する例をコードで確認する。
つまり、依存関係は・・・
- レポジトリ(B)は、データベース(C)に依存している
- サービス(A)は、レポジトリ(B)に依存している
func main() { db := defaultDB() // レポジトリに必要なオブジェクトを渡す repo := NewTicker(db) // サービスに必要なオブジェクトを渡す // サービスはrepoを生成していない service := NewTicker(repo) }
// services // 外部で使えるようインターフェースをつくる type Ticker interface { Create(ticker models.Ticker) error } type ticker struct { repo repositories.Ticker } // コンストラクタ(=New)でオブジェクトを受け取る func NewTicker(repo repositories.Ticker) *ticker { return &ticker{ repo: repo, } } // これはサービスの機能 func (r *ticker) Create(ticker models.Ticker) error { return r.repo.Create(ticker) }
// repositories // serviceで使えるようインターフェースをつくる type Ticker interface { Create(ticker models.Ticker) error } type ticker struct { db *gorm.DB } // コンストラクタ(=New)でオブジェクトを受け取る func NewTicker(db *gorm.DB) *ticker { return &ticker{ db: db, } } // これはリポジトリの機能 func (r *ticker) Create(ticker models.Ticker) error { r.db.Create(ticker) return nil }
参考
DBにおけるインデックス付与について(検証編)
別記事「DBにおけるインデックス付与について」において、インデックスの付け方を記載したので、その効果を検証してみる。
取り扱うもの
- DBインデックス付与による効果検証
- GORMのインデックス
- MySql
結論
長いので、結論から・・・
Where句で使うカラムは、インデックスをつける(もちろん、カーディナリの高いカラム)
複合インデックスに含めたカラムは、すべてWhere句で使う。一部だけ使うと、インデックスが適用されない。 つまり、where句で組み合わせて使わないカラムは、複合インデックスとしない。
複合インデックスに含めるカラムのうちカーディナリの高いカラムは、先頭に近いカラムへ配置する。 これにより、2.で述べたwhere句へ一部のカラムを使っても、インデックスが適用される。
検証
MySQL Workbenchを使って、各ケースにおいてインデックス有無のDuration/Fetch変化を計測する。
検証対象のテーブル
Rowが約1700万ある。
Columnは6つ。
カーディナリは、ざっくり以下の通り。 今回の目的はインデックス効果検証なので、カーディナリは「検証する対象データ」を考える。
検証する対象データ
pair type price amount timestamp sequence_id 1 2 10万 1万 4万 timestampと等しい 運用を踏まえたとき
pair type price amount timestamp sequence_id 10 2 100万 10万 多い timestampと等しい
検証ケース一覧
検証ケース番号を最左欄へ「CaseNo.」とする。 インデックス付与するカラムには「Index」とし、インデックス名が同じ場合は、複合インデックスを表す。
CaseNo. | pair | type | price | amount | timestamp | sequence_id |
---|---|---|---|---|---|---|
1 | - | - | - | - | - | - |
2 | - | - | - | - | index_1 | - |
3 | - | index_2 | - | - | index_2 | - |
追加:複合インデックスの優先順位をつけたケース(カーディナリの高いカラムを左欄へ)
CaseNo. | timestamp | sequence_id | pair | type | price | amount |
---|---|---|---|---|---|---|
4 | index_2 | - | index_2 | - | - | - |
各CaseNo.において、3種類のクエリを実行する。
- Q1.
timestamp
で検索
SELECT * FROM local.depths where timestamp = 1588660177238 LIMIT 0, 1000
- Q2.
timestamp
とtype
で検索
SELECT * FROM local.depths where timestamp = 1588660177238 and type = "ask" LIMIT 0, 1000
- Q3.
timestamp
とtype
で検索、price
でソート
SELECT * FROM local.depths where timestamp = 1588660177238 and type = "ask" order by price desc LIMIT 0, 1000
CaseNo.1 indexを付与しない
explainによる分析
- 分析結果
type = ALL
なのでテーブルを全検索しており、遅い。
QueryNo.,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra Q1,'SIMPLE','depths',NULL,'ALL',NULL,NULL,NULL,NULL,'17086351','10.00','Using where' Q2,'SIMPLE','depths',NULL,'ALL',NULL,NULL,NULL,NULL,'17086351','5.00','Using where' Q3,'SIMPLE','depths',NULL,'ALL',NULL,NULL,NULL,NULL,'17086351','5.00','Using where; Using filesort'
Dulation / Fetchによる分析
分析結果
QueryNo. Duration Fetch returned Row num Q1 0.0012 sec 5.932 sec 400 row(s) Q2 6.455 sec 0.000072 sec 202 row(s) Q3 6.612 sec 0.000034 sec 202 row(s)
Profilingによる分析
- profilingクエリ
set profiling = 1; select "ここへクエリ番号の内容をいれる" show profile;
- 分析結果
Status欄
Sending data
がボトムネックとなっているのがわかる。Sending data
は、検索の絞り込みと読み込みのこと。
Status,Duration Q1,Duration Q2,Duration Q3 starting,'0.000037','0.000046','0.000056' checking permissions,'0.000006','0.000005','0.000006' Opening tables,'0.000012','0.000011','0.000014' init,'0.000020','0.000028','0.000028' System lock,'0.000006','0.000007','0.000006' optimizing,'0.000008','0.000009','0.000011' statistics,'0.000013','0.000016','0.000019' preparing,'0.000010','0.000012','0.000013' 'Sorting result',-,-,'0.000003' executing,'0.000002','0.000002','0.000002' Sending data,'5.820025','6.370191','0.000006' 'Creating sort index',-,-,'6.443935' end,'0.000011','0.000012','0.000011' query end,'0.000006','0.000006','0.000006' closing tables,'0.000006','0.000006','0.000005' freeing items,'0.000105','0.000144','0.000145' cleaning up,'0.000012','0.000015','0.000012'
CaseNo.2 timestamp
へindexを付与する
分析結果の考察
- カーディナリの高いカラムへインデックスを追加すると、驚くほど高速になる。
explainによる分析
- 分析結果
type = ref
となっているので、インデックスを利用してクエリを実行している。
id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra Q1,'SIMPLE','depths',NULL,'ref','index_1','index_1','9','const','400','100.00',NULL Q2,'SIMPLE','depths',NULL,'ref','index_1','index_1','9','const','400','50.00','Using where' Q3,'SIMPLE','depths',NULL,'ref','index_1','index_1','9','const','400','50.00','Using index condition; Using where; Using filesort'
Dulation / Fetchによる分析
分析結果
QueryNo. Duration Fetch returned Row num Q1 0.0013 sec 0.00038 sec 400 row(s) Q2 0.0013 sec 0.000062 sec 202 row(s) Q3 0.0014 sec 0.000063 sec 202 row(s)
Profilingによる分析
- profilingクエリ
set profiling = 1; select "ここへクエリ番号の内容をいれる" show profile;
- 分析結果
Status,Duration Q1,Duration Q2,Duration Q3 starting,'0.000041','0.000040','0.000052' checking permissions,'0.000005','0.000004','0.000005' Opening tables,'0.000012','0.000011','0.000012' init,'0.000021','0.000026','0.000028' System lock,'0.000006','0.000006','0.000006' optimizing,'0.000007','0.000010','0.000009' statistics,'0.000057','0.000062','0.000061' preparing,'0.000009','0.000011','0.000015' 'Sorting result',-,-,'0.000005' executing,'0.000002','0.000002','0.000002' Sending data,'0.001247','0.000785','0.000006' 'Creating sort index',-,-,'0.000867' end,'0.000004','0.000004','0.000004' query end,'0.000005','0.000004','0.000005' closing tables,'0.000005','0.000004','0.000004' freeing items,'0.000087','0.000183','0.000171' cleaning up,'0.000011','0.000018','0.000010'
CaseNo.3 type
とtimestamp
へindexを付与する
分析結果の考察
- カーディナリの低いカラムを複合インデックスへ追加しても、速度は大きく変わらない
- 複合インデックスに含まれるカラムのうち、ひとつだけWhere句で使うと、インデックスが利用されない つまり、複合インデックスをすべてWhere句で使うと、インデックスが利用される。
explainによる分析
- 分析結果
id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra Q1,'SIMPLE','depths',NULL,'ALL',NULL,NULL,NULL,NULL,'17086351','10.00','Using where' Q2,'SIMPLE','depths',NULL,'ref','index_2','index_2','11',"'const,const'",'202','100.00',NULL Q3,'SIMPLE','depths',NULL,'ref','index_2','index_2','11',"'const,const'",'202','100.00','Using index condition; Using filesort'
Dulation / Fetchによる分析
分析結果
QueryNo. Duration Fetch returned Row num Q1 0.0094 sec 5.929 sec 400 row(s) Q2 0.0011 sec 0.000061 sec 202 row(s) Q3 0.0012 sec 0.000062 sec 202 row(s)
Profilingによる分析
- profilingクエリ
set profiling = 1; select "ここへクエリ番号の内容をいれる" show profile;
- 分析結果
Status,Duration id1,Duration id2,Duration id3 starting,'0.000039','0.000051','0.000057' checking permissions,'0.000005','0.000006','0.000005' Opening tables,'0.000011','0.000012','0.000013' init,'0.000022','0.000027','0.000026' System lock,'0.000006','0.000006','0.000006' optimizing,'0.000008','0.000009','0.000009' statistics,'0.000014','0.000069','0.000065' preparing,'0.000011','0.000011','0.000015' 'Sorting result',-,-,'0.000005' executing,'0.000002','0.000002','0.000002' Sending data,'5.937721','0.000545','0.000006' 'Creating sort index',-,-,'0.000627' end,'0.000013','0.000004','0.000004' query end,'0.000006','0.000005','0.000005' closing tables,'0.000006','0.000004','0.000004' freeing items,'0.000100','0.000186','0.000203' cleaning up,'0.000011','0.000017','0.000011'
CaseNo.4 timestamp
をtype
より左カラムとし、CaseNo.3実行
左カラムへ移動することで、優先度を上げる。
分析結果の考察
- カラムの優先度を上げることで、複合インデックスに含まれるカラムの一部だけwhere句として使っても、インデックスが適用される
explainによる分析
分析結果
Q1についても
type = ref
となり、インデックスが使用できている。 これは、where句で使っているtimestamp
カラムの優先順位を上げたためであり、複合インデックスに含まれるカラムをすべてwhere句に使う必要がなくなる。
id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra Q1,'SIMPLE','depths',NULL,'ref','index_2','index_2','9','const','400','100.00',NULL Q2,'SIMPLE','depths',NULL,'ref','index_2','index_2','11',"'const,const'",'202','100.00',NULL Q3,'SIMPLE','depths',NULL,'ref','index_2','index_2','11',"'const,const'",'202','100.00','Using index condition; Using filesort'
Dulation / Fetchによる分析
分析結果
Q1に対しても、インデックスが利用されているので、高速になっている。
QueryNo. Duration Fetch returned Row num Q1 0.0013 sec 0.00040 sec 400 row(s) Q2 0.0011 sec 0.000062 sec 202 row(s) Q3 0.0012 sec 0.000062 sec 202 row(s)
Profilingによる分析
- profilingクエリ
set profiling = 1; select "ここへクエリ番号の内容をいれる" show profile;
- 分析結果
Status,Duration id1,Duration id2,Duration id3 starting,'0.000036','0.000046','0.000045' checking permissions,'0.000005','0.000005','0.000005' Opening tables,'0.000012','0.000013','0.000013' init,'0.000023','0.000028','0.000028' System lock,'0.000006','0.000006','0.000007' optimizing,'0.000007','0.000009','0.000009' statistics,'0.000060','0.000067','0.000065' preparing,'0.000010','0.000011','0.000015' 'Sorting result',-,-,'0.000005' executing,'0.000002','0.000002','0.000002' Sending data,'0.001325','0.000524','0.000006' 'Creating sort index',-,-,'0.000637' end,'0.000006','0.000004','0.000004' query end,'0.000005','0.000005','0.000005' closing tables,'0.000005','0.000004','0.000004' freeing items,'0.000099','0.000185','0.000184' cleaning up,'0.000012','0.000016','0.000011'
参考
DBにおけるインデックス付与について
データベースを扱うとき、インデックスの付け方について記載する。併せて、GORMでどのように扱うかも記載する。
取り扱うもの
- データベースのインデックス
- GORMのインデックス
- 検索が遅いときの調査方法
カーディナリ(濃度)とは?
ざっくりいうと、重複しないデータの量のこと。
(絶対量ではなく、データを相対的に見たときの高低が重要となる)
この例だと・・・
- "性別"は、カーディナリが低い(2種類)
"名前"は、カーディナリが高い(名字の種類だけ)
性別 名前 男 山田 女 佐藤 女 田中 〜 〜
インデックス付与の考え方
結論として・・・
- カーディナリの高い項目に付与すること
- 複合インデックスは、カーディナリの高い順に付与すること
そもそも、なぜインデックスが必要なの?
検索速度が速くなるから。
DB内部では、検索しやすい並び順にして、その並び順を保持する。最適化された並び順に対して、2分木探索など適用すれば、検索が高速になる。
この例だと・・・
保存されているデータ
性別 名前 男 山田 女 佐藤 女 田中 〜 〜 DBの中では、名前を昇順として並び順を保持する
性別 名前 女 佐藤 女 田中 男 山田 〜 〜
複合インデックスってなに?
参考ページがとてもわかり易いので割愛する。
プライマリキーインデックスとインデックスの違いは?
参考ページがとてもわかり易いので割愛する。
GORMにおけるインデックス付与
GORMでは、構造体タグとしてインデックスを与えられる。 インデックス名称は任意だけれど、命名規則として「IXカラム1カラム2」とするのが良いらしい。
ケース1:インデックス2つ
この場合、
Role
とAddres
は別々のインデックスとなる
type User struct { Email string Role string `gorm:"index:IX_role"` Num int Address string `gorm:"index:IX_address"` }
ケース2:複合インデックス1つ
この場合、
Role
とAddress
が複合インデックスとなる
type User struct { Email string Role string `gorm:"index:IX_role_address"` Num int Address string `gorm:"index:IX_role_address"` }
複合インデックスの優先順位はどうなるの?
結論としては・・・
DBに保持されるカラムの順番で優先される。
この場合だと・・・
type User struct { Email string Role string `gorm:"index:IX_role_address"` //優先度高 Num int Address string `gorm:"index:IX_role_address"` //優先度低 }
この構造体だと、DBへ保存したとき、以下のようなテーブルとなるため、Role
の優先順位が高くなる。
Role | Num | Address | |
---|---|---|---|
~ | IX_role_address(優先) | ~ | IX_role_address |
詳細は、参考ページへ!
検索が遅いときの原因の調査方法について
実行結果の読み方は、参考ページを参照のこと。
explainを実行する
explain select * from tableName;
実行結果を解析する