Scalping

jForexで裁量トレードでスキャルピング練習をする為の、サンプルソースコードです。

 

jForexのバックテスト+本サンプルソースコードを用いることで、任意の通貨ペアを、任意の期間、任意のスピードにて、トレード戦略の有効性をフルティックの精度で確認できます。なおバックテストモードは、その時のレートのスプレッドは再現されますが、注文のスリッページまでは再現されません。

 

 

主旨

スキャルピングに特化し、バックテストモードで裁量トレードの有効性や、改善点を確認する為に、jForexのサンプルソースコードを公開する。

 

 

サンプルソースコードの概要

  • リアル口座、デモ口座、バックテストモードの3つのシチュエーションでトレード可能。但し、公開の主旨はバックテストモードでの使用を想定している
  • トレード中は、プログラム起動時から、その時点までの当プログラムから発注された取引のみの戦績を集計し、複利計算により、次トレーディングロットを自動計算する
  • トレード毎に、クローズ時のチャートイメージを自動取得する
  • トレード終了時に、トレーディング履歴をCSV保存する
  • プログラム自体に、新規注文を自動発注し、又は既存ポジションを自動決済する機能はない

 

 

前提条件

  • サンプルソースコードはLife with FXサイトのみに無償で公開する
  • サンプルソースコードの依頼改修や、内容の解説はしない
  • コンパイルしたプログラムは、リアルトレーディングでも稼働可能であるが、損害・トラブル発生時の対応や責任は全て使用者に帰属する
  • サンプルソースコードのコンパイル方法やjForexでの稼働方法は説明しない

 

 

 

T-1(ソースコードコンパイル後)稼働パラメータの説明

mls-01

 

 

 

 

 

 

 

 

 

  1. 取引対象通貨ペアの選択。使用口座で取引可能銘柄であれば、特に数に制限無く選択可能。ただし、あまり多いと高負荷になります。
  2. ONで、トレードクローズ時のチャートイメージをPNGファイルに自動取得する
  3. ONで、初期インディケータを描写する(Fractal、EMA、Awesome)
  4. ON・OFF関係無く、この機能はソースコードレベルで無効化している
  5. ONで70tickチャートを描写する。初期インディケータの有無は指定不可
  6. 初期残高。口座タイプは円口座を想定している(一応、ドルでもユーロでも稼働するはず)。口座残高とは切り離されていて、プログラム内部でその残高としてロット計算される。また、口座残高以上の金額を当該戦略金額に指定できないよに制御している
  7. 複利ロットの掛け率。StopLoss値と、換算レート、概算トレード手数料により、複利ロットが自動計算されます
  8. 注文最大ロット。この数値以上の注文ロットは、(ウィジェットに表示されるかもしれませんが)発注されない
  9. 次トレードロットの計算間隔

 

 

 

 

T-2稼働画面のウィジェットの概要

mls-2

 

 

 

 

 

 

 

  1. 買い売り注文ボタン。ポジションは、1通貨ペア毎に1ポジションのみ建てることが可能。同一通貨ペアで同時に2ポジ以上建たないように制御している
  2. 1clickでS/L値を1pips狭める。現在値から5pips?以内に来ないように制御している
  3. ROIは、このプログラムからトレードしたポジションの初期残高に対する損益。NextLotは、次のオーダーロット。ちなみに、このプログラムから発注した既存のポジションが存在する場合は、既存ポジションがS/Lにかかったものとして、ロット計算している。
  4. このプログラムから発注したポジションのみに対し、さらにチャートウィジェットの通貨ペアに対するポジションのみ決済する。このプログラムのポジションかどうかは、ポジションラベルで判別している。(MT4でいうところのマジックナンバーのようなもの)

 

※ロット計算は、新規注文向けに概算手数料は考慮しているが、必要証拠金は全く考慮していない

 

 

 

T-3(サンプル)ヒストリカルテスター開始時の状態

mls-4

 

 

 

 

T-4(サンプル)起動画面

mls-3

 

くれぐれも、”TEST”モードだけで試してください。

 

バックテスト(と、リアルやデモモード)後のプログラム停止時に、取引データをCSVファイルに保存します。

 

MT4と違って、”XX分足で稼働させる”という概念は、jForexにはありません。ので、何分足に対してもプログラムは稼働します。

 

 

 

 

 

 

 

サンプルソースコード

カスタマイズについてはどうぞご自由に、DIY。(※2015/10/14 version)

 

 

package jp.lifewithfx.mls;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.*;
import java.math.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.awt.Color;
import java.io.*;

import javax.imageio.ImageIO;
//import javax.sound.sampled.*;
import javax.swing.*;

import com.dukascopy.api.*;
import com.dukascopy.api.drawings.*;
import com.dukascopy.api.feed.*;
import com.dukascopy.api.feed.util.*;
import com.dukascopy.api.indicators.OutputParameterInfo.DrawingStyle;
import com.dukascopy.api.DataType.DataPresentationType;
import com.dukascopy.api.IEngine.OrderCommand;
// import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.Period;


/**
 * Strategy:: MoonLightScalper
 * 
 * スキャルピングロジック (を今はとっぱらっている)
 * チャートウィジェットから、複利計算ロットにて発注することを主としている
 * 
 * 
 * @author: Life with FX
 * @see <a href="http://lifewithfx.jp"> #Life with FX# web site </a>
 */

public class MoonLightScalper implements IStrategy, IFeedListener {

    private final String sName = "MoonLightScalper";
    private final String sID = "MLS";
    private       String uID;


    @Configurable("Trading Instruments")
    public HashSet<Instrument> paramTradingInstruments = new HashSet<Instrument>( 
            Arrays.asList(new Instrument[] {
                    Instrument.EURUSD,
                    Instrument.USDJPY}));

    @Configurable("Save Chart Image?")
    public boolean paramIsSaveChartImage = true;

    @Configurable("Add default chart indicators?")
    public boolean paramIsDefChartIndi = true;

    @Configurable("Add header record to report CSV?")
    public boolean paramEnableReportHeaderRec = false;

    @Configurable("Enable alarm?")
    public boolean paramEnableSound = true;

    @Configurable("Enable Tick Chart?")
    public boolean paramEnableTickChart = true;

    @Configurable("Initial Balance")
    public double paramInitialDeopsit = 10000;

    @Configurable("Risk f")
    public double paramOptimalF = 0.1;

    @Configurable("Max Trading Lots")
    public double paramMaxTradingLots = 0.2;

    @Configurable("Take Profit In Pips")
    public double paramTakeProfitInPips = 10.2;

    @Configurable("Stop Loss In Pips")
    public double paramStopLossInPips = 11.5;

    /**
     * テクニカルパラメータ
     */
    @Configurable("Strategy base period")
    public Period paramBasePeriod = Period.FIFTEEN_MINS;

    @Configurable("Strategy supplemental period")
    public Period paramSupplementalPeriod = Period.THIRTY_SECS;

    // 値洗いの間隔
    @Configurable("Mark to Market period")
    public Period paramMarkToMarketPeriod = Period.ONE_SEC;

    @Configurable("Chart: Break out channel")
    public int paramBreakOutChannel = 30;

    @Configurable("Chart: Base MA Period")
    public int paramBaseMaPeriod = 20;

    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IContext context;
    private IIndicators indicators;
    private ICurrency accountCurrency;
    private LfxStrategyParameters params;

    private boolean readyToGo;
    private long strategyBeginningTime;
    private ConcurrentHashMap<Instrument, Double> nextOrderLotMap;
    private ConcurrentLinkedQueue<IOrder>   qClosedOrderHistory;
    private ConcurrentHashMap<Instrument, MoonLightScalperChartUtil> chartUtilMap;

