FIREして市場に落ちている金を拾うブログ

30代でFIREし、プログラムを勉強して米国株式と仮想通貨で落ちている金をせっせと拾う記録。

なぜFIRE後のポートフォリオに仮想通貨を組み込むか

FIRE後に仮想通貨に手を出す人はごくわずか

FIREに関するインフルエンサーやYoutuberなどを見ても、仮想通貨をおすすめしている人はほぼいないと思います。
居たとしても、インフレヘッジのためのデジタルゴールドとして、総資産の3%くらいを上限に持ちましょうという程度。

自分は総資産の2割くらいを仮想通貨に投資してます。現状では、結構なレアケースだと思います。
そこで本稿では、なぜこのようなポートフォリオに至ったのかを説明します。

教科書的な株式:債権=50:50で安心できない

FIRE後、総資産の4%を取り崩す形にすれば資産は残存し続けるとよく言われると思います。
その際には、全て株式で持つのではなく、株式:債権=50:50程度で持つと30年後の残存確率が最も高くなると言われています。

でも、それで安心できるでしょうか?

債権の利回りもBND:1.84%、AGG:1.89%と2%を下回っており、高配当株式もVYM:2.75%、HDV:3.38%と低いです。
組み合わせても全体のインカムゲインが3%にも全く届きません。

では値上がりした分、持ち分を取り崩すかというと、
現在の米国S&P500の平均PERは20.5倍程度と高水準にあり、今後の株式のパフォーマンスは過去10年ほどではないことが予想されます。

本当に、本当に大丈夫?

いや、データの上では安心できるはず、というのはわかります。でも、自分の人生をかけられるほど確からしいのか?
人によると思いますが、自分はもうちょっとリスク分散しつつリターンを増やしたいな~と思いました。

とはいえ代替手段がほぼない

しかし代替手段がありません。

せいぜいREITですが、ほとんど株式と値動き変わらなくない?というかコロナショックのとき、なぜか株式より値下がったよね?
最近のREIT利回りって4%切ってて高配当株式と大差なくない?
となって、本当に何もないんですよねー。

あえて言えば不動産投資に手を出すかですが、個人的にこの人口減少トレンドの中で流動性の低い不動産をポートフォリオに抱える、
しかも一定のインカムを産もうとすると総資産のほとんどが不動産になるという判断に踏み出せませんでした。

仮想通貨は個人が手を出せるオルタナティブ投資として随一の自由度

そんなときに友達に仮想通貨を教えてもらいました。
読み込んだのはこの記事です。

note.com

目からウロコでした。
仮想通貨といえば、供給が増えないことを頼りに、需要が増えることをどれだけ信じて、
ビットコインを握り込めるか勝負という印象を持っていましたが、現実は全然違うことを教えてくれました。

自分の理解としては、規制がほぼ働いてない中で、勝手にデリバティブ開発が行われ、リスクの取り方の自由度が広がっていたのです。

ビットコインみたいな電子ゴミを買うとかアホでしょという人は、アービトラージ、現先、FR取り、IEOをやればいいのです。
もうちょっとブロックチェーンの将来に期待を持つ人は、現物積立をやってもいいでしょう。
ヨーロピアンさんやDEGさんみたいに、記事を書いてアフィリエイト収入を得てもいいと思います。

結構間口が広がってるということを教えられました。

FIREしてる人の目線では、相場変動リスクがないアセットとして仮想通貨をポートフォリオに組み入れるのが最適ではないか

そしてポートフォリオに立ち返ると、一定割合を仮想通貨にする合理性があるように思えました。

・今後パフォーマンス低下(もっと言えば値下がり・バブル崩壊)が懸念される株式のウェイトを下げたい。
・しかし債権の利回りは低すぎて買いたくない。

