現在、私はシステムトレードではRestAPIやブローカー提供のJava用APIで発注をしています。しかし、とある事情で、この発注をFIX APIで行う必要が出てきました。
なので、現在、FIXクライアントの構築に四苦八苦しております。
FIX APIの開発、大変です。日本語はおろか、英語ですらほとんど資料がありません。「QuickFix使って、OANDAやC-NEXに接続したよー」という記事は、いくつか見つかったのですが、実装方法やサンプルコードはありません。
そんな中、唯一、実装とコードの記載をしてくれている先駆者様がいました。
こちらは、QuickFixを用いてCentOSとC++で構築されています。
私は、C++は触ったことがないので、この方法はやや敷居が高そうです。なので、ここを参考にしつつ、QuickFixのJava対応版であるQuickFix/Jを使って構築を試みたいと思います。あとサーバーは、Ubuntu 16 LTS を使っています。
まだ始めたばかりで完成できるかどうかもわからないのですが、備忘録も兼ねて、わかってきたことをまとめていきたいと思います。
目次
開発の前に。FIXとは。
ブローカーにFIX APIを申し込むと、HostのIPとポート、SenderCompID、TargetCompID、ログインIDとパスワードがただ遅れられてくるだけです。後は、メッセージの仕様書を添えられて、
ブローカー 「テスト接続よろしくな!」
的なことを言われるだけです。
「は?」
私のような素人からしたら、最初はこの言葉しか出てきませんでした。
FIX APIでブローカーに接続するには、ブローカーではない第三者が提供するFIXエンジンというライブラリを使う必要があります。このFIXエンジンですが、情報がほとんどありません。
FIXエンジンの有料製品は未知の世界
FIXエンジンで調べると、企業の有料製品ページばかりヒットします。しかも価格はわからず、評価版を使いたいならまずは問い合わせて来い的なものばかりです。
弱小個人はお呼びでない感が半端ありません。
FIXエンジン、Python対応のフリーを試したけど全滅
ですが、フリーのFIXエンジンも少ないながらも、いくつかヒットします。
できればPythonでやりたかったので、下記のPython対応らしきFixエンジンを試してみました。しかし、どれも私には使いこなせることができませんでした。
- PyFix
https://github.com/wannabegeek/PyFIX
GitHubを見る限り、一番まともそうなFixエンジンでした。
しかし開発は3年前に止まっています。
試してみたのですが、ログインIDとパスワードの埋め込み方がよくわかりません。同じ内容のIssueが上がっているのですが、放置されています。結局、使うとなるとライブラリそのものをゴリゴリ手を入れていく必要がありそうで、敷居が高いので断念しました。 -
simplefix
https://github.com/da4089/simplefix
これも使ってみたのですが、できるのはFIXのメッセージの作成とエンコードだけです。READMEにも書かれていますが、Socketの通信やセッション管理はできないので、これだけでFIXの発注クライアントを作る事はできません。
QuickFixと連携して使うのでしょうか? サンプルコードらしきものはあるのですが、見てもよくわかりませんでした。 -
QuickFix
http://www.quickfixengine.org/
C++用のFIXエンジンですが、Pythonにも対応しているみたいなことが書かれているので、ダメ元でTOPページにあるインストールコマンドを実行してみました。
が、エラーになりインストールできませんでした。
インストールできたところで、公式のサンプルすらなく実装方法がわかりそうにないので、エラーは追っていません。
FIXエンジン、フリーで使えるのはQuickFixとQuickFix/Jのみ
結局、フリーで実用に耐えうるのはQuickFixとQuickFix/Jしかないという結論にたどり着きました。
QuickFixが対応している言語は、C++です。PythonやRubyのAPIを含まれている的な記載はありますが、使い方は不明です。海外のサイトでも、「C++以外で使っている人は見たことがない」みたいなことが書かれていました。QuickFix/Jは、QuickFixをJavaにしたものですが、やはり分家的な扱いで、ドキュメントはほとんどありません。
私はC++は触ったことがなく、Javaの方がまだわかるので、QuickFix/Jを使ってみることにします。
開発の流れ
まずは、QuickFix/Jで接続するだけのサンプルアプリを作ります。
開発の流れは下記になります。
- ローカルにQuickFixのサーバを構築
- QuickFixのクライアントのサンプルを起動して接続を確認
- QuickFix/Jでサンプルアプリを作成して、ローカルのQuickFixサーバに接続
QuickFixを実際に触ってみるまでわからなかったのですが、QuickFixにはローカルで起動するサーバもあります。QuickFixでは、このサーバのことをExecutorと呼んでいるようです。
なので、いきなりブローカーのデモ環境に接続してトライ&エラーする前に、このローカルに建てたサーバでメッセージのやりとりができることを確認します。
環境構築
まずは、ローカルにQuickFixのサーバを構築します。
基本は、冒頭に紹介した先駆者様と同じこと(http://n73.jugem.jp/?eid=67#sequel)をします。
違う点としては、とりあえずDBは使わないので、「コンパイル準備」は、
でなく
とします。
「DB準備」と「DB接続テスト準備」はとばします。
私は一度、「コンパイルチェック」でFAILとなりました。ログを確認すると、ruby not found だったのでインストールします。
https://qiita.com/idaidaidaida/items/66db61b78c42a8521c3b
その後、
すると無事終了しました。コンソールには、Success!!みたいな表示はないのですが、test/test-suite.log でログを確認するとちゃんと下記のように表示されていました
QuickFIX 1.14.3: test/test-suite.log
==========================================
# TOTAL: 1
# PASS: 1
# SKIP: 0
# XFAIL: 0
# FAIL: 0
# XPASS: 0
# ERROR: 0
後は、先駆者様のページと同様、インストールして、サンプルプログラムをコンパイルして実行、サーバとクライアントの動作を確認します。
プロジェクト作成
先駆者様はこの後は、QuickFixのサンプルプログラムをカスタマイズしていきますが、私はJavaを使いたいので、ここからQuickFix/Jで、サンプルプログラムを作っていきます。
今後の開発フローは、
- Intellijでプログラムを構築
- QuickFixのサーバを起動
- Intellijでプログラムを実行
- IntellijとQuickFixサーバのコンソールで動作を確認
になります。
まずはプロジェクトを作成します。
公式に、Mavenで作ればいいよ的なことが書いてあるので、IntellijとMavenでプロジェクトを作成します。
IntellijとMavenのプロジェクト作成については、下記のページが丁寧でわかりやすいです。
https://qiita.com/Esfahan/items/5dfd3a07cd30093092c5
プロジェクトを作成したら、上記の公式に記載されているdependenciesをpom.xmlに追記します。
loggerの設定として、src/main/resouces配下に log4j.properties を作成しておきます。
1 2 3 4 5 6 7 |
# logger log4j.rootLogger=INFO, console # console appender log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d [%-5p-%c] %m%n |
これでプロジェクトの作成は完了です。
ログオンするだけのサンプルアプリを構築
プロジェクトができたので、いよいよ実装開始です。ここから頼りになるのは、公式マニュアルやサンプルアプリ Banzai だけです。
- 公式マニュアル
https://www.quickfixj.org/usermanual/2.1.0/usage/application.html - サンプルアプリ Banzai
https://github.com/quickfix-j/quickfixj/tree/master/quickfixj-examples/banzai
Applicationクラス
まずは Applicationインターフェースを継承したApplicationクラスを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import quickfix.Application; import quickfix.SessionID; import quickfix.DoNotSend; import quickfix.FieldNotFound; import quickfix.IncorrectDataFormat; import quickfix.IncorrectTagValue; import quickfix.UnsupportedMessageType; import quickfix.RejectLogon; public class MltApplication implements Application { public MltApplication() { } public void onCreate(SessionID sessionID) { } public void onLogon(SessionID sessionID) { System.out.println("Logon - " + sessionID); } public void onLogout(SessionID sessionID) { System.out.println("Logout - " + sessionID); } public void toAdmin(quickfix.Message message, SessionID sessionID) { } public void toApp(quickfix.Message message, SessionID sessionID) throws DoNotSend { } public void fromAdmin(quickfix.Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon { } public void fromApp(quickfix.Message message, SessionID sessionID) throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType { } } |
まだ何も作り込んでいないので、インタフェースのメソッドを載せただけです。
クラス名は各自で好きなものを設定します。公式では FooApplication や BanzaiApplication となっています。私はシステムトレードを作るとき、良く Mlt というワードを使うので、 MltApplication としました。ちなみに、Mltは、 Machine Learning Trading の略です(照)
Mainクラス
Applicationクラスができたので、これを使用するメインを作成します。オブジェクト指向のかけらもない雑なコードですが、とりあえずログオンだけするサンプルを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import java.io.IOException; import java.io.FileInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quickfixj.jmx.JmxExporter; import quickfix.*; import javax.management.JMException; import java.util.concurrent.CountDownLatch; public class Main { private static final CountDownLatch shutdownLatch = new CountDownLatch(1); private static final Logger log = LoggerFactory.getLogger(Main.class); private static Initiator initiator = null; private static boolean initiatorStarted = false; public static void main(String[] args) throws IOException, ConfigError, JMException, InterruptedException { Application application = new MltApplication(); SessionSettings settings = new SessionSettings(new FileInputStream("test.cfg")); LogFactory logFactory = new FileLogFactory(settings); MessageFactory messageFactory = new DefaultMessageFactory(); MessageStoreFactory messageStoreFactory = new FileStoreFactory(settings); initiator = new SocketInitiator(application, messageStoreFactory, settings, logFactory, messageFactory); JmxExporter exporter = new JmxExporter(); exporter.register(initiator); // logon if (!initiatorStarted) { try { initiator.start(); initiatorStarted = true; } catch (Exception e) { log.error("Logon failed", e); } } else { for (SessionID sessionId : initiator.getSessions()) { Session.lookupSession(sessionId).logon(); } } shutdownLatch.await(); } } |
これは、公式マニュアルとBanzaiを参照して作りました。最初は、公式マニュアルだけ見て作っていたら、aceptor.start(); の箇所で
「Configエラー、aceptorなんてねぇよ!」というエラーが出て何でーとなっていたのですが、Banzaiのコードを見て解決しました。
公式マニュアルのサンプルコードは、クライアント側でなくサーバ側のコードになっているんですね・・・
QuickFixのconfigでは、サーバをaceptor、クライアントをinitiatorと定義するようです。aceptor.start() を initiator.start() に変更したら解決しました。
Configuration
最後に、SessionSettingで読み込んでいる、test.cfgを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[DEFAULT] ConnectionType=initiator ReconnectInterval=60 FileStorePath=store FileLogPath=log StartTime=00:00:00 EndTime=00:00:00 UseDataDictionary=Y DataDictionary=FIX44.xml HttpAcceptPort=9911 ValidateUserDefinedFields=N ResetOnLogout=Y ResetOnLogon=Y [SESSION] BeginString=FIX.4.4 SenderCompID=CLIENT1 TargetCompID=EXECUTOR SocketConnectHost=192.168.100.99 SocketConnectPort=5001 HeartBtInt=30 |
解説
- ConnectionTypeは、initiatorです。サーバサイドの場合は、aceptorになります。
- SenderCompIDとTargetCompIDは、QuickFixサーバである、Exectuor.cfgと合わせる必要があります。SenderCompIDはExecutorのTargetCompID、TargetCompIDはExecutorのSenderCompIDになります。
- SocketConnectHostは、QuickFixサーバが稼働しているホストIPです。私はメインPC(Windows10)とは別に、Ubuntuを動かしていてそこでQuickFixのサーバを稼働しています。そのubuntuのIPが192.168.100.99なのでこのように記載しています。ローカルでQuickFixサーバを稼働している場合は、127.0.0.1とします。
test.cfgですが、Bazaiと同じように、resourcesフォルダに入れると思ったのですが、実行したらFileNotFoundのエラーになりました。プロジェクトのルート直下に置いたら動きました。
動作確認
以上、3ファイル作成したら動作確認します。IntellijでMainクラスを実行します。
実行後、サーバのコンソールの出力は下記のようになりました。
(Accepted connection from 192.168.100.50 on port 5001)
<20181215-17:29:34.391341000, FIX.4.4:EXECUTOR->CLIENT1, incoming>
(8=FIX.4.49=7635=A34=149=CLIENT152=20181215-17:29:34.34556=EXECUTOR98=0108=30141=Y10=153)
<20181215-17:29:34.391388000, FIX.4.4:EXECUTOR->CLIENT1, event>
(Logon contains ResetSeqNumFlag=Y, reseting sequence numbers to 1)
<20181215-17:29:34.391575000, FIX.4.4:EXECUTOR->CLIENT1, event>
(Received logon request)
<20181215-17:29:34.391613000, FIX.4.4:EXECUTOR->CLIENT1, outgoing>
(8=FIX.4.49=7935=A34=149=EXECUTOR52=20181215-17:29:34.39159056=CLIENT198=0108=30141=Y10=059)
<20181215-17:29:34.391637000, FIX.4.4:EXECUTOR->CLIENT1, event>
(Responding to logon request)
192.168.100.50は、Intellijを実行しているPCのIPアドレスです。
ちなみに、Ubuntu上のQuickFixのサンプルクライアントを起動したときのログは下記になります。
(Accepted connection from 127.0.0.1 on port 5001)
<20181215-17:10:22.209545000, FIX.4.4:EXECUTOR->CLIENT1, incoming>
(8=FIX.4.49=7635=A34=149=CLIENT152=20181215-17:10:22.20956=EXECUTOR98=0108=30141=Y10=139)
<20181215-17:10:22.209605000, FIX.4.4:EXECUTOR->CLIENT1, event>
(Logon contains ResetSeqNumFlag=Y, reseting sequence numbers to 1)
<20181215-17:10:22.209805000, FIX.4.4:EXECUTOR->CLIENT1, event>
(Received logon request)
<20181215-17:10:22.209882000, FIX.4.4:EXECUTOR->CLIENT1, outgoing>
(8=FIX.4.49=7935=A34=149=EXECUTOR52=20181215-17:10:22.20984156=CLIENT198=0108=30141=Y10=043)
<20181215-17:10:22.209918000, FIX.4.4:EXECUTOR->CLIENT1, event>
(Responding to logon request)
ログはほぼ同じであることが確認できます。
QuickFix/Jで、QuickFixのサンプルアプリのログオン部分が実装できました。
今回はここまでになります。
以上、QuickFix/Jでローカルサーバにログオンしてみる所までやってみました。今後は、ログインIDとパスワードの送信、レート取得、発注を試みるつもりです。進捗があり次第、また記事にしたいと思います。
FIX API はとにかく、情報が少ないです。ここ違う、こうした方がいいなどあれば気軽にコメントいただければと思います。よろしくお願いします。