goroumaru41gou

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

github上にあるプライベートリポジトリをインポートする方法

github上にあるプライベートリポジトリをインポートするときの設定をいつも忘れてしまうため、備忘録として残す。

取り扱うもの

go module

git、github

githubに登録したSSHKeyを利用する方法

インポート先のローカルリポジトリにある.git/configファイルへ以下を追加する。

[url "ssh://git@github.com/"]
    insteadOf = https://github.com/

次に、GOENVファイルのパスを確認し、該当ファイルを開く。

go env GOENV

envファイルの設定を以下のとおり変更する。

GOPRIVATE=<インポートしたいプライベートリポジトリ, github.com/user/repo>

念のため、go moduleのキャッシュを削除してから、go mod tidy。

go clean --modcache
go mod tidy

プライベートリポジトリをインポートできる。

参考

【Golang】GitHub上のprivateリポジトリをimportする

MOVE取引とは

FTXで取り扱われているMOVEについての備忘録とする。

取り扱うもの

MOVE Volatility

MOVEとは?

ストラドルのこと。

  • MOVE価格上昇:通貨が上下どちらかへ価格変動があるとき
  • MOVE価格下降:通貨が上下どちらかへも価格変動がないとき

こちらが詳しい。

期間

1日、1週間、四半期の3種類あるが、1日物を対象とする。

1日物の期間は、償還期限の前日に上場、翌23:59:59に償還されて市場がなくなる。

つまり、取引期間はyyyy-mm-(dd-1) 00:00:00 ~ yyyy-mm-dd 23:59:59 @UTCとなる。

償還時の価格決定

償還価格 = abs(開始1時間のBTC index TWAP価格 - 終了1時間のBTC index TWAP価格)

ただし、算出期間は以下となることに注意する。

  • 開始1時間:償還日のyyyy-mm-dd 00:00:00 - 00:00:59
  • 終了1時間:償還日のyyyy-mm-dd 23:00:00 - 23:59:59

また、算出に使われるのは、indexのTWAP価格となる。

TWAPについては、以前の記事を参照のこと。

inplied volatility(IV、予想変動率)算出

Put Option = [STRADDLE + STRIKE - FUTURE] / 2

Put Option STRADDLE STRIKE FUTURE
PUT価格 MOVE価格 UIやAPIで提供されるstrikePrice MOVEと同時期に満期となるBTC先物価格

※FUTUREについては、無期とも四半期とも読み取れるが、おそらく無期?

ブラックショールズ方程式

つぎに、名前しか聞いたことがないが、オプションでよく使われるらしい方程式を使う。

この式にあるPut Optionが上記で計算される価格となる。

よって、各変数を与えつつ、σ(インプライド・ボラティリティ)を求める手順となる。

f:id:goroumaru41gou:20210610220556p:plain

S X r T N(x) σ
原資産価格(BTC) 行使価格 安全利子率 期間 標準正規分布の累積確率密度関数 ボラティリティ(予想変動率)
ticker future stats ? future - -

※下段はAPI名称


計算自体は難しく、あとは安全利子率を入れれば計算できるのだが、これがよくわからない。

とりあえず、MOVEがどのようなものかザックリ把握できたので、このあたりとする。

次回、もう少しオプションについて学んでみる。

また、GUI上にIV値が表示されているので逆算してみて、IVなどを売買へ応答できるのか検討してみる。

参考

MOVEトレードバトル

IV計算ページ

ブラックショールズ方程式

WSL2 + docker導入について

WSL2導入の備忘録とする。

取り扱うもの

環境

  • windows10 Pro(ver1909、build18363.1256)

 ※build 18363.1049 以降であればよい

  • windows terminal インストール済み

 ※インストールされてなくてもよい

  • docker for windows インストール済み

 ※インストールされてなくてもよい

1. 機能追加

power shellで、ふたつの機能を有効にする。

ふたつとも有効にできたら、PCを再起動する。

