package jforex.strategies;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.feed.IFeedDescriptor;
import com.dukascopy.api.feed.IFeedListener;
import com.dukascopy.api.feed.util.RangeBarFeedDescriptor;
import com.dukascopy.api.feed.util.TimePeriodAggregationFeedDescriptor;
import com.dukascopy.api.indicators.IIndicator;
import com.dukascopy.api.indicators.IndicatorInfo;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import com.dukascopy.api.feed.util.RenkoFeedDescriptor;


public class Persbands implements IStrategy, IFeedListener {

    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IIndicators indicators;
    private IContext context;
    private int counter = 0;

    @Configurable("MA period")
    public int maPeriod = 100;
    @Configurable("Deviation")
    public double deviation = 2;
    @Configurable("BUY level")
    public double buyLevel = 50;
    @Configurable("SELL level")
    public double sellLevel = 50;

    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Rengo bar size (pips)")
    public int pipSize = 3;
    @Configurable("Offer side")
    public OfferSide offerSide = OfferSide.BID;



    @Configurable("Slippage")
    public double slippage = 0;
    @Configurable("Amount")
    public double amount = 0.02;
    @Configurable("Applied price")
    public AppliedPrice appliedPrice = AppliedPrice.CLOSE;


    @Configurable("Take profit pips")
    public int tp1 = 0;
    @Configurable("Stop loss in pips")
    public int sl1 = 0;
    
    @Configurable("Take profit pips")
    public int tp2 = 0;
    @Configurable("Stop loss in pips")
    public int sl2 = 0;

    @Configurable("Take profit pips")
    public int tp3 = 0;
    @Configurable("Stop loss in pips")
    public int sl3 = 0;

    @Configurable("Take profit pips")
    public int tp4 = 0;
    @Configurable("Stop loss in pips")
    public int sl4 = 0;



    private OrderMgr longOrders;
    private OrderMgr shortOrders;
    private IBar lastTimePeriodBar;
    private long stratID;
    private boolean isLongTrend = false;
    private boolean isShortTrend = false;
    
    private int NONE = 0;
    private int trend = NONE;
    private int SHORT = -1;
    private int LONG = 1;

    public IFeedDescriptor pFeedDescriptor;
    

    @Override
    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.indicators = context.getIndicators();
        this.history = context.getHistory();
        this.engine = context.getEngine();
        this.context = context;

        Set<Instrument> instruments = new HashSet<Instrument>();
        instruments.add(Instrument.EURUSD);
        context.setSubscribedInstruments(instruments);

        pFeedDescriptor = new RenkoFeedDescriptor(instrument, PriceRange.valueOf(pipSize), OfferSide.BID);
        context.subscribeToFeed(pFeedDescriptor, this);
        IChart chart = context.getChart(pFeedDescriptor.getInstrument());
        if (chart != null) {
            chart.add(indicators.getIndicator("PERSBBANDS"), new Object[]{maPeriod, deviation});
        }

        longOrders = new OrderMgr();
        shortOrders = new OrderMgr();
        
        for(IOrder order : engine.getOrders()) {
             if(order.getComment() != null && !equals(this.getClass().getSimpleName())) {
               return;
           }
            if(order.isLong()) {
                longOrders.addOrder(new MyOrder(order));
            } else {
                shortOrders.addOrder(new MyOrder(order));
            }            
        }

