package jforex;

import com.dukascopy.api.Configurable;
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.OrderCommand;
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 java.math.BigDecimal;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;

public class TestOne_Weekend implements IStrategy{
    final OfferSide offerSide = OfferSide.BID;
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IIndicators indicators;
    private final IEngine.OrderCommand BuyLimit = IEngine.OrderCommand.BUYLIMIT;
    private final IEngine.OrderCommand SellLimit = IEngine.OrderCommand.SELLLIMIT;
    private final IEngine.OrderCommand Buy = IEngine.OrderCommand.BUY;
    private final IEngine.OrderCommand Sell = IEngine.OrderCommand.SELL;
    private int valiutCoff = 500;
    private double Balance;
    final int HIGH = 4;
    final int LOW = 6;
    long startTime;
    
    @Configurable("Risk Multiplier")
    public int riskMultiplier = 1;
    
   
    
    private final String myName3 = "myChanRev";
    private final Period myPeriod3 = Period.THIRTY_MINS;  
    private final Instrument myInstrument3 = Instrument.EURJPY;
    private IOrder myOrder3;
    
      
   
            
    private double getPercent(Instrument instrument){
        double per = 10;
        if(instrument == myInstrument3)
            per = 10;
        return per * riskMultiplier;
    }    
      
    @Override
    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.indicators = context.getIndicators();
        
        Set<Instrument> instruments = new HashSet<Instrument>();
        instruments.add(myInstrument3);
        context.setSubscribedInstruments(instruments, true);
        startTime = history.getTimeOfLastTick(myInstrument3);
        