1.1 ひとつめの機能追加

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
展開イメージのサービスと管理ツール
バージョン: 10.0.18362.1139

イメージのバージョン: 10.0.18363.1256

機能を有効にしています
[==========================100.0%==========================]
操作は正常に完了しました。

1.2 ふたつめの機能追加

dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
展開イメージのサービスと管理ツール
バージョン: 10.0.18362.1139

イメージのバージョン: 10.0.18363.1256

機能を有効にしています
[==========================100.0%==========================]
操作は正常に完了しました。

2.WSL2 カーネル更新

以下から更新プログラムをダウンロードして実行する。

x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ

f:id:goroumaru41gou:20201225105431p:plain この画面が表示されたら、Finishを押して終了。

3.WSL2を規定へ設定

wsl --set-default-version 2

これが表示されたら、wsl2を規定にセットできている。

WSL 2 との主な違いについては、https://aka.ms/wsl2 を参照してください

4.Ubuntu20.04LTSインストール

以下のリンクからインストールする。

Ubuntu 20.04 LTS

f:id:goroumaru41gou:20201225110757p:plain 入手を押すと、Microsoft Storeに移行するので、そちらで再び入手を押す。

5.Ubuntu20.04LTS起動

WindowsスタートアップメニューからUbuntuを起動する。

f:id:goroumaru41gou:20201225111712p:plain

このとき、1~2分経ってもubuntuターミナルに何も表示されなかったとき、PCを再起動してから再度試す

5.1 ubuntuアカウント登録

Ubuntu用のアカウントとパスワードを登録する。

このアカウントは、Windowsアカウントとは別ものとなる。

Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username:  // ユーザ入力
New password:       // パスワード入力
Retype new password:
passwd: password updated successfully

パスワード登録が完了すると、続けて以下が表示される。

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 4.19.128-microsoft-standard x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Fri Dec 25 11:34:37 JST 2020

  System load:  0.34               Processes:             8
  Usage of /:   0.4% of 250.98GB   Users logged in:       0
  Memory usage: 2%                 IPv4 address for eth0: <IP addr>
  Swap usage:   0%

1 update can be installed immediately.
0 of these updates are security updates.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update


This message is shown once once a day. To disable it please create the
/home/<user>/.hushlogin file.
<user name>@<PC device name>:~$ 

5.2 ubuntuアップデート&アップグレード

sudo apt update

アップデートが正常に完了したら、アップグレードする。

sudo apt upgrade

5.3 その他セットアップ

必要に応じて、ubuntuをセットアップする。

6. windows terminal導入

bash(ubuntu)、power shell(windows)の切り替えが簡単になるので、windows terminalをこちらからインストールする。

また、windows terminalのカスタマイズは、必要に応じて実施する。

qiita.com

7. docker導入

dockerが利用できるようにする。

7.1 docker desktop for windowsインストール

以下からwindows版のdocker desktopをこちらからインストールする。

インストールについては、この記事を参考にして進める。 qiita.com

7.2 dockerとwsl2起動状況

power shellなどでwsl起動状況を確認する。

wsl -l -v

dockerインストール直後だと、以下となっている。

  NAME                   STATE           VERSION
* docker-desktop-data    Running         2
  Ubuntu-20.04           Stopped         2
  docker-desktop         Running         2

wsl2のデフォルトLinuxディストリビューションubuntuへ設定する。

wsl --set-default Ubuntu-20.04

すべてRunningステータスであれば、問題ない。

  NAME                   STATE           VERSION
* Ubuntu-20.04           Running         2
  docker-desktop         Running         2
  docker-desktop-data    Running         2

7.3 dockerコンテナ起動

windows terminalを起動して、ubuntu20.04のタブを開く。 f:id:goroumaru41gou:20201225133650p:plain

試しにdocker desktopのお試しコンテナ起動するため、ubuntuでdockerコマンドを入力し、コンテナを起動する。

docker run -d -p 80:80 docker/getting-started

