無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

ReactとCordovaで、Web/モバイルのハイブリットアプリを作った話

ReactとCordovaを使って、ブラウザ向けのWebUI + Androidで動くスマホアプリ を提供するサービスを、一人で作ってみた話です。

サマリー

  • 作ったもの
  • 最大の課題:作業量
  • 一人で作りきるために意識したこと
  • 取り組み1: Cordovaを使って、Web UI/スマホアプリのコードを共通化する
  • 取り組み2: レイヤードアーキテクチャを採用し、共有できるコードを最大化する
  • 取り組み3: ユニットテストを書く
  • まとめ

作ったもの

jiji2.unageanu.net

自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんにFX自動取引を開始できる、システムトレードフレームワークです。

スクリーンショットをいくつか。

Web UI:

f:id:unageanu:20151209113018p:plain f:id:unageanu:20151209113019p:plain f:id:unageanu:20151209113020p:plain

スマホアプリ:

f:id:unageanu:20151209113043p:plain f:id:unageanu:20151209113044p:plain f:id:unageanu:20151209113045p:plain

コード規模

UI側のコード規模は以下の通り。テストケースを含めた合計で、31000行くらいです。

WebUI アプリ 合計
ソースコード 15222 4454 19676
テストケース 9676 1574 11250


また、これとは別に、サーバーのコード(ruby)が、ソースコード/テストケースあわせて、27000行くらいあります。

構成

  • UIは、いわゆるシングルページアプリケーションで、ECMAScript2015 で書いています。
  • マテリアルデザインを採用していて、Reactで動くUIライブラリの Material UI を利用しています。

前置きは以上。

最大の課題:作業量

開発にあたっての最大の課題は、なんといっても作業量でした。
初期の検討段階で、「Webアプリ or モバイルアプリのどちらかに注力できないか?」と考えましたが、

  • Push通知やモバイルでのシステム管理に対応した、スマホ時代のFXシステムトレードフレームワークにしたかった。

    • 同様の機能を提供するソフトはすでにあるのですが、モバイルでの取引状況の確認やアルゴリズムの管理に難があったのが、開発のきっかけにもなっています。
  • とはいえ、スマホで取引アルゴリズムを作成したり、取引結果を分析するのはツラい

    • Rubyのコードを作成するのは、やはりPCからになると思われる。(外出先からスマホアプリで緊急修正、というのはあるにしても)
    • 取引結果の分析も、広い画面で行いたい。

ということで、両方必要という結論に至りました。そうと決まれば、後は、如何にして作りきるかです。

一人で作りきるために意識したこと

以下の2点を、特に意識しました。

  • コードの共通化/再利用
    • Web UI/スマホアプリのコードを可能な限り共通化して、開発コストを削減する。
  • ユニットテスト
    • ユニットテストで個々のモジュールの動作を保障。
    • 機能追加や変更を、低コストで素早く行えるようにする。

そして、↑のための具体的な取り組みとして、以下を行いました。

  1. Cordovaを使って、Web UI/スマホアプリのコードを共通化する
  2. レイヤードアーキテクチャを採用し、共有できるコードを最大化する
  3. ユニットテストを書く

取り組み1: Cordovaを使って、Web UI/スマホアプリのコードを共通化する

スマホアプリをCordovaを使ったハイブリットアプリにして、Web UI/スマホアプリのコードを共通化しました。

  • Cordovaを使うことで、JavaScript + HTML + CSSスマホアプリを作ることができるので、同様の構成で作成するWeb UIのコードを再利用できるようになります。
  • React Native という選択肢もありましたが、UI側の開発に着手した時点ではAndroidには対応していなかったため、見送りました。
    • Cordovaは、Webでの情報が豊富で、実績も多くあった点がプラスでした。
    • また、Push通知や課金決済関連のプラグインがあったことも大きいです。
  • 採用に当たって懸念だった、UIのパフォーマンスについては、かんたんなプロトタイプを作って検証しました。
    • やはり、ネイティブアプリよりはもっさりしていますが、ゲームのようなパフォーマンスがアプリの価値に直結する性質のソフトウェアではないので、問題ないレベルと判断。
    • それよりも、リリースまでの開発工数を抑えること、また、リリース後の機能強化や改善をスピード感を持って行えること、を重視しました。 unageanu.hatenablog.com

「UIの反応速度とか使い勝手とか、実際どんなもんなの?」と気になる方は、ダウンロードしてお試し頂ければと思います。(まいど、ありがとうございます! / 30日の無料トライアル期間内に、定期利用を解除すれば請求は発生しないので大丈夫です)