        Random randomGenerator = new Random(new GregorianCalendar().getTimeInMillis());
        stratID = Math.abs(randomGenerator.nextLong() / 1000000);       
    }

    // this method is called from correct onBar method depending on the chart type that is subscribed
    // !!! use local method getBars instead of history.getBars
    // !!! use local method calculateIndicator instead of IIndicators methods
    private void onBar(ITick tick, IBar bar) throws JFException {
        
        longOrders.cleanClosedOrders();
        shortOrders.cleanClosedOrders();

        boolean sellSign = false;
        boolean buySign = false;

        double[][] pbb = calculateIndicatorDouble("PERSBBANDS", pFeedDescriptor, appliedPrice, new Object[]{maPeriod, deviation}, 2, bar.getTime(), 0);

        int NEW = 1;
        int PREV = 0;
                
        if(pbb[NEW][0] < sellLevel ) {
            if(trend == LONG) {
                sellSign = true;
            }            
            trend = SHORT;
        }
        if(pbb[NEW][0] > buyLevel) {
            if(trend == SHORT) {
                buySign = true;
            }
            trend = LONG;
        }

        // PLACE ORDER
        if (buySign && longOrders.orders.size() == 0) {            
            shortOrders.closeAll();
            IOrder order1 = submitOrder(OrderCommand.BUY, pFeedDescriptor.getInstrument(), tick, sl1, tp1);
            IOrder order2 = submitOrder(OrderCommand.BUY, pFeedDescriptor.getInstrument(), tick, sl2, tp2);
            IOrder order3 = submitOrder(OrderCommand.BUY, pFeedDescriptor.getInstrument(), tick, sl3, tp3);
            longOrders.addOrder(new MyOrder(order1));
            longOrders.addOrder(new MyOrder(order2));
            longOrders.addOrder(new MyOrder(order3));

        } else if (sellSign && shortOrders.orders.size() == 0) {
            longOrders.closeAll();
            IOrder order1 = submitOrder(OrderCommand.SELL, pFeedDescriptor.getInstrument(), tick, sl1, tp1);
            IOrder order2 = submitOrder(OrderCommand.SELL, pFeedDescriptor.getInstrument(), tick, sl2, tp2);
            IOrder order3 = submitOrder(OrderCommand.SELL, pFeedDescriptor.getInstrument(), tick, sl3, tp3);
            shortOrders.addOrder(new MyOrder(order1));
            shortOrders.addOrder(new MyOrder(order2));
            shortOrders.addOrder(new MyOrder(order3));
        }

    }


    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if(!this.pFeedDescriptor.getInstrument().equals(instrument)) {
            return;
        }
    }

    @Override
    public void onMessage(IMessage message) throws JFException {
    }

    @Override
    public void onAccount(IAccount account) throws JFException {
    }

    @Override
    public void onStop() throws JFException {
    }


    public IOrder submitOrder(OrderCommand orderCommand, Instrument instr, ITick t, double stopLossPips, double takeProfitPips) throws JFException {

        double takeProfitPrice = 0.0;
        double stopLossPrice = 0.0;

        // Calculating order price, stop loss and take profit prices
        if (orderCommand.isLong()) {
            if (stopLossPips > 0) {
                stopLossPrice = t.getBid() - toPrice(stopLossPips, instr);
            }
        } else {
            if (stopLossPips > 0) {
                stopLossPrice = t.getAsk() + toPrice(stopLossPips, instr);
            }
        }

        // Calculating order price, stop loss and take profit prices
        if (orderCommand.isLong()) {
            if (takeProfitPips > 0) {
                takeProfitPrice = t.getBid() + toPrice(takeProfitPips, instr);
            }
        } else {
            if (takeProfitPips > 0) {
                takeProfitPrice = t.getAsk() - toPrice(takeProfitPips, instr);
            }
        }

        IOrder order = engine.submitOrder(getLabel(), instr, orderCommand, amount, 0, slippage, stopLossPrice, takeProfitPrice, 0, this.getClass().getSimpleName());
        return order;
    }