docker imageをPullしてきているのが確認できる。

Unable to find image 'docker/getting-started:latest' locally
latest: Pulling from docker/getting-started
188c0c94c7c5: Pull complete
617561f33ec6: Pull complete
7d856acdaa9c: Pull complete
a0d3c6e28e6d: Pull complete
af69a9b963c8: Pull complete
0739f3815ad8: Pull complete
7c7b75d0baf8: Pull complete
Digest: sha256:b821569034e3b5fae03b40e64a866017067f3bf17effe185b782bdbf02179528
Status: Downloaded newer image for docker/getting-started:latest
4e673700023e91cbe1ede4463b29084aef96ca0d719f67d2a97e52f437b01dd0

コンテナ起動を確認する。

docker ps
CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS          PORTS                NAMES
4e673700023e   docker/getting-started   "/docker-entrypoint.…"   52 seconds ago   Up 49 seconds   0.0.0.0:80->80/tcp   cool_chebyshev

docker desktopでもコンテナ起動できていることを確認できる。 f:id:goroumaru41gou:20201225135229p:plain

7.4 dockerコンテナ内へ

起動したコンテナ名 or IDをdocker psで調べる。

起動したコンテナには、bashがなかったので、ashとした。

docker exec --it <コンテナ名 or ID> ash  // ここでは、ID:4e673700023e

コンテナ内に入れた。

/ #

試しにディレクトリを確認してみる。

ls -la
/ # ls -la
total 80
drwxr-xr-x    1 root     root          4096 Dec 25 04:52 .
drwxr-xr-x    1 root     root          4096 Dec 25 04:52 ..
-rwxr-xr-x    1 root     root             0 Dec 25 04:52 .dockerenv
drwxr-xr-x    2 root     root          4096 Oct 21 09:23 bin
drwxr-xr-x    5 root     root           340 Dec 25 04:52 dev
drwxr-xr-x    1 root     root          4096 Nov 25 00:31 docker-entrypoint.d
-rwxrwxr-x    1 root     root          1202 Nov 25 00:30 docker-entrypoint.sh
drwxr-xr-x    1 root     root          4096 Dec 25 04:52 etc
drwxr-xr-x    2 root     root          4096 Oct 21 09:23 home
drwxr-xr-x    1 root     root          4096 Oct 21 09:23 lib
drwxr-xr-x    5 root     root          4096 Oct 21 09:23 media
drwxr-xr-x    2 root     root          4096 Oct 21 09:23 mnt
drwxr-xr-x    2 root     root          4096 Oct 21 09:23 opt
dr-xr-xr-x  163 root     root             0 Dec 25 04:52 proc
drwx------    1 root     root          4096 Dec 25 04:59 root
drwxr-xr-x    1 root     root          4096 Dec 25 04:52 run
drwxr-xr-x    2 root     root          4096 Oct 21 09:23 sbin
drwxr-xr-x    2 root     root          4096 Oct 21 09:23 srv
dr-xr-xr-x   11 root     root             0 Dec 25 04:52 sys
drwxrwxrwt    1 root     root          4096 Nov 25 00:30 tmp
drwxr-xr-x    1 root     root          4096 Oct 21 09:23 usr
drwxr-xr-x    1 root     root          4096 Oct 21 09:23 var

コンテナから抜けるときは、exitを入力する。

7.4 dockerコンテナ削除

コンテナを削除する。

docker rm -f <コンテナ名 or ID>  // ここでは、ID:4e673700023e

削除されたか確認する。コンテナIDが表示されなければ、削除されている。

docker ps -a

これもdocker desktopで確認すると、先ほどのコンテナ表示が消えている。

7.5 docker image確認と削除

インストールされているイメージファイルを確認する。

docker images

docker imagesで確認したイメージファイルIDを使ってイメージを削除する。

docker rmi <image ID>

参考

*docker導入

*Dockerコマンド よく使うやつ

pywinautoハマったところ