    // 実行権限が不足する環境を作ってしまうので、Java標準のExecutor機構は使えない (shutdownが行えない)
    // private ScheduledExecutorService executor;

    // inherit :: Thread class
    private LfxMarkToMarketExecutor markToMarketExecutor;
    private LfxPriceAlarmExecutor priceAlarmExecutor;

    public void onStart(IContext context) throws JFException {

        this.context = context;
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.indicators = context.getIndicators();
        this.accountCurrency = context.getAccount().getAccountCurrency();

        this.uID = UUID.randomUUID().toString().toUpperCase().substring(0, 6);

        this.readyToGo = false;
        this.nextOrderLotMap = new ConcurrentHashMap<Instrument, Double>();
        this.qClosedOrderHistory = new ConcurrentLinkedQueue<IOrder>();
        this.chartUtilMap = new ConcurrentHashMap<Instrument, MoonLightScalperChartUtil>();


        if (!validateInputParams()) {
            this.context.stop();
            return;
        }

        ArrayList<Instrument> sortedInstrumentsList = new ArrayList<Instrument>(this.paramTradingInstruments.size());
        sortedInstrumentsList.addAll(this.paramTradingInstruments);
        Collections.sort(sortedInstrumentsList);

        params = new LfxStrategyParameters(
                paramInitialDeopsit,
                paramOptimalF,
                paramMaxTradingLots,
                paramTakeProfitInPips,
                paramStopLossInPips,
                paramBasePeriod,
                paramSupplementalPeriod,
                paramMarkToMarketPeriod,
                paramBreakOutChannel,
                paramBaseMaPeriod);

        for (Instrument instrument : sortedInstrumentsList) {

            this.strategyBeginningTime = this.history.getStartTimeOfCurrentBar(instrument, Period.ONE_MIN);
            this.nextOrderLotMap.put(instrument, new Double(0));

            context.setSubscribedInstruments(Collections.singleton(instrument));

            // 実運用(REAL or DEMOモード)なら新規チャートを開く。バックテストモードならテスターのチャートを利用する。
            IChart chart;
            IChart tickChart = null;
            if (this.engine.getType() == IEngine.Type.TEST) {
                chart = this.context.getChart(instrument);

                if (paramEnableTickChart) {
                    IFeedDescriptor feedDescriptor = new TickBarFeedDescriptor(instrument, TickBarSize.valueOf(70), OfferSide.BID);
                    tickChart = this.context.openChart(feedDescriptor);
                }

            } else {
                IFeedDescriptor feedDescriptor = new TimePeriodAggregationFeedDescriptor(instrument, paramBasePeriod, OfferSide.BID, Filter.ALL_FLATS);
                chart = this.context.openChart(feedDescriptor);

                if (paramEnableTickChart) {
                    feedDescriptor = new TickBarFeedDescriptor(instrument, TickBarSize.valueOf(70), OfferSide.BID);
                    tickChart = this.context.openChart(feedDescriptor);
                }
            }


            if (tickChart != null) {

                tickChart.setDataPresentationType(DataPresentationType.CANDLE);

                tickChart.add(this.indicators.getIndicator("EMA"),
                        new Object[]{this.params.getParamBaseMaPeriod()},
                        new Color[]{(new Color(141, 34, 91))},
                        new DrawingStyle[]{DrawingStyle.LINE},
                        new int[]{2});

                tickChart.add(this.indicators.getIndicator("COG"),
                        new Object[]{10, 3, 0},
                        new Color[]{Color.BLUE, Color.MAGENTA},
                        new DrawingStyle[]{DrawingStyle.LINE, DrawingStyle.LINE},
                        new int[]{1, 1});

                /**
                tickChart.add(this.indicators.getIndicator("TD_I"),
                        new Object[]{14});
                 */
            }


            LfxOrderUtil orderUtil = new LfxOrderUtil(context, this.sID);

            MoonLightScalperChartUtil chartUtil; 
            if (chart == null) {
                chartUtil = new MoonLightScalperChartUtil(context, chart, instrument, this, params, orderUtil, false, this.uID, false, false, false);
            } else {
                chartUtil = new MoonLightScalperChartUtil(context, chart, instrument, this, params, orderUtil, true, this.uID, paramIsSaveChartImage, paramIsDefChartIndi, paramEnableSound);
            }

            chartUtil.setupChartIndicators(sName, this.strategyBeginningTime);
            chartUtil.setUpChartWidgets(sName);
            chartUtil.updateSessionInfo(getMarketSessionStr(strategyBeginningTime));
            chartUtilMap.put(instrument, chartUtil);

            subscribeFeedDescriptors(instrument, params);
        }

        this.readyToGo = true;

        // 値洗い処理を非同期で実行する
        markToMarketExecutor = new LfxMarkToMarketExecutor(this,
                this.paramTradingInstruments,
                this.nextOrderLotMap,
                this.chartUtilMap);
        markToMarketExecutor.start();

        // 指定価格への到達を通知する
        priceAlarmExecutor = new LfxPriceAlarmExecutor(this,
                this.paramTradingInstruments,
                this.chartUtilMap);
        priceAlarmExecutor.start();
    }

    private boolean validateInputParams() throws JFException {

        if (this.paramTradingInstruments.size() == 0) {
            this.console.getOut().println("No Instrument selected... stop");
            return false;
        }

        if (this.paramInitialDeopsit > this.context.getAccount().getBalance()) {
            this.console.getOut().println("Over Risk :: InitialBalance :: must be less than your account balance");
            return false;
        }

        if (this.paramOptimalF > 0.9999) {
            this.console.getOut().println("Over Risk :: Risk f :: must be less than 0.999 (99.9%)");
            return false;
        }

        return true;        
    }

    /**
     * 
     * @param instrument
     * @param params
     */
    private void subscribeFeedDescriptors(Instrument instrument, LfxStrategyParameters params) {
        this.context.subscribeToFeed(new TimePeriodAggregationFeedDescriptor(instrument, params.getParamBasePeriod(), OfferSide.BID, Filter.ALL_FLATS), this);
        this.context.subscribeToFeed(new TimePeriodAggregationFeedDescriptor(instrument, params.getParamSupplementalPeriod(), OfferSide.BID, Filter.ALL_FLATS), this);
        this.context.subscribeToFeed(new TimePeriodAggregationFeedDescriptor(instrument, params.getParamMarkToMarketPeriod(), OfferSide.BID, Filter.ALL_FLATS), this);
    }