        myOrder3 = checkOpenPos(myName3,myOrder3, myInstrument3);
       }

    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if(weekend(Calendar.SATURDAY)|| (weekend(Calendar.SUNDAY) && !isValidTime(21, 00, 24, 00)) )
            return;
        if(instrument == myInstrument3)
            myOrder3 = block(myName3, myInstrument3, myOrder3, myPeriod3);
        }
    
    private IOrder block(String label, Instrument instrument, IOrder order, Period period) throws JFException{
        IBar prevBar = history.getBar(instrument, period, offerSide, 1);
        ITick lastTick = history.getTick(instrument, 0);
        ITick prevTick = history.getTick(instrument, 1);
        IEngine.OrderCommand command = IEngine.OrderCommand.PLACE_OFFER;
        if(order != null)
            command = order.getOrderCommand();
        double currBid = lastTick.getBid();
        double prevBid = prevTick.getBid();
        
        int enChanPeriod = 20;
        int exChanPeriod = 15;
        
        double sl = 5.0;
        double tp = 20.0;
        double percent = getPercent(instrument);
        
        double[][] EntryChannel = indicators.donchian(instrument, period, offerSide, enChanPeriod, Filter.ALL_FLATS, 0, prevBar.getTime(), 1);
        double enHigh = EntryChannel[HIGH][0];
        double enLow = EntryChannel[LOW][0];
        double[][] ExitChannel = indicators.donchian(instrument, period, offerSide, exChanPeriod, Filter.ALL_FLATS, 0, prevBar.getTime(), 1);
        double exHigh = ExitChannel[HIGH][0];
        double exLow = ExitChannel[LOW][0];
        
        boolean buyExit=
            currBid > exHigh;
        boolean sellExit=
            currBid < exLow; 
        boolean buyEntry= !buyExit &&
            weekend(Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY) && isValidTime(20, 00, 22, 00) && currBid < enLow && prevBid >= enLow;
        boolean sellEntry= !sellExit &&
            weekend(Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY) && isValidTime(20, 00, 22, 00) && currBid > enHigh && prevBid <= enHigh;
        double buyEntryPrice= enLow;
        double sellEntryPrice= enHigh;
        double buyExitPrice= exHigh;
        double sellExitPrice= exLow;
        
        if(command == Buy && buyExit)
            closeOrder(order, buyExitPrice, "Buy Exit");
        if(command == Sell && sellExit)
            closeOrder(order, sellExitPrice, "Sell Exit");
                
        closeUnfilledOrder(instrument, order, period, tp, buyEntry, sellEntry, buyExit, sellExit);
                
        
        if(buyEntry){                     
            if(command == Sell)
                closeOrder(order, buyEntryPrice, "Buy Entry Exit");             
            order = orderExecute(order, label, instrument, BuyLimit, period, buyEntryPrice, sl, tp, percent);
        }
        else if(sellEntry){                
            if(command == Buy)
                closeOrder(order, sellEntryPrice, "Sell Entry Exit");                
            order = orderExecute(order, label, instrument, SellLimit, period, sellEntryPrice, sl, tp, percent);
        }              
        return order;
    }
    
       
    private IOrder orderExecute(IOrder order, String label, Instrument instrument, IEngine.OrderCommand command, Period period, double en, double s, double t, double p) throws JFException{
        IBar prevBar = history.getBar(instrument, period, offerSide, 1);
        String Label;
        boolean haveOrder = engine.getOrders().contains(order);
        double percent = p;    
        double range = prevBar.getHigh() - prevBar.getLow();
                
        if(!haveOrder && ((range > instrument.getPipValue() && range !=0) || range == 0)){               
             for(IOrder o : engine.getOrders(instrument)){
                if(o.getState() == IOrder.State.OPENED || o.getState() == IOrder.State.FILLED){
                    if(command == BuyLimit && (o.getOrderCommand() == IEngine.OrderCommand.BUY ||o.getOrderCommand() == IEngine.OrderCommand.BUYLIMIT)){
                        percent = percent*0.5;
                    }
                    if(command == SellLimit && (o.getOrderCommand() == IEngine.OrderCommand.SELL ||o.getOrderCommand() == IEngine.OrderCommand.SELLLIMIT)){
                        percent = percent*0.5;
                    }
                }
            }
            
            long time = history.getLastTick(instrument).getTime();
            List<IOrder> orders = history.getOrdersHistory(instrument, history.getBar(instrument, period, offerSide, 0).getTime(), time);
            if(orders != null) {
                for(IOrder or : orders) {
                    if(command == BuyLimit && or.getLabel().contains(getLabel(label, instrument, Buy)))        
                        return order;
                    else if(command == SellLimit && or.getLabel().contains(getLabel(label, instrument, Sell)))  {     
                        return order;                        
                    }
                }
            }
            
            if(command == BuyLimit){
                double entry = round(en, instrument.getPipScale() + 1);
                 double[] ATRPrev = indicators.atr(instrument, period, offerSide, 40, Filter.ALL_FLATS, 1, prevBar.getTime(), 0);
                 double atrPrev = ATRPrev[0];
                 double stop = round((atrPrev * s), instrument.getPipScale());
                 double currBid = history.getLastTick(instrument).getBid();
                 double SLexit = round((currBid - stop), instrument.getPipScale());
                 if(currBid-en > atrPrev || history.getLastTick(instrument).getAsk()-history.getLastTick(instrument).getBid() > atrPrev){
                    console.getOut().println("Cancel order due to spread to high");
                    return order;}
                 double amount = getAmount(instrument, period, s, p);
                 double vol;
                 double TP;
                 if(t != 20){
                    double tpExit = round((atrPrev * t), instrument.getPipScale());
                    TP = round((entry + tpExit), instrument.getPipScale());}
                 else TP = 20;
                
                Label = getLabel(label, instrument, BuyLimit);
                order = engine.submitOrder(Label, instrument, Buy, amount*1);
                order.waitForUpdate(2000);
                order.setStopLossPrice(SLexit);
                if(TP != 20)
                    order.setTakeProfitPrice(TP);
                console.getOut().println(Label + ": " + entry + " || Risk= " + stop + " || Amount= " + amount + "|| Percent=" + percent + "(" + p + ")");
                systemReport(label, instrument);
            }
            else if(command == SellLimit){                
                double entry = round(en, instrument.getPipScale() + 1);
                double[] ATRPrev = indicators.atr(instrument, period, offerSide, 40, Filter.ALL_FLATS, 1, prevBar.getTime(), 0);
                 double atrPrev = ATRPrev[0];
                 double stop = round((atrPrev * s), instrument.getPipScale());
                 double currBid = history.getLastTick(instrument).getBid();
                 double SLexit = round((currBid + stop), instrument.getPipScale());
                 double amount = getAmount(instrument, period, s, p);
                 double vol;
                 if(instrument == Instrument.XAUUSD) vol = 0.000001;
                 else vol = 0.001; 
                 double TP;
                 if(t != 20){
                    double tpExit = round((atrPrev * t), instrument.getPipScale());
                    TP = round((entry - tpExit), instrument.getPipScale());}
                 else TP = 20;
             
                Label = getLabel(label, instrument, SellLimit);
                order = engine.submitOrder(Label, instrument, Sell, amount*vol);
                order.waitForUpdate(2000);
                order.setStopLossPrice(SLexit, offerSide);
                if(TP != 0)
                    order.setTakeProfitPrice(TP);
                console.getOut().println(Label + ": " + entry + " || Risk= " + stop + " || Amount= " + amount + "|| Percent=" + percent + "(" + p + ")");
                systemReport(label, instrument);
            }  
        }
           
        return order;
    }
    
   private void closeUnfilledOrder(Instrument instrument, IOrder order, Period period, double t, boolean buyEntry, boolean sellEntry, boolean buyExit, boolean sellExit) throws JFException{
        boolean haveOrder = engine.getOrders().contains(order);
        if(haveOrder && order.getState() == IOrder.State.OPENED){
            IEngine.OrderCommand command = order.getOrderCommand();
            IBar prevBar = history.getBar(instrument, period, offerSide, 1);
            long barTime = prevBar.getTime();
            long orderTime = order.getCreationTime();
            double high = prevBar.getHigh();
            double low = prevBar.getLow();
            double SL = order.getStopLossPrice();
            double TP = 20;
            if(t != 20)
                TP = order.getTakeProfitPrice();
            
            if(command == BuyLimit){
                if(low < SL && barTime > orderTime){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Buy not fillet SL exit " + SL);
                }
                else if(high > TP && barTime > orderTime && TP != 20){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Buy not fillet TP exit " + TP);
                     }
                else if(buyExit){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Buy not fillet BuyExit exit");
                }
                else if(sellEntry){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Buy not fillet SellEntry exit");
                }
            }
            else if(command == SellLimit){
                if(high > SL && barTime > orderTime){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Sell not fillet SL exit " + SL);
                }
                else if(low < TP && barTime > orderTime && TP != 20){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Sell not fillet TP exit " + TP);
                     }
                else if(sellExit){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Sell not fillet SellExit exit");
                }  
                else if(buyEntry){
                    order.close();
                    order.waitForUpdate(2000);
                    console.getOut().println("Sell not fillet BuyEntry exit");
                }  
            }
        }
    }
    
    private void closeOrder(IOrder order, double exitPrice, String type) throws JFException{
        boolean haveOrder = engine.getOrders().contains(order);
        if(haveOrder && order.getState() == IOrder.State.FILLED){
            order.close();    
            order.waitForUpdate(2000);
            console.getOut().println(type + ": " + exitPrice);
        }
    }
    
   private IOrder checkOpenPos(String label, IOrder checkOrder, Instrument instrument) throws JFException{ 
        String labelBuy = getLabel(label, instrument, IEngine.OrderCommand.BUY);
        String labelSell = getLabel(label, instrument, IEngine.OrderCommand.SELL);
        IOrder buyOrder = engine.getOrder(labelBuy);
        IOrder sellOrder = engine.getOrder(labelSell);
        
        if(engine.getOrders().contains(buyOrder)){
            checkOrder = buyOrder;
            console.getOut().println("Have Open Trade " + labelBuy);
        }
        else if(engine.getOrders().contains(sellOrder)){
            checkOrder = sellOrder;
            console.getOut().println("Have Open Trade " + labelSell);
        }
        else    console.getOut().println("No open Trade");
        
        return checkOrder;
    }
    
      private double getAmount(Instrument instrument, Period period, double sl, double percent) throws JFException{
        IBar prevBar = history.getBar(instrument, period, offerSide, 1);
        double[] ATRPrev = indicators.atr(instrument, period, offerSide, 40, Filter.ALL_FLATS, 1, prevBar.getTime(), 0);
        double atrPrev = ATRPrev[0];
        double valDiff = getValiutDiff(instrument);
        
       if(instrument == Instrument.XAUUSD)
            valiutCoff = 1;
        else
            valiutCoff = 1000;
        
        double risk = round((atrPrev * sl * valiutCoff * valDiff), 2);
        double amount = roundDown((Balance * percent / 100) / risk, 0);
        if(amount < 1)
            amount = 1;
        
        return amount;
    }
    
   private String getLabel(String LABEL, Instrument instrument, IEngine.OrderCommand command){ 
        String label = LABEL + "_" + instrument + "_"; 
        
        if(command == Buy || command == BuyLimit)
            label = label + "BUY";
        else if(command == Sell || command == SellLimit)
            label = label + "SELL";
        
        return label.replace("/", "");
    }
    
   private double getValiutDiff(Instrument instrument){
        double input;
        String name = instrument.getSecondaryCurrency().toString();
        if("AUD".equals(name))
            input = 0.81;
        else if("CAD".equals(name))
            input = 0.86;
        else if("CHF".equals(name))
            input = 1.01;
        else if("GBP".equals(name))
            input = 1.55;
        else if("NZD".equals(name))
            input = 0.77;
        else if("JPY".equals(name))
            input = 0.0083;
        else 
            input = 1;
        
        return input;
    }
    
    private void systemReport(String label, Instrument instrument) throws JFException{
        long time = history.getLastTick(instrument).getTime();
        double result = 0;
        double fee = 0;
        List<IOrder> orders = history.getOrdersHistory(instrument, startTime, time);
        if(orders != null) {
            for(IOrder or : orders) {
                if(or.getState() == IOrder.State.CLOSED && (or.getLabel().contains(getLabel(label, instrument, Buy)) || or.getLabel().contains(getLabel(label, instrument, Sell)))){        
                    result = result + or.getProfitLossInUSD();
                    fee = fee + or.getCommission();
                }
            }
        }
        result = round(result - fee,2);
        fee = round(fee,2);
        console.getOut().println(label + " " + instrument + "--> " + "Return=" + result + "|| Fee=" + fee);
    }
    
    private static double round(double amount, int decimalPlaces) {
        return (new BigDecimal(amount)).setScale(decimalPlaces, BigDecimal.ROUND_HALF_EVEN).doubleValue();
    }
    private static double roundDown(double amount, int decimalPlaces) {
        return (new BigDecimal(amount)).setScale(decimalPlaces, BigDecimal.ROUND_DOWN).doubleValue();
    }
    
   private boolean isValidTime(int fromHour, int fromMin, int toHour, int toMin) throws JFException {
               
        long lastTickTime = history.getLastTick(myInstrument3).getTime();
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
        calendar.setTimeInMillis(lastTickTime);
        calendar.set(Calendar.HOUR_OF_DAY, fromHour);
        calendar.set(Calendar.MINUTE, fromMin);
        calendar.set(Calendar.SECOND, 0);
        long from = calendar.getTimeInMillis();

        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));      
        calendar.setTimeInMillis(lastTickTime);
        calendar.set(Calendar.HOUR_OF_DAY, toHour);
        calendar.set(Calendar.MINUTE, toMin);
        calendar.set(Calendar.SECOND, 0);
        long to = calendar.getTimeInMillis();
              
        boolean result = lastTickTime > from  && lastTickTime < to;
        return result;
    }
    
    private boolean weekend(Integer... days) throws JFException {
        
        long lastTickTime = history.getLastTick(myInstrument3).getTime();
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        calendar.set(Calendar.SECOND, 0);

        calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
        calendar.setTimeInMillis(lastTickTime);
        calendar.set(Calendar.SECOND, 0);
        
        boolean isDayOk = (Arrays.asList(days)).contains(calendar.get(Calendar.DAY_OF_WEEK));
        
        return isDayOk;
    }
    
    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
    }
    
    @Override
    public void onStop() throws JFException {
    }
    
    @Override
    public void onMessage(IMessage message) throws JFException {
    }

    @Override
    public void onAccount(IAccount account) throws JFException {
        Balance = account.getEquity();       
    } 
}