pythonのアプリケーションからwindows上のGUIを自動化するpywinautoライブラリを利用した際に、ハマった点を備忘録とする。

取り扱うもの

ハマった点

  1. window上で操作したいItemが分かり難い

  2. 操作方法が分かり難い

どのように操作するのか?

例えば、pythonからPDFファイルのプリンタ設定を操作したいとき、 印刷ダイアログを開き、詳細設定ボタンを押下して詳細画面を開いてから、各種設定を実行する。

このとき、詳細設定画面には、様々なアイテム(テキスト・ボタン・チェックボックスなど)が存在するため、それらを識別して操作する必要がある。

pywinautoを利用すると、自動的にそれらを識別してくれるが、どのようなデータ構造で識別されているか知る必要がある。

pywinautoでデータ構造表示する

操作対象であるウィンドウを"window"オブジェクトすると、以下コードで表示できる。

print(window.dump_tree())

//
// 表示結果
//

Dialog - '印刷'    (L76, T16, R615, B493)
['印刷Dialog', 'Dialog', '印刷']
child_window(title="印刷", class_name="#32770")
   | 
 ------------------------ 省略 ------------------------
   | GroupBox - ''    (L380, T295, R587, B432)
   | ['ページ範囲GroupBox2', 'GroupBox4']
   | child_window(class_name="Button")
   | 
   | Static - '部数(&C):'    (L389, T319, R496, B338)
   | ['部数(&C):', '部数(&C):Static', 'Static8']
   | child_window(title="部数(&C):", class_name="Static")
   | 
   | Edit - '1'    (L504, T316, R536, B339)
   | ['Edit5', '部数(&C):Edit']
   | child_window(title="1", class_name="Edit")
   | 
   | CheckBox - '部単位で印刷(&O)'    (L393, T359, R509, B378)
   | ['部単位で印刷(&O)CheckBox', '部単位で印刷(&O)', 'CheckBox2']
   | child_window(title="部単位で印刷(&O)", class_name="Button")
   | 
   | Static - ''    (L463, T379, R580, B418)
   | ['Static9', '部単位で印刷(&O)Static']
   | child_window(class_name="Static")
   | 
   | UpDown - ''    (L534, T316, R552, B339)
   | ['部数(&C):UpDown', 'UpDown']
   | child_window(class_name="msctls_updown32")
   | 
   | Button - '印刷(&P)'    (L329, T456, R417, B482)
   | ['印刷(&P)Button', '印刷(&P)', 'Button3']
   | child_window(title="印刷(&P)", class_name="Button")
   | 
   | Button - 'キャンセル'    (L424, T456, R512, B482)
   | ['キャンセル', 'キャンセルButton', 'Button4']
   | child_window(title="キャンセル", class_name="Button")
   | 
   | Button - '適用(&A)'    (L518, T456, R606, B482)
   | ['適用(&A)Button', 'Button5', '適用(&A)']
   | child_window(title="適用(&A)", class_name="Button")
   | 
   | Button - 'ヘルプ'    (L613, T456, R701, B482)
   | ['ヘルプ', 'Button6', 'ヘルプButton']
   | child_window(title="ヘルプ", class_name="Button")
   | 
   | TabControl - ''    (L86, T50, R605, B449)
   | ['状態:TabControl', 'TabControl全般', 'TabControl']
   | child_window(class_name="SysTabControl32")

構造の操作方法

例えば、印刷部数を変更したいとき、テキストボックスへ部数を入力する。

このとき、以下で表示されるアイテムを操作する。

//
// 表示結果(抜粋)
//

   | Edit - '1'    (L504, T316, R536, B339)
   | ['Edit5', '部数(&C):Edit']
   | child_window(title="1", class_name="Edit")
  • 部数1と入力したいとき

    window["部数(&C):Edit"].set_edit_text("1")

    これは'部数(&C):Edit'をKeyとしてアイテムへアクセスし、メソッド(set_edit_text()など)でアイテムを操作している。


