noteのタイトル画像

誰が書いても同じようになるBotのソースコードは”クラス”に押し込めちゃおう🎵

目次
・車輪の再発明はしたくない
・ソースコードは短いほど、分岐が少ないほど、良い
・設計思想も立派なライブラリ
・オブザーバ とは
・短小は美学?
・特殊注文などもサポート予定

こんばんは。今日もせっせとBot作りに励んでおります。

もっと早くに投稿しようと思ったのですが、プログラムのブラッシュアップにちょいちょいノメり込んでしまい、あっと気づいたらこんな時間。

●車輪の再発明はしたくない

今日のお題は

注文を出したり、ポジションを確認したり、OHLCVデータを取ってきたり、一定時間でループしたり、、、
そんな誰が書いたって似たり寄ったりなソースコードは便利なクラスに押し込めちゃおう🎵

です。
そう、誰だって車輪の再発明はしたくないですよね。
っていうことで、さくっと書き上げようと思ったら結構熱が入ってしまいました(笑)

私は現在ccxtというオープンソースのライブラリを使用しています。

100を超える取引所をサポートしている人気急上昇中のライブラリなんですが、多くの取引所をサポートするために取引所固有の機能や例外処理などの対応はある程度利用者任せなところがあります。

個人で100を超える取引所と自動トレードする方はまずいらっしゃらないだろうと思い、人気どころの数カ所の取引所を対象にして

ccxtライブラリに皮をかぶせて、
さらに使いやすく、
例外処理を手厚く、
命令を簡単にした

なクラスを作ってみました。
今日のところはBitMEX対応だけですが、あと数カ所対応したクラスも作りたいと思っています。

クラスは

 cBaseクラス: ccxtの機能で共通に使えそうなものをラップ
 cBitMEXクラス: BitMEX固有命令や便利そうな機能を”がばっ”とラップ

で構成されています。
そう、cBItMEXがcBaseを継承(機能を受け継ぐ)形で構成されています。

BitMEX専用だと思っていた機能で「この機能、共通じゃん?」っていう機能があったら基底クラスである「cBase」に機能を移していけばいいや、くらいのノリで作り始めました。

●ソースコードは短いほど、分岐が少ないほど、良い

私の信念として「ソースコードは短いほど、分岐が少ないほど、良い」と考えています。(疲れてくると冗長なソースコードも書きますけどね)

短いほど読みやすい。だから自分は当然のこと、他人にもバグ(プログラムの傷)を発見してもらいやすい。
分岐が少ないほど良い。if文やswitch文で分岐しまくったソースコードは思考の限界を超えて、バグの大名行列が発生します。

そのためには、よくテストされ、枯れた(障害がほとんど発生しないレベルまで品質を高めた)ソースコードに手を加えることなく、再利用できるところは再利用したい。

その代表がccxtのようなライブラリでしょう。

●設計思想も立派なライブラリ

ccxtのようなものだけが「ライブラリ」ではありません。

こんな風に作ると失敗が少ない、とか。
この構造にすると、スピードが早く、メモリの消費も少ない。

などなど、設計思想や構造の蓄積も立派な「ライブラリ」です。

世間では「デザインパターン」とか呼んでたりしてますが。

そんなパターンの一つである「オブザーバ・パターン」という手法を取り入れてみました。

●オブザーバとは

詳しいことは上記を参照してもらうとして、簡単にいうと

「用事があったら電話してね」と言って、相手に携帯電話を持たせる

ようなもんです。
つまり、用事が無ければ相手から電話はかかってこないので、それまでは「寝て待つ」わけです。

●短小は美学?

能書きはこれくらいにして、今回作成したクラスを使って「ドテン君」の実装をした例が以下です。

// ==================================================================
// サーバ設定
// ==================================================================
// 売買USD値
const LOT = 1;                              // 売買USD

// ==================================================================
// bitmexオブジェクトを習得
// ==================================================================
const mex = new cBitMEX({
    fSimulation: true,                      // シミュレーションモードフラグ true: シミュレーション、false: 本番
    fOrigin: true,                          // サイト選択 true : 本家(www.bitmex.com)、true: デモサイト(testnet.bitmex.com)
    resolution: 60,                         // OHLCVデータ取得の足幅、1,5,60,1440のどれか
    sleepTime: 10000,                       // メインループのsleepTimeを設定する (default: 10秒)
    apiKey : ApiKey,
    secret : ApiSecret
});

