package jforex.strategies;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.IIndicators.MaType;
import com.dukascopy.api.feed.FeedDescriptor;
import com.dukascopy.api.feed.IRangeBar;
import com.dukascopy.api.feed.IRangeBarFeedListener;

@RequiresFullAccess
public class MACD_STOCH_Strat4 implements IStrategy {

    private static final int HIST = 2;
    private IConsole console;
    private IHistory history;
    private IEngine engine;
    private IIndicators indicators;
    private SimpleDateFormat sdf;    
    @Configurable("Price range (pips)")
    public int priceRangePips = 2; 
    // range bars
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Offer Side")
    public OfferSide offerSide = OfferSide.BID;
    @Configurable("Amount")
    public double amount = 0.02;
    @Configurable("Slippage")
    public double slippage = 0;
    @Configurable("Take profit pips")
    public int takeProfitPips = 0;
    @Configurable("Stop loss in pips")
    public int stopLossPips = 0;
    // MACD
    @Configurable("MACD Fast period")
    public int fastMACDPeriod = 12;
    @Configurable("MACD Slow period")
    public int slowMACDPeriod = 26;
    @Configurable("MACD Signal period")
    public int signalMACDPeriod = 9;
    // STOCH
    @Configurable("STOCH Fast K period")
    public int fastKPeriod = 5;
    @Configurable("STOCH Slow K period")
    public int slowKPeriod = 3;
    @Configurable("STOCH K MA Type")
    public MaType slowKMaType = MaType.SMA;
    @Configurable("STOCH Slow D period")
    public int slowDPeriod = 5;
    @Configurable("STOCH D MA Type")
    public MaType slowDMaType = MaType.SMA;
    public static DecimalFormat df = new DecimalFormat("0.00000");
    private PriceRange priceRange;
    
    private IOrder order;
    private int counter;
    FeedDescriptor feedDescriptor;

    @Override
    public void onStart(IContext context) throws JFException {

        this.history = context.getHistory();
        this.console = context.getConsole();
        this.indicators = context.getIndicators();

        this.engine = context.getEngine();

        priceRange = PriceRange.valueOf(priceRangePips);

        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        
        feedDescriptor = new FeedDescriptor();
        feedDescriptor.setInstrument(instrument);
        feedDescriptor.setDataType(DataType.PRICE_RANGE_AGGREGATION);
        feedDescriptor.setOfferSide(offerSide);
        feedDescriptor.setPriceRange(priceRange);   

        context.subscribeToRangeBarFeed(instrument, offerSide, priceRange, new IRangeBarFeedListener() {

            public void onBar(Instrument instrument, OfferSide offerSide, PriceRange priceRange, IRangeBar bar) {
                
                //reduced nesting
                try {
                    execOnBar(bar);
                } catch (JFException ex) {
                    ex.printStackTrace(console.getErr());
                }
            }
        });

    }
    
    private void execOnBar(IRangeBar bar) throws JFException {

        if (!isActive(order)) {
            order = null;
        }

        // MACD SIGNAL
        OrderCommand macdSign = null;
        OrderCommand stochSign = null;

        Object[] macdFeed = indicators.calculateIndicator(feedDescriptor, new OfferSide[] { offerSide }, "MACD",
                new AppliedPrice[] { AppliedPrice.CLOSE }, new Object[] { fastMACDPeriod, slowMACDPeriod, signalMACDPeriod }, 2,
                bar.getTime(), 0);
        
        double[][] macd = { (double[]) macdFeed[0], (double[]) macdFeed[1], (double[]) macdFeed[2] };

        double[] macd0 = new double[] { macd[0][0], macd[1][0], macd[2][0] };
        double[] macd1 = new double[] { macd[0][1], macd[1][1], macd[2][1] };

        if (macd0[HIST] > 0 /* && macd1[HIST] <= 0 */) {
            macdSign = OrderCommand.BUY;
        }
        if (macd0[HIST] < 0 /* && macd1[HIST] >= 0 */) {
            macdSign = OrderCommand.SELL;
        }

        // STOCH SIGNAL
        int K = 0;
        int D = 1;

        Object[] stochFeed = indicators.calculateIndicator(feedDescriptor, new OfferSide[] { offerSide }, "STOCH",
                new AppliedPrice[] { AppliedPrice.CLOSE }, new Object[] { fastKPeriod, slowKPeriod, slowKMaType.ordinal(), slowDPeriod,
                        slowDMaType.ordinal() }, 2, bar.getTime(), 0);
        double[][] stoch = { (double[]) stochFeed[0], (double[]) stochFeed[1] };

        double[] stoch0 = new double[] { stoch[0][0], stoch[1][0] };
        double[] stoch1 = new double[] { stoch[0][1], stoch[1][1] };

        if (stoch0[K] > stoch0[D] && stoch1[K] <= stoch1[D]) {
            stochSign = OrderCommand.BUY;

        } else if (stoch0[K] < stoch0[D] && stoch1[K] >= stoch1[D]) {
            stochSign = OrderCommand.SELL;
        }

        placeOrder(macdSign, stochSign);
    }
    
    private void placeOrder(OrderCommand stochSign, OrderCommand macdSign) throws JFException {
        
        if (stochSign == OrderCommand.BUY && macdSign == OrderCommand.BUY) {
            if (order != null) {
                if (!order.isLong()) {
                    order.close();
                    order = submitOrder(OrderCommand.BUY);
                }
            } else {
                order = submitOrder(OrderCommand.BUY);
            }

        } else if (stochSign == OrderCommand.SELL && macdSign == OrderCommand.SELL) {
            if (order != null) {
                if (order.isLong()) {
                    order.close();
                    order = submitOrder(OrderCommand.SELL);
                }
            } else {
                order = submitOrder(OrderCommand.SELL);
            }
        }
    }
    
    private IOrder submitOrder(OrderCommand orderCmd) throws JFException {

        double stopLossPrice = 0.0, takeProfitPrice = 0.0;

        // Calculating order price, stop loss and take profit prices
        if (orderCmd == OrderCommand.BUY) {
            if (stopLossPips > 0) {
                stopLossPrice = history.getLastTick(instrument).getBid() - getPipPrice(stopLossPips);
            }
            if (takeProfitPips > 0) {
                takeProfitPrice = history.getLastTick(instrument).getBid() + getPipPrice(takeProfitPips);
            }
        } else {
            if (stopLossPips > 0) {
                stopLossPrice = history.getLastTick(instrument).getBid() + getPipPrice(stopLossPips);
            }
            if (takeProfitPips > 0) {
                takeProfitPrice = history.getLastTick(instrument).getBid() - getPipPrice(takeProfitPips);
            }
        }

        return engine.submitOrder(getLabel(instrument), instrument, orderCmd, amount, 0, slippage, stopLossPrice, takeProfitPrice);
    }

    private void closeOrder(IOrder order) throws JFException {
        if (order == null) {
            return;
        }
        if (order.getState() != IOrder.State.CLOSED && order.getState() != IOrder.State.CREATED && order.getState() != IOrder.State.CANCELED) {
            order.close();
            order = null;
        }
    }

    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(int pips) {
        return pips * instrument.getPipValue();
    }

    private String getLabel(Instrument instrument) {
        return (instrument.name() + (counter++)).toUpperCase();
    }

    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {   }

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

    @Override
    public void onMessage(IMessage message) throws JFException {
        if(message.getOrder() != null){
            print(message);
        }
    }

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

    @Override
    public void onStop() throws JFException {
    }

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

}
