package jforex;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.indicators.IIndicator;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;


public class GroupStrat implements IStrategy {

    @Configurable("Instrument 1")
    public Instrument instrument1 = Instrument.NZDUSD;
    @Configurable("Instrument 2")
    public Instrument instrument2 = Instrument.NZDCHF;
    @Configurable("Instrument 3")
    public Instrument instrument3 = Instrument.EURUSD;


    @Configurable(">")
    public String str1 = "Strategy 1";    
    @Configurable("Enabled")
    public boolean str1Enabled = true;

    @Configurable("RSI period")
    public int rsiPeriod = 14;
    @Configurable("MOM period")
    public int momPeriod = 14;


    @Configurable("RSI level (50 +/-)")
    public int rsiLevel = 0;
    @Configurable("MOM level")
    public double momLevel = 0;

    @Configurable("Risk percent (of pAccount equity)")
    public double risk = 1;

    @Configurable("Period")
    public Period selectedPeriod = Period.THIRTY_MINS;
    @Configurable("Offer side")
    public OfferSide offerSide = OfferSide.ASK;
    @Configurable("Slippage")
    public double slippage = 0;
    @Configurable("Filter")
    public Filter filter = Filter.ALL_FLATS;

    @Configurable("Take profit pips")
    public int takeProfitPips = 100;
    @Configurable("Stop loss in pips")
    public int stopLossPips = 100;
    @Configurable("Applied price")
    public AppliedPrice appliedPrice = AppliedPrice.CLOSE;


    @Configurable(">")
    public String str2 = "Strategy 2";   
    @Configurable("Enabled")
    public boolean str2Enabled = true;

    @Configurable("RSI period")
    public int rsiPeriod2 = 14;
    @Configurable("MOM period")
    public int momPeriod2 = 14;


    @Configurable("RSI level (50 +/-)")
    public int rsiLevel2 = 0;
    @Configurable("MOM level")
    public double momLevel2 = 0;

    @Configurable("Risk percent (of pAccount equity)")
    public double risk2 = 1;

    @Configurable("Period")
    public Period selectedPeriod2 = Period.THIRTY_MINS;
    @Configurable("Offer side")
    public OfferSide offerSide2 = OfferSide.ASK;
    @Configurable("Slippage")
    public double slippage2 = 0;
    @Configurable("Filter")
    public Filter filter2 = Filter.ALL_FLATS;

    @Configurable("Take profit pips")
    public int takeProfitPips2 = 100;
    @Configurable("Stop loss in pips")
    public int stopLossPips2 = 100;
    @Configurable("Applied price")
    public AppliedPrice appliedPrice2 = AppliedPrice.CLOSE;



    @Configurable(">")
    public String str3 = "Strategy 3";   
    @Configurable("Enabled")
    public boolean str3Enabled = true;

    @Configurable("RSI period")
    public int rsiPeriod3 = 14;
    @Configurable("MOM period")
    public int momPeriod3 = 14;


    @Configurable("RSI level (50 +/-)")
    public int rsiLevel3 = 0;
    @Configurable("MOM level")
    public double momLevel3 = 0;

    @Configurable("Risk percent (of pAccount equity)")
    public double risk3 = 1;

    @Configurable("Period")
    public Period selectedPeriod3 = Period.THIRTY_MINS;
    @Configurable("Offer side")
    public OfferSide offerSide3 = OfferSide.ASK;
    @Configurable("Slippage")
    public double slippage3 = 0;
    @Configurable("Filter")
    public Filter filter3 = Filter.ALL_FLATS;

    @Configurable("Take profit pips")
    public int takeProfitPips3 = 100;
    @Configurable("Stop loss in pips")
    public int stopLossPips3 = 100;
    @Configurable("Applied price")
    public AppliedPrice appliedPrice3 = AppliedPrice.CLOSE;

    private int pCounter = 0;
    List<RSI_MOM_Strat_RiskAmount> strategies = new ArrayList<RSI_MOM_Strat_RiskAmount>(3);
    