ヘッジすれば元本変動リスクをなくせる仮想通貨は、債権の代替として機能すると思えました。
完全にヘッジして元本変動リスクゼロにしても、低く見ても20%程度は利回りが出せると感じています。
債権の代わりにポートフォリオに組み込めば、インカムゲインで十分以上に生活できます。

一方で取引所GOX、コントラクトバグ、Rug pullなどの元本全損リスクがあるので、すべて仮想通貨にするようなことはできません。
あくまで一定割合をポートフォリオとして組み込むことで、様々なリスクのバランスを取れるものとして捉えることができると思います。

例えば50%株式、30%REIT、20%仮想通貨とすると、
年間のインカムゲインが総資産の6%くらいになるので8000万円ほどあれば人並みの生活ができます。

さらに踏み出せば事業にできそう

さらに色々調べていると、仮想通貨botterという人種がいることがわかりました。要はシステムトレードです。

最近はAWSなど、個人でも使えるクラウドが普及していることもあり、個人でHFTがやれるようになってきました。
加えて、仮想通貨は大手金融事業者が手を出していないので個人でも儲かる環境にあります。
結果として、ルールを理解して出し抜いた人が儲かる世界で、今月1億円儲かりましたという報告もしばしば見られます。

事業としても面白くない?

そのような経緯で、正確にはFIはしたがREせず、仮想通貨botter目指してPythonを勉強中です。
本ブログは、そのPythonを勉強したことを忘れないようにするために取っているメモが中心です。気が向けば投資全般の話も書きたいと思います。

Websocket実装に向けて非同期処理を勉強する(WebSocket)その4

前回に引き続き、まちゅけんさん記事を見ながら勉強。

結論としては、何個かバグ取りをした結果、動くようになった。
以下メモ。

place orderのresponseでresultの中身のないものが返ってくる

例えば既に出している指値をmodify orderしたときに、すでに指値が約定してしまっていることがある。
その場合、resultの中身が入ってないので、ハンドラで返ってきたデータにあわせて挙動を調整することとした。

    async def handler(self, obj):
        try:
            data = await obj.json()
        except ValueError:
            logger.debug('Value Error')
            raise
        else:
            if not data['success']:
                logger.debug(f'{data["error"]}')
            return data['result']            

FTXからResponseが返ってこないときにTimeout Errorが出る

よくある話。
以前、自分でクライアントを設定していたときはRequestにHTTPAdapterを設定しRetryしていたが、今回はpybottersのClientを使っているので中身を確認。
aiohttpのClientを使っていたので、aiohttp.ClientTimeoutを設定し一定時間でTimeoutするようにして、
Try Exceptでエラーを吐いてループを続行するように変更した。

結果

まだ駄目だめだと思われるが、Maker注文約定から、ヘッジのためのTaker注文発注約定まで、
RESTでは0.7~1.3秒くらいかかっていたところが、0.4~1秒くらいに短縮された。微妙?笑
まぁ、まだできたばかりで最適化余地がいっぱいあると思うのでこれからちょびちょびやってこうと思います。

Websocket実装に向けて非同期処理を勉強する(WebSocket)その3

前回に引き続き、まちゅけんさん記事を見ながら勉強。

RESTベースで作っていたロジックを移植

どうやらBalanceについては実装されてないらしい。
また、Positionについても、Store.Positionは自炊よりも遅れる模様(RESTで取得しているため?)。
よって、Balance, Positionいずれも自炊することにした。Storeで使うのはOrderbook、Fills、Ordersのみ。

そういうわけなので、RESTベースで過去に作っていたBotからロジックを移植。
過去ロジックでは、Balance, PositionについてループのはじめにRESTで取得していたが、
Bot起動時に初期値を取得したあと、Store.Fillsのデータを使って自炊するように変更した。

なんとか動く感じになった。

Place Orderの待機ロジックを変更

