package jforex.requests;

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

import com.dukascopy.api.*;
import com.dukascopy.api.drawings.IOhlcChartObject;
import com.dukascopy.api.feed.*;
import com.dukascopy.api.IIndicators.AppliedPrice;
import com.dukascopy.api.indicators.IIndicator;
import com.dukascopy.api.IIndicators.MaType;

/**
 * The strategy calculates MACD and EMA values over the last 10 range bars
 * and prints the result to the strategy console.
 * 
 * Optionally the strategy plots the indicators on chart, if the
 * chart configuration matches the feed data. The strategy also adds
 * OHLC informer with indicator showing option
 *
 */
@RequiresFullAccess
public class MacdEmaOnFeed5 implements IStrategy {
    private IConsole console;
    private IHistory history;
    private IIndicators indicators;
    private SimpleDateFormat sdf;
    
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("side")
    public OfferSide side = OfferSide.BID;
    @Configurable("priceRangePips")
    public int priceRangePips = 5;
    @Configurable("Range Bar Count for indicator calculation")
    public int rangeBarCount = 10;
    @Configurable("Add to chart?")
    public boolean addToChart = true;    
    @Configurable("smaTimePeriod")
    public int smaTimePeriod = 3;
    @Configurable("emaTimePeriod")
    public int emaTimePeriod = 12;
    @Configurable("macdSignalPeriod")
    public int macdSignalPeriod = 9;
    @Configurable("macdSlowPeriod")
    public int macdSlowPeriod = 26;
    @Configurable("macdFastPeriod")
    public int macdFastPeriod = 12;
    
    private PriceRange priceRange;
    
    private static int SMA_LINE = 0;
    private static int EMA_LINE = 0;
    private static int MACD = 0;
    private static int MACD_SIGNAL = 1;
    private static int MACD_HIST = 2;
    