    public double calcRoi() throws JFException {
        double currentBalance = calcCurrentBalance(false);
        double initialBalance = this.paramInitialDeopsit;

        if (initialBalance < 0.001) {
            return 0;
        } else {
            return new BigDecimal((currentBalance / initialBalance * 100) - 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        }
    }

    public void calcNextTradingLots(Instrument instrument) throws JFException {

        JFUtils utils = this.context.getUtils();

        double currencyConversionRatio = utils.convertPipToCurrency(instrument, accountCurrency);

        if (Double.isNaN(currencyConversionRatio)) {
            return;    
        }

        double currentBalance = calcCurrentBalance(true);

        double lot = currentBalance * this.paramOptimalF / (this.paramStopLossInPips * (currencyConversionRatio / instrument.getPipValue()));

        int movePoints;

        // JPY口座の場合のみ、次回トレードの概算手数料を取引余力から差し引く
        if (context.getAccount().getAccountCurrency().getCurrencyCode().equals("JPY")) {
            movePoints = 6 - instrument.getPipScale();

            // 0.7pipsを概算手数料とする
            double estimatedMinCommition = 0.7 * currencyConversionRatio / instrument.getPipValue();

            double estimatedCommition = estimatedMinCommition * lot;

            lot = (currentBalance - estimatedCommition) * this.paramOptimalF / (this.paramStopLossInPips * (currencyConversionRatio / instrument.getPipValue()));

        } else {
            movePoints = 8 - instrument.getPipScale();
        }

        BigDecimal formatedOrderLots = (new BigDecimal(lot)).movePointLeft(movePoints).setScale(3, BigDecimal.ROUND_DOWN);

        double orderLots =  (formatedOrderLots.doubleValue() >= params.getParamMaxTradingLots()) ? params.getParamMaxTradingLots() : formatedOrderLots.doubleValue();

        this.nextOrderLotMap.replace(instrument, orderLots);

        //this.console.getOut().println(currencyConversionRatio + "  " + lot +  "  " + instrument.getPipScale() + "  " + orderLots + " " + context.getAccount().getAccountCurrency().getCurrencyCode());       
    }


    public double calcCurrentBalance(boolean isDealOpenPos) throws JFException {
        double currentProfitLoss = 0;
        for (IOrder order : qClosedOrderHistory) {
            currentProfitLoss = currentProfitLoss + order.getProfitLossInAccountCurrency() - order.getCommission();                
        }

        if (isDealOpenPos) {
            for (IOrder order : this.engine.getOrders()) {
                if (order.getState() == IOrder.State.FILLED && order.getLabel().startsWith(sID)) {

                    double plInPips = order.getProfitLossInPips();

                    if (-0.1 < plInPips && plInPips < 0.1) {
                        currentProfitLoss = currentProfitLoss + order.getProfitLossInAccountCurrency() - order.getCommission();
                    } else {
                        double slInPips = Math.abs((order.getOpenPrice() - order.getStopLossPrice()) / order.getInstrument().getPipValue());
                        double nFactor = slInPips / plInPips;
                        currentProfitLoss = currentProfitLoss - Math.abs(order.getProfitLossInAccountCurrency() * nFactor) - order.getCommission(); 
                    }  
                }
            }
        }

        double currentBalance = this.paramInitialDeopsit + currentProfitLoss;

        return (new BigDecimal(currentBalance)).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
    }


    /**
     * @param message
     * @throws JFException
     */
    public void onMessage(IMessage message) throws JFException {

        if (!this.readyToGo) {
            return;
        }

        IOrder order = message.getOrder();
        if (order == null) {
            return;
        }

        if (order.getLabel().startsWith(this.sID)) {
            // OK
        } else {
            return;
        }

        if (message.getType() == IMessage.Type.ORDER_CLOSE_OK) {
            qClosedOrderHistory.add(order);
            Instrument instrument = order.getInstrument();

            // クローズ時点のチャートイメージをPNGファイルで保存する
            if (this.paramIsSaveChartImage) {
                this.console.getOut().println("save chart image:  " + uID + "_" + order.getLabel() + ".png");
                chartUtilMap.get(order.getInstrument()).saveChartImage(order.getLabel());
            }

            for (IMessage.Reason reason : message.getReasons()) {
                if (reason == IMessage.Reason.ORDER_CLOSED_BY_TP) {
                    this.chartUtilMap.get(instrument).playSound("alarmtp.wav");
                } else if (reason == IMessage.Reason.ORDER_CLOSED_BY_SL) {
                    this.chartUtilMap.get(instrument).playSound("alarmsl.wav");
                }
            }
        }
    }

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

        if (this.paramTradingInstruments.contains(instrument) &&
                bidBar.getVolume() > 0.5) {

            if (period == this.paramBasePeriod){
                this.chartUtilMap.get(instrument).updateSessionInfo(getMarketSessionStr(bidBar.getTime()));
            }
        }
    }


