凍りついた番人と、それを外から叩き起こす番犬
前回、建玉の出口は guardian に任せた、と書きました。数秒おきに全部の玉を見て、損切りとトレールを動かす番人です。
その番人には、一つ致命的な弱点があります。止まったら、守っている玉が全部いっぺんに無防備になる。 損切りも、トレールも、建値移動も、動かす人がいなくなる。そして、その「止まる」が実際に起きました。
番人が凍った。見張りごと。
6月20日、guardian のポーリングループが丸ごとフリーズしました。プロセスは生きている。CPUも食っていない。でも、何もしていない。死んでいるより質が悪い「生きたまま固まる」状態です。外から見ると動いているように見えるので、気づけない。
さらに間抜けなことに、当時 guardian の中には「自分が固まっていないか見張る」仕組みを内蔵させていました。これが役に立たなかった。ループごと凍ったので、内蔵の見張りも一緒に凍った。 沈む船の中で「浸水を知らせる係」も一緒に沈んだわけです。だから誰も再起動をかけなかった。
ここで学んだことは一つです。自分で自分は見張れない。 固まったプロセスは、自分が固まっていることに気づけない。見張りは、必ず外に置かないと意味がない。
外から叩き起こす番犬
そこで、監視を完全に別のプロセスに追い出しました。仕組みはばかばかしいほど単純です。
- guardian は、一周まわるたびに「今この時刻に生きてます」という心拍(ハートビート)をファイルに書く。
- まったく別のプロセス(OSのlaunchdが1分おきに起動する小さなスクリプト)が、その心拍ファイルの更新時刻だけを見る。
- 心拍が古すぎたら(guardianは180秒)、「こいつ固まってる」と判断して問答無用で kill。あとは launchd が自動で新品を立ち上げる。
番犬は guardian の中身を何も知りません。ただ「最後に心拍を打ったのはいつか」だけを見て、古ければ叩き起こす。中で何が凍っていようが関係ない。外にいるからこそ、一緒に凍らない。
これが効いた記録です。6月27日、guardian の心拍が止まりました。番犬のログはこうです。
06-27 10:55 guardian: 心拍が6049s停止(>180s)。kill→launchd再起動
6049秒。約1時間40分、番人は凍っていました。 もし外の番犬がいなかったら、その1時間40分、私の建玉は損切りもトレールもない丸腰で市場に晒されていたことになります。想像すると寒い。番犬はそれを検知して、叩き起こしました。
(ちなみに、週末や市場クローズ中も心拍は打ち続ける設計にしてあります。「静かだから止まった」と「固まって止まった」を取り違えて、動いているものを誤ってkillしないように。)
相手はGMOの都合でも落ちてくる
自分の凍りだけでなく、取引所側の都合でも止まります。GMOには二つ、定番の落とし穴がありました。
一つ目、定例メンテナンス(ERR-5201)。 毎朝9時ごろ、GMOのAPIは点検で一時的に消えます。何も考えていないと、消えたAPIを叩いてはエラー、叩いてはエラーでクラッシュを繰り返す。なので「これはメンテだ」と分かったら、市場再開まで黙って待つようにしました。しかもこの待機中は、番犬に「今はメンテで待ってるだけだから殺すな」と伝える。動いていないのが正常な時間帯なので。
07-04 09:00 GMO定例メンテ中(ERR-5201)。市場再開まで待機
二つ目、レート制限(ERR-5003)。 APIを短時間に叩きすぎると「Requests are too many」と弾かれます。一度これで自滅しかけました。もっと怖かったのはこれです。
07-14 21:30 保険STOP設置失敗: ERR-5003 Requests are too many
建玉に保険の損切りを置こうとした、まさにその瞬間に、レート制限で弾かれた。 一瞬、ストップのない玉ができたということです。心臓に悪い。なので今は、API呼び出しの間隔を必ず1秒以上空け、弾かれたら間隔を倍々に広げて必ず置き直すようにしています。
戦略より、死なせない仕組みのほうが長い
ボックスブレイクの核は百数十行でした。でも、その核を「止まらせない・黙って死なせない・取引所のメンテやレート制限で自滅させない」ための足回りは、たぶん核より長い。
自動売買でいちばん時間を食ったのは、勝つ工夫ではなく、この「静かに壊れる」を一個ずつ塞ぐ作業でした。そして最後の砦は、いつも外にいる番犬です。中の誰も気づけないから。次は、その番人や番犬が今どうなっているかを、私が外出先からスマホで覗く話をします。