package jforex.strategies;
 
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.util.ArrayList;
import java.util.List;
import java.util.Currency;
import java.util.HashSet;
import java.util.Set;
 
public class strator implements IStrategy {
     
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private int counter = 0;
    private IOrder order;
    private IIndicators indicators;
	private IContext context;
    private IAccount account;
     // ma indicators
   
     @Configurable("cci")
    public int cciperiod = 8;
   
    @Configurable("Take profit multiplier")
    public double takeProfitMult = 3;
    @Configurable("Risk")
    public double risk = 2;
    //price
  
    @Configurable("Pips above p")
    public int pipsAbove = 2;
    @Configurable("Pips below p")
    public int pipsBelow = 2;
    
    
 
  
 
  
     
    //instrument   
    @Configurable("Instrument")
    public Instrument instrument = Instrument.XAUUSD;
    @Configurable("Period")
    public Period selectedPeriod = Period.ONE_HOUR;
    @Configurable("Slippage")
    public double slippage = 1;
    
    @Configurable("Offer side")
    public OfferSide offerSide = OfferSide.ASK; 
 //   @Configurable("Amount")
 //   public double amount = 0.05;
     
    
   
     
     
    @Override
    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.engine = context.getEngine();
        this.indicators = context.getIndicators();
        this.account = context.getAccount();
        this.context = context;     
    }
     
    @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;
        }
        
      
        
            
 
        boolean sellSign = false;
        boolean buySign = false;
        double buyPrice = 0.0, sellPrice = 0.0;
        
        // SIGNALS       
         
        IBar currBar = history.getBar(instrument, period, offerSide,1);
        IBar prevBar = history.getBar(instrument, period, offerSide,2);
		
		
        double cci1 = indicators.cci(instrument,period,offerSide,cciperiod,1);
        double cci2 = indicators.cci(instrument,period,offerSide,cciperiod,2);
		
		
         
		double 	trix1 = indicators.trix(instrument,period, offerSide, IIndicators.AppliedPrice.CLOSE, 30,1); 
		double 	trix2 = indicators.trix(instrument,period, offerSide, IIndicators.AppliedPrice.CLOSE, 30,2);
		
       
               
		
        int ccibuy = 100;
        int ccisell = -100;
		int ccimax = 65;
		int ccimin = -65;
		int ccizero = 0;
     
       //console.getOut().println("toleranceMinH: " + toleranceMinH + "toleranceMinL:" + toleranceMinL);
       //for buy
         if(
			 
           cci1 > ccizero
           && cci2 < ccizero
		   && trix1 > trix2	
           ){
              
            buySign = true;
            buyPrice = currBar.getClose();
            }
     
       //for sell
        if (
			 
            cci1 < ccizero
           && cci2 > ccizero
		   && trix1 < trix2
           
           ){
            
            sellSign = true;
            sellPrice = currBar.getClose();   
            }
        
    console.getOut().println("buy: " + buySign + "sell:" + sellSign);
         
           
        // PLACE ORDER       
         
          
         
        if (buySign) {
                if (order == null || !order.isLong()){
          closeOrder(order);
          
          double stopLossPrice = 0.0;
		
         stopLossPrice = prevBar.getLow() - getPipPrice(pipsBelow, instrument);
		
         order = submitOrder (OrderCommand.BUY, buyPrice, stopLossPrice);
          
        //  trailingPips = currBar.getLow(); 
           
            }
      } 
        if (sellSign) {
                   if(order == null || order.isLong()){
         closeOrder(order);
          
          double stopLossPrice = 0.0;
			
         stopLossPrice = prevBar.getHigh() + getPipPrice (pipsAbove, instrument);
           
          order = submitOrder (OrderCommand.SELL, sellPrice, stopLossPrice);
            
         //  trailingPips = currBar.getHigh();
         
            
         }
        }
		//close open position
			
			/*if (order != null && order.isLong() && cci1 < ccimax && cci2 > ccimax){
				closeOrder(order);
			}else if (order != null && !order.isLong() && cci1 > ccimin && cci2 < ccimin){
				closeOrder(order);
			}*/
      }
    
     
    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;
        ITick tick = history.getLastTick(instrument);
        
        double stopLossDiff = price - stopLossPrice;
        takeProfitPrice = price + stopLossDiff * takeProfitMult;
		
		double stopLossPips = Math.abs(stopLossDiff) / instrument.getPipValue();
		
	//	double stopLossPips = 300;
		
		console.getOut().println("stoplossPips: " + stopLossPips);
		console.getOut().println("stoplossDiff: " + stopLossDiff);
		double amount = XRate.getAmount(context, account, instrument, risk / 100, stopLossPips);
        return engine.submitOrder(getLabel(instrument), instrument, orderCmd, amount, price, slippage, getRoundedPrice(stopLossPrice), getRoundedPrice(takeProfitPrice));
       // return engine.submitOrder(getLabel(instrument), instrument, orderCmd, amount, price, slippage, getRoundedPrice(stopLossPrice), getRoundedPrice(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, Instrument instr) {
        return pips * instr.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));
    }
 

  static class XRate {

        private IHistory history;
        private IContext context;
        private IConsole console;

        public XRate(IContext context) {
            this.history = context.getHistory();
            this.context = context;
            this.console = context.getConsole();

            System.out.println(history + " " + context + " " + console);
        }
        private String[] mainCurrencies = {"USD", "GBP", "AUD", "CAD", "CHF", "GBP", "HKD", "NZD"};

        private double getExchangeRate(Currency c1, Currency c2) throws JFException {

            Instrument instr = getInstrument(c1.getCurrencyCode(), c2.getCurrencyCode());

            if (instr != null) {
                subscribe(instr);
                return getPrice(instr);
            } else {
                instr = getInstrument(c2.getCurrencyCode(), c1.getCurrencyCode());
                if (instr != null) {
                    subscribe(instr);
                    return 1 / getPrice(instr);
                }
            }

            double EURCUR1 = exchangeRateEUR(c1);
            double EURCUR2 = exchangeRateEUR(c2);
            return EURCUR2 / EURCUR1;
        }

        private double exchangeRateEUR(Currency CUR) throws JFException {

            if (CUR.equals(Currency.getInstance("EUR"))) {
                return 1;
            }

            Instrument instr2 = null;
            Instrument instr = getInstrument("EUR", CUR.getCurrencyCode());

            if (instr == null) {
                for (String mainCUR : mainCurrencies) {

                    if (mainCUR.equals(CUR.getCurrencyCode())) {
                        instr = getInstrument("EUR", mainCUR);
                        break;
                    }

                    instr = getInstrument(mainCUR, CUR.getCurrencyCode());
                    if (instr != null) {
                        instr2 = getInstrument("EUR", mainCUR);
                        if (instr2 == null) {
                            throw new JFException("Error in currency exchange rate calculation.");
                        }
                        break;
                    }
                }
            }

            if (instr != null) {
                if (instr2 == null) {
                    return getPrice(instr); // EUR <-> MAIN_CUR
                } else {
                    return getPrice(instr) * getPrice(instr2); // EUR <-> MAIN_CUR + MAIN_CUR <-> CUR
                }
            } else {
                if (CUR.equals(Currency.getInstance("XAU")) || CUR.equals(Currency.getInstance("XAG"))) {
                    instr = Instrument.EURUSD;
                    instr2 = getInstrument(CUR.getCurrencyCode(), "USD");
                } else {
                    throw new JFException("Error in currency exchange rate calculation.");
                }
                subscribe(instr);
                subscribe(instr2);

                // EURUSD * (1 / XXXUSD) = EURUSD * USDXXX = EURXXX
                return getPrice(instr) * (1 / getPrice(instr2)); // EUR <-> MAIN_CUR + MAIN_CUR <-> CUR
            }
        }

        private Instrument getInstrument(String c1, String c2) {

            if (Instrument.contains(c1 + "/" + c2)) {
                return Instrument.valueOf(c1 + c2);
            }
            return null;
        }

        private void subscribe(Instrument instr) throws JFException {
            Set<Instrument> instruments = context.getSubscribedInstruments();
            Set<Instrument> newInstr = new HashSet<Instrument>();
            if (!instruments.contains(instr)) {

                newInstr.add(instr);
                context.setSubscribedInstruments(newInstr);

                int i = 10;
                while (!context.getSubscribedInstruments().containsAll(newInstr) && i > 0) {
                    try {
                        console.getOut().println("Instruments not subscribed yet " + i);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        console.getOut().println(e.getMessage());
                    }
                    i--;
                }

            }
        }

        private double getPrice(Instrument instr) throws JFException {
            subscribe(instr);
            ITick tick = history.getLastTick(instr);
            return (tick.getAsk() + tick.getBid()) / 2;
        }

        static private double getAmount(IContext context, IAccount account, Instrument instrument, double risk, double slPips) throws JFException {
            return getAmount(context, account, instrument, account.getEquity(), risk, slPips);
        }

        static private double getAmount(IContext context, IAccount account, Instrument instrument, double riskAmount, double risk, double slPips) throws JFException {
            // !!! USD EUR and JPY in variable names are used only for simplicity
            // this method is universal and USD EUR and JPY can be any other currencies
            // EURJPY - instrument, USD account currency

            if (risk < 0.0 || risk > 1.0) {
                throw new JFException("Risk must be from 0.0 to 1.0.");
            }
            double riskUSD = riskAmount * risk;

            double pipJPY = instrument.getPipValue(); // USDJPY pip value in JPY
            XRate xrate = new XRate(context);

            double USDJPY = xrate.getExchangeRate(account.getCurrency(), instrument.getSecondaryCurrency());

            double riskJPY = riskUSD * USDJPY;
            double amountEUR = riskJPY / (slPips * pipJPY);

            return amountEUR / 1000000;
        }

        static void print(double d, IContext context) {
            context.getConsole().getOut().println((new DecimalFormat("#.#######")).format(d));
        }
    }

}
