package jforex;

import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;

import com.dukascopy.api.Filter;
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.IHistory;
import com.dukascopy.api.IIndicators;
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.OfferSide;
import com.dukascopy.api.Period;
import com.dukascopy.api.IEngine.OrderCommand;

/*******************************************
 * 
 * @author Fukain
 * @version 1.1
 ******************************************/

public class MyMay implements IStrategy {

    private IContext context = null;
    private IAccount account = null;
    private IHistory history = null;
    private IEngine engine = null;
    private IConsole console = null;
    private IIndicators indicators = null;

    // Trading session permitted (GMT)
    private int openTime = 500;
    private int closeTime = 2000;

    private Instrument myInstrument = Instrument.AUDUSD;
    private Period myPeriod = Period.FIFTEEN_MINS;
    private double mySlippage = 5.0;

    private String labelSuffix = "FUKAIN";
    private IOrder outstandingOrder = null;
    private boolean isOrderSubmissionAllowed = true;

    private double minAmllowedAmount = 0.001; // in million
    private double marginCutLevel;

    private int myLeverage = 20;
    private int minLeverage = 10;
    private int maxLeverage = 50; // 100
    private int leverageStep = 5;
    
    private double minSL = 20.0;
    private double maxSL = 20.0;
    private double defaultTP = 300.0;
    
    private double minVolume = 250.0;
    private Filter indicatorFilter = Filter.NO_FILTER;

    private int defaultSmoothing = 2;

    private Period ATRPeriod = Period.FIVE_MINS;
    private int ATRTimePeriod = 15;
    private double ATRThreshold = 0.0015;
    private double ATRMultiplier = 1.50;

    private int SMASlow = 60;
    private int SMAFast = 28;

    private int DITimePeriod = 21;
    private int DITimeDepth = 4;

    public void onStart(IContext context) throws JFException {
        this.context = context;

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

        Set<Instrument> subscribedInstruments = new HashSet<Instrument>();
        subscribedInstruments.add(this.myInstrument);
        context.setSubscribedInstruments(subscribedInstruments);

        List<IOrder> orders = engine.getOrders(myInstrument);

        if (orders.size() > 0) {
            for (IOrder order : orders) {
                if (order.getState() == IOrder.State.FILLED) {
                    outstandingOrder = order;
                    // this.console.getOut().println("onStart::Order=" +
                    // order.getLabel());
                }
            }

            this.isOrderSubmissionAllowed = outstandingOrder != null ? false
                    : true;
        }
    }
    