    public void onStart(IContext context) throws JFException {

        if(str1Enabled) {
            RSI_MOM_Strat_RiskAmount strat1 = new RSI_MOM_Strat_RiskAmount();
            strat1.pInstrument = instrument1;
            strat1.pAppliedPrice = appliedPrice;
            strat1.pFilter = filter;
            strat1.pMomLevel = momLevel;
            strat1.pMomPeriod = momPeriod;
            strat1.pOfferSide = offerSide;
            strat1.pRisk = risk;
            strat1.pRsiLevel = rsiLevel;
            strat1.pRsiPeriod = rsiPeriod;
            strat1.pSelectedPeriod = selectedPeriod;
            strat1.pSlippage = slippage;
            strat1.pStopLossPips = stopLossPips;
            strat1.pTakeProfitPips = takeProfitPips;   
            strategies.add(strat1);         
        }

        if(str2Enabled) {
            RSI_MOM_Strat_RiskAmount strat2 = new RSI_MOM_Strat_RiskAmount();
            strat2.pInstrument = instrument2;
            strat2.pAppliedPrice = appliedPrice2;
            strat2.pFilter = filter2;
            strat2.pMomLevel = momLevel2;
            strat2.pMomPeriod = momPeriod2;
            strat2.pOfferSide = offerSide2;
            strat2.pRisk = risk2;
            strat2.pRsiLevel = rsiLevel2;
            strat2.pRsiPeriod = rsiPeriod2;
            strat2.pSelectedPeriod = selectedPeriod2;
            strat2.pSlippage = slippage2;
            strat2.pStopLossPips = stopLossPips2;
            strat2.pTakeProfitPips = takeProfitPips2;
            strategies.add(strat2);
        }

        if(str3Enabled) {
            RSI_MOM_Strat_RiskAmount strat3 = new RSI_MOM_Strat_RiskAmount();
            strat3.pInstrument = instrument3;
            strat3.pAppliedPrice = appliedPrice3;
            strat3.pFilter = filter3;
            strat3.pMomLevel = momLevel3;
            strat3.pMomPeriod = momPeriod3;
            strat3.pOfferSide = offerSide3;
            strat3.pRisk = risk3;
            strat3.pRsiLevel = rsiLevel3;
            strat3.pRsiPeriod = rsiPeriod3;
            strat3.pSelectedPeriod = selectedPeriod3;
            strat3.pSlippage = slippage3;
            strat3.pStopLossPips = stopLossPips3;
            strat3.pTakeProfitPips = takeProfitPips3;
            strategies.add(strat3);
        }

        for(IStrategy strat : strategies) {
            strat.onStart(context);
        }
    }

    public void onAccount(IAccount account) throws JFException {
        for(IStrategy strat : strategies) {
            strat.onAccount(account);
        }
    }

    public void onMessage(IMessage message) throws JFException {
        for(IStrategy strat : strategies) {
            strat.onMessage(message);
        }
    }

