package jforex.strategies.indicators;
 
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.feed.FeedDescriptor;
import com.dukascopy.api.feed.IRenkoBar;
import com.dukascopy.api.feed.IRenkoBarFeedListener;
import com.dukascopy.api.indicators.IIndicator;
import java.math.BigDecimal;
import java.math.RoundingMode;
 
public class InverseFisherStrat implements IStrategy, IRenkoBarFeedListener {
 
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IIndicators indicators;
    private int counter = 0;
    private IOrder order;
    @Configurable("Instrument")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("Price range (pips)")
    public int priceRangePips = 2;
    @Configurable("RSI period")
    public int rsiPeriod = 5;
    @Configurable("WMA period")
    public int wmaPeriod = 9;
    @Configurable("Offer side")
    public OfferSide offerSide = OfferSide.BID;
    @Configurable("Slippage")
    public double slippage = 0;
    @Configurable("Amount")
    public double amount = 0.01;
    @Configurable("Filter")
    public Filter filter = Filter.ALL_FLATS;
    @Configurable("Take profit 1 pips")
    public int takeProfit1Pips = 10;
    @Configurable("Take profit 2 pips")
    public int takeProfit2Pips = 40;
    @Configurable("Stop loss in pips")
    public int stopLossPips = 0;
    @Configurable("Breake even margin")
    public double beMargin = 0;
     
    @Configurable("Applied price")
    public AppliedPrice appliedPrice = AppliedPrice.CLOSE;
    int appliedPriceParam = 1;
    FeedDescriptor feedDescriptor;
    boolean isPratillyClosed = false;
    
    int timePeriod = 5;
    double nbDev = 1;
    int shift = 0;
 
    @Override
    public void onStart(IContext context) throws JFException {
        this.console = context.getConsole();
        this.indicators = context.getIndicators();
        this.history = context.getHistory();
        this.engine = context.getEngine();
 
        if (appliedPrice.equals(AppliedPrice.OPEN)) {
            appliedPriceParam = 0;
        } else if (appliedPrice.equals(AppliedPrice.CLOSE)) {
            appliedPriceParam = 1;
        } else if (appliedPrice.equals(AppliedPrice.HIGH)) {
            appliedPriceParam = 2;
        } else if (appliedPrice.equals(AppliedPrice.LOW)) {
            appliedPriceParam = 3;
        }
 
        feedDescriptor = new FeedDescriptor();
        feedDescriptor.setDataType(DataType.RENKO);
        feedDescriptor.setOfferSide(offerSide);
        feedDescriptor.setInstrument(instrument);
        feedDescriptor.setPriceRange(PriceRange.valueOf(priceRangePips));
        feedDescriptor.setFilter(filter);
 
        IChart chart = context.getChart(instrument);
        if (chart != null) {
            chart.addIndicator(indicators.getIndicator("INV_FISH"), new Object[]{appliedPriceParam, rsiPeriod, wmaPeriod});
        }
 
        context.subscribeToRenkoBarFeed(instrument, offerSide, PriceRange.valueOf(priceRangePips), this);
    }
 
