package jforex.strategies;

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

import com.dukascopy.api.Configurable;
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;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IHistory;
import com.dukascopy.api.IIndicators;
import com.dukascopy.api.IIndicators.MaType;
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 com.dukascopy.api.indicators.IIndicator;

public class BBandStrategy2 implements IStrategy {
    private IEngine                 engine;
    private IHistory                history;
    private IIndicators             indicators;
    private int                     counter                 = 0;
    private IConsole                console;

    @Configurable("Instrument")
    public Instrument               instrument              = Instrument.EURUSD;

    @Configurable("Period")
    public Period                   selectedPeriod          = Period.FIVE_MINS; // the duration of a bar             

    @Configurable("Amount")
    public double                   amount                  = 0.2;    
    @Configurable("Slippage")
    public double                   slippage                = 2;

    @Configurable("Stop loss")
    public int                      stopLossPips            = 5;

    @Configurable("Profit")
    public int                      profitPips          = 15;

    @Configurable("BBands Moving average length")
    public int                      maLength                = 20; // the number of bars that are considered in the calculation of the MA    
    @Configurable("BBands MA price")
    public IIndicators.AppliedPrice appliedPrice            = IIndicators.AppliedPrice.CLOSE;
    @Configurable("BBands nb dev up")
    public double                   nbDevUp                 = 3;
    @Configurable("BBands nb dev down")
    public double                   nbDevDn                 = 3;
    @Configurable("BBands MA type")
    public MaType                   maType                  = MaType.EMA;
    
    @SuppressWarnings("serial")
    private final SimpleDateFormat  sdf                     = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
                                                                {
                                                                    setTimeZone(TimeZone.getTimeZone("GMT"));
                                                                }
                                                            };
                                                            
    private IOrder                  buyOrder;
    private IOrder                  sellOrder;
    private double                  maxPrice;
    private double                  minPrice;

    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.history = context.getHistory();
        this.indicators = context.getIndicators();
        this.console = context.getConsole();
        
        context.getChart(instrument).addIndicator(indicators.getIndicator("BBANDS"), new Object[]{maLength, nbDevUp, nbDevDn, maType.ordinal()});
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException {
        String label = (message.getOrder() == null ? "" : message.getOrder().getLabel());
    }

    public void onStop() throws JFException {
        // close all orders
        for (IOrder order : engine.getOrders()) {
            engine.getOrder(order.getLabel()).close();
        }
    }

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

    public void onBar(Instrument instrument, Period period, IBar askBar,
                      IBar bidBar) throws JFException {
                
        if (!instrument.equals(this.instrument) || !period.equals(selectedPeriod))
            return;
        
        double price = history.getLastTick(instrument).getBid();
        double stopLossPrice = price - getPipPrice(this.stopLossPips);
        
        // Buy signal  
        double currentBidPrice = history.getLastTick(instrument).getBid();
        IBar bidBar1 = history.getBar(instrument, selectedPeriod, OfferSide.BID, 1);
        double buyBBand1 = indicators.bbands(instrument, selectedPeriod, OfferSide.BID, appliedPrice, maLength, nbDevUp, nbDevDn, maType, 0)[2];
        double buyBBand2 = indicators.bbands(instrument, selectedPeriod, OfferSide.BID, appliedPrice, maLength, nbDevUp, nbDevDn, maType, 1)[2];
        
        // The strategy buys when the bid price touches bollinger low band from up to down
        if (currentBidPrice <= buyBBand1 && bidBar1.getLow() > buyBBand2) {
            engine.submitOrder(getLabel(instrument), this.instrument, OrderCommand.BUY, this.amount, 0, slippage, stopLossPrice, 0);
        }
        
        // Sell signal
        double sellBBand1 = indicators.bbands(instrument, selectedPeriod, OfferSide.BID, appliedPrice, maLength, nbDevUp, nbDevDn, maType, 0)[0];
        double sellBBand2 = indicators.bbands(instrument, selectedPeriod, OfferSide.BID, appliedPrice, maLength, nbDevUp, nbDevDn, maType, 1)[0];
                
        // The strategy sells when the bid price touches bollinger high band down to up          
        if (currentBidPrice >= sellBBand1 && bidBar1.getHigh() < sellBBand2) {
            engine.submitOrder(getLabel(instrument), this.instrument, OrderCommand.SELL, this.amount, 0, slippage, stopLossPrice, 0);
        }
    }

    private boolean isActive(IOrder order) {
        return order != null && !order.getState().equals(IOrder.State.CANCELED) && !order.getState().equals(IOrder.State.CLOSED);
    }

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

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

    private void print(long time, Object o) {
        print(sdf.format(time) + " " + o);
    }

    private void print(Object o) {
        console.getOut().println(o);
    }
    
    private void print(double[] arr) {
        print(arrayToString(arr));
    }
    
    public static String arrayToString(double[] arr) {
        String str = "";
        for (int r = 0; r < arr.length; r++) {
            str += "[" + r + "] " + (new DecimalFormat("#.#######")).format(arr[r]) + "; ";
        }
        return str;
    }

}