公式サンプルでは、高頻度botにおいてはRESTよりWebSocketが早い可能性があるので、
下記コードでイベント待機することになっている。

            # オーダー執行
            if cond:
                # 高頻度では重複オーダーしないようにオーダー後WebSocketでデータ受信するまで待機させる
                # RESTの応答よりWebSocketのイベントの方が速い可能性があるので先にイベント待機タスクをスケジュールする
                event = asyncio.create_task(store.order.wait())
                await client.post('/v2/private/order/create', data={
                    'symbol': 'BTCUSD',
                    'side': side,
                    'order_type': 'Limit',
                    'qty': qty,
                    'price': price,
                    'time_in_force': 'GoodTillCancel',
                })
                await event

やってみたんだが、うまく動作しないようであった。

今回は両建てbotを移植したので、出しておいたMaker注文が約定したら、反対側のTaker注文を発するロジックになってるが、
上記コードでは

1. Maker注文約定認識(store.fills.findで把握)
2. Balance/ Position自炊(未ヘッジ分を把握)
3. 未ヘッジ分のTaker注文発注
4. Taker発注認識(store.order.waitイベント発生)
5. Taker注文約定認識(store.fills.findで把握)
6. Balance/ Position自炊(全量ヘッジを確認)

となるはずが、

1. Maker注文約定認識(store.fills.findで把握)
2. Balance/ Position自炊(未ヘッジ分を把握)
3. 未ヘッジ分の①Taker注文発注
4. ①Taker発注認識(store.order.waitイベント発生)
5. Balance/ Position自炊(3のTaker注文約定未認識のため、一部未ヘッジのままと認識)
6. 誤認識の未ヘッジ分②Taker注文発注

7. ①Taker注文約定認識(store.order.waitイベント発生)
8. ②Taker注文約定認識(時間経過)
9. Balance/ Position自炊(二回ヘッジオーダーをしたため、反対側へのヘッジが必要と認識)
10. 3~9をエンドレスループ

となり、ひたすら往復ビンタでOIを積み上げることになってしまった。
ロットを絞ってなかったらえらい目に合うとこだった。

対応策としては、シンプルだが

                event = asyncio.create_task(store.fills.wait())

として、ヘッジオーダーの約定まで待機することで解決。

Websocket実装に向けて非同期処理を勉強する(WebSocket)その2

前回に引き続き、まちゅけんさん記事を見ながら勉強。
Pybottersを触る。

自分のドキュメント読めてなさに絶望

