package jforex.strategies.indicators;
 
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
 
import com.dukascopy.api.ITick;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.IIndicators;
import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.indicators.IIndicator;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
 
 
public class reversalfractal 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 = 50;
         
    @Configurable("Number of Fractal values back")
    public int valuesBack    = 4; 
    
    @Configurable("mabonne")
    public double mabonne = 1;
    
    //price
    @Configurable("Take profit pips")
    public int takeProfitPips = 100;
    @Configurable("stop loss pips")
    public int stopLossPips = 50;
    @Configurable("Pips above p")
    public int pipsAbove = 0;
    @Configurable("Pips below p")
    public int pipsBelow = 0;
    @Configurable("Order expire candle count")
    public int orderExpireCandles = 2;
    
  
  
  
  
     
    //instrument   
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Period")
    public Period selectedPeriod = Period.FIVE_MINS;
    @Configurable("Slippage")
    public double slippage = 1;
    @Configurable("Amount")
    public double amount = 0.05;
     
    
   //time to trade
     @Configurable("Open hour")
    public int openHour = 6;
    @Configurable("Open min")
    public int openMin = 30;
    @Configurable("Close hour")
    public int closeHour = 20;
    @Configurable("Close min")
    public int closeMin = 30;
     
     
    @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;
        
        // SIGNALS       
         
        IBar currBar = history.getBar(instrument, period, OfferSide.ASK,1);
        IBar prevBar = history.getBar(instrument, period, OfferSide.ASK,2);
        IBar prevBar1 = history.getBar(instrument, period, OfferSide.ASK,3);
        IBar prevBar2 = history.getBar(instrument, period, OfferSide.ASK,4);
        IBar prevBar3 = history.getBar(instrument, period, OfferSide.ASK,5);
        
         
         Double fractalMax = Double.NaN;
         Double fractalMin = Double.NaN;
         int i =0;
         while (fractalMax.isNaN() || (fractalMin.isNaN()) ){
            double[] fractal = indicators.fractal (instrument,period,OfferSide.BID,5,i);
            if (!Double.isNaN(fractal[0]))
               fractalMax = fractal[0];   
            if (!Double.isNaN(fractal[1]))
               fractalMin = fractal[1];   
            i++;
         }
         //console.getOut().println("fractal max: " + fractalMax + "fractal min:" + fractalMin);
         
            Double fractalMax10 = Double.NaN;
            Double fractalMin10 = Double.NaN;
            int maxCount = 0;
            int minCount = 0;
            int shift = 0;
            while (maxCount < valuesBack || minCount < valuesBack) {
                double[] fractal10 = indicators.fractal(instrument, period, OfferSide.BID, 5, shift);
                if (!Double.isNaN(fractal10[0]) && maxCount < valuesBack) {
                    fractalMax10 = fractal10[0];
                    maxCount++;
                }
                if (!Double.isNaN(fractal10[1]) && minCount < valuesBack) {
                    fractalMin10 = fractal10[1];
                    minCount++;
                }
                shift++;
            }
       
       //console.getOut().println("fractal max10: " + fractalMax10 + "fractal min10:" + fractalMin10);
		
		
        
               
		
        double ma1 = indicators.ma(instrument,period,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,1);
        double ma11 = indicators.ma(instrument,period,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,2);
		
		double maH1 = indicators.ma(instrument,Period.ONE_HOUR,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,1);
        double maH11 = indicators.ma(instrument,Period.ONE_HOUR,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,2);
		
		double marange1 = indicators.ma(instrument,period,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,1);
		double marange10 = indicators.ma(instrument,period,OfferSide.ASK,IIndicators.AppliedPrice.CLOSE,maPeriod,IIndicators.MaType.SMA,11);
		double resultmarange = Math.abs((marange1 - marange10)*1000);
		
       console.getOut().println("maH1: " + maH1 + "maH11:" + maH11);              
      
      
      
       //for buy
         if(prevBar1.getOpen() > prevBar1.getClose()
			&& prevBar.getHigh() < prevBar1.getOpen()
			&& prevBar.getClose() > prevBar1.getLow()
			&& currBar.getClose() > prevBar.getHigh()
			
			//&& currBar.getClose() > fractalMax
			//&& ma1 > ma11
			&& maH1 > maH11
			&& resultmarange > mabonne
           ){
             
            buySign = true;
            buyPrice = currBar.getHigh() + getPipPrice(pipsAbove);
            }
     
       //for sell
        if (prevBar1.getOpen() < prevBar1.getClose()
			&& prevBar.getLow() > prevBar1.getOpen()
			&& prevBar.getClose() < prevBar1.getHigh()
			&& currBar.getClose() < prevBar.getLow()
			//&& currBar.getClose() < fractalMin
			//&& ma1 < ma11
			&& maH1 < maH11
            && resultmarange > mabonne
            ){
            
            sellSign = true;
            sellPrice = currBar.getLow() - getPipPrice(pipsBelow);   
            }
        
      
          
           
        // PLACE ORDER       
         
          
         
        if (buySign) {
                if (order == null || !order.isLong()){
          closeOrder(order);
            if (isRightTime(askBar.getTime(), openHour, openMin, closeHour, closeMin)){
         order = submitOrder (OrderCommand.BUYSTOP, currBar.getHigh(), buyPrice);
            }
            }
      }
        if (sellSign) {
                   if(order == null || order.isLong()){
          closeOrder(order);
               if (isRightTime(bidBar.getTime(), openHour, openMin, closeHour, closeMin)){
          order = submitOrder (OrderCommand.SELLSTOP, currBar.getLow(), sellPrice);
              
            }
         }
        }
    
      }
    
     
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (instrument != this.instrument) {
            return;
         }
    }
 
  
     
   private IOrder submitOrder(OrderCommand orderCmd, double price, double stopLossPrice) throws JFException {
 
        double takeProfitPrice = 0.0;
        
 
        // stop loss and take profit
        if (orderCmd == OrderCommand.BUYSTOP) {
            if (takeProfitPips > 0) {
                takeProfitPrice = price + getPipPrice(takeProfitPips);
                stopLossPrice = price - getPipPrice(stopLossPips);
            }
        } else {
            if (takeProfitPips > 0) {
                takeProfitPrice = price - getPipPrice(takeProfitPips);
                stopLossPrice = price + getPipPrice(stopLossPips);
            }
        }
 
        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 * instrument.getPipValue();
    }
    
    private String getLabel(Instrument instrument) {
        String label = instrument.name();
        label = label + (counter++);
        label = label.toUpperCase();
        return label;
    }
     
     
    //calendar
     
    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;
    }
      
     //time to trade
      public boolean isRightTime(long time, int fromHour, int fromMin, int toHour, int toMin) {
        Calendar cal = new GregorianCalendar();
        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
        cal.setTimeInMillis(time);
        cal.set(Calendar.HOUR_OF_DAY, fromHour);
        cal.set(Calendar.MINUTE, fromMin);
  
        Calendar cal2 = new GregorianCalendar();
        cal2.setTimeZone(TimeZone.getTimeZone("GMT"));
        cal2.setTimeInMillis(time);
        cal2.set(Calendar.HOUR_OF_DAY, toHour);
        cal2.set(Calendar.MINUTE, toMin);
  
        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));
    }
 
}