    /*
     * (non-Javadoc)
     * @see com.dukascopy.api.feed.IFeedListener#onFeedData(com.dukascopy.api.feed.IFeedDescriptor, com.dukascopy.api.ITimedData)
     */
    public void onFeedData(IFeedDescriptor feedDescriptor, ITimedData feedData) {

        /**
        try {
            if (feedDescriptor.getPeriod() == this.paramSupplementalPeriod &&
                    this.paramTradingInstruments.contains(feedDescriptor.getInstrument())) {

                // Signal 1
                String sAshi;
                double[] heikinAshi = this.indicators.heikinAshi(feedDescriptor.getInstrument(), feedDescriptor.getPeriod(), OfferSide.BID, 1);
                if (heikinAshi[0] < heikinAshi[1]) {
                    sAshi = "L";
                } else {
                    sAshi = "S";
                }

                // Signal 2
                String sRvi;
                double[] rvi = this.indicators.rvi(feedDescriptor.getInstrument(), feedDescriptor.getPeriod(), OfferSide.BID, 10, 1);
                if (rvi[0] > rvi[1]) {
                    sRvi = "L";
                } else {
                    sRvi = "S";
                }

                // Signal 3
                String sSlope;
                double slope1 = this.indicators.linearRegSlope(feedDescriptor.getInstrument(), feedDescriptor.getPeriod(), OfferSide.BID, AppliedPrice.WEIGHTED_CLOSE, 14, 1);
                double slope2 = this.indicators.linearRegSlope(feedDescriptor.getInstrument(), feedDescriptor.getPeriod(), OfferSide.BID, AppliedPrice.WEIGHTED_CLOSE, 14, 2);                
                if (slope1 > slope2) {
                    sSlope = "L";
                } else {
                    sSlope = "S";
                }

                this.chartUtilMap.get(feedDescriptor.getInstrument()).updateIndicatorInfo("Indicators:   " + sAshi + " : " + sRvi + " : " + sSlope);

            }
        } catch (Exception e) {
            e.printStackTrace(this.console.getOut());
        }
         */
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {

        if (!this.readyToGo) {
            return;
        }

        if (paramTradingInstruments.contains(instrument)) {
            MoonLightScalperChartUtil chartUtil = this.chartUtilMap.get(instrument);

            double bid = tick.getBid();
            double ask = tick.getAsk();
            double spread = new BigDecimal((ask - bid) / instrument.getPipValue()).setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
            chartUtil.updateTickInfo(Double.valueOf(bid).toString(), Double.valueOf(ask).toString(), Double.valueOf(spread).toString(), tick.getTime());
        }
    }


    public void onStop() throws JFException {

        if (!this.readyToGo) {
            return;
        }

        // 発注ボタンの非活性化
        for (Instrument instrument: this.paramTradingInstruments) {
            this.chartUtilMap.get(instrument).disableWidgetButtons();
        }

        // 非同期の値洗い処理を停止
        try {
            markToMarketExecutor.shutdown();
            markToMarketExecutor.join();
        } catch (Exception e) {
            this.console.getOut().println(e.getMessage());
        }

        // 非同期のアラーム処理を停止
        try {
            priceAlarmExecutor.shutdown();
            priceAlarmExecutor.join();
        } catch (Exception e) {
            this.console.getOut().println(e.getMessage());
        }

        this.console.getOut().println("Summary:: InitialBalance-> " + paramInitialDeopsit + "   ResultBalance-> " + calcCurrentBalance(false) + "   Gain-> " + (new BigDecimal(calcCurrentBalance(false) - this.paramInitialDeopsit).setScale(2, BigDecimal.ROUND_HALF_UP)) + "    Roi-> " + calcRoi());

        createTradingSummary();

        createTradingStatement();
    }

    private void createTradingSummary() throws JFException {

        final String separator = ",";
        final String newLine   = "\r\n";

        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmm");
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        String fileStr = sName + "_" + "Summary_" + dateFormat.format(strategyBeginningTime) + ".csv";
        File statementCsv = new File(this.context.getFilesDir(), fileStr);


        StringBuffer sb = new StringBuffer();
        Writer fileWriter;

        try {
            fileWriter = new BufferedWriter(new FileWriter(statementCsv));
        } catch (Exception e) {
            console.getErr().println(e.getMessage());
            e.printStackTrace(console.getErr());
            context.stop();
            return;
        }

        double tradingProfitLoss = 0;
        double tradingProfitLossInPips = 0;
        double tradingCommission = 0;

        for (IOrder order : qClosedOrderHistory) {
            tradingProfitLoss = tradingProfitLoss + order.getProfitLossInAccountCurrency() - order.getCommission();
            tradingProfitLossInPips = tradingProfitLossInPips + order.getProfitLossInPips();
            tradingCommission = tradingCommission + order.getCommission();
        }

        DecimalFormat plFormat = new DecimalFormat("0.##");
        dateFormat = new SimpleDateFormat("yyyy/MM/dd");

        //
        if (this.paramEnableReportHeaderRec) {
            sb.append("StrategyStartingTime");
            sb.append(separator);

            sb.append("UniqueKey");
            sb.append(separator);        

            sb.append("System");
            sb.append(separator);

            sb.append("InitialBalance");
            sb.append(separator);

            sb.append("ProfitLossInAccountCurrency");
            sb.append(separator);

            sb.append("ResultBalance");
            sb.append(separator);

            sb.append("ROI");
            sb.append(separator);

            sb.append("ProfitLossInPips");
            sb.append(separator);

            sb.append("Commission");
            sb.append(separator);

            sb.append("NumOfTrades");
            sb.append(separator);

            sb.append("Risk");
            sb.append(newLine);
        }
        //
        sb.append(dateFormat.format(this.strategyBeginningTime));
        sb.append(separator);

        sb.append(this.uID);
        sb.append(separator);

        sb.append(this.sName);
        sb.append(separator);

        sb.append(plFormat.format(this.paramInitialDeopsit));
        sb.append(separator);

        sb.append(plFormat.format(tradingProfitLoss));
        sb.append(separator);

        sb.append(plFormat.format(this.paramInitialDeopsit + tradingProfitLoss));
        sb.append(separator);

        sb.append(calcRoi());
        sb.append(separator);

        sb.append(plFormat.format(tradingProfitLossInPips));
        sb.append(separator);

        sb.append(tradingCommission);
        sb.append(separator);

        sb.append(this.qClosedOrderHistory.size());
        sb.append(separator);

        sb.append(this.paramOptimalF);
        sb.append(newLine);

        try {
            fileWriter.write(sb.toString());
            this.console.getOut().println("ReportFile1 :  " + fileStr);
        } catch (Exception e) {
            console.getErr().println(e.getMessage());
            e.printStackTrace(console.getErr());
            context.stop();
        } finally {
            try {
                fileWriter.close();
            } catch (Exception e) {
                console.getErr().println(e.getMessage());
                e.printStackTrace(console.getErr());
                context.stop();
            }
        }
    }

    private void createTradingStatement() throws JFException {

        final String separator = ",";
        final String newLine   = "\r\n";

        DateFormat filaDateFormat = new SimpleDateFormat("yyyyMMddHHmm");
        filaDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        String fileStr = sName + "_" + "Statement_" + filaDateFormat.format(strategyBeginningTime) + ".csv";
        File statementCsv = new File(this.context.getFilesDir(), fileStr);


        StringBuffer sb = new StringBuffer();
        Writer fileWriter;

        try {
            fileWriter = new BufferedWriter(new FileWriter(statementCsv));
        } catch (Exception e) {
            console.getErr().println(e.getMessage());
            e.printStackTrace(console.getErr());
            context.stop();
            return;
        }


        DecimalFormat plFormat = new DecimalFormat("0.#");

        DateFormat recDateFormat = new SimpleDateFormat("yyyy/MM/dd");
        recDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        DateFormat itemDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        itemDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        if (this.paramEnableReportHeaderRec) {

            sb.append("StrategyStartingTime");
            sb.append(separator);

            sb.append("UniqueKey");
            sb.append(separator);

            sb.append("System");
            sb.append(separator);

            sb.append("PositionID");
            sb.append(separator);

            sb.append("OrderLable");
            sb.append(separator);

            sb.append("Instrument");
            sb.append(separator);

            sb.append("OrderType");
            sb.append(separator);

            sb.append("OrderLots");
            sb.append(separator);

            sb.append("OrderOpenTime");
            sb.append(separator);

            sb.append("OrderCloseTime");
            sb.append(separator);

            sb.append("ProfitLossInPips");
            sb.append(separator);

            sb.append("Commission");
            sb.append(separator);

            sb.append("ProfitLossInAccountCurrency");
            sb.append(newLine);
        }

        for (IOrder order : qClosedOrderHistory) {

            sb.append(recDateFormat.format(this.strategyBeginningTime));
            sb.append(separator);

            sb.append(this.uID);
            sb.append(separator);

            sb.append(this.sID);
            sb.append(separator);

            sb.append(order.getId());
            sb.append(separator);

            sb.append(order.getLabel());
            sb.append(separator);

            sb.append(order.getInstrument().name().replace("/", ""));
            sb.append(separator);

            sb.append((order.isLong()) ? "BUY" : "SELL");
            sb.append(separator);

            sb.append(order.getAmount());
            sb.append(separator);

            sb.append(itemDateFormat.format(order.getFillTime()));
            sb.append(separator);

            sb.append(itemDateFormat.format(order.getCloseTime()));
            sb.append(separator);

            sb.append(plFormat.format(order.getProfitLossInPips()));
            sb.append(separator);

            sb.append(order.getCommission());
            sb.append(separator);

            sb.append(order.getProfitLossInAccountCurrency());
            sb.append(newLine);
        }


        try {
            fileWriter.write(sb.toString());
            this.console.getOut().println("ReportFile2 :  " + fileStr);
        } catch (Exception e) {
            console.getErr().println(e.getMessage());
            e.printStackTrace(console.getErr());
            context.stop();
        } finally {
            try {
                fileWriter.close();
            } catch (Exception e) {
                console.getErr().println(e.getMessage());
                e.printStackTrace(console.getErr());
                context.stop();
            }
        }
    }

    public String getMarketSessionStr(long time) {

        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(new Date(time));

        int hour = cal.get(Calendar.HOUR_OF_DAY);

        if (hour >= 18) {
            return "Asian Session";
        } else if (hour >= 12) {
            return "NewYork Session";
        } else if (hour >= 6) {
            return "European Session";
        } else {
            return "Tokyo Session";
        }      
    }


    public String getsID() {
        return sID;
    }

    public IEngine getEngine() {
        return engine;
    }

    public IContext getContext() {
        return context;
    }

    public ConcurrentHashMap<Instrument, Double> getNextOrderLotMap() {
        return nextOrderLotMap;
    }


    public Double getNextOrderLotMap(Instrument instrument) {
        return this.nextOrderLotMap.get(instrument);
    }


    public LfxStrategyParameters getParams() {
        return params;
    }


    /********************************************************************************
     * NOT USE
     ********************************************************************************/
    public void onAccount(IAccount account) throws JFException {
    }

}


class MoonLightScalperChartUtil {

    private IEngine engine;
    private IConsole console;
    private IContext context;
    private IIndicators indicators;
    private IChart chart;

    private Instrument instrument;

    private String uID;
    private MoonLightScalper ggl;
    private LfxStrategyParameters params;

