JForexで注文画面を自作する(成行注文と全決済)

FX

まずは成行注文と全決済が出来るようにしたいと思います。

GUIのパーツはEclipseとWindowBuilderでちゃちゃっと作ってこんな感じに。

スポンサーリンク

IStrategyのコールバックメソッドへの実装

onStartメソッド

public void onStart(IContext context) throws JFException {
    this.context = context;
    this.engine = context.getEngine();
    this.console = context.getConsole();

    EventQueue.invokeLater(new Runnable() {
    public void run() {
        try {
            setVisible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});
console.getOut().println("Strategy Started.");
}
  • 開始時に渡されるIContext型のcontextオブジェクトへの参照を保持します
  • 注文執行のために必要となるIEngine型のオブジェクトを生成します
  • コンソール出力に必要となるIConsole型のオブジェクトを生成します
  • 注文画面を別スレッドで開きます

onTickメソッド

public void onTick(Instrument instrument, final ITick tick) throws JFException {
    if (!cmbInstrument.getSelectedItem().equals(instrument)) return;

    final String scaleFormat = "%." + String.valueOf(instrument.getPipScale() + 1) + "f";

    SwingUtilities.invokeLater(new Runnable () {
        public void run() {
            btnBid.setText(String.format(scaleFormat, tick.getBid()));
            btnAsk.setText(String.format(scaleFormat, tick.getAsk()));
        }
    });
}

<

一般的なストラテジーではストラテジーの開始前にどの通貨ペアで動かすかを先に指定して走らせることになりますが、今回の注文画面では画面上で通貨ペアを選択したいので、まずは選択している通貨ペア以外のティックはスルーするようにしています。

ただ、さすがに全ての通貨ペアをトレードすることはないと思いますので、ある程度は絞っておいて、その中から選択するようにしています。

具体的には

private final Vector<Instrument> getInstruments() {
    Vector<Instrument> instruments = new Vector<Instrument>();
    instruments.add(Instrument.USDJPY);
    instruments.add(Instrument.EURUSD);
    instruments.add(Instrument.EURJPY);
    instruments.add(Instrument.GBPUSD);
    instruments.add(Instrument.GBPJPY);
    instruments.add(Instrument.AUDUSD);
    instruments.add(Instrument.AUDJPY);

    return instruments;
}

こんな感じで注文画面から選べる通貨ペアを絞りました。

で、その後売り注文ボタンと買い注文ボタンにレートを表示するようにしています。

onMessageメソッド

public void onMessage(IMessage message) throws JFException {
    if (message.getOrder() == null) return;
    if (!cmbInstrument.getSelectedItem().equals(message.getOrder().getInstrument())) return;

    console.getOut().println(message.toString());
}

トレードの執行状況を確認するため、選択した通貨ペアの注文に対するメッセージをコンソールに出力しています。


ココまでがIStrategyへの実装です。

次に、その他のコンポーネントについてちょっと注意するところをメモっておきます。

取引数量(Amount)について

この注文画面では1.0を10,000通貨として1,000通貨刻みでの取引数量にしていますが、JForexでは1.0は100万通貨となります。

なので、注文時は

double amount = (Double)spnAmount.getValue() * 0.01D;

としています。

新規注文について

注文のためのメソッドはIEngine.submitOrderです。

IOrder order = engine.submitOrder(label, instrument, orderCommand, amount, price);

このメソッドには引数の違うオーバーロードメソッドがいくつもありますが、今回は一番カンタンな成行注文で発注しています。

設定している引数は

  • label … 注文毎に設定する一意の文字列を設定する必要があります(256文字以内)
  • instrument … 通貨ペア
  • orderCommand … 注文内容。BUY(成行買い),SELL(成行売り),LIMITBUY(指値買い),LIMITSELL(指値売り)などがあります。
  • amount … 建玉数量。1.0が100万通貨です。
  • price … 指定レート。成行注文の場合ホントはコレも省略出来るんですが、後々のことを考えて指定しています。

このメソッドの注意点として、スリッページがデフォルトで5pipsに設定されています。

ですので、もしスリッページを任意に変更したい場合はスリッページも設定できるsubmitOrderメソッドで注文する必要があります。

決済注文について

決済注文のメソッドはIEngine.closeOrderになります。

今回は保有中の全てのポジションを全て決済するためにIEngine.closeOrdersメソッドを使っています。

engine.closeOrders(orders);

設定する引数は

  • orders … 決済したい注文のリスト

です。

注文時のスレッド処理について

実は、買い注文ボタン、売り注文ボタンにsubmitOrderメソッドを、全決済注文ボタンにcloseOrdersメソッドを実装してもスレッドなんちゃら例外が発生してうまく注文出来ません。

ストラテジーは1つのスレッドからしか実行できないことが原因らしいのですが、コレを回避するためにIContext.executeTaskメソッドを使います。

このメソッドは引数に注文や決済のタスクを与えることで注文が出来ます。

新規注文のタスク

private class SubmitOrder implements Callable<IOrder> {

    private OrderCommand orderCommand;
    private double price;

    public SubmitOrder(OrderCommand orderCommand, double price) {
        this.orderCommand = orderCommand;
        this.price = price;
    }

    public IOrder call() throws Exception {
        Instrument instrument = (Instrument)cmbInstrument.getSelectedItem();
        double amount = (Double)spnAmount.getValue() * 0.01D;

        Calendar c = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
        String label = orderCommand.toString() + sdf.format(c.getTime());

        IOrder order = engine.submitOrder(label, instrument, orderCommand, amount, price);

        return order;
    }
}

注文時のラベルとして、「注文コマンド+日時」を設定しています。

決済注文のタスク

private class CloseOrders implements Callable<CloseOrders> {
    public CloseOrders call() throws JFException {
        Instrument instrument = (Instrument)cmbInstrument.getSelectedItem();</p>

        List<IOrder> orders = new ArrayList<IOrder>();
        for (IOrder o: engine.getOrders()) {
            if (o.getState().equals(IOrder.State.FILLED) && o.getInstrument().equals(instrument)) {
                orders.add(o);
            }
        }
        engine.closeOrders(orders);

        return this;
    }
}

現在の注文内容の全てはIEngine.getOrders()メソッドで取得することが出来ます。

各注文にはOrderStateが設定されていて、既に執行された注文のOrderStateはFILLEDとなります。

なので、このFILLEDとなっている注文のリストを取得してcloseOrdersメソッドに渡すことで全決済が出来ます。

全コード

package jforex;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.Callable;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.NumberEditor;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import com.dukascopy.api.IAccount;
import com.dukascopy.api.IBar;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IEngine;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IMessage;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IStrategy;
import com.dukascopy.api.ITick;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.Period;

public class SimpleOrder01 extends JFrame implements IStrategy {
    private IEngine engine;
    private IConsole console;
    private IContext context;

private JPanel pnlAmount;
private JComboBox cmbInstrument;
private JLabel lblAmount;
private JSpinner spnAmount;

private JPanel pnlOrder;
private JLabel lblBid;
private JLabel lblAsk;
private JButton btnBid;
private JButton btnAsk;
private JButton btnCloseAll;

public SimpleOrder01() {
    addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosed(WindowEvent e) {
            try {
                onStop();
            } catch (JFException e1) {
                e1.printStackTrace();
            }
        }
    });
    setResizable(false);
    setTitle("SimpleOrder");
    setBounds(100, 100, 280, 220);
    setLocationRelativeTo(null);
    GridBagLayout gridBagLayout = new GridBagLayout();
    gridBagLayout.columnWidths = new int[] {200};
    gridBagLayout.rowHeights = new int[] {40, 60};
    getContentPane().setLayout(gridBagLayout);

    pnlAmount = new JPanel();
    GridBagConstraints gbc_pnlAmount = new GridBagConstraints();
    gbc_pnlAmount.insets = new Insets(0, 0, 5, 0);
    gbc_pnlAmount.fill = GridBagConstraints.BOTH;
    gbc_pnlAmount.gridx = 0;
    gbc_pnlAmount.gridy = 0;
    getContentPane().add(pnlAmount, gbc_pnlAmount);
    GridBagLayout gbl_pnlAmount = new GridBagLayout();
    gbl_pnlAmount.rowHeights = new int[] {30};
    gbl_pnlAmount.columnWidths = new int[] {80, 60, 60};
    gbl_pnlAmount.columnWeights = new double[]{0.0, 1.0, 0.0};
    pnlAmount.setLayout(gbl_pnlAmount);

    cmbInstrument = new JComboBox(getInstruments().toArray());
    GridBagConstraints gbc_cmbInstrument = new GridBagConstraints();
    gbc_cmbInstrument.fill = GridBagConstraints.BOTH;
    gbc_cmbInstrument.gridx = 0;
    gbc_cmbInstrument.gridy = 0;
    pnlAmount.add(cmbInstrument, gbc_cmbInstrument);

    lblAmount = new JLabel("Amount:");
    lblAmount.setHorizontalAlignment(SwingConstants.RIGHT);
    GridBagConstraints gbc_lblAmount = new GridBagConstraints();
    gbc_lblAmount.anchor = GridBagConstraints.EAST;
    gbc_lblAmount.gridx = 1;
    gbc_lblAmount.gridy = 0;
    gbc_lblAmount.fill = GridBagConstraints.BOTH;
    pnlAmount.add(lblAmount, gbc_lblAmount);

    SpinnerNumberModel snm;
    NumberEditor ne;

    snm = new SpinnerNumberModel(new Double(1), new Double(1), new Double(2500), new Double(0.1));
    spnAmount = new JSpinner(snm);
    ne = new NumberEditor(spnAmount, "0.0");
    spnAmount.setEditor(ne);
    GridBagConstraints gbc_spnAmount = new GridBagConstraints();
    gbc_spnAmount.fill = GridBagConstraints.BOTH;
    gbc_spnAmount.gridx = 2;
    gbc_spnAmount.gridy = 0;
    pnlAmount.add(spnAmount, gbc_spnAmount);

    pnlOrder = new JPanel();
    GridBagConstraints gbc_pnlOrder = new GridBagConstraints();
    gbc_pnlOrder.insets = new Insets(0, 0, 5, 0);
    gbc_pnlOrder.fill = GridBagConstraints.BOTH;
    gbc_pnlOrder.gridx = 0;
    gbc_pnlOrder.gridy = 1;
    getContentPane().add(pnlOrder, gbc_pnlOrder);
    GridBagLayout gbl_pnlOrder = new GridBagLayout();
    gbl_pnlOrder.columnWidths = new int[] {100, 100};
    gbl_pnlOrder.rowHeights = new int[] {30, 30, 30};
    pnlOrder.setLayout(gbl_pnlOrder);

    lblBid = new JLabel("Bid(SELL)");
    lblBid.setForeground(Color.RED);
    lblBid.setHorizontalAlignment(SwingConstants.CENTER);
    GridBagConstraints gbc_lblBid = new GridBagConstraints();
    gbc_lblBid.fill = GridBagConstraints.BOTH;
    gbc_lblBid.gridx = 0;
    gbc_lblBid.gridy = 0;
    pnlOrder.add(lblBid, gbc_lblBid);

    lblAsk = new JLabel("Ask(BUY)");
    lblAsk.setForeground(Color.BLUE);
    lblAsk.setHorizontalAlignment(SwingConstants.CENTER);
    GridBagConstraints gbc_lblAsk = new GridBagConstraints();
    gbc_lblAsk.fill = GridBagConstraints.BOTH;
    gbc_lblAsk.gridx = 1;
    gbc_lblAsk.gridy = 0;
    pnlOrder.add(lblAsk, gbc_lblAsk);

    btnBid = new JButton();
    btnBid.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            JButton source = (JButton)e.getSource();
            double price = Double.parseDouble(source.getText());
            context.executeTask(new SubmitOrder(OrderCommand.SELL, price));
        }
    });
    GridBagConstraints gbc_btnBid = new GridBagConstraints();
    gbc_btnBid.fill = GridBagConstraints.BOTH;
    gbc_btnBid.gridx = 0;
    gbc_btnBid.gridy = 1;
    pnlOrder.add(btnBid, gbc_btnBid);

    btnAsk = new JButton();
    btnAsk.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            JButton source = (JButton)e.getSource();
            double price = Double.parseDouble(source.getText());
            context.executeTask(new SubmitOrder(OrderCommand.BUY, price));
        }
    });
    GridBagConstraints gbc_btnAsk = new GridBagConstraints();
    gbc_btnAsk.fill = GridBagConstraints.BOTH;
    gbc_btnAsk.gridx = 1;
    gbc_btnAsk.gridy = 1;
    pnlOrder.add(btnAsk, gbc_btnAsk);

    btnCloseAll = new JButton();
    btnCloseAll.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            context.executeTask(new CloseOrders());
        }
    });
    btnCloseAll.setText("Close All");
    GridBagConstraints gbc_btnCloseAll = new GridBagConstraints();
    gbc_btnCloseAll.gridwidth = 2;
    gbc_btnCloseAll.fill = GridBagConstraints.BOTH;
    gbc_btnCloseAll.gridx = 0;
    gbc_btnCloseAll.gridy = 2;
    pnlOrder.add(btnCloseAll, gbc_btnCloseAll);
}

