package jforex;

import java.util.*;
import java.util.concurrent.*;
import java.text.*;
import java.math.*;

import com.dukascopy.api.*;

public class exStrategy implements IStrategy {
    private IEngine engine;
    private IConsole console;
    private IHistory history;
    private IContext context;
    private IIndicators indicators;
    private IUserInterface userInterface;
    
    @Configurable("strategyName") public String strategyName = "TEST";
    @Configurable("curInstrument") public Instrument curInstrument = Instrument.AUDNZD;
    @Configurable("magicNumber") public String magicNumber = "2012083001";
    @Configurable("stopLoss") public double stopLoss = 100.0;
    @Configurable("maxTakeProfit") public double maxTakeProfit = 20.0;
    @Configurable("minTakeProfit") public double minTakeProfit = 10.0;
    @Configurable("useLot") public double useLot = 0.1;
    @Configurable("waitForUpdate") public int waitForUpdate = 5000;
    
    private int label;
    private GregorianCalendar curDatetime;
    private SimpleDateFormat dateFormat;
    private NumberFormat priceFormat;
    private long curTimestamp;
    private double pip;
    private double tbid, task;

    private void print(String str) {
        console.getOut().println(dateFormat.format(curDatetime.getTime()) + ": " + str);
    }

    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.history = context.getHistory();
        this.context = context;
        this.indicators = context.getIndicators();
        this.userInterface = context.getUserInterface();
        
        HashSet si = new HashSet();
        si.add(curInstrument);
        context.setSubscribedInstruments(si);
        pip = curInstrument.getPipValue();
        tbid = history.getLastTick(curInstrument).getBid();
        task = history.getLastTick(curInstrument).getAsk();
        curTimestamp = history.getLastTick(curInstrument).getTime();
        curDatetime = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        curDatetime.setTimeInMillis(curTimestamp);

        dateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        priceFormat = NumberFormat.getNumberInstance(Locale.getDefault());
        priceFormat.setMaximumFractionDigits(5);