仮に、ほかの表示内容でアクセスを試みると、エラーとなる場合がある。

これは、EditButton1は、固有な名称でないか、可変となる名称であるためで、 それらをKeyとして利用することは、避けた方がよい。

参考

【FTX Legend】クリアするために

FTXでキャンペーンが行われるので、その対策を備忘録とする。

今回は、ざっくりとした前提条件までとする。

取り扱うもの

FTX Legend (今回のキャンペーンのこと)

FTX Legendとは?

2〜5名でチームを組み、チーム合計取引量が既定以上となれば、賞金がもらえるキャンペーン。

しかも、規定を達成したチームすべてに賞金が与えられるので、キャンペーンという扱い。

キャンペーンと認識していいのか?

トレーディング大会(例えば、期間内の純利益で対戦する)ではなく、誰でも貰えるキャンペーンと捉えていいのか疑問に思った。

というのも、規定取引量を達成するという性質上、取引コストを下げるだけで純利益プラスで終了できる可能性が高くなる。当然、maker取引を考える参加者が増えれば、板の椅子取りゲームとなる。

となると、成行き出来高を奪い合うbot対戦となると思われるので、概ね大会と同じかと考えている。 (キャンペーンなくてもbot対戦は起きてるので、いつもどおりと言われれば・・・)

収支プラスとなるラインは?

クリア条件と取得金額

  • キャンペーン条件1:取引量1000万ドルを達成すると、5000ドル取得
  • キャンペーン条件2:取引量2000万ドルを達成すると、+5000ドル取得
  • キャンペーン条件3:取引量上位5名を達成すると、+5000ドル取得

取引手数料

キャンペーン条件1をクリアするために、すべてtakerで取引したときの手数料を示す。

手数料割引ごとに表示する。

割引なし リファラル5%割引 リファラル5% + FTT3%割引
手数料 0.07% 手数料 0.0665% 手数料 0.064505%
7000ドル 6650ドル 6450.5ドル
  • リファラルキャンペーン

    ここからFTXへ登録すると、手数料5%割引される。

    紹介した人は、登録者が取引したときの取引手数料30%を貰える。

  • FTTトークンの所有による割引

    30FTT(現時点で約0.01BTC)保有で3%割引される。

    さらに保有すれば割引率も上がるが、FTT変動リスクもあるので、30FTTとしておく。

    (FTX Legendの効果なのか、FTT価格は上昇しているので、さらに保有していてもよいかも)

プラスライン

下記金額より、taker手数料の支払いを少なくすれば、プラスとなる。

パーセンデージは、1000万ドルに占めるmaker割合を表していて、この値よりmaker割合を増やせば、プラスとなる。

割引なし リファラル5%割引 リファラル5% + FTT3%割引
2,857,143ドル 2,481,203ドル 2,248,663ドル
29% 25% 23%

市場ごとに取引量は?

上述のとおり、各ポジションの損益を考えない前提として、取引の20~30%をmaker取引すれば、このキャンペーンはプラスで終われそう。

では、数多くの強者がいる中でmakerを勝ち取るためには、どの市場を選ぶべきなのだろうか?

  • botのいない市場を狙う
  • 取引高の高い市場を狙う

競合botがいると、板の席取り合戦の勝率が下がるので、botがいない市場を狙いたい。 また、取引高が少なすぎると、そもそも約定しにくいので避けたい。

よって、うまくバランスのとれた市場で、いくつかbot稼働させるのが良いかもしれない。

最後に、市場ごとの24h出来高を見てみると、以下のようになる。

ひと昔前と違ってBCHとかXRPよりも、出来高の多い知らない通貨が多いことに驚いた。

縦軸のスケールに注意 f:id:goroumaru41gou:20200826115039p:plain f:id:goroumaru41gou:20200826115103p:plain f:id:goroumaru41gou:20200826115250p:plain

参考

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

並行処理について(orDoneチャンネル編)

以前、teeチャンネルの備忘録を書いた。 goroumaru41gou.hatenablog.com