public void onStart(IContext context) throws JFException {
    this.engine = context.getEngine();
    this.console = context.getConsole();
    this.context = context;

    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    console.getOut().println("Strategy Started.");
}

public void onAccount(IAccount account) throws JFException {
}

public void onMessage(IMessage message) throws JFException {
    if (message.getOrder() == null) return;
    if (!cmbInstrument.getSelectedItem().equals(message.getOrder().getInstrument())) return;

    console.getOut().println(message.toString());
}

public void onStop() throws JFException {
    console.getOut().println("Strategy Stopped.");
}

public void onTick(Instrument instrument, final ITick tick) throws JFException {
    if (!cmbInstrument.getSelectedItem().equals(instrument)) return;

    final String scaleFormat = "%." + String.valueOf(instrument.getPipScale() + 1) + "f";

    SwingUtilities.invokeLater(new Runnable () {
        public void run() {
            btnBid.setText(String.format(scaleFormat, tick.getBid()));
            btnAsk.setText(String.format(scaleFormat, tick.getAsk()));
        }
    });
}

public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
}

    //通貨ペア
    private final Vector<Instrument> getInstruments() {
        Vector<Instrument> instruments = new Vector<Instrument>();
        instruments.add(Instrument.USDJPY);
        instruments.add(Instrument.EURUSD);
        instruments.add(Instrument.EURJPY);
        instruments.add(Instrument.GBPUSD);
        instruments.add(Instrument.GBPJPY);
        instruments.add(Instrument.AUDUSD);
        instruments.add(Instrument.AUDJPY);

        return instruments;
    }
    //新規注文
    private class SubmitOrder implements Callable<IOrder> {

        private OrderCommand orderCommand;
        private double price;

        public SubmitOrder(OrderCommand orderCommand, double price) {
            this.orderCommand = orderCommand;
            this.price = price;
        }

        public IOrder call() throws Exception {
            Instrument instrument = (Instrument)cmbInstrument.getSelectedItem();
            double amount = (Double)spnAmount.getValue() * 0.01D;

            Calendar c = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
            String label = orderCommand.toString() + sdf.format(c.getTime());

            IOrder order = engine.submitOrder(label, instrument, orderCommand, amount, price);

            return order;
        }
    }
    //全決済注文
    private class CloseOrders implements Callable<CloseOrders> {
        public CloseOrders call() throws JFException {
            Instrument instrument = (Instrument)cmbInstrument.getSelectedItem();

            List<IOrder> orders = new ArrayList<IOrder>();
            for (IOrder o: engine.getOrders()) {
                if (o.getState().equals(IOrder.State.FILLED) && o.getInstrument().equals(instrument)) {
                    orders.add(o);
                }
            }
            engine.closeOrders(orders);

            return this;
        }
    }

細かいエラー処理や例外処理は省いちゃってますのでご注意下さい。

コメント

タイトルとURLをコピーしました