/************************************************/
/**  ORDER MANAGER                              */
/************************************************/

    private class OrderMgr {
        public LinkedList<MyOrder> orders = new LinkedList<MyOrder>();

        private void addOrder(MyOrder myOrder) throws JFException {
            orders.add(myOrder);
        }

        // remove all orders that are in state CLOSED or CANCELLED
        // discandrs orders closed by user, take profit or stop loss
        public void cleanClosedOrders() throws JFException {
            cleanClosedOrders(orders);
        }

        public void closeAll() throws JFException {
            closeAllOdrers();
        }

        public void closeAllOdrers() throws JFException {
            closeAll(orders);
        }

        private void cleanClosedOrders(List<MyOrder> orders) throws JFException {
            List<String> remove = new ArrayList<String>();

            Iterator<MyOrder> it = orders.iterator();
            while(it.hasNext()) {
                MyOrder myOrder = it.next();
                if (!myOrder.isActive()) {
                    myOrder.close();
                    it.remove();
                }
            }
        }

        private void closeAll(List<MyOrder> orders) throws JFException {
            for (MyOrder order : orders) {
                order.close();
            }
            orders.clear();
        }

        public void updateTrailingStopLoss(ITick tick, double pTriggerPips, double pStopLossPips) throws JFException {
            if (pStopLossPips > 0) {
                for (MyOrder order : orders) {
                    order.updateTrailingStopLoss(tick, pTriggerPips, pStopLossPips);
                }
            }
        }

        public void setBreakEven(double beTriggerPips, double beShift) throws JFException {
            if (beTriggerPips > 0) {
                for (MyOrder order : orders) {
                    order.setBreakEven(beTriggerPips, beShift);
                }
            }
        }

    }


