package jforex.strategies.indicators;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;      
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.IIndicators;
import com.dukascopy.api.indicators.IIndicator;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.GregorianCalendar;


public class TwoBarStratmodifytrailing implements IStrategy {
    
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private int counter = 0;
    private IOrder order;
    private IIndicators indicators;
    //indicators
    @Configurable("MA period")
    public int maPeriod = 21;
   /* @Configurable("MA type")
    public int maType = MaType.SMA;*/
    @Configurable("ATR period")
    public int atrPeriod = 12;
    
    //price  
    @Configurable("Take profit pips")
    public int takeProfitPips = 50;
    @Configurable("Stop loss pips")
    public int stopLossPips = 25;
    @Configurable("Pips above p")
    public int pipsAbove = 0;
    @Configurable("Pips below p")
    public int pipsBelow = 0;
    @Configurable("Order expire candle count")
    public int orderExpireCandles = 3;
    //instrument    
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Period")
    public Period selectedPeriod = Period.FIFTEEN_MINS;
    @Configurable("Slippage")
    public double slippage = 0;
    @Configurable("Amount")
    public double amount = 0.02;
    //trailing
    @Configurable("Trailing trigger pips")
    public int trailingPips = 0;
    @Configurable("Stop loss ATR multiplier")
    public double stopLossATRMult = 5;
    @Configurable("Take profit ATR multiplier")
    public double takeProfitATRMult = 5;
    @Configurable("Trailing stop ATR multiplier")
    public double trailingStopATRMult = 5;

