goroumaru41gou

遊んでいる中でインプットした内容をアウトプットする場

【仮想通貨取引所】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って??

f:id:goroumaru41gou:20200811140559p:plain


TWAPを計算してみる

検証するのは、 TWAPf - TWAPiの計算結果。

  1. ポジション0.01を保持したまま放置し、そのときのFRと手数料のデータを取得する

  2. 先物とインデックの1分間隔のOHCLをFTXから取得する

    これは、ドキュメントに1時間TWAPと記載されていたため、1分足くらいで良いかと判断したので。

  3. TWAPf - TWAPiを比較する

    OHCLから算出したTWAPから算出したものと手数料から逆算した結果を比較する。

結果をみる・・・

  • 右から3列目:手数料から逆算したTWAP乖離値
  • 右から2列目:1分足Closeで算出したTWAP乖離値
  • 右から1列目:3列目と2列目の差分

最右列diff値が小さく、1分足CLOSEでTWAPを計算しても問題はなさそう。

完全に一致していない理由は、TWAP定義にあるように、期間内にあるすべての取引価格の単純平均をとっていないからだと考えられる。つまり、1分サンプリングでは、情報が欠けているので、近しい値にしかならない。

f:id:goroumaru41gou:20200811141412p:plain

手数料の適用タイミングは?

1時間ごとに徴収されること、取引所UIには次回資金調達時刻がhh:mm:ss表記となっていることから、hh:59:59までの保持ポジションが徴収対象となると予想できる。

実際に、そのようになっているか検証してみる。

検証1
  1. hh:59:ssにおいてポジション保持する

  2. hh:00:ssにおいてポジションを閉じる

  3. FR徴収されているか確認する

検証2
  1. hh:58:ssにおいてポジション保持しておく

  2. hh:59:ssにおいてポジションを閉じる

  3. FR徴収されているか確認する

検証結果
検証1 検証2
徴収対象 徴収対象でない

参考

S&P ダウ・ジョーンズ・インデックス:指数算出メソドロジー

【参考書】クリーンアーキテクチャ

今回は電子書籍なので、枕にはならない・・・


また、新しく参考書を積んだので、読み進めていきながら、備忘録を残していく予定。

購入したのは、「Clean Architecture」

もっと軽いタブレットでないと、手が疲れるし読まなくなりそう・・・ タブレットも検討しないと。

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先ファイルで名称を手作業で変えてしまうと、またエラーとなるのでやらないこと。

  1. cloneしたライブラリにあるgo.modのmodule名称を変える

    [was] module github.com/thrasher-corp/gocryptotrader

    [is] module github.com/goroumaru/gocryptotrader

  2. 自作レポジトリにあるgo.modへ追加する

    [add] github.com/thrasher-corp/gocryptotrader => ../gocryptotrader

参考

go.mod で利用している外部ライブラリをforkして修正したのを使う Go Modules でインターネット上のレポジトリにはないローカルパッケージを import する方法 ForkしてからPull Requestをするまでの流れ

【新しい枕(参考書)】効果検証入門、ヘッジファンドのアクティブ投資戦略

f:id:goroumaru41gou:20200728143231j:plain うちの辞書よりは薄い・・・


新しく参考書を購入したので、読み進めていきながら、備忘録を残していく予定。

購入したのは、この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
}

参考

GO言語とDependency Injection

Clean Architecture

DBにおけるインデックス付与について(検証編)

別記事「DBにおけるインデックス付与について」において、インデックスの付け方を記載したので、その効果を検証してみる。

取り扱うもの

  • DBインデックス付与による効果検証
  • GORMのインデックス
  • MySql

結論

長いので、結論から・・・

  1. Where句で使うカラムは、インデックスをつける(もちろん、カーディナリの高いカラム)

  2. 複合インデックスに含めたカラムは、すべてWhere句で使う。一部だけ使うと、インデックスが適用されない。 つまり、where句で組み合わせて使わないカラムは、複合インデックスとしない。

  3. 複合インデックスに含めるカラムのうちカーディナリの高いカラムは、先頭に近いカラムへ配置する。 これにより、2.で述べたwhere句へ一部のカラムを使っても、インデックスが適用される。

検証

MySQL Workbenchを使って、各ケースにおいてインデックス有無のDuration/Fetch変化を計測する。

検証対象のテーブル

Rowが約1700万ある。

f:id:goroumaru41gou:20200727210234p:plain

Columnは6つ。 f:id:goroumaru41gou:20200727210311p:plain

カーディナリは、ざっくり以下の通り。 今回の目的はインデックス効果検証なので、カーディナリは「検証する対象データ」を考える。

  • 検証する対象データ

    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. timestamptypeで検索
    SELECT * FROM local.depths where timestamp = 1588660177238 and type = "ask" LIMIT 0, 1000
  • Q3. timestamptypeで検索、priceでソート
    SELECT * FROM local.depths where timestamp = 1588660177238 and type = "ask" order by price desc LIMIT 0, 1000

CaseNo.1  indexを付与しない

f:id:goroumaru41gou:20200727210311p:plain

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を付与する

f:id:goroumaru41gou:20200727212216p:plain

分析結果の考察

  • カーディナリの高いカラムへインデックスを追加すると、驚くほど高速になる。

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  typetimestampへindexを付与する

f:id:goroumaru41gou:20200727212253p:plain

分析結果の考察

  • カーディナリの低いカラムを複合インデックスへ追加しても、速度は大きく変わらない
  • 複合インデックスに含まれるカラムのうち、ひとつだけ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  timestamptypeより左カラムとし、CaseNo.3実行

左カラムへ移動することで、優先度を上げる。

f:id:goroumaru41gou:20200727212346p:plain

分析結果の考察

  • カラムの優先度を上げることで、複合インデックスに含まれるカラムの一部だけ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つ

    この場合、RoleAddresは別々のインデックスとなる

type User struct {
  Email        string
  Role         string  `gorm:"index:IX_role"`
  Num          int     
  Address      string  `gorm:"index:IX_address"`
}
  • ケース2:複合インデックス1つ

    この場合、RoleAddressが複合インデックスとなる

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の優先順位が高くなる。

Email Role Num Address
~ IX_role_address(優先) ~ IX_role_address

詳細は、参考ページへ!

検索が遅いときの原因の調査方法について

実行結果の読み方は、参考ページを参照のこと。

  • explainを実行する explain select * from tableName;

  • 実行結果を解析する

参考

カーディナリとインデックス付与の関係について

インデックスの効果とは?

複合インデックスの適用順と有効な検索方法

MySQLのexplainの読み方