Java RTS金融アプリケーションのデモ
指値/逆指値注文システムは、以下の3つのコンポーネントで構成されます(図1を参照)。
- データフィードアプリケーション
- 取引エンジンアプリケーション
- チャート作成アプリケーション
取引エンジン(この記事の重点項目)では、以下の2つのメインスレッドが動作します。
- データフィードをリッスンし、監視対象の株の現在価格を更新するスレッド(MarketManager)。
- 既存の指値/逆指値を現在の株価と比較するタイムクリティカルなループを実行するスレッド(OrderManager)(図2を参照)。株価が指値/逆指値の水準まで動いた場合、取引エンジンが取引を執行します。
リアルタイムではない取引エンジン実装では、外部イベント(ガベージコレクションなど)がタイムクリティカルなループに割り込んできて、余分なレイテンシを発生させる可能性があります。その遅延の間に市場の値動きがあって、システムで1つ以上の無条件注文(オープンオーダー)の範囲内にいったん株価が入ってから範囲外にまた戻すことが起こりえます。事実、この現象は本稿のデモで発生します。
このシナリオでは、注文の機会を失ったことは運用機関の金銭的損失を意味します。理想的な取引のタイミングを逃したのに、顧客の注文を引き受けなければならないからです(図3を参照)。ガベージコレクションの割り込みが取引に影響を与える可能性は微々たるものですが、リスクは非常に大きなものです。次のデモでは、リスクが現実に起こりうることを示します。
しかし、同じ取引エンジンを(スレッドモデルにちょっとした変更を加えてから)Java RTSで実行すると、JVMはタイムクリティカルなコード内でデッドラインに対応することができ、取引の機会は失われません。エンジンは、市場のすべてのティックを取得し、すべての取引機会に応じます。その結果、市場の動きに確実に反応し、条件が満たされたときに顧客の注文を正確に取引できるシステムが手に入ります。
取引エンジンのタイムクリティカルな部分
取引エンジンのオーダーブックは、HashMapで構成されます。HashMapはSubBookクラスのオブジェクトで構成され、このオブジェクトは株価を示す2つのLinkedListで構成されます。1つのLinkedListは買い注文用(上昇時)で、もう1つのLinkedListは売り注文用(下落時)です(図4を参照)。
このデータ構造のコードを以下のリストに示します。
class OrderEntry { private boolean active; private double price; private long quantity; private StringBuffer symbol; private int type; } class SubBook { LinkedList<OrderEntry> buy; LinkedList<OrderEntry> sell; } HashMap<StringBuffer,SubBook> orderBook = new HashMap<StringBuffer,SubBook>(INITIAL_CAPACITY);
以下のリストは、OrderManagerのタイムクリティカルなループを標準Javaで作成したものです。ここでは、オーダーブックを最新の株価と比較します。
public class OrderManager implements Runnable { ... public void run() { // 株価を注文価格と比較するループ while ( true ) { for ( int i = 0; i < orderBookKeys.length; i++ ) { StringBuffer symbol = orderBookKeys[i]; StringBuffer sPrice = marketMgr.marketBook.get(symbol); if ( sPrice == null ) continue; double marketPrice = Double.parseDouble( sPrice.toString() ); // この銘柄コードのサブブックを取得する SubBook sb = orderBook.get(symbol); // 売り注文リストを検索する for ( int x = 0; x < sb.sell.size(); x++ ) { OrderEntry entry = sb.sell.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } // 買い注文リストを検索する for ( int x = 0; x < sb.buy.size(); x++ ) { OrderEntry entry = sb.buy.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } } Thread.sleep(1); // 1ミリ秒スリープする } } ... }
1ミリ秒ごとにそれぞれの株の売り注文と買い注文が、市場の株価と比較されます。条件を満たす場合は、取引が執行されます。このデモでは、取引に関するメッセージが送信され、指値と約定した価格の差がユーザーインターフェイスコンポーネントによって画面にチャートとして表示されます。ほとんどの場合、差はゼロですが、ガベージコレクションの実行による休止時間が差(損失)を生む可能性があります。
ループのリアルタイムバージョンは、上記のコードとほとんど同じです。ちょっとした変更として、Thread.sleep
の呼び出しをRealtimeThread.waitForNextPeriod
に置き換えます(以下のリストを参照)。この変更には、大きく2つのメリットがあります。
- RealtimeThreadの作成時に指定したとおりの時間でループが正確に実行されます。つまり、1ミリ秒の時間を指定し、処理に250マイクロ秒かかった場合は、
waitForNextPeriod
が正確に750マイクロ秒間ブロックします。これにより、コードは確実に1ミリ秒間隔で実行されます。標準Javaでは、この動作を実現できません。 waitForNextPeriod
の呼び出しにはJava RTSの高分解能タイマーを利用して、標準Javaに組み込まれているタイマーよりも高い精度で処理を行います。
public class OrderManager implements Runnable { ... public void run() { // 株価を注文価格と比較するループ while ( true ) { for ( int i = 0; i < orderBookKeys.length; i++ ) { StringBuffer symbol = orderBookKeys[i]; StringBuffer sPrice = marketMgr.marketBook.get(symbol); if ( sPrice == null ) continue; double marketPrice = Double.parseDouble( sPrice.toString() ); // この銘柄コードのサブブックを取得する SubBook sb = orderBook.get(symbol); // 売り注文リストを検索する for ( int x = 0; x < sb.sell.size(); x++ ) { OrderEntry entry = sb.sell.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } // 買い注文リストを検索する for ( int x = 0; x < sb.buy.size(); x++ ) { OrderEntry entry = sb.buy.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } } // 次の処理サイクルの開始まで待機する RealtimeThread.waitForNextPeriod(); } } ... }
どちらのバージョンのOrderManagerもRunnableインターフェイスを実装して、スレッドの作成を要求します。リアルタイムバージョンの取引エンジンではjavax.realtime.RealtimeThreadが作成されますが、標準Javaバージョンではjava.lang.Threadです(以下のリストを参照)。RealtimeThreadを作成するときに時間と優先順位を指定しますが、この優先順位はJava RTSに本当に実装されているものです。
// 実行可能コードを作成する OrderManager orderMgr = new OrderManager(); // スレッドの優先順位を決定する long maxPriority = PriorityScheduler.instance().getMaxPriority(); PriorityParameters sched = new PriorityParameters( maxPriority ); // スレッドの時間を決定する RelativeTime one_millisecond = new RelativeTime(1,0); PeriodicParameters period = new PeriodicParameters(one_millisecond); // リアルタイムスレッドを上記のパラメータで作成する RealtimeThread orderThread = new RealtimeThread(sched,period,null,null,null,orderMgr);