その中でorDoneチャンネルを使っているので、今回はその備忘録とする。

今回も参考にしたのはこちら

取り扱うもの

  • orDoneチャンネルについて

テストコード

github.com/goroumaru/test-code

orDoneチャンネルとは?

例えば、上位構成からdoneチャンネルやcontextで停止指示がきたとき、select ~ caseでゴルーチンを抜ける処理を書くケースが多くて煩雑になる。かといってselect ~ caseしないと、ゴルーチンリークしてしまう。

そんなときにorDoneチャンネルを使うと、可読性が向上する。

後述するcontextを使ったほうがよい

// OrDone :
func OrDone(done, c <-chan interface{}) <-chan interface{} {
    valStream := make(chan interface{})

    go func() {
        defer close(valStream)
        for {
            select {
            case <-done:
                return
            case v, ok := <-c:
                if !ok {
                    return
                }
                select {
                case valStream <- v:
                case <-done:
                    // <-doneがないとdoneチャネルが送信されてきても、
                    // valStream <- vが送信されてくるまでブロックされ続けてしまう。
                    // ここで<-doneとなると、1つ上のネストにおける<-doneで抜けられる。
                }
            }
        }
    }()
    return valStream
}

なぜorDoneを利用するのか?

実行したい処理について、可読性があがる。

以降、利用有無でコードの可読性を見てみる。

orDoneを利用しないとき

メインとなる処理が埋もれて、わかりづらい・・・

// 比較部分のみ抜粋
loop:
for {
    select{
    case <- done:
        break loop
    case val,ok := <-myChannel:
        if !ok {
            return // またはforからbreakとか
        }
        fmt.Printf("valに対して何かするところ: %v\n", val)
     // ここがメインなのに埋もれてしまう
    }
}

orDoneを利用したとき

// 比較部分のみ抜粋
for val := range orDoneChannel.OrDone(done, myChannel) {
    fmt.Printf("valに対して何かするところ: %v\n", val) 
   // メインがわかり易くなった
}

全体はこんな感じ・・・

func TestMain(t *testing.T) {
    // ここでやっていること
    // 定時実行した結果をmychannelとして渡す。
    // このとき、orDoneを利用する。
    // 時限処置によりタイムアウトして、すべての処理を終える。

    // doneチャンネルをクローズするために、コンテキストでタイムアウトする
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    // 定時実行
    tick := time.NewTicker(1 * time.Second)
    defer tick.Stop()

    // メインゴルーチンが先に終了してしまうので、子ゴルーチンを待たせる
    wg := sync.WaitGroup{}
    wg.Add(1)
    myChannel := make(chan interface{})
    defer close(myChannel)

    done := make(chan interface{})
    go func() {
        defer close(done) // gorutineから抜けるとき、doneチャンネルも閉じられる
        defer wg.Done()
        for {
            select {
            case <-ctx.Done(): // 時限でcontextがクローズする
                fmt.Println("context is closed!")
                return
            case <-tick.C: // 定時実行
                myChannel <- "my channel!"
            }
        }
    }()

    // orDoneのゴルーチンは、doneチャンネルが送信される(閉じれれる)まで終了しない。
    for val := range orDoneChannel.OrDone(done, myChannel) {
        fmt.Printf("valに対して何かするところ: %v\n", val)
    }

    // 子ゴルーチンを待機する
    wg.Wait()
}

doneチャンネルの代わりにcontextを利用する

便利な標準パッケージcontextを使う。