    private boolean isDrawable;
    private boolean isSaveChartImage;
    private boolean isDefaultChartIndi;
    //    private boolean isPlaySound;

    private LfxOrderUtil orderUtil;
    private String keyOfMainWidget;

    private ConcurrentHashMap<String, JLabel>  orderWidgetJLableMap;
    private ConcurrentHashMap<String, JLabel>  marketWidgetJLableMap;

    public MoonLightScalperChartUtil(IContext context, IChart chart, Instrument instrument, MoonLightScalper ggl, LfxStrategyParameters params, LfxOrderUtil orderUtil, boolean isDrawable, String uID, boolean isSaveChartImage, boolean isDefaultChartIndi, boolean isPlaySound) {

        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.context = context;
        this.indicators = context.getIndicators();

        this.instrument = instrument;

        this.ggl = ggl;

        this.chart = chart;
        this.params = params;
        this.isDrawable = isDrawable;
        this.uID = uID;
        this.orderUtil = orderUtil;
        this.isSaveChartImage = isSaveChartImage;
        this.isDefaultChartIndi = isDefaultChartIndi;
        //        this.isPlaySound = isPlaySound;

        this.orderWidgetJLableMap = new ConcurrentHashMap<String, JLabel>();  
        this.marketWidgetJLableMap = new ConcurrentHashMap<String, JLabel>();  
    }

    public boolean isDrawable() {
        return isDrawable;
    }

    /**
     * ストラテジ開始時に、初期処理としてチャートにインディケータや開始時刻を描写する
     * 
     * @param strategyName
     * @param strategyStartsTime
     * @throws JFException
     */
    public void setupChartIndicators(String strategyName, long strategyStartsTime) throws JFException {

        if (!isDrawable() || !isDefaultChartIndi) {
            return;
        }

        /**
        IVerticalLineChartObject line = 
                this.chart.getChartObjectFactory().createVerticalLine("onStart", strategyStartsTime);
        line.setLineStyle(LineStyle.DASH);
        line.setColor(Color.RED.darker());
        line.setText(strategyName);
        this.chart.add(line);
         */

        this.chart.add(this.indicators.getIndicator("FRACTALLINES"),
                new Object[]{this.params.getParamBreakOutChannel()},
                new Color[]{Color.RED, Color.RED},
                new DrawingStyle[]{DrawingStyle.LINE, DrawingStyle.LINE},
                new int[]{2,2});

        this.chart.add(this.indicators.getIndicator("EMA"),
                new Object[]{this.params.getParamBaseMaPeriod()},
                new Color[]{(new Color(141, 34, 91))},
                new DrawingStyle[]{DrawingStyle.LINE},
                new int[]{2});

        this.chart.add(this.indicators.getIndicator("FRACTAL"),
                new Object[]{8},
                new Color[]{Color.CYAN.darker(), Color.GREEN.darker()},
                new DrawingStyle[]{},
                new int[]{});

        /**
    IIndicator ind = this.indicators.getIndicator("AWESOME");
    InputParameterInfo info0 = ind.getInputParameterInfo(0);

    IndicatorInfo indinfo = ind.getIndicatorInfo();
    this.console.getOut().println("input: " + indinfo.getNumberOfInputs() + "  opinput: " + indinfo.getNumberOfOptionalInputs());
    this.console.getOut().println("type: " + info0.getType());


    for (int i = 0; i < ind.getIndicatorInfo().getNumberOfOptionalInputs(); i++){
        this.console.getOut().println(String.format("Opt Input %s: %s - %s", i, ind.getOptInputParameterInfo(i).getName(), ind.getOptInputParameterInfo(i).getType()));
    }
         */

        // 8  MaType.T3 :: AWESOMEに関しては、第2、4引数には、MaTypeそのものではなく、チャートパネルプルダウンのMATypeの番号でしているみたい。どはまりした。。。
        this.chart.add(this.indicators.getIndicator("AWESOME"),
                //new Object[]{3, MaType.T3, 21, MaType.T3});
                new Object[]{3, 8, 21, 8});

        this.chart.add(this.indicators.getIndicator("VOLUME"),
                new Object[]{},
                new Color[]{Color.MAGENTA},
                new DrawingStyle[]{},
                new int[]{});
    }


    public void setUpChartWidgets(String strategyName) {
        setUpOrderWidget(strategyName);
        setUpMarketWidget();
    }


    private void setUpOrderWidget(String strategyName) {

        if (this.isDrawable) {
            ICustomWidgetChartObject widget = this.chart.getChartObjectFactory().createChartWidget();

            //widget.setText(strategyName + "  @ " + this.engine.getType());
            widget.setFillOpacity(0.25f);
            widget.setColor(Color.RED);
            widget.setPosX(0.01f);

            JPanel widgetPanel = widget.getContentPanel();

            JLabel jlWidgetTitle = new JLabel(strategyName + " @ " + this.engine.getType());
            jlWidgetTitle.setAlignmentX(Component.LEFT_ALIGNMENT);
            widgetPanel.add(jlWidgetTitle);
            widgetPanel.add(Box.createRigidArea(new Dimension(230,0)));

            JLabel jlPosState = new JLabel("... ");
            jlPosState.setAlignmentX(Component.LEFT_ALIGNMENT);
            this.orderWidgetJLableMap.put("POSSTATE", jlPosState);

            JLabel jlOpenOrder = new JLabel(" -- open position -- ");
            jlOpenOrder.setAlignmentX(Component.CENTER_ALIGNMENT);


            JButton jbOpenSell = new JButton("SELL");
            jbOpenSell.setForeground(Color.RED.darker());
            jbOpenSell.setAlignmentX(Component.CENTER_ALIGNMENT);
            jbOpenSell.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {

                    try {
                        if (orderUtil.prepareForOpeningPosition(instrument, 
                                OrderCommand.SELL, 
                                ggl.getNextOrderLotMap(instrument), 
                                params.getParamTakeProfitInPips(), 
                                params.getParamStopLossInPips())) {

                            context.executeTask(orderUtil);
                        }

                    } catch (JFException el) {
                        el.printStackTrace();
                    }
                }
            });