    public void onStop() throws JFException {
        for(IStrategy strat : strategies) {
            strat.onStop();
        }
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        for(IStrategy strat : strategies) {
            strat.onTick(instrument, tick);
        }
    }
    
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        for(IStrategy strat : strategies) {
            strat.onBar(instrument, period, askBar, bidBar);
        }
    }

    class RSI_MOM_Strat_RiskAmount implements IStrategy {

        private IEngine pEngine;
        private IConsole pConsole;
        private IHistory pHistory;
        private IIndicators pIndicators;
        private IAccount pAccount;
        private IOrder pOrder;
        private IContext pContext;

        public int pRsiPeriod = 14;
        public int pMomPeriod = 14;


        public int pRsiLevel = 0;
        public double pMomLevel = 0;

        public double pRisk = 1;

        public Period pSelectedPeriod = Period.THIRTY_MINS;
        public OfferSide pOfferSide = OfferSide.ASK;
        public double pSlippage = 0;
        public Filter pFilter = Filter.ALL_FLATS;

        public int pTakeProfitPips = 100;
        public int pStopLossPips = 100;

        public AppliedPrice pAppliedPrice = AppliedPrice.CLOSE;

        public Instrument pInstrument = Instrument.EURUSD;


        @Override
        public void onStart(IContext context) throws JFException {
            this.pConsole = context.getConsole();
            this.pIndicators = context.getIndicators();
            this.pHistory = context.getHistory();
            this.pEngine = context.getEngine();
            this.pContext = context;
            this.pAccount = context.getAccount();

           }

        @Override
        public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
            if (period != pSelectedPeriod || instrument != this.pInstrument) {
                return;
            }
        }

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

            if (!isActive(pOrder)) {
                if(pOrder != null) {
                    print(pOrder.getLabel(), pOrder.getState(), "closed sl/tp", "long" );
                }
                pOrder = null;
            } else if(pOrder.equals(IOrder.State.CREATED)) {
                return;
            }

            double[] rsi = pIndicators.rsi(instrument, pSelectedPeriod, pOfferSide, pAppliedPrice, pRsiPeriod, pFilter, 1, tick.getTime(), 0);
            double[] mom = pIndicators.mom(instrument, pSelectedPeriod, pOfferSide, pAppliedPrice, pMomPeriod, pFilter, 1, tick.getTime(), 0);

            int rsiUpper = 50 + pRsiLevel;
            int rsiLower = 50 - pRsiLevel;

            if(pOrder != null) {
                if (rsi[0] <= rsiUpper && mom[0] <= pMomLevel) {
                    if(pOrder.isLong()) {
                        closeOrder(pOrder);
                        pOrder = null;
                    }
                }
            }
            if(pOrder != null) {
                if (rsi[0] >= rsiLower && mom[0] >= -pMomLevel) {
                    if(!pOrder.isLong()) {
                        closeOrder(pOrder);
                        pOrder = null;
                    }
                }
            }

            if(pOrder == null) {
                if (rsi[0] > rsiUpper && mom[0] > pMomLevel) {
                    pOrder = submitOrder(OrderCommand.BUY, instrument);
                    print(pOrder.getLabel(), "openned", "long" );
                }

                if (rsi[0] < rsiLower && mom[0] < -pMomLevel) {
                    pOrder = submitOrder(OrderCommand.SELL, instrument);
                    print(pOrder.getLabel(), "openned", "short");
                }
            }
        }

        private IOrder submitOrder(OrderCommand orderCmd, Instrument instr) throws JFException {

        double stopLossPrice = 0.0, takeProfitPrice = 0.0;

            // Calculating pOrder price, stop loss and take profit prices
            if (orderCmd == OrderCommand.BUY) {
                if(pStopLossPips > 0) {
                    stopLossPrice = pHistory.getLastTick(instr).getBid() - getPipPrice(pStopLossPips);
                }
                if(pTakeProfitPips > 0) {
                    takeProfitPrice = pHistory.getLastTick(instr).getBid() + getPipPrice(pTakeProfitPips);
                }
            } else {
                if(pStopLossPips > 0) {
                    stopLossPrice = pHistory.getLastTick(instr).getAsk() + getPipPrice(pStopLossPips);
                }
                if(pTakeProfitPips > 0) {
                    takeProfitPrice = pHistory.getLastTick(instr).getAsk() - getPipPrice(pTakeProfitPips);
                }
            }

            double amount = XRate.getAmount(pContext, pAccount, pInstrument, pRisk / 100, pStopLossPips);

            return pEngine.submitOrder(getLabel(instr), instr, orderCmd, amount, 0, pSlippage, stopLossPrice, takeProfitPrice);
        }

        private void closeOrder(IOrder order) throws JFException {
            print(order.getLabel(), "closed", order.isLong() ? "long" : "short");
            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.CANCELED) {
                return true;
            }
            return false;
        }

        private double getPipPrice(double pips) {
            return pips * this.pInstrument.getPipValue();
        }

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


        private double getRoundedPrice(double price) {
            BigDecimal bd = new BigDecimal(price);
            bd = bd.setScale(pInstrument.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 {
            print(message);
        }

        public void onAccount(IAccount account) throws JFException {
        }

        public void onStop() throws JFException {
        }

        /**************** debug print functions ***********************/
        private void print(Object... o) {
            for (Object ob : o) {
                //pConsole.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(" ");
            }
            pConsole.getOut().println();
        }

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

        private void print2(Object o) {
            pConsole.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());
            }
            pConsole.getOut().println();
        }

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

        public 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) {
            pConsole.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 - pInstrument, USD pAccount 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));
        }
    }
}