/************************************************/
/*  ORDER                                       */
/************************************************/

    private class MyOrder {

        private IOrder order;
        private Map<String, Object> properties;

        MyOrder(IOrder order) {
            this.order = order;
        }

        void setProperty(String key, Object value) {
            if (properties == null) {
                properties = new HashMap();
            }
            properties.put(key, value);
        }

        Object getProperty(String key) {
            if (properties == null) {
                return null;
            }
            return properties.get(key);
        }

        void close() throws JFException {
            if (order == null) {
                return;
            }
            if (order.getState() == IOrder.State.CREATED) {
                order.waitForUpdate();
            }
            if (order.getState() == IOrder.State.OPENED) {
                order.close(); // close 1
                order.waitForUpdate();
            }
            if (order.getState() == IOrder.State.FILLED) {
                // order in state OPENNED -> close 1 -> order FILLED before CLOSED -> recieves message ORDER_ALREADY_FILLED -> close 2
                order.close(); // close 2
                order = null;
            }
        }

        void close(double ammount) throws JFException {
            if (order == null) {
                return;
            }
            if (order.getState() == IOrder.State.CREATED) {
                order.waitForUpdate();
            }
            if (order.getState() == IOrder.State.OPENED) {
                order.close(ammount); // close 1
                order.waitForUpdate();
            }
            if (order.getState() == IOrder.State.FILLED) {
                // order in state OPENNED -> close 1 -> order FILLED before CLOSED -> recieves message ORDER_ALREADY_FILLED -> close 2
                order.close(ammount); // close 2
            }
        }

        boolean isActive() throws JFException {
            if (order != null && order.getState() != IOrder.State.CLOSED && order.getState() != IOrder.State.CANCELED) {
                return true;
            }
            return false;
        }

        boolean inState(IOrder.State state) {
            return order.getState() == state;
        }

        public void updateTrailingStopLoss(ITick tick, double pTriggerPips, double pStopLossPips) throws JFException {

            if (pTriggerPips <= 0.0) {
                return;
            }

            if (order != null && inState(IOrder.State.FILLED)) {

                Instrument instr = order.getInstrument();

                double newStop;
                double openPrice = order.getOpenPrice();
                double currentStopLoss = order.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 pStopLossPips

                if (order.isLong()) { // long side order
                    if ((currentStopLoss == 0.0 || tick.getBid() > currentStopLoss + toPrice(pStopLossPips, instr))
                            && tick.getBid() > openPrice + toPrice(pTriggerPips, instr)) {
                        // trailing stop loss
                        newStop = tick.getBid() - toPrice(pStopLossPips, instr);
                        newStop = round(newStop, instr);

                        if (currentStopLoss != newStop) {
                            order.setStopLossPrice(newStop);
                            return;
                        }
                    }

                } else { // short side order
                    if ((currentStopLoss == 0.0 || tick.getAsk() < currentStopLoss - toPrice(pStopLossPips, instr))
                            && tick.getAsk() < openPrice - toPrice(pTriggerPips, instr)) {

                        // trailing stop loss
                        newStop = tick.getAsk() + toPrice(pStopLossPips, instr);
                        newStop = round(newStop, instr);

                        if (currentStopLoss != newStop) {
                            order.setStopLossPrice(newStop);
                            return;
                        }
                    }
                }
            }
        }

        // sets break even of "order" to "order.getOpenPrice() + pBreakEvenShift" if price is above "order.getOpenPrice() + pTriggerPips"
        public void setBreakEven(double pTriggerPips, double pBreakEvenShift) throws JFException {

            if (pTriggerPips <= 0.0) {
                return;
            }

            if (order != null && order.getState() == IOrder.State.FILLED
                    && order.getProfitLossInPips() >= pTriggerPips) {

                Instrument instr = order.getInstrument();
                double currentStopLoss = order.getStopLossPrice();

                if (order.isLong()) { // long side order

                    double newStop = order.getOpenPrice() + toPrice(pBreakEvenShift, order.getInstrument());
                    newStop = round(newStop, instr);

                    if (currentStopLoss == 0.0 || currentStopLoss < newStop) {
                        order.setStopLossPrice(newStop);;
                    }

                } else { // short side order

                    double newStop = order.getOpenPrice() - toPrice(pBreakEvenShift, order.getInstrument());
                    newStop = round(newStop, instr);

                    if (currentStopLoss == 0.0 || currentStopLoss > newStop) {
                        order.setStopLossPrice(newStop);
                    }
                }
            }
        }

        public void setStopLossPips(double currentPrice,  double pips) throws JFException {
            double newStopLoss = currentPrice + toPrice(pips, order.getInstrument());
            setStopLossPrice(newStopLoss);
        }

        public void setStopLossPrice(double newStopLoss) throws JFException {
            newStopLoss = round(newStopLoss, order.getInstrument());
            if(newStopLoss != order.getStopLossPrice()) {
                order.setStopLossPrice(newStopLoss);
            }

        }
    }

    /************************************************/
    /*  INTEGRATE WITH DIFFERENT CHART TYPES        */
    /************************************************/

    @Override
    public void onFeedData(IFeedDescriptor feedDescriptor, ITimedData feedData) {
        try {
            ITick tick = history.getLastTick(pFeedDescriptor.getInstrument());

            if(feedDescriptor.getDataType().equals(DataType.TIME_PERIOD_AGGREGATION)) {
                if (!pFeedDescriptor.getPeriod().equals(feedDescriptor.getPeriod()) || pFeedDescriptor.getInstrument() != feedDescriptor.getInstrument()) {
                    return;
                }
                lastTimePeriodBar = (IBar) feedData;
            }
            onBar(tick, (IBar) feedData);

        } catch (JFException ex) {
            print(ex);
        }
    }

    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        // replaced by onFeedData
    }

    private IBar getBar(IFeedDescriptor feedDescr, int shift) throws JFException {
        switch (feedDescr.getDataType()) {
            case POINT_AND_FIGURE:
                return history.getPointAndFigure(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getPriceRange(), feedDescr.getReversalAmount(), shift);

            case RENKO:
                return history.getRenkoBar(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getPriceRange(), shift);

            case PRICE_RANGE_AGGREGATION:
                return history.getRangeBar(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getPriceRange(), shift);

            case TICK_BAR:
                return history.getTickBar(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getTickBarSize(), shift);

            default:
                if(shift == 0) {
                    return history.getBar(feedDescr.getInstrument(), feedDescr.getPeriod(), feedDescr.getOfferSide(), 0);
                }
                if(lastTimePeriodBar == null) {
                    return null;
                }
                long time = lastTimePeriodBar.getTime();
                return history.getBars(feedDescr.getInstrument(), feedDescr.getPeriod(), feedDescr.getOfferSide(), feedDescr.getFilter(), shift, time, 0).get(0);
        }
    }

    private List getBars(IFeedDescriptor feedDescr, int barsBefore, long time, int barsAfter) throws JFException {
        switch (feedDescr.getDataType()) {
            // default Bar charts
            case POINT_AND_FIGURE:
                return history.getPointAndFigures(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getPriceRange(), feedDescr.getReversalAmount(), barsBefore, time, barsAfter);

            case RENKO:
                return history.getRenkoBars(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getPriceRange(), barsBefore, time, barsAfter);

            case PRICE_RANGE_AGGREGATION:
                return history.getRangeBars(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getPriceRange(), barsBefore, time, barsAfter);

            case TICK_BAR:
                return history.getTickBars(feedDescr.getInstrument(), feedDescr.getOfferSide(), feedDescr.getTickBarSize(), barsBefore, time, barsAfter);

            default:
                return history.getBars(feedDescr.getInstrument(), feedDescr.getPeriod(), feedDescr.getOfferSide(), feedDescr.getFilter(), barsBefore, time, barsAfter);
        }
    }


    // time - start time of a bar
    private double[] calculateIndicatorDouble(String indicatorName, IFeedDescriptor feedDescr, AppliedPrice appliedPrice, Object[] optInputs, int shift) throws JFException {
        IBar bar = getBar(feedDescr, 0);
        double[][] result = calculateIndicatorDouble(indicatorName, feedDescr, appliedPrice, optInputs, shift + 1, bar.getTime(), 0);
        return result[0];
    }

    private double[][] calculateIndicatorDouble(String indicatorName, IFeedDescriptor feedDescr, AppliedPrice appliedPrice, Object[] optInputs, int candlesBefore, long time, int candlesAfter) throws JFException {
        IndicatorInfo info = indicators.getIndicator(indicatorName).getIndicatorInfo();

        int inputCount = info.getNumberOfInputs();

        int additionlBarCount = 0;
        int minBarCount = 1000;
        if(info.isRecalculateAll() || info.isUnstablePeriod()) {
            additionlBarCount = minBarCount - (candlesBefore + candlesAfter);
            if(additionlBarCount < 0) {
                additionlBarCount = 0;
            }
        }

        OfferSide[] offerSides = new OfferSide[inputCount];
        AppliedPrice[] appliedPrices = new AppliedPrice[inputCount];

        for(int i = 0; i < inputCount; i++) {
            offerSides[i] = feedDescr.getOfferSide();
            appliedPrices[i] = appliedPrice;
        }

        Object[] result = indicators.calculateIndicator(feedDescr, offerSides, indicatorName, appliedPrices, optInputs, additionlBarCount + candlesBefore, time, candlesAfter);
        double[][] newResult = new double[candlesBefore + candlesAfter][result.length];

        for(int i = 0; i < result.length; i++) {
            double[] values = (double[])result[i];

            for(int j = 0; j < candlesBefore + candlesAfter; j++) {
                newResult[j][i] = values[j + additionlBarCount];
            }
        }

        return newResult;
    }

    // time - start time of a bar
    private Object[] calculateIndicator(String indicatorName, IFeedDescriptor feedDescr, AppliedPrice appliedPrice, Object[] optInputs, int shift) throws JFException {

        IIndicator ind = indicators.getIndicator(indicatorName);
        IndicatorInfo info = ind.getIndicatorInfo();
        IBar bar = getBar(feedDescr, 1);

        Object[][] array = calculateIndicator(indicatorName, feedDescr, appliedPrice, optInputs, shift + 1, bar.getTime(), 0);
        if(array.length == 0) {
            return new Object[0];
        }
        Object[] result = new Object[array[0].length];

        for(int i = 0; i < result.length; i++) {
            switch(ind.getOutputParameterInfo(i).getType()) {
                case DOUBLE:
                    result[i] = (Double) array[0][i];
                    break;
                case INT:
                    result[i] = (Integer) array[0][i];
                    break;
                case OBJECT:
                    result[i] = (Object) array[0][i];
                    break;
            }
        }
        return result;
    }

    private Object[][] calculateIndicator(String indicatorName, IFeedDescriptor feedDescr, AppliedPrice appliedPrice, Object[] optInputs, int candlesBefore, long time, int candlesAfter) throws JFException {

        IIndicator ind = indicators.getIndicator(indicatorName);
        IndicatorInfo info = ind.getIndicatorInfo();

        int inputCount = info.getNumberOfInputs();

        OfferSide[] offerSides = new OfferSide[inputCount];
        AppliedPrice[] appliedPrices = new AppliedPrice[inputCount];

        for(int i = 0; i < inputCount; i++) {
            offerSides[i] = feedDescr.getOfferSide();
            appliedPrices[i] = appliedPrice;
        }

        int additionlBarCount = 0;
        int minBarCount = 4000;
        if(info.isRecalculateAll() || info.isUnstablePeriod()) {
            additionlBarCount = minBarCount - (candlesBefore + candlesAfter);
            if(additionlBarCount < 0) {
                additionlBarCount = 0;
            }
        }

        Object[] array = indicators.calculateIndicator(feedDescr, offerSides, indicatorName, appliedPrices, optInputs, additionlBarCount + candlesBefore, time, candlesAfter);

        int paramCount = array.length;
        int valueCount = candlesBefore + candlesAfter;

        Object[][] newResult = new Object[valueCount][paramCount];

        for(int i = 0; i < paramCount; i++) {
            switch(ind.getOutputParameterInfo(i).getType()) {
                case DOUBLE:
                    double[] doubleArr = (double []) array[i];
                    for(int j = 0; j < valueCount; j++) {
                        newResult[j][i] = doubleArr[j + additionlBarCount];
                    }
                    break;
                case INT:
                    int[] intArr = (int []) array[i];
                    for(int j = 0; j < valueCount; j++) {
                        newResult[j][i] = intArr[j + additionlBarCount];
                    }
                    break;
                case OBJECT:
                    Object[] objArr = (Object []) array[i];
                    for(int j = 0; j < valueCount; j++) {
                        newResult[j][i] = objArr[j + additionlBarCount];
                    }
                    break;
            }
        }

        return newResult;
    }