// OrDoneCtx : doneの代わりにcontextを使う
func OrDoneCtx(ctx context.Context, c <-chan interface{}) <-chan interface{} {
    valStream := make(chan interface{})

    go func() {
        defer close(valStream)
        for {
            select {
            case <-ctx.Done():
                return
            case v, ok := <-c:
                if !ok {
                    return
                }
                select {
                case valStream <- v:
                case <-ctx.Done():
                }
            }
        }
    }()
    return valStream
}
func TestOrDoneCtx(t *testing.T) {

    // ここでやっていること
    // 定時実行した結果をmychannelとして渡す。
    // このとき、orDoneを利用する。
    // 時限処置によりタイムアウトして、すべての処理を終える。

    // doneチャンネルをクローズするために、コンテキストでタイムアウトする
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    // 定時実行
    tick := time.NewTicker(1 * time.Second)
    defer tick.Stop()

    // メインゴルーチンが先に終了してしまうので、子ゴルーチンを待たせる
    wg := sync.WaitGroup{}
    wg.Add(1)
    myChannel := make(chan interface{})
    defer close(myChannel)

    go func() {
        defer wg.Done()
        for {
            select {
            case <-ctx.Done(): // 時限でcontextがクローズする
                fmt.Println("context is closed!")
                return
            case <-tick.C: // 定時実行
                myChannel <- "my channel!"
            }
        }
    }()

    // doneからctxへ変更
    childCtx, childCancel := context.WithCancel(ctx)
    defer childCancel()

    // contextがキャンセルされるまで終了しない。
    for val := range orDoneChannel.OrDoneCtx(childCtx, myChannel) {
        fmt.Printf("valに対して何かするところ: %v\n", val)
    }

    // 子ゴルーチンを待機する
    wg.Wait()
}

参考

並行処理について(teeチャンネル編)

1つのチャンネル値を2つに別けて別の場所で使用したいとき、teeチャンネルを使うことについて、備忘録とする。

参考にしたのはこちら

ネットで調べるのもいいけど、情報の正しさを判断できないなら、この本に記載されてるコードを見た方が良いと思う。

取り扱うもの

テストコード

github.com/goroumaru/test-code

teeチャンネルとは?

ひとつのチャンネルデータを、ふたつのチャンネルへ分割する。

だから、1入力2出力を"T"という文字で表して、teeチャンネル。

S/Wだけでなく、H/W分野でもteeチャンネルって言葉は使われてる。

teeチャンネル

// Tee :
func Tee(ctx context.Context, in <-chan interface{}) (_, _ <-chan interface{}) {
    out1 := make(chan interface{})
    out2 := make(chan interface{})

    go func() {
        defer close(out1)
        defer close(out2)
        for val := range orDoneChannel.OrDoneCtx(ctx, in) {
            var out1, out2 = out1, out2 // コピーしてローカル変数用意
            for i := 0; i < 2; i++ {    // out1とout2を確実に選択するため。
                select {
                case out1 <- val:
                    out1 = nil
            // コピー側へnil代入し、out1チャンネルをブロックさせる。
            // (=out2を選択させる)
                case out2 <- val:
                    out2 = nil
            // コピー側へnil代入し、out2チャンネルをブロックさせる。
            // (=out1を選択させる)
                }
            }
            // out1とout2の書き込みが終わると、inチャンネルが読み込み可能となる。
            // for ~ rangeのイテレーションがひとつ進むから。
        }
    }()
    return out1, out2
}

なぜteeチャンネルを利用するのか?

チャンネルブロックを避けて、2チャンネルへ分岐できる。

teeチャンネル使い方

func TestTeeChannel(t *testing.T) {

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // 定時実行
    tick := time.NewTicker(1 * time.Second)
    defer tick.Stop()

    inData := make(chan interface{})
    go func() {
        defer close(inData)
        var cnt int
        for {
            select {
            case <-tick.C:
                cnt++
                inData <- cnt
            case <-ctx.Done():
                return
            }
        }
    }()

    // teeチャンネルを利用してチャンネルを分岐する
    out1, out2 := teeChannel.Tee(ctx, inData) // teeChannelというパッケージ名にしたので・・・

    for v1 := range out1 {
        v2 := <-out2
        fmt.Printf("2つに分割:\n(out1, ok) = (%v)\n(out2, ok) = (%v)\n", v1, v2)
    }
}

参考