なお、Crosswalkの効果は絶大でした。体感速度が劇的に向上したうえ、怪しい動きをしていた箇所も治ったり。 apkのサイズ/起動時間は増えますが、採用する価値はあります。

取り組み2: レイヤードアーキテクチャを採用し、共有できるコードを最大化する

MVVMのレイヤードアーキテクチャを採用して、Modelレイヤのコードを共通化。
ViewやViewModelも、変更の度合いに応じて最適なレイヤーでカスタマイズすることで、共有できるコードを最大化しました。

f:id:unageanu:20151209145903p:plain

  • Model/View/ViewModel + 通信などのInfrastructureで構成。
  • Model(UIに依存しない、アプリ共通のコアドメイン)は、Web UI/スマホアプリでそのまま共有。
  • スタイルの変更だけで済む場合は、CSSのレベルでカスタマイズ。
  • DOM構造を変える必要がある場合は、View(Reactコンポーネント)のコードを差し替えて対応。
  • Viewで管理するデータや機能がそもそも異なる場合は、ViewModel(Modelをラップして、Viewに依存するデータや操作を提供するクラス群)のレベルで、カスタマイズして再利用。
    • 例えば、WebUIでは、"通知の一覧を表示する機能"と"選択した通知の詳細を表示する機能"を同じ画面で提供していますが、スマホアプリでは別画面にしています。 このため、WebUI版では、通知の一覧画面のViewModelで通知一覧と選択状態を管理していますが、スマホアプリ版では2つのViewModelで管理する形に変更しています。

このほか、Infrastructureの通信部分やGoogle Analyticsでの利用状況解析も、WebUI/スマホアプリでカスタマイズして利用しています。

  • 通信部分は接続先REST APIを差し替える必要があるため(Web UIは自ホストに、スマホアプリはUIで設定したサーバーに接続)、URLResolverとして機能を切り出しカスタマイズしています。
  • 利用状況解析は、スマホアプリではJava APIを使うようカスタマイズしたクラスを用意して差し替え。

以下は、利用状況解析のコードの抜粋です。 インターフェイスを統一しつつ、実装を差し替える形で、別のクラスを用意しました。

WebUI版:

export default class GoogleAnalytics {

  // 略
  
  // Google Analytics のJavaScript版APIを呼び出す
  sendEvent( action, label="", value={} ) {
    this.run((ga) => ga('send', 'event', this.category, action, label, value));
  }

  sendTiming( category, timingVar, time, label ) {
    this.run((ga) => {
      ga('timing', category, timingVar, time, label);
    });
  }
  
  // 略
}

スマホアプリ版:

export default class GoogleAnalytics {

  // 略
  
  // Cordova google-analytics-plugin のAPIを呼び出す
  // https://github.com/danwilson/google-analytics-plugin
  sendEvent( action, label="", value={} ) {
    this.run((ga) => {
      ga.trackEvent(this.category, action, label, value,
        ()  => {},
        (e) => console.log(e));
    });
  }

  sendTiming( category, timingVar, time, label ) {
    this.run((ga) => {
      ga.trackTiming(category, time, timingVar, label,
        ()  => {},
        (e) => console.log(e.message));
    });
  }
  
  // 略
}

これら、コンポーネントの差し替えは、DI(Dependency Injection)コンテナを利用して、行うようにしました。 DIコンテナを使うことで、コンポーネントの外側で、環境ごとにどのクラスを使うのか宣言的に指定できるようになります。

WebUI版のコンポーネント定義:

binder.bind("googleAnalytics").to("utils.GoogleAnalytics")
  .withProperties({
    category: "web-ui",
    version:  "1.0"
  }).onInitialize("initialize");

スマホアプリ版のコンポーネント定義:

// スマホアプリ用のクラスを使うように変更
binder.bind("googleAnalytics").to("app.utils.GoogleAnalytics")
  .withProperties({
    category: "app",  // category, versionもカスタマイズ。
    version: "1.0.6"
  }).onInitialize("initialize");

利用側のコードは以下のような感じ。Injectとしてマークしておくと、コンポーネント定義で宣言されたコンポーネントが注入されるので、利用側はAPIを呼び出すだけです。

export default class RmtService extends AbstractService {

  constructor() {
    this.urlResolver     = ContainerJS.Inject;
    this.xhrManager      = ContainerJS.Inject;
    this.googleAnalytics = ContainerJS.Inject; 
    // コンポーネント定義で宣言されたコンポーネントが注入される
  }

  // 略

  putAgentSetting(settings) {
    this.googleAnalytics.sendEvent( "put rmt agent setting" ); // GAにイベントを送信
    return this.xhrManager.xhr(this.serviceUrl("agents"), "PUT", settings);
  }

}

