package JForex.myStrategies;

import com.dukascopy.api.*;
import java.text.DecimalFormat;
import java.util.*;

/**
 *
 * 
 */
public class myRSI implements IStrategy {

    //Strategy base objects
    private IContext myContext          = null;
    private IAccount myAccount          = null; 
    private IEngine myEngine            = null;
    private IOrder myOrder              = null; 
    private IHistory myHistory          = null;
    private IIndicators myIndicators    = null;
    private IConsole myConsole          = null;
    private IUserInterface myInterface  = null;
    
    //Strategy configurable variables
    @Configurable("Instrument to use:")
    public Instrument myInstrument = Instrument.EURUSD;
    @Configurable("Period to use:")
    public Period myPeriod = Period.FOUR_HOURS;
    @Configurable("Initial position value:")
    public double myInitialPosition = 4;
    @Configurable("Take Profit:")
    public int myTakeProfit = 100;
    @Configurable("Stop Loss:")
    public int myStopLoss = 30;
    @Configurable("Difference Value in Pips:")
    public double myDifference = 20;
    // Time specifics (related to server time - GMT)
    @Configurable("Control time:")
    public boolean myControlTime = true;
    @Configurable("Trade weekends:")
    public boolean myTradeWeekends = false;
    @Configurable("Beggin Trade hour:")
    public int myBeginTime = 7;
    @Configurable("End trade hour")
    public int myEndTime = 21;
    // RSI Specifics
    @Configurable("RSI Min:")
    public double myRSIMin = 30.0;
    @Configurable("RSI Max:")
    public double myRSIMax = 70.0;
    @Configurable("RSI interval:")
    public int myRSIInterval = 5;
    @Configurable("Before trigger value:")
    public double myRSITrigger = 10;
    @Configurable("Order inverter")
    public boolean myOrderInverter = false;
    //Strategy fixed variables
    private DecimalFormat myDF = new DecimalFormat("####.######");
    //Strategy variables
    private boolean myCanTrade;
    private double myEquitity;
    private IEngine.OrderCommand myBuy = IEngine.OrderCommand.BUY;
    private IEngine.OrderCommand mySell = IEngine.OrderCommand.SELL;
    private double mySLValue;
    private double myTPValue;
    private double myPipValue;
    private double myOrderSL;
    private boolean myOtherBar = true; 
    private boolean myRSIMaxOk;
    private boolean myRSIMinOk; 
    private double myDifferencePips;
    
    /**
     * Executed at strategy start
     * 
     * @param context
     * @throws JFException 
     */
    @Override
    public void onStart(IContext context) throws JFException {
        // initialize base objects
        myContext = context;
        myConsole = myContext.getConsole();
        myInterface = myContext.getUserInterface();
        myEngine = myContext.getEngine();
        myIndicators = myContext.getIndicators();
        myAccount = myContext.getAccount();
        myHistory = myContext.getHistory();
        // close all orders if any open on strategy start
        closeAllOrders();
        myEquitity = myAccount.getBaseEquity();
        // Load instrument
        Set subscribedInstruments = new HashSet();
        subscribedInstruments.add(myInstrument);
        myContext.setSubscribedInstruments(subscribedInstruments);
        // set some variables
        myPipValue = myInstrument.getPipValue();
        if (myOrderInverter) orderInverter();
        myTPValue = myTakeProfit * myPipValue;
        mySLValue = myStopLoss * myPipValue;
        myCanTrade = true;
        // some debug
        // myConsole.getOut().println(myPipValue * myDifference);
        myDifferencePips = myPipValue * myDifference;
    }// End onStart