Exampleで書かれているBybitの例を適当にFTXに書き換えて入れてみるが動作しない。
しょうがないので、Data storeのInitializeで使われているclient.get('orders)などで何が吐き出されているのか見ようとするが、そもそもこの吐き出しがうまくいかない。デバッグのためのコードすらかけないことに絶望。

色々いじっていると、元のドキュメントに書いてある。。。

戻り値はライブラリ aiohttp.ClientResponse のインターフェースです。 status プロパティでHTTPステータスを取得できます。
json, text メソッドでレスポンスボディを取得できます。

そりゃclient.getで取得したオブジェクトをprintしてもちゃんと出ないわ。.json()して出力してようやく中身が見えた。

さらにちゃんと見ると、FTXのDatastore関連の話が書いてあるじゃん。
この時間は一体なんだったのか。
コピペしてポジション取得して、FillsをWebSocketで捕捉できるようになった。

しかしBalanceがないな。もしかしてここは自分で書かないといけない感じだろうか。。。

Websocket実装に向けて非同期処理を勉強する(WebSocket)

前回に引き継ぎ、まちゅけんさんの記事を見ながら勉強。今回はWebSocket。
WebSocketの記事は流し読みして、pybotterライブラリを使うところにいきなり突入。

これまでWebSocketがうまく動作しなかった理由がようやくわかる

これまで、いろんなラッパーを使おうとするも、どうしてもエラーが出て使えなかったが、ようやくわかった。
Jupyter NotebookとPythonでは、微妙に非同期処理の使い方が違うらしい。

import asyncio
import pybotters

apis = {"bybit": ["YOUR_API_KEY", "YOUR_API_SECRET"]}

async def main():
    async with pybotters.Client(base_url="https://api.bybit.com", apis=apis) as client:
        r = await client.get("/v2/public/tickers", params={"symbol": "BTCUSD"})
        data = await r.json()
        price = round(float(data["result"][0]["bid_price"]) - 300.0, 1)

        r = await client.post(
            "/v2/private/order/create",
            data={"symbol": "BTCUSD", "side": "Buy", "order_type": "Limit", "qty": 1, "price": price, "time_in_force": "PostOnly"},
        )
        data = await r.json()
        print(data)
 
asyncio.run(main())

上記コードをFTX用に書き換えて実行したら、下記エラーが出た。

asyncio.run() cannot be called from a running event loop

なんでか調べたところ、こちらに行き着いた。

The asyncio.run() documentation says:

This function cannot be called when another asyncio event loop is running in the same thread.

The problem in your case is that jupyter (IPython) is already running an event loop (for IPython ≥ 7.0):

You can now use async/await at the top level in the IPython terminal and in the notebook, it should — in most of the cases — “just work”. Update IPython to version 7+, IPykernel to version 5+, and you’re off to the races.

That's the reason why you don't need to start the event loop yourself in jupyter and you can directly call await main(url) even outside asynchronous functions.

In jupyter

async def main():
print(1)

await main()
In plain Python (≥3.7)

import asyncio

async def main():
print(1)

asyncio.run(main())

Jupyter Notebookでは、プログラムとは別にEvent Loopを回しているようで、
それとは別にEvent Loopにタスクを突っ込んで回そうとしてもだめらしい。
今回は最終的にPythonとして実行するため、Jupyter NotebookではなくPythonで開発するように変更した。

エンドポイント設定でハマる

Pythonでサンプルコードを変えて入れていたが、ずっとNot Logged Inとエラーが出てハマる。
FTX側のホワイトリスト設定も見てみるが、特に問題はない。
どうもわからなかったので、もしかしてPybottersのAuthentificationにミスが有るのでは?と思って見るがバグを発見できず。

しばらく試行錯誤して、base_url = 'https://ftx.com/api/'となっており、r = await client.get('/positions')としていたことに気づく。
もしかして"/"が重複してるのでは?と思い片方削ると無事成功。
えー、、、URL間違えてもNot Logged Inって出るの・・・。Authentificationミスのときだけそのエラーが出てほしかった。

self設定でハマる

なんか引数が足りんと言われるなぁ・・・と思ってると、Classの中の関数で、selfを引数に設定し忘れていた。

そんなわけて一応WebSocket動作

import time
import asyncio
import pybotters

class FTX():
    # 定数
    URLS = {'REST': 'https://ftx.com/api/',
           'WebSocket': 'wss://ftx.com/ws/',
          } 

    def __init__(self, api_key = None, api_secret = None, subaccount = None):
        # APIキー、Secretをセット
        self.api_key = api_key
        self.api_secret = api_secret
        self.subaccount = subaccount    
        self.apis = {
            'ftx': [self.api_key, self.api_secret],
        }

    # ------------------------------------------------ #
    # WebSocket
    # ------------------------------------------------ #
    async def main(self):
        async with pybotters.Client(base_url = self.URLS['REST'], apis = self.apis, headers={'FTX-SUBACCOUNT': self.subaccount}) as client:
            symbol = 'BTC-PERP'
            await client.ws_connect(
                self.URLS['WebSocket'],
                send_json=[
                    {'op': 'subscribe', 'channel': 'orders'},
                    {'op': 'subscribe', 'channel': 'fills'},
                ],
                hdlr_json=store.onmessage,)

            while True:
                start = time.time()
                await store.orders.wait()
                orders = store.orders.find()
                print(orders)
                await asyncio.sleep(5)
                end = time.time()
                elpsed_time = end - start
                print(elpsed_time)

try:
    _api_key = ''
    _api_secret = ''
    _subaccount = ''
    
    ftx = FTX(_api_key, _api_secret, _subaccount)
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())  # need windowsOS python3.8
    asyncio.run(ftx.main())