取り組み3: ユニットテストを書く

MVVMアーキテクチャを採用したことで、View以外の部分はDOMに依存しなくなるため、容易にユニットテストができるようになります。 ユニットテストを意識的に用意したことで、一人でも何とか作りきることができたかな、と感じます。

  • 一人で開発したとこもあり、サーバー側の開発で1か月くらいrubyのコードばかり書いていると、自分が書いたコードでも忘れてしまって、変なバグを埋め込んだりします。テストコードがあることでデグレードが検知され、バグに気付くことが多くありました。
  • また、コードがどう動くか、についても、テストケースをみると思い出せるということもありました。

ユニットテストで確認された"動く"コードを少しずつ積み上げていく、という作り方は、心理的にも良かったと思います。 一人なので、開発期間はどうしても長くなりますし、やってもやってもタスクがなくならない、終わりが見えない感じになりますが、"動く"コードが積み上がっていくことで、毎日少しずつでもコードを書いていれば、いつか完成すると思えるようになります。

なお、初版リリースの段階ではViewのテストは省略しました。

  • 初版リリースでは、UIの精緻化などでDOMの構造を変更する可能性が高く、テストを作成しても、効果が変更コストに見合わないと考えました。
  • また、Viewの機能は「ViewModelの状態に応じて仮想DOMを生成する」だけで、テストすべきロジックが少ない && ViewModelのテストで、アプリケーションがこういう状態の時はこうなる、〇〇操作を実行するとこういう状態になる、といった機能は確認できている、というのも理由としてあります。
  • 同じ理由で、E2Eテストも行っていません。このあたりは、今後、サービスが利用されるようになって、メンテナンスの比重が大きくなってからの課題と考えています。

まとめ

  • Cordovaを使ってちゃんと作れば、一人でもサービスを作れる。
    • プロトタイプや最初のMVPは、Cordovaで素早くコストをかけずに作る、そして、手ごたえがあったらネイティブで作り直す、という選択肢は割とアリなのではと思います。
    • もちろん、パフォーマンスが重要なアプリの場合は、不相応な場合もありますが。
  • ユニットテスト重要。テスタビリティは最初から考慮して作ろう。
    • ユニットテストを用意することで、変更ややり直しのコストを最小にでき、トータルの開発コストを大きく削減できます。
    • ユニットテストで確認された"動く"コードを少しずつ積み上げていく、という作り方は、心理的にも効果大です。
  • Crosswalk速い。使おう。

FXシステムトレードフレームワーク「Jiji2」の開発状況 その2

追記(2015-12-01):

FXシステムトレードフレームワーク「Jiji」、リリースしました!

jiji2.unageanu.net

使ってみて、ご意見など頂けるとうれしいです。



前回の更新からだいぶ時間が空いてしまいました・・・。少しずつ実装は進んでいます。

github.com

9月10月でスマホアプリの実装とUIデザインがだいたいできたので、スクリーンショットを公開してみます。 例によって、コメントなど頂ければ嬉しいです。

ホーム

  • 最初に表示される画面です。
  • 以下のような情報を、1画面でさっと確認できます。
    • 口座残高/直近の勝率などのサマリ
    • 現在のレートと値動きをチャートで
    • トレードシステムからの新着の通知
    • 最新の建玉

f:id:unageanu:20151031090006p:plain

取引状況

  • 合計損益や勝率などシステムトレードの取引状況を確認する画面です。
  • 左上のメニューから集計期間を変えて分析することができます。

f:id:unageanu:20151031085323p:plain

チャート

  • 詳細チャートです。最大10年前までさかのぼってレートを閲覧できます。
  • ローソク足のほか、エージェントで描画した移動平均線などのグラフを表示できます。
  • 真ん中あたりにある緑と赤のバーが建玉の保有期間を示していて、チャートの値動きにあわせて期待通りトレードができているかチェックできるようになっています。

f:id:unageanu:20151031085316p:plain

取引ロジック(エージェント)エディタ

  • 取引ロジックを作成/編集する画面です。取引ロジックはRubyで記述します。
  • 外出先から緊急に編集が必要な場合もあるかな、ということでスマホアプリにも簡易な編集機能を用意しました。

f:id:unageanu:20151031085314p:plain

通知一覧

  • 取引ロジック(エージェントと呼んでいます)から送られてきた通知の一覧画面。
  • クリックで詳細が閲覧可能です。

f:id:unageanu:20151031090213p:plain

ポジション一覧

  • 取引ロジックが行った取引の一覧です。
  • こちらもクリックで詳細が表示されます。