    @Override
    public void onBar(Instrument instrument, OfferSide offerSide, PriceRange priceRange, IRenkoBar bar) {
 
        try {
            if (!isActive(order)) {
                order = null;
            } else if (order.equals(IOrder.State.CREATED)) {
                return;
            }
 
            boolean sellSign = false;
            boolean buySign = false;
 
            Object[] invFisher = indicators.calculateIndicator(
                    feedDescriptor, new OfferSide[]{offerSide}, "INV_FISH",
                    new AppliedPrice[]{appliedPrice}, new Object[]{appliedPriceParam, rsiPeriod, wmaPeriod},
                    100, bar.getTime(), 0);
                    
                    
                    long start1 = System.currentTimeMillis();
                    Object[] stddev = indicators.calculateIndicator(feedDescriptor, new  OfferSide[] {OfferSide.BID}, "STDDEV", new IIndicators.AppliedPrice[] {AppliedPrice.CLOSE},
            new Object[]{timePeriod, nbDev}, shift);
            double result = (Double) stddev[0];
            console.getOut().println("indicator1 calculated in: " + (System.currentTimeMillis() - start1) + " milliseconds");
             
            double firstValue = ((double[]) invFisher[0])[98];
            double secondValue = ((double[]) invFisher[0])[99];
 
            if (firstValue < 0 && secondValue > 0 && result > 0.0001) {
                buySign = true;
            }
            if (firstValue > 0 && secondValue < 0 && result > 0.0001) {
                sellSign = true;
            }
 
            // PLACE ORDER        
            if (buySign) {
                if (order == null || !order.isLong()) {
                    closeOrder(order, 0);
                    order = submitOrder(OrderCommand.BUY, instrument);
                    isPratillyClosed = false;
                }
 
            } else if (sellSign) {
                if (order == null || order.isLong()) {
                    closeOrder(order, 0);
                    order = submitOrder(OrderCommand.SELL, instrument);
                    isPratillyClosed = false;
                }
 
            }
        } catch (JFException ex) {
            ex.printStackTrace(console.getErr());
        }
    }
 
    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
    }
 
    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (order == null) {
            return;
        }
        if (takeProfit2Pips != 0 && order.getProfitLossInPips() > takeProfit2Pips) {
            closeOrder(order, 0);
            order = null;
 
        } else if (order.getProfitLossInPips() > takeProfit1Pips && !isPratillyClosed) {
            if (takeProfit2Pips != 0) {
                closeOrder(order, order.getAmount() / 2);
                setBreakEven(order, beMargin);
                isPratillyClosed = true;
            } else {
                closeOrder(order, 0);
                order = null;
            }
        }
    }
 
    private IOrder submitOrder(OrderCommand orderCmd, Instrument instr) throws JFException {
 
        double stopLossPrice = 0.0, takeProfitPrice = 0.0;
 
        ITick t = history.getLastTick(instr);
 
        // Calculating order price, stop loss and take profit prices
        if (orderCmd == OrderCommand.BUY) {
            if (stopLossPips > 0) {
                stopLossPrice = t.getBid() - getPipPrice(stopLossPips, instr);
            }
//            if (takeProfit1Pips > 0) {
//                takeProfitPrice = t.getBid() + getPipPrice(takeProfit1Pips, instr);
//            }
        } else {
            if (stopLossPips > 0) {
                stopLossPrice = t.getAsk() + getPipPrice(stopLossPips, instr);
            }
//            if (takeProfit1Pips > 0) {
//                takeProfitPrice = t.getAsk() - getPipPrice(takeProfit1Pips, instr);
//            }
        }
 
        return engine.submitOrder(getLabel(instr), instr, orderCmd, amount, 0, slippage, stopLossPrice, takeProfitPrice);
    }
 
    private void closeOrder(IOrder order, double amount) throws JFException {
        if (order == null) {
            return;
        }
        if (order.getState() == IOrder.State.CREATED) {
            order.waitForUpdate();
        }
        if (order.getState() != IOrder.State.CLOSED && order.getState() != IOrder.State.CANCELED) {
            if (Double.compare(amount, 0.0) != 0) {
                order.close(amount);
            } else {
                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, Instrument instr) {
        return pips * instr.getPipValue();
    }
 
    private String getLabel(Instrument instr) {
        String label = instr.name();
        label = label + (counter++);
        label = label.toUpperCase();
        return label;
    }
 
    // sets stop loss to "tick.getAsk - pStopLossPips" if price is higher than "pOrder.getOpenPrice() + pTriggerPips"
    public boolean setBreakEven(IOrder pOrder, double pStopLossPips) throws JFException {
        boolean triggered = false;
 
        if (pOrder != null && pOrder.getState() == IOrder.State.FILLED) {
 
            Instrument instr = pOrder.getInstrument();
 
            double newStop;
            double openPrice = pOrder.getOpenPrice();
            double currentStopLoss = pOrder.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 stopLossMargin
 
            if (pOrder.isLong()) { // long side order         
                // trailing stop loss
                newStop = openPrice - getPipPrice(pStopLossPips, instr);
                newStop = getRoundedPrice(newStop, instr);
 
                if (currentStopLoss != newStop) {
                    pOrder.setStopLossPrice(newStop);
                    triggered = true;
                }
 
 
            } else { // short side order
                // trailing stop loss
                newStop = openPrice + getPipPrice(pStopLossPips, instr);
                newStop = getRoundedPrice(newStop, instr);
 
                if (currentStopLoss != newStop) {
                    pOrder.setStopLossPrice(newStop);
                    triggered = true;
                }
            }
        }
        return triggered;
    }
 
    private double getRoundedPrice(double price, Instrument instr) {
        BigDecimal bd = new BigDecimal(price);
        bd = bd.setScale(instr.getPipScale() + 1, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }
 
    public void onMessage(IMessage message) throws JFException {
    }
 
    public void onAccount(IAccount account) throws JFException {
    }
 
    public void onStop() throws JFException {
    }
 
    /**************** debug print functions ***********************/
    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 (ob instanceof Long) {
                print2(toStr((Long) ob));
            } else if (ob instanceof IBar) {
                print2(toStr((IBar) ob));
            } else {
                print2(ob);
            }
            print2(" ");
        }
        console.getOut().println();
    }
 
    private void print(Object o) {
        console.getOut().println(o);
    }
 
    private void print2(Object o) {
        console.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());
        }
        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 printTime(Long time) {
        console.getOut().println(toStr(time));
    }
}