    /**
     * Executed on every tick received
     * 
     * @param instrument
     * @param tick
     * @throws JFException 
     */
    @Override
    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (!myInstrument.equals(instrument)) return;
        if (myControlTime){
            boolean allowTrade = timeAllowsTrades(tick.getTime(), myBeginTime, myEndTime, myTradeWeekends); 
            if (!allowTrade) return;
        }
        // if the trade gets on our favor, lock profits
        if (myOrder != null){
            double orderOpenPrice = myOrder.getOpenPrice();
            double actualPrice = Double.parseDouble(myDF.format(tick.getBid() + (tick.getAsk() - tick.getBid()) / 2));
            double difference = Double.parseDouble(myDF.format(orderOpenPrice - actualPrice));
            myOrderSL = myOrder.getStopLossPrice();
            //double debug = 0;
            //double debug1 = 0;
            if (myOrder.getOrderCommand() == IEngine.OrderCommand.BUY){
                double actualSLValue = Double.parseDouble(myDF.format(actualPrice - mySLValue));
                //debug = actualSLValue;
                if (difference < 0 && myOrderSL < actualSLValue) {
                    // lock at least 50% profits
                    if (Math.abs(difference) > myDifferencePips){
                        actualSLValue = Double.parseDouble(myDF.format(actualPrice - myDifferencePips / 2));
                        //debug1 = actualSLValue;
                    }
                    myOrder.setStopLossPrice(actualSLValue);
                }
            }
            if (myOrder.getOrderCommand() == IEngine.OrderCommand.SELL){
                double actualSLValue = Double.parseDouble(myDF.format(actualPrice + mySLValue));
                //debug = actualSLValue;
                if (difference > 0 && myOrderSL > actualSLValue) {
                    // lock at least 50% profits
                    if (Math.abs(difference) > myDifferencePips){
                        actualSLValue = Double.parseDouble(myDF.format(actualPrice + myDifferencePips / 2));
                        //debug1 = actualSLValue;
                    }
                    myOrder.setStopLossPrice(actualSLValue);
                }
            }
            // some debug
            //myConsole.getOut().println(myOrder.getLabel() + " open:" + orderOpenPrice + " OSL:" + myOrderSL + " AP:" + actualPrice + " diff:" + myDF.format(difference) + " ASL:" + debug + " ASL2:" + debug1);
        }
        if (!myCanTrade || !myOtherBar) return;
        double rsiValue = myIndicators.rsi(myInstrument, myPeriod, OfferSide.BID, IIndicators.AppliedPrice.OPEN, myRSIInterval, 0);
        double tickValue = tick.getBid();