f:id:unageanu:20151031090212p:plain

バックテストの作成

  • 任意の期間とエージェントを指定してバックテストを実行できます。

f:id:unageanu:20151031085319p:plain

バックテスト詳細

  • バックテストの結果を確認できます。

f:id:unageanu:20151031090305p:plain

ログ

  • エージェントが出力したログを確認できます。

f:id:unageanu:20151031085318p:plain

設定

  • 使用する証券会社やアクセスパスワードを設定できます。

f:id:unageanu:20151031085322p:plain

残タスク

残っているタスクは以下です。

  • ロゴの作成とデザインの最終調整
  • デバッグと使い込みテスト
  • 使い方などのドキュメント準備
  • 導入手順の整備

1か月でこれらを消化して、11月末にはなんとかリリースしたいところ。もうしばらくお待ちください。

Jiji2 - ホーム画面のスクリーンショット

追記(2015-12-01):

FXシステムトレードフレームワーク「Jiji」、リリースしました!

jiji2.unageanu.net

使ってみて、ご意見など頂けるとうれしいです。



ホーム画面のデザインができてきたので、現状のスクリーンショットを公開します。
ご意見などいただければ嬉しいです。

PC版:

f:id:unageanu:20150829130640p:plain

スマホ版:

f:id:unageanu:20150829130717p:plain

ホームはアプリ利用時のエントリーポイントになる画面です。
以下のような、システムトレードの主要な情報を1画面でさっと確認できるようにしました。

  • 口座残高/直近の勝率などのサマリ
  • 現在のレートと値動きをチャートで
  • トレードシステムからの新着の通知
  • 最新の建玉

まずはこの画面で状況を把握した後、エージェント設定の変更などのアクションを実行していくイメージです。

ソースコード

コードはGitHubで公開しているので、気になる方はこちらをご覧ください。

github.com

Jiji2の開発状況

追記(2015-12-01):

FXシステムトレードフレームワーク「Jiji」、リリースしました!

jiji2.unageanu.net

使ってみて、ご意見など頂けるとうれしいです。



しばらく更新できていなかったので、現在の開発状況を簡単にまとめておきます。
徐々にですが、動くようになってきています。

できていること

  • エージェントの作成/バックテスト/リアル口座でのトレードなど、コアとなる機能は一通り動く。
    • サーバー側のREST APIは概ね実装完了(のはず)。
    • バックエンドをOANDA APIに変更する対応も済みです。
  • UIも主要な機能は一通り実装完了。
    • UIから、エージェントを作成→バックテスト→リアル口座で動かす、というところまでできます。
    • ただし、「とりあえず機能を配置しました」レベルです。デザイン等はまだまだです。
    • また、バックテストの削除など細かなところの作りこみもできていません。

  • スマホアプリ
    • Cordoaのハイブリットアプリにして、UIコンポーネントはなるべくWebUI側と共有する方針ですが、それでもデザインの調整作業等は必要とみています。
    • あとはPush通知など、アプリだけの機能の部分ですね。
  • UIデザイン
  • 細かな機能の作りこみ
    • 使ってみて最低限必要と思われる機能はリリースまでに入れておきたい。
  • デバッグ
  • ドキュメント整備
  • 導入手順の整備
    • Herokuへの1クリックデプロイ
    • Dockerコンテナの用意

初版では見送った機能

以下の機能は、とりあえず初版では見送ることにしました。

  • UIからの成行注文機能
    • OANDA のクライアントアプリを使えばできるので。
    • また、Jijiではレートの更新が15秒ごとになってしまうので、作っても使いにくい機能になるかなと。

思ったよりもだいぶ時間がかかっていますが、あと1~2か月くらいあればリリースできるかなー、というところです。

コードはGitHubにあるので、気になる方はこちらをご覧ください。

github.com

導入手順とかまだ何も整備できていないので、動かすのは難しいですが。

マニュアルに書かれていないOANDA fx Trade APIの仕様3つ

OANDA fx Trade APIで、マニュアル に詳細が書かれていない仕様がいくつかあったので、動かしてみて確認した内容をまとめておきます。

サマリ

  • REST APIでは、stop と marketIfTouched で注文が可能
  • 注文のレスポンスに含まれる、tradeOpened、tradesClosed、tradeReduced, orderOpened の違い
  • tradeOpened、tradesClosed、tradeReduced のidは建玉(trade)のID

REST APIでは、stop と marketIfTouched で注文が可能