except Exception as e:
    print('=== エラー内容 ===')
    print(f'{str(e)}')

except KeyboardInterrupt:
    pass

あとはこいつを使ってラッパー化していくつもり。

Websocket実装に向けて非同期処理を勉強する(REST API)

REST APIだけで実装しているbotにWebsocket入れたーい。
そんな思いでまちゅけんさんの記事を勉強したメモです。元記事はこちら。

zenn.dev

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.bybit.com/v2/public/tickers?symbol=BTCUSD") as resp:
            data = await resp.text()
        print(data)

asyncio.run(main())

5行目で非同期コンテキストマネージャーを用いて HTTPクライアントセッション [3] を作成しています

いきなり用語がわからない。非同期コンテキストマネージャーとはなんぞや?
調べてみると、withがコンテキストマネージャというらしい。それをasyncで使ってるので非同期コンテキストマネージャ。
調べてないが、ClientSessionをwithで開くと、with構文終了後、セッションが勝手に閉じるようになってると思われる。

6行目でまた非同期コンテキストマネージャーを用いて HTTPリクエスト(GET)を行いレスポンスを待機 ( HTTP通信 [4] )します ( HTTPヘッダ が取得されます)

書いてあることはわかるが、必要性が理解できない。
5行目で非同期コンテキストマネージャー使ってるので、セッションは閉じるようになってるはず。6行目でも非同期コンテキストマネージャーを使う必要があるのか?なにか閉じるものがあるのか?
7行目のawaitとはどう使い分けているのか?根底コネクタってなんだ?

なお、コルーチン関数で実装されているメソッドはawait文で実行する必要があるらしい。へー。

そう考えていると、次にもう一つ違う書き方が。

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        r = await session.get("https://api.bybit.com/v2/public/tickers", params={"symbol": "BTCUSD"})
        data = await r.json()
        print(data["result"])
 
asyncio.run(main())

やっぱasync withでgetする必要はないらしい。だよねー。

import asyncio
import aiohttp
import time

async def fetch(url, params={}):
    r = await session.get(url, params=params)
    data = await r.json()
    return data

async def main():
    global session
    async with aiohttp.ClientSession() as session:
        stime = time.time()

        results = []
        coro1 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "BTCUSD"})
        coro2 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "ETHUSD"})
        coro3 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "EOSUSD"})
        coro4 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "XRPUSD"})
        task1 = asyncio.create_task(coro1)
        task2 = asyncio.create_task(coro2)
        task3 = asyncio.create_task(coro3)
        task4 = asyncio.create_task(coro4)
        await task1
        await task2
        await task3
        await task4
        results.append(task1.result())
        results.append(task2.result())
        results.append(task3.result())
        results.append(task4.result())
        print(results)

        etime = time.time()
        print(f"Total {etime - stime:.4f} sec")

asyncio.run(main())

前節との最も重要な違いは20-23行目でコルーチンをTask化し実行を イベントループ にスケジュールし、それを24-27行目で各Taskが終了するのを await文 で待機しているところです。
前節ではイベントループにスケジュールせずに直接await文で実行していました。ここで分かるのは await文は "実行する" というよりも、awaitを行った行で制御を離してイベントループに任せて "awaitしたオブジェクトが終了するまで待機する" という命令 であることです。

すごくよくわかった。
非同期処理の本質的なメリットは、1つ目のリクエストへのレスポンスが返ってくる前に、2つ目のリクエストを投げられること。
そのため重要なのは、イベントループにタスクを入れきったあと、awaitで終了するまで待機すること!

Websocketは記事を分けます。