    public void onBar(Instrument instrument, Period period, IBar askbar,
            IBar bidbar) throws JFException {

        // this.console.getOut().println("currentMarginUsage="+this.account.getUseOfLeverage());

        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(bidbar.getTime());
        int militaryTime = calendar.get(Calendar.HOUR_OF_DAY) * 100
                + calendar.get(Calendar.MINUTE);

        if (period == this.myPeriod && bidbar.getVolume() > this.minVolume) {
            // && militaryTime < this.closeTime
            // && militaryTime > this.openTime) {

            if (instrument == this.myInstrument) {

                // IBar previousBar = this.history.getBar(instrument,
                // myPeriod,OfferSide.BID, 1);

                double atr = this.indicators.atr(instrument, this.myPeriod,
                        OfferSide.BID, this.ATRTimePeriod, 1);

                double SMAF = this.indicators.sma(myInstrument, myPeriod,
                        OfferSide.BID, IIndicators.AppliedPrice.CLOSE,
                        this.SMAFast, 1);
                double SMAS = this.indicators.sma(myInstrument, myPeriod,
                        OfferSide.BID, IIndicators.AppliedPrice.CLOSE,
                        this.SMASlow, 1);

                // DI
                int DICrossing = 0;

                double[] plusDi = new double[2];
                double[] minusDi = new double[2];
                for (int i = 1; i < this.DITimeDepth; i++) {

                    plusDi[0] = 0.0;
                    plusDi[1] = 0.0;

                    minusDi[0] = 0;
                    minusDi[1] = 0;

                    plusDi[0] = this.indicators.plusDi(myInstrument, myPeriod,
                            OfferSide.BID, this.DITimePeriod, i);
                    plusDi[1] = this.indicators.plusDi(myInstrument, myPeriod,
                            OfferSide.BID, this.DITimePeriod, i + 1);

                    minusDi[0] = this.indicators.minusDi(myInstrument,
                            myPeriod, OfferSide.BID, this.DITimePeriod, i);
                    minusDi[1] = this.indicators.minusDi(myInstrument,
                            myPeriod, OfferSide.BID, this.DITimePeriod, i + 1);

                    if (plusDi[0] > minusDi[0] && plusDi[1] < minusDi[1]) {
                        DICrossing = 1;
                    } else if (plusDi[0] < minusDi[0] && plusDi[1] > minusDi[1]){
                        DICrossing = -1;
                    } else {
                        DICrossing = 0;
                    }

                    if (DICrossing != 0) {
                        break;
                    }
                }

                int NEW_ORDER = 0;

                // Low volatility regime
                // this.console.getOut().println("atr="+atr);
                if (atr < this.ATRThreshold) {

                    if (SMAF > SMAS && DICrossing == 1) {
                        NEW_ORDER = 1;
                    } else if (SMAF < SMAS && DICrossing == -1) {
                        NEW_ORDER = -1;
                    }

                } else { // High volatility regime

                    double sPrice = this.indicators.ema(myInstrument, myPeriod,
                            OfferSide.BID, IIndicators.AppliedPrice.CLOSE,
                            this.defaultSmoothing, 1);

                    if (sPrice > SMAF && DICrossing == 1) {
                        NEW_ORDER = 1;
                    } else if (sPrice < SMAF && DICrossing == -1) {
                        NEW_ORDER = -1;
                    }
                }

                // this.console.getOut().println("NEW_ORDER=" + NEW_ORDER);

                double[] oDetails = this.getOrderDetails(NEW_ORDER, atr);

                if (NEW_ORDER == 1) {
                    if (this.isOrderSubmissionAllowed) {

                        this.outstandingOrder = this.engine.submitOrder(this
                                .getLabel(), this.myInstrument,
                                OrderCommand.BUY, oDetails[0], 0.0,
                                this.mySlippage, oDetails[1], oDetails[2]);

                        this.outstandingOrder.waitForUpdate();

                    } else {
                        if (!this.outstandingOrder.isLong()) {
                            this.outstandingOrder.close();
                            
                            //this.console.getOut().println("NEW_ORDER is 1 => outstanding short closed");
                            
                            IMessage update = this.outstandingOrder
                                    .waitForUpdate();

                            if (update.getType() == IMessage.Type.ORDER_CLOSE_OK) {
                                this.outstandingOrder = this.engine
                                        .submitOrder(this.getLabel(),
                                                this.myInstrument,
                                                OrderCommand.BUY, oDetails[0],
                                                0.0, this.mySlippage,
                                                oDetails[1], oDetails[2]);

                                this.outstandingOrder.waitForUpdate();
                            }

                        }
                    }
                } else if (NEW_ORDER == -1) {

                    if (this.isOrderSubmissionAllowed) {

                        this.outstandingOrder = this.engine.submitOrder(this
                                .getLabel(), this.myInstrument,
                                OrderCommand.SELL, oDetails[0], 0.0,
                                this.mySlippage, oDetails[1], oDetails[2]);

                        this.outstandingOrder.waitForUpdate();

                    } else {
                        if (this.outstandingOrder.isLong()) {
                            
                            //this.console.getOut().println("NEW_ORDER is -1 => outstanding long closed");
                            
                            this.outstandingOrder.close();

                            IMessage update = this.outstandingOrder
                                    .waitForUpdate();

                            if (update.getType() == IMessage.Type.ORDER_CLOSE_OK) {
                                this.outstandingOrder = this.engine
                                        .submitOrder(this.getLabel(),
                                                this.myInstrument,
                                                OrderCommand.SELL, oDetails[0],
                                                0.0, this.mySlippage,
                                                oDetails[1], oDetails[2]);

                                this.outstandingOrder.waitForUpdate();
                            }

                        }
                    }
                }
            }
        }
    }