OANDA 取引ツールでは成行(market)、指値(limit) での注文ができますが、REST APIではこれらに加えて 逆指値(stop)、marketIfTouchedでの注文が可能です。

  • 成行/指値/逆指値は、他の証券会社でも提供している一般的な注文方法なので、説明はこのあたりを参照 していただければわかるかと。
  • marketIfTouchedは、「価格が指定価格になったら成行きで発注する」注文方法です。

    • 現在価格が 122.8 で、123 を指定してmarketIfTouchedで注文した場合、価格が123まで上昇した時点で成行きで発注が行われます。
    • 指値/逆指値と違って、現在価格が発注条件となる価格を満たしていても、即時約定はしません。
      • 例えば、現在価格 122.8 で、123 でを指定してmarketIfTouchedで買注文を行っても即時には約定しません。 (limitだと、123は現在価格より既に高い価格なので、発注と同時に約定してしまいます。)
  • トレールストップなど、建玉の決済条件は注文方法とは別に指定できるようになっています。

    • 指定できる条件は以下の3つです。
      • 利益確定価格(takeProfit)
      • 損切価格(stopLoss)、
      • トレールストップのpip数(trailingStop))
    • これらの決済条件は、注文方法(成行、指値..etc..)を問わず指定可能です。
  • なお、OCOのような両建ての注文方法はありません。

注文のレスポンスに含まれる、tradeOpened、tradesClosed、tradeReduced, orderOpened の違い

注文(order)の結果に応じて、これらのレスポンスの内容が変わります。

  • 注文が約定しなかった場合、新規注文が作成され、 orderOpened で注文の情報が返されます。
    • 成行き以外の指値や逆指値注文では、一般に即時には約定しないので、orderOpenedが返されます。
    • 成行注文の場合、即時約定するのでorderOpenedが返されることはありません (ちなみに、週末など市場が閉じている場合は、403 応答になります。)
  • 注文が約定し、新しい建玉ができた場合、 tradeOpened で作成された建玉の情報が返されます。
  • 注文が約定し、既存の建玉が決済された場合、 全決済された建玉が tradesClosed、部分決済された建玉が tradeReduced で返されます。
    • 例: EURJPY/3/売 の建玉を3つ所有した状態で、成行でEURJPY/7/買の注文を行うと、EURJPY/3/売の2つが全決済され、残りの1つが部分決済されます。
    • 複数まとめて決済される場合があるので、tradesClosedは配列になっています。ご注意。

f:id:unageanu:20150629170743p:plain

tradeOpened、tradesClosed、tradeReduced のidは建玉(trade)のID

細かい話ですが、注文のレスポンス tradeOpened、tradesClosed、tradeReduced のidは建玉(trade)のIDになっています。 ちょっと紛らわしいので、念のため。

追記(2015-12-01)

OANDA FX trade APIを利用した、無料のFXシステムトレードフレームワーク「Jiji」をリリースしました!

jiji2.unageanu.net

使ってみて、ご意見など頂けるとうれしいです。

Webアプリ向け高機能コードエディタ「Ace」をReactに組み込んで使ってみる

Ace とは?

Ace は、Webアプリケーション向けのリッチなコードエディタです。 シンタックスハイライトや正規表現検索に対応した高機能なコードエディタを、Webアプリにさくっと組み込めます。

機能一覧: (Aceのサイトより)

  • Java,Ruby等を含む、110言語のシンタックスハイライトに対応
  • 自動インデント
  • マルチカーソル
  • 正規表現での検索/置換
  • 関数やクラスの折り畳み機能(Code folding)
  • 「Ctrl + /」での一括コメント
  • ..etc..

おお、なんかすごい高機能だ。

Reactに組み込んで使う

React-Ace を使います。

$ npm install react-ace

jsのソースは以下。

import React     from 'react'
import AceEditor from 'react-ace'

// webpackで統合するので、必要なテーマや拡張モジュールは
// 明示的にimportしておく必要があります。
import 'brace/mode/ruby'
import 'brace/theme/github'
import 'brace/ext/searchbox'

class RubyEditor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  render() {
    return (
      <AceEditor
        mode="ruby"
        theme="github"
        value={this.props.source}
        onChange={this.onChange.bind(this)}
        name="editor"
      />
    );
  }

  onChange(newValue) {
    console.log(newValue); // とりあえず
  }
}
RubyEditor.propTypes =  {
  source: React.PropTypes.string.isRequired
};
RubyEditor.defaultProps =  {
  source: "# test. \nclass Foo\nend"
};

React.render(
  <RubyEditor />,
  document.body );

unageanu/sandbox/ace - GitHub にPushしているので、package.jsonなど他のソースはこちらを参照ください。

動いているところはこちら。