import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
import java.util.Vector;
import java.util.concurrent.TimeUnit;

import com.dukascopy.api.Configurable;
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.IHistory;
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.JFUtils;
import com.dukascopy.api.OfferSide;
import com.dukascopy.api.Period;

public class Grids_Hedge_Merge_GlobalShift_V2 implements IStrategy {

    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IAccount account;
    private int counter = 0;
    private List<IOrder> longOrders = new LinkedList<IOrder>();
    private List<IOrder> shortOrders = new LinkedList<IOrder>();

    private JFUtils utils;
    public int maxLongCount = 1;
    public int maxShortCount = 1;
    public Period selectedPeriod = Period.TEN_MINS;
    public OfferSide offerSide = OfferSide.BID;


    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;

    @Configurable("Step pips")
    public double step = 5;
    
    @Configurable("Slippage")
    public double slippage = 2;
   
    @Configurable("Amount")
    public double amount = 0.1;

    @Configurable("Pozitve Global Shift")
    public double tpProfit = 1000;
    @Configurable("Negative Global Shift")
    public double ngProfit = 0;

    private ITick previousTick = null;
    private double balance;

    @Override
    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        context.getIndicators();
        this.setHistory(context.getHistory());
        this.engine = context.getEngine();

        balance = context.getAccount().getEquity();
        console.getOut().println("Initial balance = " + (new DecimalFormat("#.#######")).format(balance)+" $");

    }

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

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (instrument != this.instrument) {
            return;
        }

        Vector<IOrder> newOrders;
        Vector<IOrder> removeOrders;

        removeOrders = new Vector<IOrder>(maxLongCount);
        for (IOrder order : longOrders) {
            if (!isActive(order)) {
                removeOrders.add(order);
            }
        }
        longOrders.removeAll(removeOrders);

        removeOrders = new Vector<IOrder>(maxShortCount);
        for (IOrder order : shortOrders) {
            if (!isActive(order)) {
                removeOrders.add(order);
            }
        }
        shortOrders.removeAll(removeOrders);

        boolean buySign = false;
        boolean sellSign = false;

        if (previousTick == null) {
            previousTick = tick;
            return;
        }
        /////////////////////////////////////////////////////////////////////
               if ((tick.getBid() > previousTick.getBid()+step*0.0001) && (balance >= history.getEquity()))  {
               sellSign = true; previousTick = null;
//         balance = history.getEquity();
//        console.getOut().println("UPTREND balance = " + (new DecimalFormat("#.#######")).format(balance)+" $");

        } else if ((tick.getAsk() < previousTick.getAsk()-step*0.0001) && (balance >= history.getEquity())) {
               buySign = true; previousTick = null;
//         balance = history.getEquity();
//        console.getOut().println("DOWNTREND balance = " + (new DecimalFormat("#.#######")).format(balance)+" $");
        }
        /////////////////////////////////////////////////////////////////////////


        // PLACE ORDERS
        if (buySign) {

            if (longOrders.size() == 0) {
                longOrders.add(submitOrder(OrderCommand.BUY));

            } else {

                // for same signal direction, losing position
                // open new order and merge with it
                removeOrders = new Vector<IOrder>();
                newOrders = new Vector<IOrder>();

                for (IOrder order : longOrders) {
                    if (order.getProfitLossInPips() < 0) {
                        IOrder newOrder = submitOrder(OrderCommand.BUY);

                        newOrder.waitForUpdate(2, TimeUnit.SECONDS);

                        // order is FILLED on successful merge with amount > 0
                        if (newOrder.getState() == IOrder.State.FILLED) {
                            IOrder mergedOrder = engine.mergeOrders(getLabel(instrument), order, newOrder);
                            mergedOrder.waitForUpdate(2, TimeUnit.SECONDS);

                            // order is FILLED on successful merge with amount > 0
                            if (mergedOrder.getState() == IOrder.State.FILLED) {
                                removeOrders.add(order);
                                newOrders.add(mergedOrder);
                            }
                        }
                    }
                }
                longOrders.removeAll(removeOrders);
                longOrders.addAll(newOrders);
            }

            // for opposite signal direction, close winning positions
            removeOrders = new Vector<IOrder>();
            for (IOrder order : shortOrders) {
                if (order.getProfitLossInPips() > 0) {
                    closeOrder(order);
                    removeOrders.add(order);
                }
            }
            shortOrders.removeAll(removeOrders);

            // for opposite signal direction, open hedge position
            newOrders = new Vector<IOrder>();
            for (IOrder order : shortOrders) {
                if (order.getProfitLossInPips() <= 0) {
                    if (longOrders.size() + newOrders.size() < maxLongCount) {
                        newOrders.add(submitOrder(OrderCommand.BUY));
                    }
                }
            }
            longOrders.addAll(newOrders);

        } else if (sellSign) {

            if (shortOrders.size() == 0) {
                shortOrders.add(submitOrder(OrderCommand.SELL));

            } else {

                // for same signal direction, losing position
                // open new order and merge with it
                removeOrders = new Vector<IOrder>();
                newOrders = new Vector<IOrder>();

                for (IOrder order : shortOrders) {
                    if (order.getProfitLossInPips() < 0) {

                        IOrder newOrder = submitOrder(OrderCommand.SELL);
                        newOrder.waitForUpdate(2, TimeUnit.SECONDS);

                        // order is FILLED on successful merge with amount > 0
                        if (newOrder.getState() == IOrder.State.FILLED) {
                            IOrder mergedOrder = engine.mergeOrders(getLabel(instrument), order, newOrder);
                            mergedOrder.waitForUpdate(2, TimeUnit.SECONDS);

                            // order is FILLED on successful merge with amount > 0
                            if (mergedOrder.getState() == IOrder.State.FILLED) {
                                removeOrders.add(order);
                                newOrders.add(mergedOrder);
                            }
                        }
                    }
                }
                shortOrders.removeAll(removeOrders);
                shortOrders.addAll(newOrders);
            }

            // for opposite signal direction, close winning positions
            removeOrders = new Vector<IOrder>();
            for (IOrder order : longOrders) {
                if (order.getProfitLossInPips() > 0) {
                    closeOrder(order);
                    removeOrders.add(order);
                }
            }
            longOrders.removeAll(removeOrders);

            // for opposite signal direction, open hedge position
            newOrders = new Vector<IOrder>();
            for (IOrder order : longOrders) {
                if (order.getProfitLossInPips() <= 0) {
                    if (shortOrders.size() + newOrders.size() < maxShortCount) {
                        newOrders.add(submitOrder(OrderCommand.SELL));
                    }
                }
            }
            shortOrders.addAll(newOrders);

        }
    }

    private IOrder submitOrder(OrderCommand orderCmd) throws JFException {
        return engine.submitOrder(getLabel(instrument), instrument, orderCmd, amount, 0, slippage, 0, 0);
    }

    private void closeOrder(IOrder order) throws JFException {
        if (order != null && isActive(order)) {
            order.close();
        }
    }

    private boolean isActive(IOrder order) throws JFException {
        if (order != null && order.getState() != IOrder.State.CLOSED && order.getState() != IOrder.State.CREATED
                && order.getState() != IOrder.State.CANCELED) {
            return true;
        }
        return false;
    }

    private String getLabel(Instrument instrument) {
        String label = instrument.name();
        label = label + (counter++);
        label = label.toUpperCase();
        return label;
    }

    public void onMessage(IMessage message) throws JFException {
        //print(message);
    }

    public void onStop() throws JFException {
    }

    private void print(Object o) {
        console.getOut().println(o);
    }

    public static String toStr(double[] arr) {
        String str = "";
        for (int r = 0; r < arr.length; r++) {
            str += "[" + r + "] " + (new DecimalFormat("#.#######")).format(arr[r]) + "; ";
        }
        return str;
    }

    public static String toStr(double[][] arr) {
        String str = "";
        if (arr == null) {
            return "null";
        }
        for (int r = 0; r < arr.length; r++) {
            for (int c = 0; c < arr[r].length; c++) {
                str += "[" + r + "][" + c + "] " + (new DecimalFormat("#.#######")).format(arr[r][c]);
            }
            str += "; ";
        }
        return str;
    }

    public String toStr(double d) {
        return (new DecimalFormat("#.#######")).format(d);
    }

    public String toStr(Long time) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
            private static final long serialVersionUID = 1L;

            {
                setTimeZone(TimeZone.getTimeZone("GMT"));
            }
        };
        return sdf.format(time);
    }

    public IHistory getHistory() {
        return history;
    }

    public void setHistory(IHistory history) {
        this.history = history;
    }

    public IAccount getAccount() {
        return account;
    }

    public void setAccount(IAccount account) {
        this.account = account;
    }

    public JFUtils getUtils() {
        return utils;
    }

    public void setUtils(JFUtils utils) {
        this.utils = utils;
    }
    private double getPipPrice(double pips) {
        return pips * this.instrument.getPipValue();
    }
    public void onAccount(IAccount account) throws JFException {

        double pozitivebalance = balance + tpProfit;
        double negativebalance = balance - ngProfit;

        if (tpProfit > 0) {
        if (pozitivebalance < history.getEquity()) {
            
            balance = account.getEquity();
            console.getOut().println("UP GLOBAL balance = " + (new DecimalFormat("#.#######")).format(balance)+"$");
            }}
       if (ngProfit > 0) {        
        if (negativebalance > history.getEquity()) {
            
            balance = account.getEquity();
            console.getOut().println("DOWN GLOBAL balance " + (new DecimalFormat("#.#######")).format(balance)+"$");
            }}
    }
}
