今回は、JForex Wikiで掲載されているサンプルプログラムの「Simple Strategy」を用いてソースコードを読み解いていきたいと思います。
Simple Strategyの概要
Simple Strategyがやってることは以下の通りです。
ストラテジー開始時に前日の日足を確認して、終値が始値より大きければロングポジション、始値以下ならショートポジションを持つ。
その際、ストップロスは10pips、テイクプロフィットは10pipsに設定。
テイクプロフィットに到達して決済となった場合は、同じ方向にストップロス10pips、テイクプロフィット5pipsに設定してポジションを持つ。
ストップロスに到達して決済となった場合は、方向を反転させ、ストップロス10pips、テイクプロフィット10pipsに設定してポジションを持つ。
後は決済される度に上記を判定してオーダーを繰り返す。
マニュアルで決済された時点でストラテジーは終了。
Simple Strategyのソースコード
Simple Strategyのソースコードは以下の通りです。
ソースファイル自体は、「Simple Strategy」ページの一番下の「Download 」の所にある「SimpleTpSlStrategy.java」というリンクからダウンロードできます。
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
package jforex; import com.dukascopy.api.*; import com.dukascopy.api.IEngine.OrderCommand; import com.dukascopy.api.IMessage.Type; /** * The strategy maintains one order with TP and SL. * As soon as the order gets closed either a new order gets created: * - on close by TP the same direction order gets opened * - on close by SL the opposite direction order gets opened and with a different TP distance * - on close from outside we stop the strategy * * On strategy stop the strategy closes its order */ public class SimpleTpSlStrategy implements IStrategy { // Configurable parameters @Configurable("Instrument") public Instrument instrument = Instrument.EURUSD; @Configurable("Amount") public double amount = 0.001; @Configurable("Stop loss") public int slPips = 10; @Configurable("Take profit on loss") public int tpPipsOnLoss = 10; @Configurable("Take profit on profit") public int tpPipsOnProfit = 5; private IEngine engine; private IHistory history; private IConsole console; private IContext context; private IOrder order; public void onStart(IContext context) throws JFException { this.engine = context.getEngine(); this.history = context.getHistory(); this.console = context.getConsole(); this.context = context; // subscribe the instrument that we are going to work with context.setSubscribedInstruments(java.util.Collections.singleton(instrument)); // Fetching previous daily bar from history IBar prevDailyBar = history.getBar(instrument, Period.DAILY, OfferSide.ASK, 1); // Identifying the side of the initial order OrderCommand orderCmd = prevDailyBar.getClose() > prevDailyBar.getOpen() ? OrderCommand.BUY : OrderCommand.SELL; // submitting the order with the specified amount, command and take profit submitOrder(amount, orderCmd, tpPipsOnLoss); } public void onAccount(IAccount account) throws JFException { } public void onMessage(IMessage message) throws JFException { if (message.getType() != Type.ORDER_CLOSE_OK || !message.getOrder().equals(order) //only respond to our own order close ) { return; } console.getInfo().format("%s closed with P/L %.1f pips", order.getLabel(), order.getProfitLossInPips()).println(); if (message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_TP)) { // on close by TP we keep the order direction submitOrder(amount, order.getOrderCommand(), tpPipsOnProfit); } else if (message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_SL)) { // on close by SL we change the order direction and use other TP distance OrderCommand orderCmd = order.isLong() ? OrderCommand.SELL : OrderCommand.BUY; submitOrder(amount, orderCmd, tpPipsOnLoss); } else { //on manual close or close by another strategy we stop our strategy console.getOut().println("Order closed either from outside the strategy. Stopping the strategy."); context.stop(); } } private void submitOrder(double amount, OrderCommand orderCmd, double tpPips) throws JFException { double slPrice, tpPrice; ITick lastTick = history.getLastTick(instrument); // Calculating stop loss and take profit prices if (orderCmd == OrderCommand.BUY) { slPrice = lastTick.getAsk() - slPips * instrument.getPipValue(); tpPrice = lastTick.getAsk() + tpPips * instrument.getPipValue(); } else { slPrice = lastTick.getBid() + slPips * instrument.getPipValue(); tpPrice = lastTick.getBid() - tpPips * instrument.getPipValue(); } // Submitting the order for the specified instrument at the current market price order = engine.submitOrder(orderCmd.toString() + System.currentTimeMillis(), instrument, orderCmd, amount, 0, 20, slPrice, tpPrice); } public void onStop() throws JFException { if(order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.OPENED){ order.close(); } } public void onTick(Instrument instrument, ITick tick) throws JFException {} public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {} } |
ソースコード解説
ソースコードに何が記述されているのかを具体的に解説していきます。
使用するクラスのインポート
3から5行目で使用するクラスをインポートしています。
1 2 3 |
import com.dukascopy.api.*; import com.dukascopy.api.IEngine.OrderCommand; import com.dukascopy.api.IMessage.Type; |
メンバ変数の定義
17から33行目でクラスのメンバ変数を定義しています。
27行目までは外部パラメータの宣言となり、@cofigurableをつけてメンバ変数を外部パラメータに指定しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Configurable parameters @Configurable("Instrument") public Instrument instrument = Instrument.EURUSD; @Configurable("Amount") public double amount = 0.001; @Configurable("Stop loss") public int slPips = 10; @Configurable("Take profit on loss") public int tpPipsOnLoss = 10; @Configurable("Take profit on profit") public int tpPipsOnProfit = 5; private IEngine engine; private IHistory history; private IConsole console; private IContext context; private IOrder order; |
「instrument」は取引する通貨ペア名、「amount」は取引数量、「slPips」は損失確定pips数、「tpPipsOnLoss」は前回のトレードが負けトレードだった時に次のトレードで使用する利益確定pips数、「tpPipsOnProfit」は、前回のトレードが勝ちトレードだった時に次のトレードで使用する利益確定pips数となり、ユーザーはこれらの値をプログラム開始時に表示されるパラメーター定義ダイアログ上で変更することができます。
29行目からは通常のメンバ変数となり、プログラム中で使用する各オブジェクトへの参照を格納する変数が定義されています。
onStart関数
onStart関数では、各メンバ変数の初期化及び、最初のトレードの方向を決定するための処理、及び最初の発注処理を行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void onStart(IContext context) throws JFException { this.engine = context.getEngine(); this.history = context.getHistory(); this.console = context.getConsole(); this.context = context; // subscribe the instrument that we are going to work with context.setSubscribedInstruments(java.util.Collections.singleton(instrument)); // Fetching previous daily bar from history IBar prevDailyBar = history.getBar(instrument, Period.DAILY, OfferSide.ASK, 1); // Identifying the side of the initial order OrderCommand orderCmd = prevDailyBar.getClose() > prevDailyBar.getOpen() ? OrderCommand.BUY : OrderCommand.SELL; // submitting the order with the specified amount, command and take profit submitOrder(amount, orderCmd, tpPipsOnLoss); } |
41行目は、JForexの銘柄リストに対象の通貨ペアが表示されているかチェックし、表示されていない場合は銘柄リストに追加するための処理です。
1 |
context.setSubscribedInstruments(java.util.Collections.singleton(instrument)); |
contextオブジェクトの「setSubscribedInstruments」関数に「Instrumentのコレクション」を渡すことで指定の通貨ペアを取引できる状態にしてくれます。
今回は単一の通貨ペアのみを指定するため、「java.util.Collections.singleton」を用いて、instrumentを要素が一つだけのコレクションに変換し引数に設定しています。
43行目は、前日の日足情報を取得するコードです。
historyオブジェクトの「getBar」関数を使用して、前日の日足情報をIBarオブジェクトで取得します。
1 |
IBar prevDailyBar = history.getBar(instrument, Period.DAILY, OfferSide.ASK, 1); |
getBarの仕様は以下の通り。
IBar getBar(Instrument instrument,Period period,OfferSide side,int shift) throws JFException
引数:
instrument:どの通貨ペアの足を取得するのかを指定
period :足のタイムフレーム
side – BidとAskのどちらかを指定
shift – 現在の足から何本前の情報を取得するかを指定、1なら1つ前の足、2なら2つ前の足となります戻り値:IBarオブジェクト、取得できなかった場合はNULL
ここでは、1つ前の日足の情報を取得するために、「period」にPeriod.DAILY、「shift」に1を指定、「side」にはOfferSide.ASKを指定しAskの情報を取得しています。
getBarで取得したIBarオブジェクトの参照はprevDailyBarに格納しておきます。
45~47行目で最初のオーダーを買い注文をうのか、売り注文を行うのかを判定しています。
1 2 3 4 |
// Identifying the side of the initial order OrderCommand orderCmd = prevDailyBar.getClose() > prevDailyBar.getOpen() ? OrderCommand.BUY : OrderCommand.SELL; |
条件判定には三項演算子が使われており、先ほど取得した前日の足情報が格納されたprevDailyBarを使用してgetCloseで前日の終値を、getOpenで前日の始値を取得。終値の方が始値より大きければ「OrderCommand.BUY」を、終値が始値以下なら「OrderCommand.SELL」をorderCmdに設定しています。
49行目は、79行目から89行目で定義されているユーザー定義の「submitOrder」関数を呼び出して発注処理を行います。
ユーザー定義のsubmitOrderの引数は、「amount(取引数量)」,「orderCmd(取引種別)」,「tpPips(テイクプロフィットpips数)」の3つがあり、「amount」には外部パラメーター化したメンバ変数amountを、「orderCmd」は先ほど作成したorderCmdを、「tpPips」には1番最初のトレードなので、前回のトレードが負けトレードだった時に使用する外部パラメーター化したメンバ変数「tpPipsOnLoss」を指定しています。
この処理が終わると、一番最初のポジションを持つことになります。
ユーザー定義のsubmitOrder関数
79行目から89行目で定義した「submitOrder」関数は、ストップロスの価格とテイクプロフィットの価格を現在のレートから計算し発注処理を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private void submitOrder(double amount, OrderCommand orderCmd, double tpPips) throws JFException { double slPrice, tpPrice; ITick lastTick = history.getLastTick(instrument); // Calculating stop loss and take profit prices if (orderCmd == OrderCommand.BUY) { slPrice = lastTick.getAsk() - slPips * instrument.getPipValue(); tpPrice = lastTick.getAsk() + tpPips * instrument.getPipValue(); } else { slPrice = lastTick.getBid() + slPips * instrument.getPipValue(); tpPrice = lastTick.getBid() - tpPips * instrument.getPipValue(); } // Submitting the order for the specified instrument at the current market price order = engine.submitOrder(orderCmd.toString() + System.currentTimeMillis(), instrument, orderCmd, amount, 0, 20, slPrice, tpPrice); } |
historyオブジェクトのgetLastTickを利用して最新のティック情報をITickオブジェクトとして取得。
1 |
ITick lastTick = history.getLastTick(instrument); |
submitOrder関数の引数で渡された「orderCmd」で買いか売りかを判断し、実際のストップロス価格とテイクプロフィット価格を計算し「slPrice」「tpPrice」にそれぞれ格納します。
1 2 3 4 5 6 7 8 |
// Calculating stop loss and take profit prices if (orderCmd == OrderCommand.BUY) { slPrice = lastTick.getAsk() - slPips * instrument.getPipValue(); tpPrice = lastTick.getAsk() + tpPips * instrument.getPipValue(); } else { slPrice = lastTick.getBid() + slPips * instrument.getPipValue(); tpPrice = lastTick.getBid() - tpPips * instrument.getPipValue(); } |
ストップロス価格とテイクプロフィット価格の計算ですが、まず、instrument.getPipValue()を使って、指定の通貨ペアの1pipsに相当するレートを取得しそれを指定のpipsを乗算することで指定pipsあたりの変動価格を計算。
買い注文の場合は、lastTickのgetAsk関数で現在レートのASK値を、売り注文の場合は、lastTickのgetBid関数でBid値を取得し、そのレートに対して変動価格を増減することで実際のストップロスとテイクプロフィットの価格を割り出しています。
88行目はIEngineオブジェクト「engine」のsubmitOrder関数を使用して、実際に発注を行っています。
1 2 |
// Submitting the order for the specified instrument at the current market price order = engine.submitOrder(orderCmd.toString() + System.currentTimeMillis(), instrument, orderCmd, amount, 0, 20, slPrice, tpPrice); |
IEngineオブジェクトのsubmitOrder関数の仕様は以下の通りです。
IOrder submitOrder(String label,
Instrument instrument,
IEngine.OrderCommand orderCommand,
double amount,
double price,
double slippage,
double stopLossPrice,
double takeProfitPrice)
throws JFException引数:
label:ユーザー定義の注文識別子(最大256文字からなるユニークな文字列)
instrument:通貨ペアを指定
orderCommand:オーダーの種類を指定
smount:発注数量(単位は100万通貨、1000通貨を指定する場合0.001となる)
price:発注価格(0指定は最新のプライス)
slippage:スリッページ
stopLossPrice:ストップロス価格
takeProfitPrice:テイクプロフィット価格戻り値:IOrder.State.CREATEDで作成されたIOrderオブジェクト
IEngineオブジェクトのsubmitOrderの引数「label」には「orderCmd.toString() + System.currentTimeMillis()」でユニーク識別子を作成して設定、「instrument」にはクラスのメンバ変数のinstrument、「orderCommand」と「amount」は渡されてきた引数をそのまま指定、「price」には最新価格で発注するので0を、「slippage」は20、「stopLossPrice」と「takeProfitPrice」には直前で作成したslPriceとtpPriceを指定しています。
決済した時にオーダーに関する情報を取得できるようにするため、submitOrderの戻り値をメンバ変数のorderに格納しておきます。
ユーザー定義のsubmitOrderは戻り値がvoidなのでそのまま処理を終了して呼び出し元に戻ります。
onMessage関数
onMessage関数では直前のトレードの勝ち負けを読み取り、次のオーダーを発注します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public void onMessage(IMessage message) throws JFException { if (message.getType() != Type.ORDER_CLOSE_OK || !message.getOrder().equals(order) //only respond to our own order close ) { return; } console.getInfo().format("%s closed with P/L %.1f pips", order.getLabel(), order.getProfitLossInPips()).println(); if (message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_TP)) { // on close by TP we keep the order direction submitOrder(amount, order.getOrderCommand(), tpPipsOnProfit); } else if (message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_SL)) { // on close by SL we change the order direction and use other TP distance OrderCommand orderCmd = order.isLong() ? OrderCommand.SELL : OrderCommand.BUY; submitOrder(amount, orderCmd, tpPipsOnLoss); } else { //on manual close or close by another strategy we stop our strategy console.getOut().println("Order closed either from outside the strategy. Stopping the strategy."); context.stop(); } } |
56~60行目でメッセージのタイプを判定し、自身が発注したオーダーと関係がないメッセージおよび、オーダーのクローズが成功したというメッセージ以外をフィルタリングしています。
1 2 3 4 5 |
if (message.getType() != Type.ORDER_CLOSE_OK || !message.getOrder().equals(order) //only respond to our own order close ) { return; } |
このストラテジーで発注したポジションのクローズが成功したというメッセージを受け取った場合のみ次の処理に進みます。
61行目はコンソールにメッセージを表示しています。
1 |
console.getInfo().format("%s closed with P/L %.1f pips", order.getLabel(), order.getProfitLossInPips()).println(); |
新規注文の際に取得したIOrderオブジェクト「order」を使用して、getLabel関数で発注の際に設定したユニークなラベルを、getProfitLossInPips関数で利益もしくは損失になったpips数を表示しています。
consoleのgetInfo関数を使用してメッセージを出力すると、コンソール上のメッセージの背景を緑色にすることができます。
62~73行目は、クローズした理由によって処理を分岐させています。
1 2 3 4 5 6 7 8 9 10 11 12 |
if (message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_TP)) { // on close by TP we keep the order direction submitOrder(amount, order.getOrderCommand(), tpPipsOnProfit); } else if (message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_SL)) { // on close by SL we change the order direction and use other TP distance OrderCommand orderCmd = order.isLong() ? OrderCommand.SELL : OrderCommand.BUY; submitOrder(amount, orderCmd, tpPipsOnLoss); } else { //on manual close or close by another strategy we stop our strategy console.getOut().println("Order closed either from outside the strategy. Stopping the strategy."); context.stop(); } |
messageの「getReasons」関数で返されるコレクションに対して「contains」関数を用い、クローズ理由が「IMessage.Reason.ORDER_CLOSED_BY_TP」なのか「IMessage.Reason.ORDER_CLOSED_BY_SL」なのかを判定します。
「IMessage.Reason.ORDER_CLOSED_BY_TP」は、テイクプロフィット価格に到達してポジションをクローズ、「IMessage.Reason.ORDER_CLOSED_BY_SL」はストップロス価格に到達してポジションをクローズの意味となります。
テイクプロフィット価格でのクローズの場合は、ユーザー定義の「submitOrder」関数の引数「orderCmd」には前回と同じ方向で発注するためorder.getOrderCommand()を指定、「tpPips」には勝ちトレードだった時に使用する外部パラメーター化したメンバ変数「tpPipsOnProfit」を指定しています。
1 |
submitOrder(amount, order.getOrderCommand(), tpPipsOnProfit); |
ストップロス価格でのクローズの場合は、注文方向を反転させないといけません。
そのため、orderのisLong()を使用して前回の注文の方向を確認し、ロングだった場合、orderCmdに「OrderCommand.SELL」を、ショートだった場合は「OrderCommand.BUY」をセットしてユーザー定義の「submitOrder」関数の引数「orderCmd」に渡します。
また、「tpPips」には負けトレードだった時に使用する外部パラメーター化したメンバ変数「tpPipsOnLoss」を指定しています。
1 2 |
OrderCommand orderCmd = order.isLong() ? OrderCommand.SELL : OrderCommand.BUY; submitOrder(amount, orderCmd, tpPipsOnLoss); |
マニュアル操作でポジションをクローズした場合等、テイクプロフィットとストップロス以外の理由でポジションクローズが起こった場合は、最後の条件分岐が実行されます。
1 2 3 4 5 |
} else { //on manual close or close by another strategy we stop our strategy console.getOut().println("Order closed either from outside the strategy. Stopping the strategy."); context.stop(); } |
この場合はコンソールにメッセージを表示。
そして、IContextオブジェクトのcontextに対してstop関数を呼び、ストラテジーを終了させます。
onStop関数
onStop関数では、ストラテジー終了時にポジションが残っていれば、ポジションをクローズするという処理が書かれています。
1 2 3 4 5 |
public void onStop() throws JFException { if(order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.OPENED){ order.close(); } } |
ポジションやオーダーが残っているかどうかをorderのgetState関数で判定し、状態がFILLD(ポジション保有中)、もしくはOPEND(注文中)ならば、orderのclose関数を読んでポジションをクローズ、もしくはオーダーを削除します。
まとめ
今回は、実際に売買を行うストラテジーのソースを元に、ソースコードにどういう処理が記述されているかを解説しました。
あまり深く解説できていないかもしれませんが、今回のソースを一通り理解できれば単純な売買プログラムは書けるようになると思います。
コメント