    @Override
    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.engine = context.getEngine();
        this.indicators = context.getIndicators();
    }
    
    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        if (period != this.selectedPeriod || instrument != this.instrument) {
            return;
        }
        
        if (!isActive(order)) {
            order = null;
        }
        if(order != null && order.getState().equals(IOrder.State.OPENED)) {
            printTime(order.getCreationTime()); 
            printTime(askBar.getTime());
            printTime(order.getCreationTime() + (period.getInterval() * orderExpireCandles));
            if(order.getCreationTime() + (period.getInterval() * orderExpireCandles) <= askBar.getTime()) {
                order.close();
                order = null;
            }
        }      

         boolean sellSign = false;
        boolean buySign = false;
        double buyPrice = 0.0, sellPrice = 0.0;
        double ma150 = indicators.ma(instrument,selectedPeriod,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,0);
        double ma151 = indicators.ma(instrument,selectedPeriod,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,1);    
        
        // SIGNALS        
        //IBar askBar3 = history.getBar(instrument, period, OfferSide.ASK, 3);
        IBar askBar2 = history.getBar(instrument, period, OfferSide.ASK, 2);
        IBar askBar1 = history.getBar(instrument, period, OfferSide.ASK, 1);
        
        
        
        // three green candles
        if(askBar1.getOpen() < askBar1.getClose() && askBar2.getOpen() < askBar2.getClose() && askBar2.getHigh() < askBar1.getClose() && ma150 > ma151 /* && askBar3.getOpen() < askBar3.getClose()*/) {
            buySign = true;
            buyPrice = askBar.getClose() + askBar1.getLow() - askBar2.getHigh() - getPipPrice(pipsBelow);            
        }
        
        //IBar bidBar3 = history.getBar(instrument, period, OfferSide.BID, 3);
        IBar bidBar2 = history.getBar(instrument, period, OfferSide.BID, 2);
        IBar bidBar1 = history.getBar(instrument, period, OfferSide.BID, 1);
        
        // three red candles
        if(bidBar1.getOpen() > bidBar1.getClose() && bidBar2.getOpen() > bidBar2.getClose() && bidBar2.getLow () > bidBar1.getClose() && ma150 < ma151 /*&& bidBar3.getOpen() > bidBar3.getClose()*/) {
            sellSign = true;
            sellPrice = bidBar.getClose() + bidBar1.getHigh() - bidBar2.getLow() + getPipPrice(pipsAbove);
        }
        
        // PLACE ORDER        
        if (buySign) {
            if (order == null || !order.isLong()) {
                closeOrder(order);
                order = submitOrder (OrderCommand.BUYLIMIT, buyPrice, bidBar.getClose());
            }

        } else if (sellSign) {
            if (order == null || order.isLong()) {
                closeOrder(order);
                order = submitOrder(OrderCommand.SELLLIMIT, sellPrice, askBar.getClose());
            }
        }
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (instrument != this.instrument) {
            return;
        }
        if (!isActive(order)) {
            order = null;
        }
        double atr = indicators.atr(instrument, selectedPeriod, OfferSide.BID, atrPeriod, 1);
        double stopLossPips = atr * trailingStopATRMult / instrument.getPipValue();
        updateTrailingStopLoss(tick, order, trailingPips, getRoundedPips(stopLossPips));
    }

    private IOrder submitOrder(OrderCommand orderCmd, double price) throws JFException {

        ITick tick = history.getLastTick(instrument);
        
        double atr = indicators.atr(instrument, selectedPeriod, OfferSide.BID, atrPeriod, 1);
        double stopLossPrice = 0.0, takeProfitPrice = 0.0, atrSL, atrTP, tp, sl; 
        

        atrSL = atr * stopLossATRMult;
        sl = getPipPrice(stopLossPips);

        atrTP = atr * takeProfitATRMult;
        tp = getPipPrice(stopLossPips);


        // Calculating order price, stop loss and take profit prices
        if (orderCmd == OrderCommand.BUYLIMIT) {
            stopLossPrice = price - Math.max(sl, atrSL);
            takeProfitPrice = price + Math.max(tp, atrTP);
            
        } else {
            stopLossPrice = price + Math.max(sl, atrSL);
            takeProfitPrice = price - Math.max(tp, atrTP);
            }
        

      return engine.submitOrder(getLabel(instrument), instrument, orderCmd,amount, price, slippage, stopLossPrice, takeProfitPrice);
             
    }

    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 double getPipPrice(double pips) {
        return pips * this.instrument.getPipValue();
    }

    private String getLabel(Instrument instrument) {
        String label = instrument.name();
        label = label + (counter++);
        label = label.toUpperCase();
        return label;
    }
    public boolean updateTrailingStopLoss(ITick tick, IOrder pOrder, double pTriggerPips, double pStopLossPips) throws JFException {
        boolean triggered = false;
        if (pTriggerPips > 0 && pStopLossPips > 0
                && pOrder != null && pOrder.getState() == IOrder.State.FILLED) {
            double newStop;
            double openPrice = pOrder.getOpenPrice();
            double currentStopLoss = pOrder.getStopLossPrice();

            // (START) trailing stop loss is activated when price is higher than oper price + trailingTrigger pips
            // (TRAILING STOP) if price moves further up (for BUY order), stop loss is updated to stopLossPips

            if (pOrder.isLong()) { // long side order                
                if (tick.getBid() > currentStopLoss + getPipPrice(pStopLossPips)
                        && tick.getBid() > openPrice + getPipPrice(pTriggerPips)) {
                    // trailing stop loss
                    newStop = tick.getBid() - getPipPrice(pStopLossPips);
                    newStop = (new BigDecimal(newStop)).setScale(instrument.getPipScale(), BigDecimal.ROUND_HALF_UP).doubleValue();

                    if (currentStopLoss != newStop) {
                        pOrder.setStopLossPrice(newStop);
                        triggered = true;
                    }
                }

            } else { // short side order                
                if (tick.getAsk() < currentStopLoss - getPipPrice(pStopLossPips)
                        && tick.getAsk() < openPrice - getPipPrice(pTriggerPips)) {
                    // trailing stop loss
                    newStop = tick.getAsk() + getPipPrice(pStopLossPips);
                    newStop = (new BigDecimal(newStop)).setScale(instrument.getPipScale(), BigDecimal.ROUND_HALF_UP).doubleValue();

                    if (currentStopLoss != newStop) {
                        pOrder.setStopLossPrice(newStop);
                        triggered = true;
                    }
                }
            }
        }
        return triggered;
    }
    public boolean timeIsEqual(long time, int hour, int min){
        Calendar cal = new GregorianCalendar();
        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
        cal.setTimeInMillis(time);
        cal.set(Calendar.HOUR_OF_DAY, hour);
        cal.set(Calendar.MINUTE, min);

        Calendar cal2 = new GregorianCalendar();
        cal2.setTimeZone(TimeZone.getTimeZone("GMT"));
        cal2.setTimeInMillis(cal.getTimeInMillis());
        cal2.add(Calendar.MINUTE, 5);

        if(cal.getTimeInMillis() <= time
                && time <= cal2.getTimeInMillis() ) {
            return true;
        }
        return false;
    }
    
    private double getRoundedPrice(double price) {
        BigDecimal bd = new BigDecimal(price);
        bd = bd.setScale(instrument.getPipScale() + 1, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    private double getRoundedPips(double pips) {
        BigDecimal bd = new BigDecimal(pips);
        bd = bd.setScale(1, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    public void onMessage(IMessage message) throws JFException {
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onStop() throws JFException {
    }

    /**************** debug print functions ***********************/
    private void print(Object... o) {
        for (Object ob : o) {
            //console.getOut().print(ob + "  ");
            if (ob instanceof Double) {
                print2(toStr((Double) ob));
            } else if (ob instanceof double[]) {
                print((double[]) ob);
            } else if (ob instanceof double[]) {
                print((double[][]) ob);
            } else if (ob instanceof Long) {
                print2(toStr((Long) ob));
            } else if (ob instanceof IBar) {
                print2(toStr((IBar) ob));
            } else {
                print2(ob);
            }
            print2(" ");
        }
        console.getOut().println();
    }

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

    private void print2(Object o) {
        console.getOut().print(o);
    }
    
    private void print(double d) {
        print(toStr(d));
    }

    private void print(double[] arr) {
        print(toStr(arr));
    }

    private void print(double[][] arr) {
        print(toStr(arr));
    }
    private void print(IBar bar) {
        print(toStr(bar));
    }

    private void printIndicatorInfos(IIndicator ind) {
        for (int i = 0; i < ind.getIndicatorInfo().getNumberOfInputs(); i++) {
            print(ind.getIndicatorInfo().getName() + " Input " + ind.getInputParameterInfo(i).getName() + " " + ind.getInputParameterInfo(i).getType());
        }
        for (int i = 0; i < ind.getIndicatorInfo().getNumberOfOptionalInputs(); i++) {
            print(ind.getIndicatorInfo().getName() + " Opt Input " + ind.getOptInputParameterInfo(i).getName() + " " + ind.getOptInputParameterInfo(i).getType());
        }
        for (int i = 0; i < ind.getIndicatorInfo().getNumberOfOutputs(); i++) {
            print(ind.getIndicatorInfo().getName() + " Output " + ind.getOutputParameterInfo(i).getName() + " " + ind.getOutputParameterInfo(i).getType());
        }
        console.getOut().println();
    }

    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") {

            {
                setTimeZone(TimeZone.getTimeZone("GMT"));
            }
        };
        return sdf.format(time);
    }
    
    private String toStr(IBar bar) {
        return toStr(bar.getTime()) + "  O:" + bar.getOpen() + " C:" + bar.getClose() + " H:" + bar.getHigh() + " L:" + bar.getLow();
    }

    private void printTime(Long time) {
        console.getOut().println(toStr(time));
    }

}