/************************************************/
/*  HELPERS                                     */
/************************************************/

    private String getLabel() {
        String label = this.getClass().getSimpleName() + stratID + (counter++);
        label = label.toUpperCase();
        return label;
    }

    // return true if time is in from-to interval of the day
    public boolean isInInterval(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;
    }

    public boolean isInInterval(long time, int fromDayOfWeek, int fromHour, int fromMin, int toDayOfWeek, 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);

        Calendar cal3 = new GregorianCalendar();
        cal3.setTimeZone(TimeZone.getTimeZone("GMT"));
        cal3.setTimeInMillis(time);

        if(cal.getTimeInMillis() <= time && time <= cal2.getTimeInMillis() &&
                fromDayOfWeek <= cal3.get(Calendar.DAY_OF_WEEK) && cal3.get(Calendar.DAY_OF_WEEK) <= toDayOfWeek ) {
            return true;
        }
        return false;
    }

    private double round(double price, Instrument instr) {
        BigDecimal bd = new BigDecimal(price);
        bd = bd.setScale(instr.getPipScale() + 1, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

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

    private double toPrice(double pips, Instrument instr) {
        return pips * instr.getPipValue();
    }


/************************************************/
/*  PRINT                                       */
/************************************************/

    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 (Long.class.isInstance(ob)) {
                print2(toStr((Long) ob));
            } else if (ob instanceof IBar) {
                print2(toStr((IBar) ob));
            } else {
                print2(ob);
            }
            print2(" ");
        }
        console.getOut().println();
    }

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

    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 print(Long time) {
        console.getOut().println(toStr(time));
    }

    private void print(Throwable th) {
        StackTraceElement[] elem = th.getStackTrace();

        // print stack trace in reverse order because console in jforex client prints in reverse
        for(int i = elem.length - 1; i >= 0; i--) {
            console.getErr().println(elem[i]);
        }
        console.getErr().println(th.toString() + ": "+ th.getMessage());
    }


}