            JButton jbOpenBuy = new JButton("BUY");
            jbOpenBuy.setForeground(Color.BLUE);
            jbOpenBuy.setAlignmentX(Component.CENTER_ALIGNMENT);
            jbOpenBuy.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {
                    try {
                        if (orderUtil.prepareForOpeningPosition(instrument, 
                                OrderCommand.BUY, 
                                ggl.getNextOrderLotMap(instrument), 
                                params.getParamTakeProfitInPips(), 
                                params.getParamStopLossInPips())) {

                            context.executeTask(orderUtil);
                        }
                    } catch (JFException el) {
                        el.printStackTrace();
                    }
                }
            });


            JButton jbModify = new JButton("@");
            jbModify.setForeground(Color.CYAN);
            jbModify.setAlignmentX(Component.CENTER_ALIGNMENT);
            jbModify.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {
                    try {
                        if (orderUtil.prepareForModifingPosition(instrument)) {
                            context.executeTask(orderUtil);
                        }
                    } catch (JFException el) {
                        el.printStackTrace();
                    }
                }
            });


            JButton jbCloseAll = new JButton("CLOSE");
            jbCloseAll.setAlignmentX(Component.CENTER_ALIGNMENT);
            jbCloseAll.addActionListener(new ActionListener() {

                public void actionPerformed(ActionEvent e) {
                    try {
                        if (orderUtil.prepareForClosingPosition(instrument)) {
                            context.executeTask(orderUtil);
                        }
                    } catch (JFException el) {
                        el.printStackTrace();
                    }
                }
            });

            widgetPanel.add(Box.createRigidArea(new Dimension(0,10)));
            widgetPanel.add(jlPosState);
            widgetPanel.add(Box.createRigidArea(new Dimension(200,2)));
            widgetPanel.add(jbOpenSell);
            widgetPanel.add(Box.createRigidArea(new Dimension(10,10)));
            widgetPanel.add(jbOpenBuy);
            widgetPanel.add(Box.createRigidArea(new Dimension(230,2 )));
            widgetPanel.add(jbModify);
            widgetPanel.add(Box.createRigidArea(new Dimension(230,2)));

            //
            widgetPanel.add(Box.createRigidArea(new Dimension(230,1)));
            JLabel jlRoi = new JLabel("  ROI  :  ");
            jlRoi.setAlignmentX(Component.LEFT_ALIGNMENT);
            this.orderWidgetJLableMap.put("ROI", jlRoi);
            widgetPanel.add(jlRoi);

            widgetPanel.add(Box.createRigidArea(new Dimension(30,0)));

            //
            JLabel jlNextLot = new JLabel("  NextLot:  ");
            jlNextLot.setAlignmentX(Component.LEFT_ALIGNMENT);
            this.orderWidgetJLableMap.put("NEXTLOT", jlNextLot);
            widgetPanel.add(jlNextLot);

            widgetPanel.add(Box.createRigidArea(new Dimension(230,2)));
            widgetPanel.add(jbCloseAll);


            widgetPanel.setSize(new Dimension(230, 210));

            this.keyOfMainWidget = widget.getKey();
            this.chart.add(widget);
        }
    }


    private void setUpMarketWidget() {   
        if (this.isDrawable) {
            ICustomWidgetChartObject marketWidget = this.chart.getChartObjectFactory().createChartWidget();
            marketWidget.setText("-- Market Infomation --");
            marketWidget.setFillOpacity(0.25f);
            marketWidget.setColor(Color.RED);
            marketWidget.setPosX(0.01f);
            marketWidget.setPosY(0.225f);

            JPanel widgetPanel = marketWidget.getContentPanel();

            JLabel jlSession= new JLabel("    ");
            jlSession.setAlignmentX(Component.LEFT_ALIGNMENT);
            this.marketWidgetJLableMap.put("SESSION", jlSession);
            widgetPanel.add(jlSession);

            widgetPanel.add(Box.createRigidArea(new Dimension(80,0)));

            JLabel jlTime = new JLabel("    ");
            jlTime.setAlignmentX(Component.LEFT_ALIGNMENT);
            this.marketWidgetJLableMap.put("TIME", jlTime);

            widgetPanel.add(jlTime);
            widgetPanel.add(Box.createRigidArea(new Dimension(50,10)));

            /////////////////////
            JLabel jlTick = new JLabel("  exchange rates:  ");
            jlTick.setAlignmentX(Component.LEFT_ALIGNMENT);
            widgetPanel.add(jlTick);

            //
            JLabel jlTickBid = new JLabel("       ");
            jlTickBid.setAlignmentX(Component.LEFT_ALIGNMENT);
            this.marketWidgetJLableMap.put("TICK_BID", jlTickBid);
            widgetPanel.add(jlTickBid);

            //
            JLabel jlTickAsk = new JLabel("       ");
            jlTickAsk.setAlignmentX(Component.LEFT_ALIGNMENT);
            this.marketWidgetJLableMap.put("TICK_ASK", jlTickAsk);
            widgetPanel.add(jlTickAsk);

            ///////////////////////
            JLabel jlSpread = new JLabel("  spread:  ");
            jlSpread.setAlignmentX(Component.LEFT_ALIGNMENT);
            widgetPanel.add(jlSpread);

            //
            JLabel jlTickSpread = new JLabel("    ");
            jlTickSpread.setAlignmentX(Component.CENTER_ALIGNMENT);
            this.marketWidgetJLableMap.put("TICK_SPREAD", jlTickSpread);
            widgetPanel.add(jlTickSpread);

            //
            JLabel jlSpreadPips = new JLabel(" pips ");
            jlSpreadPips.setAlignmentX(Component.CENTER_ALIGNMENT);
            widgetPanel.add(jlSpreadPips);

            widgetPanel.add(Box.createRigidArea(new Dimension(180,0)));

            /**
            JLabel jlIndiInfo = new JLabel("       ");
            jlIndiInfo.setAlignmentX(Component.CENTER_ALIGNMENT);
            this.marketWidgetJLableMap.put("INDIINFO", jlIndiInfo);
            widgetPanel.add(jlIndiInfo);
             */


            widgetPanel.setSize(230, 90);
            this.chart.add(marketWidget);
        }
    }


    public void updateOrderInfoToWidget(boolean hasPos, String plInPips, double roi, double nextLot) {

        if (this.isDrawable) {
            if (hasPos) {
                this.orderWidgetJLableMap.get("POSSTATE").setText(":: Now Trading  ->   " + plInPips +"  pips");
            } else {
                this.orderWidgetJLableMap.get("POSSTATE").setText(" ... ");
            }
            this.orderWidgetJLableMap.get("ROI").setText("   ROI    :  " + roi);
            this.orderWidgetJLableMap.get("NEXTLOT").setText("   NextLot:  " + nextLot);
        }
    }


    public void updateTickInfo(String bid, String ask, String spread, long time) {
        if (this.isDrawable) {
            this.marketWidgetJLableMap.get("TICK_BID").setText(bid);
            this.marketWidgetJLableMap.get("TICK_ASK").setText(ask);
            this.marketWidgetJLableMap.get("TICK_SPREAD").setText(spread);


            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
            this.marketWidgetJLableMap.get("TIME").setText("   " + dateFormat.format(time));
        }
    }

    public void updateSessionInfo(String session) {
        if (this.isDrawable) {
            this.marketWidgetJLableMap.get("SESSION").setText(session);
        }
    }

    public void updateIndicatorInfo(String str) {
        if (this.isDrawable) {
            this.marketWidgetJLableMap.get("INDIINFO").setText(str);
        }
    }

    public void disableWidgetButtons() {

        if (this.isDrawable) {

            for (IChartObject chartObj : this.chart.getAll()) {

                if (chartObj instanceof ICustomWidgetChartObject &&
                        chartObj.getKey().equals(this.keyOfMainWidget)) {

                    JPanel contentPanel = ((ICustomWidgetChartObject)chartObj).getContentPanel();
                    Component[] components = contentPanel.getComponents();

                    for (int i = 0; i < components.length ; i++) {

                        if (components[i] instanceof JButton) {

                            ((JButton)components[i]).setEnabled(false);
                        }
                    }
                }
            }
        }
    }


    public void saveChartImage(String pictureLabel) throws JFException {
        String filename;

        if (!isDrawable() || !isSaveChartImage) {
            return;
        }

        filename = this.uID + "_" + pictureLabel + ".png";

        try {
            File file = new File(this.context.getFilesDir().getPath() + File.separator + filename);
            ImageIO.write(chart.getImage(), "png", file);
        } catch (Exception e) {
            e.printStackTrace(console.getErr());
        }
    }

    public boolean playSound() {
        return playSound("alarm.wav");
    }

    public boolean playSound(String filename) {

        /**
        if (this.isDrawable && this.isPlaySound) {    
            File alertFile = new File(this.context.getFilesDir().getPath(), filename);

            if (!alertFile.exists()) {
                return false;
            }

            try {
                AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(alertFile);
                AudioFormat af = audioInputStream.getFormat();
                int nSize = (int) (af.getFrameSize() * audioInputStream.getFrameLength());
                byte[] audio = new byte[nSize];
                DataLine.Info info = new DataLine.Info(Clip.class, af, nSize);
                audioInputStream.read(audio, 0, nSize);
                Clip clip = (Clip) AudioSystem.getLine(info);
                clip.open(af, audio, 0, nSize);
                clip.start();

            } catch (IOException ioe) {
                ioe.printStackTrace(console.getOut());
                return false;
            } catch (Exception e) {
                e.printStackTrace(console.getOut());
                return false;
            }

            return true;
        }
         */

        return false;
    }

    public IChart getChart() {
        return chart;
    }

}