    public void onMessage(IMessage message) throws JFException {
        IOrder order = null;

        switch (message.getType()) {

        case ORDER_FILL_OK:
            order = message.getOrder();
            outstandingOrder = order;

            this.isOrderSubmissionAllowed = false;
            this.console.getOut().println(
                    "ORDER_FILLED: " + order.getLabel() + ";"
                            + order.getOrderCommand() + ";"
                            + order.getStopLossPrice() + ";"
                            + order.getTakeProfitPrice());

            break;
        case ORDER_CLOSE_OK:
            order = message.getOrder();

            outstandingOrder = null;
            double pnlPips = order.getProfitLossInPips();
            this.isOrderSubmissionAllowed = true;
            this.console.getOut().println(
                    "ORDER_CLOSED: " + order.getLabel() + ";"
                            + order.getOrderCommand() + ";PiPs=" + pnlPips
                            + ";PnL=" + order.getProfitLossInAccountCurrency());

            if (pnlPips > 20.0) {
                if (!((this.myLeverage + this.leverageStep) > this.maxLeverage)) {
                    this.myLeverage = this.myLeverage + this.leverageStep;
                }
            } else if (pnlPips < -10) {
                if (!((this.myLeverage - this.leverageStep) < this.minLeverage)) {
                    this.myLeverage = this.myLeverage - this.leverageStep;
                }
            }

            break;
            
        default:
            //this.console.getOut().println(message.getContent());
        }
    }

    public void onAccount(IAccount account) throws JFException {
        this.account = account;
        this.marginCutLevel = this.account.getMarginCutLevel();
    }

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

    public void onStop() throws JFException {
        
        List<IOrder> orders = engine.getOrders(myInstrument);

        if (orders.size() > 0) {
            for (IOrder order : orders) {
                if (order.getState() == IOrder.State.FILLED) {
                    order.close();
                    order.waitForUpdate();
                }
            }
        }
    }

    private String getLabel() {
        long time = new java.util.Date().getTime();
        return (this.labelSuffix + "_" + myInstrument.name() + "_" + time);
    }

    /**
     * 
     * @param side
     * @param atr
     * @return 0- Amount; 1- Stop Limit; 2- Take Profit
     * @throws JFException
     */
    private double[] getOrderDetails(int side, double atr) throws JFException {
        double[] oDetails = new double[3];

        oDetails[0] = (this.account.getEquity() * this.myLeverage) / 1000000.0;
        if (oDetails[0] < this.minAmllowedAmount) {
            oDetails[0] = this.minAmllowedAmount;
        }

        oDetails[1] = this.ATRMultiplier * atr * 10000.0;
        oDetails[2] = Math.round((this.history.getLastTick(myInstrument)
                .getBid() + side * this.defaultTP) * 100000.0) / 100000.0;

        if (oDetails[1] < this.minSL) {
            oDetails[1] = Math.round((this.history.getLastTick(myInstrument)
                    .getBid() - side * minSL) * 100000.0) / 100000.0;
        } else if (oDetails[1] > this.maxSL) {
            oDetails[1] = Math.round((this.history.getLastTick(myInstrument)
                    .getBid() - side * maxSL) * 100000.0) / 100000.0;
        } else {
            oDetails[1] = Math.round((this.history.getLastTick(myInstrument)
                    .getBid() - side * oDetails[1]) * 100000.0) / 100000.0;
        }

        return oDetails;
    }
}