    public Instrument selectedInstrument = Instrument.EURUSD;
    public double onePip = selectedInstrument.getPipValue();
    public double currentPrice;
    public long currentTime;
    public double barHigh = 0.0;
    public double barLow = 1000.0;
    public IRangeBar lastRangeBar;
    public IRangeBar iRangeBar;
    public long time[] = new long[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    public double open[] = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    public double high[] = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    public double low[] = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    public double close[] = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    public int starting = 0;
    IIndicator maIndicator;    
   
    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.indicators = context.getIndicators();
        maIndicator = indicators.getIndicator("MA");        
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT")); 

        priceRange = PriceRange.valueOf(priceRangePips);
        
        //just subscribe for feed to be read in the cache
        //context.subscribeToRangeBarFeed(Instrument.EURUSD, OfferSide.BID, PriceRange.valueOf(5), new IRangeBarFeedListener() {
            //public void onBar(Instrument instrument, OfferSide offerSide, PriceRange priceRange, IRangeBar bar) {}

        
        //add indicators to chart and setup OHLC informer to show indicator values
        if(this.addToChart){
            addToChart(context.getChart(instrument));
        }   
        
        /*final FeedDescriptor feedDescriptor = new FeedDescriptor();
        feedDescriptor.setInstrument(instrument);
        feedDescriptor.setDataType(DataType.PRICE_RANGE_AGGREGATION);
        feedDescriptor.setOfferSide(side);
        feedDescriptor.setPriceRange(priceRange);*/
        
        context.subscribeToRangeBarFeed(Instrument.EURUSD, OfferSide.BID, PriceRange.valueOf(5), new IRangeBarFeedListener() {
            public void onBar(Instrument instrument, OfferSide offerSide, PriceRange priceRange, IRangeBar bar) {
                try {
                    final FeedDescriptor feedDescriptor = new FeedDescriptor();
                    feedDescriptor.setInstrument(instrument);
                    feedDescriptor.setDataType(DataType.PRICE_RANGE_AGGREGATION);
                    feedDescriptor.setOfferSide(side);
                    feedDescriptor.setPriceRange(priceRange);        
                    
                    if (starting == 1) {
                        //print("Last bar: " + sdf.format(lastRangeBar.getTime()) + "; " + lastRangeBar.getOpen() + "; " + barHigh + "; " + barLow + "; " + currentPrice);
                        for (int i=19; i>1; i--) {
                            time[i] = time[i-1];
                            open[i] = open[i-1];
                            high[i] = high[i-1];
                            low[i] = low[i-1];
                            close[i] = close[i-1];
                        }                    
                        time[1] = lastRangeBar.getTime();
                        open[1] = lastRangeBar.getOpen();
                        high[1] = barHigh;                    
                        low[1] = barLow;
                        if (low[1] + (onePip * 2.5) < currentPrice) {
                            close[1] = roundToTenthPip(low[1] + (onePip * 5));
                            if (close[1] > high[1]){high[1] = close[1];}
                        }
                        else if (high[1] - (onePip * 2.5) > currentPrice) {
                            close[1] = roundToTenthPip(high[1] - (onePip * 5));
                            if (close[1] < low[1]){low[1] = close[1];}
                        }
                        else {close[1] = currentPrice;}
                        print("Last bar on screen: " + sdf.format(time[1]) + "; " + open[1] + "; " + high[1] + "; " + low[1] + "; " + close[1]);
                        time[0] = currentTime;
                        open[0] = currentPrice;
                        high[0] = currentPrice;
                        low[0] = currentPrice;                                        
                    }
                    else if (starting == 0) {
                        for (int i=19; i>=0; i--) {
                            iRangeBar = history.getRangeBar(instrument, side, priceRange, i);
                            time[i] = iRangeBar.getTime();
                            open[i] = iRangeBar.getOpen();
                            high[i] = iRangeBar.getHigh();
                            low[i] = iRangeBar.getLow();
                            close[i] = iRangeBar.getClose();
                        }
                        starting = 1;
                    }
                    lastRangeBar = history.getRangeBar(instrument, side, priceRange, 0);
                    print("Last bar on feed: " + sdf.format(lastRangeBar.getTime()) + "; " + lastRangeBar.getOpen() + "; " + lastRangeBar.getHigh() + "; " + lastRangeBar.getLow() + "; " + lastRangeBar.getClose());
                    //IRangeBar pRangeBar = history.getRangeBar(instrument, side, priceRange, 1);
                    //print("Penult. bar: " + sdf.format(pRangeBar.getTime()) + "; " + pRangeBar.getOpen() + "; " + pRangeBar.getHigh() + "; " + pRangeBar.getLow() + "; " + pRangeBar.getClose());
                    barHigh = lastRangeBar.getOpen();
                    barLow = lastRangeBar.getOpen();
                    //starting = 1;
                                                                                                    
                    maIndicator = indicators.getIndicator("MA");
                    //set optional inputs
                    maIndicator.setOptInputParameter(0, 3);
                    maIndicator.setOptInputParameter(1, MaType.SMA.ordinal());
                    //set inputs
                    maIndicator.setInputParameter(0, close);
                    //set outputs
                    double [] resultArr = new double [close.length];
                    maIndicator.setOutputParameter(0, resultArr);
                    //calculate
                    maIndicator.calculate(0, close.length - 1);
                    //print results
                    console.getOut().println("SMA on array: " + arrayToString(resultArr));
                    
                    //context.stop();                    
                    
                    //calculate and print SMA
                    Object[] smaFeed = indicators.calculateIndicator(feedDescriptor, new OfferSide[] {OfferSide.BID},
                            "SMA", new AppliedPrice[] {AppliedPrice.CLOSE}, new Object[] {smaTimePeriod}, rangeBarCount, 
                            time[1], 0);//lastRangeBar.getTime(), 0);
                    double[] sma = (double[]) smaFeed[SMA_LINE]; //sma has just 1 output
                    print("SMA on feed: %s", arrayToStringInv(sma));
                    
                    //calculate and print EMA
                    /*Object[] emaFeed = indicators.calculateIndicator(feedDescriptor, new OfferSide[] { OfferSide.BID }, "EMA",
                            new AppliedPrice[] { AppliedPrice.CLOSE }, new Object[] { emaTimePeriod },  rangeBarCount, lastRangeBar.getTime(), 0);
                    double[] ema = (double[]) emaFeed[EMA_LINE]; //ema has just 1 output
                    print("ema for last %s range bars: %s",rangeBarCount, arrayToString(ema));
                    
                    //calculate and print MACD        
                    Object[] macdFeed = indicators.calculateIndicator(feedDescriptor, new OfferSide[] { OfferSide.BID }, "MACD",
                            new AppliedPrice[] { AppliedPrice.CLOSE }, new Object[] {  macdFastPeriod, macdSlowPeriod, macdSignalPeriod },  
                            rangeBarCount, lastRangeBar.getTime(), 0);
                    double[][] macd = {(double[]) macdFeed[MACD], (double[]) macdFeed[MACD_SIGNAL], (double[]) macdFeed[MACD_HIST] };        
            
                    IIndicator macdIndicator = indicators.getIndicator("MACD");
                    for(int i = 0; i < macdIndicator.getIndicatorInfo().getNumberOfOutputs(); i++){
                        String outputName = macdIndicator.getOutputParameterInfo(i).getName();
                        print("%s for last %s range bars: %s",outputName, rangeBarCount, arrayToString(macd[i]));
                    }*/
                } catch (JFException ex) {
                        ex.printStackTrace(console.getErr());
                }
            }        
        });    
    }
    