        label = 0;
    }

    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException {
        IOrder o;
        double profit;
        IMessage.Type it;
        it = message.getType();
        double oAmount, oOriginalAmount, oRequestedAmount;
        if (it == IMessage.Type.ORDER_CLOSE_OK)
        {
            o = message.getOrder();
            profit = o.getProfitLossInAccountCurrency();
            oAmount = o.getAmount();
            oOriginalAmount = o.getOriginalAmount();// if (Testing) oOriginalAmount /= 1000000.0;
            oRequestedAmount = o.getRequestedAmount();
            if (Double.compare(profit, 0.0) != 0)
            {
                profit = o.getProfitLossInPips();
                if (o.getState() == IOrder.State.FILLED)
                {
                    print("Partial close order id " + o.getId() + ", amount " + priceFormat.format(oAmount) + ", requested amount " + priceFormat.format(oRequestedAmount) + ", original amount " + priceFormat.format(oOriginalAmount) + ", result " + priceFormat.format(profit));
                }
                else
                {
                    print("Closed order id " + o.getId() + ", amount " + priceFormat.format(oAmount) + ", original amount " + priceFormat.format(oOriginalAmount) + ", result " + priceFormat.format(profit));
                }
                if (profit < -0.9 * stopLoss)
                {
                    if (o.isLong()) {print("SL buy, result " + priceFormat.format(profit) + ", slippage " + priceFormat.format((tbid - o.getClosePrice()) / pip));}
                    else print("SL sell, result " + priceFormat.format(profit) + " slippage " + priceFormat.format((o.getClosePrice() - task) / pip));
                }
            }
        }
        else if (it == IMessage.Type.ORDER_FILL_OK)
        {
            o = message.getOrder();
            oAmount = o.getAmount();
            oOriginalAmount = o.getOriginalAmount(); //if (Testing) oOriginalAmount /= 1000000.0;
            oRequestedAmount = o.getRequestedAmount();
            if (Double.compare(oAmount, oOriginalAmount) != 0)
            {
                print("Partial fill order id " + o.getId() + ", amount " + priceFormat.format(oAmount) + ", requested amount " + priceFormat.format(oRequestedAmount) + ", original amount " + priceFormat.format(oOriginalAmount));
            }
            else
            {
                print("Filled order id " + o.getId() + ", amount  " + priceFormat.format(oAmount));
            }
        }
        else if (it == IMessage.Type.ORDER_CLOSE_REJECTED) {print("Order id " + message.getOrder().getId() + " close rejected!");}
        else if (it == IMessage.Type.ORDER_SUBMIT_REJECTED) {print("Order id " + message.getOrder().getId() + " submit rejected!");}
        else if (it == IMessage.Type.ORDER_CHANGED_REJECTED) {print("Order id " + message.getOrder().getId() + " change rejected!");}
        else if (it == IMessage.Type.ORDER_FILL_REJECTED) {print("Order id " + message.getOrder().getId() + " fill rejected!");}
        else if (it == IMessage.Type.CONNECTION_STATUS) {print("Disconnected?");}
        else return;
    }

    public void onStop() throws JFException {
        print("Stopped.");
    }

    private double NormalizeDouble(double d, int precision) throws JFException
    {
        BigDecimal bd = new BigDecimal(d).setScale(precision, RoundingMode.HALF_EVEN);
        return(bd.doubleValue());
    }
   
    private String getLabel() throws JFException {return(strategyName + "_" + magicNumber + "_" + curInstrument.ordinal() + "_" + ++label);}
    
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        IOrder.State oState;
        double oOpenPrice, oTPPrice, tp;
        IMessage msg;
        int buyPositions;
        
        if (instrument != curInstrument) return;
        curTimestamp = tick.getTime();
        curDatetime.setTimeInMillis(curTimestamp);
        tbid = tick.getBid(); task = tick.getAsk();
        
        buyPositions = 0;
        for (IOrder o : engine.getOrders(curInstrument))
        {
            if (!o.getLabel().startsWith(strategyName + "_" + magicNumber)) continue;
            oState = o.getState();
            if (oState != IOrder.State.FILLED) continue;
            if (!o.isLong()) {continue;} else {buyPositions++;}
            oOpenPrice = o.getOpenPrice();
            oTPPrice = o.getTakeProfitPrice();
            tp = NormalizeDouble(oTPPrice + minTakeProfit * pip, 4);
            if (Double.compare(tp, oTPPrice) != 0)
            {
                try
                {
                    o.setTakeProfitPrice(tp);
                    msg = o.waitForUpdate(waitForUpdate, TimeUnit.MILLISECONDS);
                    if (msg == null) {print("TP buy change: WaitForUpdate() timeout!");}
                }
                catch (JFException e) {console.getErr().println(e.getMessage()); e.printStackTrace(console.getErr()); print("Exception on changing TP buy!!! Error message: " + e.getMessage());}
                try {Thread.sleep(waitForUpdate);} catch (InterruptedException e) {console.getErr().println(e.getMessage()); return;}
                return;
            }
         }
         
         IOrder o;
         
         if (buyPositions == 0)
         {
            try
            {
                o = engine.submitOrder(getLabel(), curInstrument, IEngine.OrderCommand.BUY, useLot, task, 3.0, tbid - stopLoss * pip, tbid + maxTakeProfit * pip);
                if (o != null)
                {
                    msg = o.waitForUpdate(waitForUpdate, TimeUnit.MILLISECONDS);
                    if (msg == null) {print("Submitting buy: WaitForUpdate() timeout!");}
                    oState = o.getState();
                    if (oState == IOrder.State.FILLED) {print("Buy filled!"); return;}
                    else if (oState == IOrder.State.OPENED) {}
                    else if (oState == IOrder.State.CREATED) {}
                    else  {print("Looks like buy " + priceFormat.format(useLot) + " " + curInstrument.toString() + " @" + priceFormat.format(task) + " order was rejected. Order state: " + oState + ", message type: " + msg.getType() + ", content: " + msg.getContent()); return;}
                    msg = o.waitForUpdate(waitForUpdate, TimeUnit.MILLISECONDS);
                    if (msg == null) {print("Submitting buy: WaitForUpdate() timeout!");}
                    oState = o.getState();
                    if (oState == IOrder.State.FILLED) {print("Buy filled!"); return;}
                    else if (oState == IOrder.State.OPENED) {print("Buy order still opened!"); return;}
                    else if (oState == IOrder.State.CREATED) {print("Buy order still created!"); return;}
                    else  {print("Looks like buy " + priceFormat.format(useLot) + " " + curInstrument.toString() + " @" + priceFormat.format(task) + " order was rejected. Order state: " + oState + ", message type: " + msg.getType() + ", content: " + msg.getContent()); return;}
                } else {print("Submitting buy: null order returned!"); return;}
            }
            catch (JFException e) {console.getErr().println(e.getMessage()); e.printStackTrace(console.getErr()); print("Exception on creating buy order!!! " + e.getMessage()); return;}
         }
    }
    
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
    }
}