class LfxStrategyParameters {

    private double paramInitialDeopsit;
    private double paramOptimalF;
    private double paramMaxTradingLots;
    private double paramTakeProfitInPips;
    private double paramStopLossInPips;
    private Period paramBasePeriod;
    private Period paramSupplementalPeriod;
    private Period paramMarkToMarketPeriod;
    private int paramBreakOutChannel;
    private int paramBaseMaPeriod;

    public LfxStrategyParameters(
            double paramInitialDeopsit,
            double paramOptimalF,
            double paramMaxTradingLots,
            double paramTakeProfitInPips,
            double paramStopLossInPips,
            Period paramBasePeriod,
            Period paramSupplementalPeriod,
            Period paramMarkToMarketPeriod,
            int paramBreakOutChannel,
            int paramBaseMaPeriod) {

        this.paramInitialDeopsit = paramInitialDeopsit;
        this.paramOptimalF = paramOptimalF;
        this.paramMaxTradingLots = paramMaxTradingLots;
        this.paramTakeProfitInPips = paramTakeProfitInPips;
        this.paramStopLossInPips = paramStopLossInPips;
        this.paramBasePeriod = paramBasePeriod;
        this.paramSupplementalPeriod = paramSupplementalPeriod;
        this.paramMarkToMarketPeriod = paramMarkToMarketPeriod;
        this.paramBreakOutChannel = paramBreakOutChannel;
        this.paramBaseMaPeriod = paramBaseMaPeriod;
    }

    public double getParamInitialDeopsit() {
        return paramInitialDeopsit;
    }

    public double getParamOptimalF() {
        return paramOptimalF;
    }

    public double getParamMaxTradingLots() {
        return paramMaxTradingLots;
    }

    public double getParamTakeProfitInPips() {
        return paramTakeProfitInPips;
    }

    public double getParamStopLossInPips() {
        return paramStopLossInPips;
    }

    public Period getParamBasePeriod() {
        return paramBasePeriod;
    }

    public Period getParamSupplementalPeriod() {
        return paramSupplementalPeriod;
    }

    public Period getParamMarkToMarketPeriod() {
        return paramMarkToMarketPeriod;
    }

    public int getParamBreakOutChannel() {
        return paramBreakOutChannel;
    }

    public int getParamBaseMaPeriod() {
        return paramBaseMaPeriod;
    }

}

class LfxOrderUtil implements Callable<IOrder> {

    enum OrderUtilMode {
        OPEN,
        CLOSE,
        MODIFY,
        NONE;
    }

    private IEngine engine;
    private IHistory history;
    private String sID;

    private int orderCounter;
    private OrderUtilMode mode;

    private Instrument instrument;
    private OrderCommand orderCommand;
    private double amount;
    private double tpInPips;
    private double slInPips;

    public LfxOrderUtil(IContext context, String sID) {
        this.engine = context.getEngine();
        this.history = context.getHistory();
        this.sID = sID;

        this.orderCounter = 1;
        this.mode = OrderUtilMode.NONE;
    }

    // 重複発注防止の為に、synchronized を利用
    public synchronized boolean prepareForOpeningPosition(Instrument instrument, OrderCommand orderCommand, double amount, double tpInPips, double slInPips) throws JFException {

        // 1通貨ペアにつき、1ポジションのみ
        for (IOrder order : this.engine.getOrders(instrument)) {
            if ((order.getState() == IOrder.State.CREATED ||
                    order.getState() == IOrder.State.OPENED ||
                    order.getState() == IOrder.State.FILLED) && order.getLabel().startsWith(this.sID)) {
                this.mode = OrderUtilMode.NONE;
                return false;
            }
        }

        // 最小取引単位チェック
        if (amount < 0.001) {
            this.mode = OrderUtilMode.NONE;
            return false;
        }

        // 重複発注防止
        if (this.mode == OrderUtilMode.OPEN) {
            this.mode = OrderUtilMode.NONE;
            return false;
        }

        this.instrument = instrument;
        this.orderCommand = orderCommand;
        this.amount = amount;
        this.tpInPips = tpInPips;
        this.slInPips = slInPips;
        this.mode = OrderUtilMode.OPEN;

        return true;
    }

    public boolean prepareForClosingPosition(Instrument instrument) throws JFException {

        for (IOrder order : this.engine.getOrders(instrument)) {
            if (order.getState() == IOrder.State.FILLED && order.getLabel().startsWith(this.sID)) {
                this.mode = OrderUtilMode.CLOSE;
                return true;
            }
        }

        this.mode = OrderUtilMode.NONE;

        return false;
    }

    public boolean prepareForModifingPosition(Instrument instrument) throws JFException {

        for (IOrder order : this.engine.getOrders(instrument)) {
            if (order.getState() == IOrder.State.FILLED && order.getLabel().startsWith(this.sID)) {
                this.mode = OrderUtilMode.MODIFY;
                return true;
            }
        }

        this.mode = OrderUtilMode.NONE;

        return false;
    }

    /**
     * 
     * @return
     * @throws Exception
     */
    public IOrder call() throws Exception {

        IOrder resultOrder = null;

        if (this.mode == OrderUtilMode.NONE) {
            resultOrder =  null;
        } else if (this.mode == OrderUtilMode.OPEN) {

            if (amount < 0.001) {
                this.mode = OrderUtilMode.NONE;
                return null;
            }

            String label = getNextLabel(new Date(this.history.getTimeOfLastTick(this.instrument)));

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

            order.waitForUpdate(10000, IOrder.State.FILLED);

            if (order.getState() == IOrder.State.FILLED) {

                double openPrice = order.getOpenPrice();
                double stopLossPrice;
                double takeProfitPrice;

                if (this.orderCommand == OrderCommand.BUY) {
                    openPrice = this.history.getLastTick(this.instrument).getAsk();
                } else {
                    openPrice = this.history.getLastTick(this.instrument).getBid();
                }

                stopLossPrice = openPrice + getStopLoss(instrument, orderCommand, slInPips);
                order.setStopLossPrice(stopLossPrice);
                order.waitForUpdate(10000, IOrder.State.FILLED);

                takeProfitPrice = openPrice + getTakeProfit(instrument, orderCommand, tpInPips);
                order.setTakeProfitPrice(takeProfitPrice);
                order.waitForUpdate(10000, IOrder.State.FILLED);
            } else {
                if (order.getState() == IOrder.State.CANCELED) {
                    // well    do nothing
                } else {
                    throw new JFException("error!! updating stoploss price");
                }
            }

            resultOrder =  order;

        } else if (this.mode == OrderUtilMode.CLOSE) {
            for (IOrder order : this.engine.getOrders(instrument)) {
                if (order.getState() == IOrder.State.FILLED && order.getLabel().startsWith(this.sID)) {
                    order.close();
                    resultOrder = order;
                }
            }
        } else if (this.mode == OrderUtilMode.MODIFY) {
            for (IOrder order : this.engine.getOrders(instrument)) {
                if (order.getState() == IOrder.State.FILLED && order.getLabel().startsWith(this.sID)) {

                    // オーダーのS/L値を取得する
                    double currentStopLossPrice = order.getStopLossPrice();

                    // 新しいS/L値を計算する
                    double modifyStepInPips = 1.0;
                    double newStopLossPrice;

                    if (order.isLong()) {
                        newStopLossPrice = currentStopLossPrice + (modifyStepInPips * instrument.getPipValue());
                    } else {
                        newStopLossPrice = currentStopLossPrice - (modifyStepInPips * instrument.getPipValue());
                    }

                    // 現在値との差分を取得して、5pips以内なら処理スキップ為の準備
                    double currentPrice;
                    if (order.isLong()) {
                        currentPrice = this.history.getLastTick(instrument).getBid();
                    } else {
                        currentPrice = this.history.getLastTick(instrument).getAsk();
                    }

                    // S/L値を修正する
                    if (Math.abs(newStopLossPrice - currentPrice) / instrument.getPipValue() > 4.999) {
                        order.setStopLossPrice(newStopLossPrice);
                        order.waitForUpdate(10000, IOrder.State.FILLED);
                    }

                    // wait for update
                    resultOrder = order;
                }
            }
        }


        this.mode = OrderUtilMode.NONE;
        return resultOrder;
    }