    private void addToChart(IChart chart){
        if(chart == null){
            print("chart not opened!");    
            return;
        } 
        if (chart.getSelectedOfferSide() != this.side) {
            print("chart offer side is not " + this.side);
            return;
        }  
        if (chart.getPriceRange() != this.priceRange) {
            print("chart price range is not " + this.priceRange);
            return;
        } 
        
        chart.addIndicator(indicators.getIndicator("EMA"), new Object[] { emaTimePeriod });
        chart.addIndicator(indicators.getIndicator("MACD"), new Object[] { macdFastPeriod, macdSlowPeriod, macdSignalPeriod });

        IOhlcChartObject ohlc = null;
        for (IChartObject obj : chart.getAll()) {
            if (obj instanceof IOhlcChartObject) {
                ohlc = (IOhlcChartObject) obj;
            }
        }
        if (ohlc == null) {
            ohlc = chart.getChartObjectFactory().createOhlcInformer();
            chart.addToMainChart(ohlc);
        }
        ohlc.setShowIndicatorInfo(true);
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException {
    }

    public void onStop() throws JFException {
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if(instrument.equals(Instrument.EURUSD)){
            currentTime = tick.getTime();
            currentPrice = tick.getBid();
            if (currentPrice > barHigh){barHigh = currentPrice;}
            if (currentPrice < barLow){barLow = currentPrice;}
        }    
    }
    
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
    }

    private void print(String format, Object...args){
        print(String.format(format,args));
    }
    
    private void print(Object o){
         console.getOut().println(o);
    }

    private static String arrayToString(double[] arr) {
        String str = "";
        for (int r = 1; r < arr.length; r++) {
            str += String.format("[%s] %.5f; ", r, arr[r]);
        }
        return str;
    }
    
    private static String arrayToStringInv(double[] arr) {
        String str = "";
        for (int r = arr.length - 1; r >= 0; r--) {
            str += String.format("[%s] %.5f; ", r, arr[r]);
        }
        return str;
    }
    
    private double roundToTenthPip(double price) {
        BigDecimal bd = new BigDecimal(price);
        bd = bd.setScale(selectedInstrument.getPipScale() + 1, BigDecimal.ROUND_HALF_UP);
        return bd.doubleValue();
    }
}