        if (rsiValue >= myRSIMax + myRSITrigger ) myRSIMaxOk = true; 
        if (rsiValue < myRSIMax && myRSIMaxOk){
            myCanTrade = false;
            myOtherBar = false;
            myRSIMaxOk = false;
            placeBuyorSell(mySell, (myOrderInverter ? tickValue - mySLValue : tickValue + mySLValue) , (myOrderInverter ? tickValue + myTPValue : tickValue - myTPValue), tick.getTime());
        }
        if (rsiValue <= myRSIMin - myRSITrigger) myRSIMinOk = true;
        if (rsiValue > myRSIMin && myRSIMinOk){
            myCanTrade = false;
            myOtherBar = false;
            myRSIMinOk = false;
            placeBuyorSell(myBuy, (myOrderInverter ? tickValue + mySLValue : tickValue - mySLValue) , (myOrderInverter ? tickValue - myTPValue : tickValue + myTPValue), tick.getTime());
        }
    }// end onTick

    /**
     * Executed at any basic period bar received
     * 
     * @param instrument
     * @param period
     * @param askBar
     * @param bidBar
     * @throws JFException 
     */
    @Override
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
        if (myInstrument != instrument || myPeriod != period) return;
        myOtherBar = true;

    }// End onBar

    /**
     * Executed everytime a message is received from stream data 
     * 
     * @param message - the message itself
     * @throws JFException
     */
    @Override
    public void onMessage(IMessage message) throws JFException {       
        if (message.getType() == IMessage.Type.ORDER_FILL_OK ||
                message.getType() == IMessage.Type.ORDER_SUBMIT_OK ||
                message.getType() == IMessage.Type.SENDING_ORDER){
            myCanTrade = false;
        }
        if (//message.getType() == IMessage.Type.ORDER_CLOSE_REJECTED ||
                message.getType() == IMessage.Type.ORDER_FILL_REJECTED ||
                message.getType() == IMessage.Type.ORDER_SUBMIT_REJECTED ||
                message.getType() == IMessage.Type.ORDER_CLOSE_OK){
            myCanTrade = true;
            myOrder = null;
            myOrderSL = 0;
        }    
    }// end onMessage

    /**
     * Executed at any received account change
     * 
     * @param account
     * @throws JFException 
     */
    @Override
    public void onAccount(IAccount account) throws JFException {
        
    }// End onAccount

    /**
     * Executed at strategy end
     * 
     * @throws JFException 
     */
    @Override
    public void onStop() throws JFException {
       closeAllOrders(); 
    }// End onStop

    /**
     * Close all open orders at function execution
     * 
     * @throws JFException 
     */
    private void closeAllOrders() throws JFException {
        List<IOrder> openOrders = myEngine.getOrders();
        if (openOrders.isEmpty()) return;
        for (IOrder order : openOrders) order.close();
    }// End closeAllOrders

    /**
     * Inverts order execution
     */
    private void orderInverter() throws JFException {
        myBuy = IEngine.OrderCommand.SELL;
        mySell = IEngine.OrderCommand.BUY;
    }// End orderInverter
    
    /**
     * Controls time paramenters and sets if it's possible to trade at given time paramenters
     * Also close all orders if we are outside time interval
     * 
     * @param barTime
     * @param bHour
     * @param eHour
     * @param weekend
     * @return 
     */
    private boolean timeAllowsTrades(long barTime, int bHour, int eHour, boolean weekend) throws JFException{
        boolean allowsTrades = false;
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        cal.setTimeInMillis(barTime);
        int day = cal.get(Calendar.DAY_OF_WEEK);
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        
        if (hour >= bHour && hour < eHour) allowsTrades = true;
        if (weekend && (day == 1 || day == 7)) allowsTrades = false;
        if (!allowsTrades) closeAllOrders();
        return allowsTrades;
    }// End timeAllowsTrades
    
    /**
     * Returns the value for order label in the format AAABBB_YYYYMMDDHHMM
     * where AAABBB is the pair and followed by the date and time
     * 
     * @param instrument - the instrument in use
     * @return 
     * @throws JFException 
     */
    private String getLabel (Instrument instrument, long time) throws JFException{
        String label;
        String stripSlash = instrument.toString();
        String[] stripped = stripSlash.split("/");
        String instrumentName = stripped[0] + stripped[1];
        Calendar now = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        now.setTimeInMillis(time);
        String nowYear = "" + now.get(Calendar.YEAR);
        String nowMonth =  "00" + now.get(Calendar.MONTH); //leading 0 if needed
        String nowDay = "00" + now.get(Calendar.DAY_OF_MONTH);
        String nowHours = "00" + now.get(Calendar.HOUR_OF_DAY);
        String nowMinutes = "00" + now.get(Calendar.MINUTE);
        
        label = instrumentName + "_" + nowYear +
                nowMonth.substring(nowMonth.length()- 2) + // get only the last 
                nowDay.substring(nowDay.length() - 2) +  // two digits of 
                nowHours.substring(nowHours.length() - 2) + // the string
                nowMinutes.substring(nowMinutes.length() - 2);
        return label;
    }// end getLabel
    
    /**
     * Executes buy or sell orders with TP and SL
     * 
     * @param myOrderCommand - Sell or Buy
     * @param stopLossPrice - Price for StopLoss
     * @param takeProfitPrice - Price for Take profit
     * @throws JFException 
     */
    private void placeBuyorSell(IEngine.OrderCommand myOrderCommand,
            double stopLossPrice, double takeProfitPrice, long time) throws JFException{

        //buy @ price market and with 0.5 slippage
        if (myOrderCommand == IEngine.OrderCommand.BUY){
            myOrder = myEngine.submitOrder("B" + getLabel(myInstrument, time), myInstrument, 
                    myOrderCommand, myInitialPosition, 0d, 0d, stopLossPrice, 
                    takeProfitPrice);
        }
        //sell @ price market and with 0.5 slippage
        if (myOrderCommand == IEngine.OrderCommand.SELL){
            myOrder = myEngine.submitOrder("S" + getLabel(myInstrument, time), myInstrument, 
                    myOrderCommand, myInitialPosition, 0d, 0d, stopLossPrice,
                    takeProfitPrice);
        }

    }// end placeBuyorSell
 
    /**
     * Executes buy or sell orders without TP and SL
     * 
     * @param myOrderCommand
     * @throws JFException 
     */
    private void placeBuyorSell(IEngine.OrderCommand myOrderCommand, long time) throws JFException{

        //buy @ price market and with 0.0 slippage
        if (myOrderCommand == IEngine.OrderCommand.BUY){
            myOrder = myEngine.submitOrder("B_" + getLabel(myInstrument, time), myInstrument, 
                    myOrderCommand, myInitialPosition, 0, 0);
        }
        //sell @ price market and with 0 slippage
        if (myOrderCommand == IEngine.OrderCommand.SELL){
            myOrder = myEngine.submitOrder("S_" + getLabel(myInstrument, time), myInstrument, 
                    myOrderCommand, myInitialPosition, 0, 0);
        }
        
    }// end placeBuyorSell
}