    private double getStopLoss(Instrument instrument, OrderCommand orderCommand, double stopLossInPips) {

        if (orderCommand == OrderCommand.BUY ||
                orderCommand == OrderCommand.BUYLIMIT ||
                orderCommand == OrderCommand.BUYSTOP) {

            return stopLossInPips * instrument.getPipValue() * -1;
        } else {
            return stopLossInPips * instrument.getPipValue();
        }
    }

    private double getTakeProfit(Instrument instrument, OrderCommand orderCommand, double takeProfitInPips) {

        if (orderCommand == OrderCommand.BUY ||
                orderCommand == OrderCommand.BUYLIMIT ||
                orderCommand == OrderCommand.BUYSTOP) {

            return takeProfitInPips * instrument.getPipValue();
        } else {
            return takeProfitInPips * instrument.getPipValue() * -1;
        }
    }

    private String getNextLabel(Date date) {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
        DecimalFormat df = new DecimalFormat("00000");

        String label = this.sID + instrument.name() + sdf.format(date) + df.format(this.orderCounter);
        this.orderCounter++;

        return label;
    }
}

class LfxMarkToMarketExecutor extends Thread {

    private MoonLightScalper mls;
    private HashSet<Instrument> paramTradingInstruments;  
    private ConcurrentHashMap<Instrument, Double> nextOrderLotMap;
    private ConcurrentHashMap<Instrument, MoonLightScalperChartUtil> chartUtilMap;

    private boolean ctr;

    public LfxMarkToMarketExecutor(MoonLightScalper mls,
            HashSet<Instrument> paramTradingInstruments, 
            ConcurrentHashMap<Instrument, Double> nextOrderLotMap, 
            ConcurrentHashMap<Instrument, MoonLightScalperChartUtil> chartUtilMap) {

        this.mls = mls;
        this.paramTradingInstruments = paramTradingInstruments;
        this.nextOrderLotMap = nextOrderLotMap;
        this.chartUtilMap = chartUtilMap;

        this.ctr = true;

    }

    public void run() {

        double roi;

        try {
            while(ctr) {

                Thread.sleep(mls.getParams().getParamMarkToMarketPeriod().getInterval());

                roi = mls.calcRoi();

                for (Instrument instrument : paramTradingInstruments) {
                    mls.calcNextTradingLots(instrument);

                    boolean hasPos = false;
                    String plInPips = "0.0";
                    for (IOrder order : mls.getEngine().getOrders(instrument)) {
                        if (order.getState() == IOrder.State.FILLED && order.getLabel().startsWith(mls.getsID())) {
                            hasPos = true;
                            plInPips = (new DecimalFormat("0.0;-0.0")).format(order.getProfitLossInPips());                        
                            break;
                        }
                    }
                    chartUtilMap.get(instrument).updateOrderInfoToWidget(hasPos, plInPips, roi, nextOrderLotMap.get(instrument));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void shutdown() {
        this.ctr = false;
    }
}

class LfxPriceAlarmExecutor extends Thread {

    private MoonLightScalper mls;
    private IContext context;
    private IHistory history;
    private HashSet<Instrument> paramTradingInstruments;  
    private ConcurrentHashMap<Instrument, MoonLightScalperChartUtil> chartUtilMap;
    private ArrayList<String> expiredAlarmList;

    private boolean ctr;

    public LfxPriceAlarmExecutor(MoonLightScalper mls,
            HashSet<Instrument> paramTradingInstruments, 
            ConcurrentHashMap<Instrument, MoonLightScalperChartUtil> chartUtilMap) {

        this.mls = mls;
        this.context = mls.getContext();
        this.history = context.getHistory();

        this.paramTradingInstruments = paramTradingInstruments;
        this.chartUtilMap = chartUtilMap;
        this.expiredAlarmList = new ArrayList<String>(1000);

        this.ctr = true;

    }

    public void run() {

        try {
            while(ctr) {

                Thread.sleep(mls.getParams().getParamMarkToMarketPeriod().getInterval());

                for (Instrument instrument : paramTradingInstruments) {

                    MoonLightScalperChartUtil chartUtil = this.chartUtilMap.get(instrument);

                    doTask(instrument, chartUtil.getChart(), chartUtil);

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void shutdown() {
        this.ctr = false;
    }

    private void doTask(Instrument instrument, IChart chart, MoonLightScalperChartUtil chartUtil) throws JFException {

        long timeOfLastTick = this.history.getTimeOfLastTick(instrument);
        double lastPrice = this.history.getLastTick(instrument).getBid();

        for (IChartObject chartObj : chart.getAll()) {

            String key = getListKey(instrument, chartObj);

            if (expiredAlarmList.contains(key)) {
                continue;
            }

            long timeS;
            long timeE;
            double priceH;
            double priceL;

            // check type
            if (chartObj.getType() == IChart.Type.RECTANGLE || chartObj.getType() == IChart.Type.ELLIPSE) {
                timeS = (chartObj.getTime(0) < chartObj.getTime(1)) ? chartObj.getTime(0) : chartObj.getTime(1);
                timeE = (chartObj.getTime(0) > chartObj.getTime(1)) ? chartObj.getTime(0) : chartObj.getTime(1);
                priceH = (chartObj.getPrice(0) > chartObj.getPrice(1)) ? chartObj.getPrice(0) : chartObj.getPrice(1);
                priceL = (chartObj.getPrice(0) < chartObj.getPrice(1)) ? chartObj.getPrice(0) : chartObj.getPrice(1);
            } else {
                continue;
            }

            // check is expired?
            if (timeOfLastTick > timeE) {
                expireChartObj(chartObj, key);
                continue;
            }

            if (timeS <= timeOfLastTick &&
                    timeOfLastTick <= timeE &&
                    priceL <= lastPrice &&
                    lastPrice <= priceH) {

                expireChartObj(chartObj, key);
                alarm(chartUtil);

            } else {
                continue;
            }
        }
    }

    private String getListKey(Instrument instrument, IChartObject obj) {
        return instrument.name() + obj.getKey();
    }

    private void expireChartObj(IChartObject obj, String key) {
        this.expiredAlarmList.add(key);
        obj.setOpacity(0.033f);
    }

    private void alarm(MoonLightScalperChartUtil chartUtil) {
        if (!chartUtil.playSound()) {
            shutdown();
        }
    }

}