// ==================================================================
// private関数
// ==================================================================
// ログ出力
// side: buy or sell
// price: 売買価格
const Log = (side, price) => {
    const logDt = mex.isSimulation ? (new Date(mex.lastOHLCV.timestamp * 1000)) : (new Date());
    console.log('time: [' + mex.dateFormat(logDt) + '] ' + (side == 'buy' ? 'Buy 価格' : 'Sell価格') + ': [' + price.toFixed(1) + ']');
}

// ==================================================================
// 売買イベント作成
// ==================================================================
const em = new events.EventEmitter();

// --------------------------------
// buy, position == 0
// --------------------------------
fromEvent(em, 'buy')
    .pipe(filter( pos => pos == 0))
    .subscribe(
        async (pos) => {
            const ret = await mex.marketBuy(LOT);
            await mex.waitUntilWantPosition(LOT);
            Log('buy', ret.price);
        }
    );

// --------------------------------
// buy, position < 0
// --------------------------------
fromEvent(em, 'buy')
    .pipe(filter( pos => pos < 0))
    .subscribe(
        async (pos) => {
            const ret = await mex.marketBuy(LOT * 2);
            await mex.waitUntilWantPosition(LOT);
            Log('buy', ret.price);
        }
    );

// --------------------------------
// sell, position == 0
// --------------------------------
fromEvent(em, 'sell')
    .pipe(filter( pos => pos == 0))
    .subscribe(
        async (pos) => {
            const ret = await mex.marketSell(LOT);
            await mex.waitUntilWantPosition(-LOT);
            Log('sell', ret.price);
        }
    );

// --------------------------------
// buy, position > 0
// --------------------------------
fromEvent(em, 'sell')
    .pipe(filter( pos => pos > 0))
    .subscribe(
        async (pos) => {
            const ret = await mex.marketSell(LOT * 2);
            await mex.waitUntilWantPosition(-LOT);
            Log('sell', ret.price);
        }
    );

// ==================================================================
// OHLCV収集タイミングで呼び出される。イベントの引数はそのときのポジション値
// ==================================================================
fromEvent(mex.ohlcvEvent, 'ohlcv')
    .subscribe(
        (x) => {    // xにはpositionが入ってくる
            // --------------------------------
            // ドテン計算
            // --------------------------------
            const result = judge(mex.ohlcv);
            if('none' != result) em.emit(result, x);    // 売買イベントを発火  
        }
    );

// ==================================================================
// メイン
// ==================================================================
(async () => {
    console.log('main start from [' + mex.dateFormat(mex.toDateTime) + '] site: [' + mex.apiUrl + '] period: [' + mex.resolution + '分] Lot: [' + LOT + '] mode: [' + (mex.isSimulation ? 'シミュレーション' : '運転') + ']');
    // 指定した時間間隔でOHLCVデータを収集して 収集したタイミングでohlcvEventを発火する
    await mex.autoFetch();

})();

え?全然短くないって?
いやぁ、これでもNO.16のドテン君よりは格段に短くなり、シミュレーション機能もそのままです。

そしてお気づきかもしれませんが上記のソースコード上には

IF文がほぼ登場しない!

のです。
時間があればもっともっとブラッシュアップしたいところですが、凝れば凝りすぎるところがあるので、この辺でひとまずは置いて居ます。

処理は各イベント(何かが起こったという事象)が発生してから動きます。
イベントは受け取る側が取捨選択できますし、全部の条件をそれぞれ与えて分岐の試験をする必要がありません。

売買判定ロジックは「judge」メソッドに分離してあります。
ドテン以外のロジックをここに組み込めば簡単に別な売買Botに早変わり🎵

●特殊注文などもサポート予定

現在は、fetch系の基本的なメソッドは組み込み。
通信異常でOHLCVデータが取れなかった時もリトライするようにしています。

marketBuy, marketSell, limitBuy, limitSell, stop, stopLimitなどの基本的なorder系メソッドは実装済み。

今日は疲れたので特殊注文系(OTOとかOCOなど)は今度に持ち越しです。

ご期待いただければ「スキ」ボタンをポチっと、「フォロー」ボタンをクリック、よろしくお願いいたします。

note: https://note.mu/o_matsuo

twitter: @o_matsuo
もフォローしてくださると、喜びます。

あ、それから私の師匠である
コンドウ様のnoteもポチっとしていただけると、さらに喜びます。

みなさんと一緒に、楽しいBotライフを🎵

ソフトウェア・エンジニアを40年以上やってます。 「Botを作りたいけど敷居が高い」と思われている方にも「わかる」「できる」を感じてもらえるように頑張ります。 よろしくお願い致します。