package strategies;
// package singlejartest;

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

import com.dukascopy.api.*;
import com.dukascopy.api.IIndicators.MaType;

public class Backtester_Bug_Strategy implements IStrategy 
{
    /////////////////////////////
    //constants used for console logging
    /////////////////////////////
    static final int INFO = 0;
    static final int WARNING = 1;
    static final int ERROR = 2;
    
    /////////////////////////////
    //Configurable input parameters
    /////////////////////////////
    
    @Configurable("Strategy Label") public String _label = "Test";
    
    @Configurable("Stop Loss PIPs") public int _stopLossPips = 25;
    @Configurable("Take Profit PIPs") public double _takeProfitPips = 40;
    
    @Configurable("LR Period") public int _lrPeriod = 10;
    @Configurable("EMA Period") public int _emaPeriod = 24;
    @Configurable("BB Period") public int _bbPeriod = 12;
    @Configurable("Inner BB Std Dev") public int _bbDevInner = 2;
    @Configurable("Outer BB Std Dev") public int _bbDevOuter = 3;
    
    @Configurable("Max Risk % Per Trade") public double _maxRisk = 3;
    @Configurable("Max Lot Size") public double _maxLotSize = 10.0;
    @Configurable("Min Lot Size") public double _minLotSize = 0.001;
    @Configurable("Slippage PIPs") public double _slippage = 10;
    
    @Configurable("Debug Printing") public boolean _debugPrintFlag = true;

    
    /////////////////////////////
    //JForex interfaces for future use
    /////////////////////////////
    private IEngine _engine;
    private IConsole _console;
    private IContext _context;
    private IIndicators _indicators;
    private IHistory _history;
    private IAccount _account;
    
    /////////////////////////////
    //logic parameters values
    /////////////////////////////
    enum trade { NONE, IN_LONG, IN_SHORT };
    enum trend { NONE, UP, DOWN };
    
    double lr;
    double lr1;
    double ema;
    double ema1;
    double bbInnerUpper;
    double bbInnerLower;
    double bbOuterUpper;
    double bbOuterLower;
    double bandwidth;
 
    private class InstrumentState
    {
        public trade _trade = trade.NONE;
        public trend _trend = trend.NONE;
    }
    
    private HashMap<Instrument, InstrumentState> _instrStates;

    private Set<Instrument> _instSet;   
    Period _period = Period.THIRTY_MINS; // Period.createCustomPeriod(Unit.Hour, 2);//ONE_HOUR
    
    private String _delimiter = "_";
    int _orderCounter = 1;
    
    final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd 'at' HH:mm:ss z");
    final DecimalFormat df = new DecimalFormat("0.000000");

    /////////////////////////////
    // helper methods
    /////////////////////////////
        

    private void debugPrint (int level, String strToPrint)
    {
        if (_debugPrintFlag)
        {
            switch (level)
            {
                case ERROR:
                    _console.getErr().println(strToPrint);
                    break;
                case WARNING:
                    _console.getWarn().println(strToPrint);
                    break;
                case INFO:
                default:
                    _console.getOut().println(strToPrint);
            }
        }
    }
    
    public double roundDouble(double number, int decimalPlaces)
    {
        double modifier = Math.pow(10.0, decimalPlaces);
        return Math.round(number * modifier) / modifier;
    }
    
    public void exit(Instrument inst) throws JFException 
    {    
        for (IOrder order : _engine.getOrders(inst)) 
        {
            if ((order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.CREATED) && // pending order received or processed
                order.getLabel().indexOf(_label) != -1 )
            {
                 order.close();
            }
        }
        return;
    } 
    
    
    public String inShort(Instrument inst) throws JFException 
    {    
        for (IOrder order : _engine.getOrders(inst)) 
        {
            if ((order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.CREATED) && // pending order received or processed
                order.getLabel().indexOf(_label) != -1 &&
                !order.isLong() )
            {
                return order.getId();
            }
        }
        return null;
    } 
    
    public String inLong(Instrument inst) throws JFException 
    {    
        for (IOrder order : _engine.getOrders(inst)) 
        {
            if ((order.getState() == IOrder.State.FILLED || order.getState() == IOrder.State.CREATED) && // pending order received or processed
                order.getLabel().indexOf(_label) != -1 &&
                order.isLong() )
            {
                return order.getId();
            }
        }   
        return null;
    } 
    
    public void enterLong(Instrument instrument, InstrumentState instrState, long time, double bid, double target) throws JFException
    {
        debugPrint (INFO, instrument.toString() + " " + sdf.format(new Date(time)) + ", " + instrState._trade.toString()+ " TRADE TRIGGERED, bid = " + bid );

        double orderTP;
        double orderSL = roundDouble (bid - instrument.getPipValue() * _stopLossPips, instrument.getPipScale()+1);
        
        double lots = _minLotSize;
        
        if (target == 0)
        {
            orderTP = roundDouble (bid + instrument.getPipValue() * _takeProfitPips, instrument.getPipScale()+1);
        }
        else { orderTP = target; }

        // submit long at market with take profit
        IOrder submittedLong = _engine.submitOrder(_label + _delimiter +((Integer)(_orderCounter)).toString()+ _delimiter + 
                                                    instrument.getPrimaryCurrency()+instrument.getSecondaryCurrency() + _delimiter + 
                                                    instrState._trade.toString(), instrument, IEngine.OrderCommand.BUY, lots, 0, _slippage, orderSL, orderTP);
        debugPrint (INFO, instrument.toString() + ", " + "Submitted order: " + submittedLong.getLabel() + ", Lots: " + submittedLong.getAmount());
                       
        _orderCounter++;
        instrState._trade = trade.IN_LONG;
    }
    
    public void enterShort(Instrument instrument, InstrumentState instrState, long time, double bid, double target) throws JFException
    {
        debugPrint (INFO, instrument.toString() + " " + sdf.format(new Date(time)) +  ", " + instrState._trade.toString()+ " TRADE TRIGGERED, bid = " + bid );

        double orderTP;
        double orderSL = roundDouble (bid + instrument.getPipValue() * _stopLossPips, instrument.getPipScale()+1);
        double lots = _minLotSize;
        
        if (target == 0)
        {
            orderTP = roundDouble (bid - instrument.getPipValue() * _takeProfitPips, instrument.getPipScale()+1);
        }
        else { orderTP = target; }
        
        // submit short at market with take profit
        IOrder submittedShort = _engine.submitOrder(_label + _delimiter +((Integer)(_orderCounter)).toString()+ _delimiter + 
                                                    instrument.getPrimaryCurrency()+instrument.getSecondaryCurrency() + _delimiter + 
                                                    instrState._trade.toString(), instrument, IEngine.OrderCommand.SELL, lots, 0, _slippage, orderSL, orderTP);
        debugPrint (INFO, instrument.toString() + ", " + "Submitted order: " + submittedShort.getLabel() + ", Lots: " + submittedShort.getAmount());
         _orderCounter++;
        instrState._trade = trade.IN_SHORT;
        
    }    
 
    /////////////////////////////
    // IStrategy interface methods
    /////////////////////////////
    
    public void onStart(IContext context) throws JFException 
    {
        _engine = context.getEngine();
        _console = context.getConsole();
        _context = context;
        _indicators = context.getIndicators();
        _history = context.getHistory();
        _account = context.getAccount();
        
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

        //subscribe to instruments
        _instSet = new HashSet<Instrument>();
        _instSet.add(Instrument.GBPUSD);
        context.setSubscribedInstruments(_instSet);    
    
        debugPrint (INFO, "Subscribing to: " + _instSet.toString() + " " + _period);
        
        _instrStates = new HashMap<Instrument, InstrumentState>();
        for (Instrument inst : _instSet)
        {
            _instrStates.put(inst, new InstrumentState());
        }     
        
    }
    
    public void onAccount(IAccount account) throws JFException {
    }

    public void onMessage(IMessage message) throws JFException 
    {
        IOrder msgOrder = message.getOrder();
        
        if (msgOrder != null)
        {
            String orderLabel = msgOrder.getLabel();            
            if (orderLabel.indexOf(_label)!=-1)
            {
                switch (message.getType())
                {
                    case ORDER_CLOSE_OK :
                    {
                        debugPrint (INFO, msgOrder.getInstrument().toString() + " " + sdf.format(new Date(msgOrder.getCloseTime())) + ", ORDER_CLOSE_OK message received for " + msgOrder.getLabel() + ", Profit = " + msgOrder.getProfitLossInPips() + " PIPs, Profit = " + msgOrder.getProfitLossInAccountCurrency() + " " + _account.getCurrency() + ",  Equity = " + _account.getEquity() + " " + _account.getCurrency());
                         _instrStates.get(msgOrder.getInstrument())._trade = trade.NONE;               
                        break;
                    }
                    case ORDER_FILL_OK :
                    {
                        debugPrint (INFO, msgOrder.getInstrument().toString() + " " + sdf.format(new Date(msgOrder.getFillTime())) + ", ORDER_FILL_OK message received for " + msgOrder.getLabel() + ", Open Price = " + msgOrder.getOpenPrice() + ",  Equity = " + _account.getEquity() + " " + _account.getCurrency());
                        if (msgOrder.isLong())
                        {
                            _instrStates.get(msgOrder.getInstrument())._trade = trade.IN_LONG;
                        }
                        else
                        {
                            _instrStates.get(msgOrder.getInstrument())._trade = trade.IN_SHORT;
                        }
                        break;
                    }
                    default :
                    {
                        break;
                    }
                }
            }
        }
    }

    public void onStop() throws JFException {
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException 
    {
    }
    
    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException 
    {        
        if (_instSet.contains(instrument) && period == _period)
        {
            // update indicator values
            try 
            {
                long currentBarTime = _history.getBarStart(_period, bidBar.getTime());
                               
                // linear regression 
                Object[] values = _indicators.calculateIndicator(instrument, 
                                    _period, 
                                    new  OfferSide[] {OfferSide.BID }, 
                                    "LINEARREG", 
                                    new IIndicators.AppliedPrice[] { IIndicators.AppliedPrice.CLOSE }, 
                                    new Object[] { _lrPeriod, false}, 
                                    Filter.WEEKENDS,
                                    2,
                                    currentBarTime,
                                    0);

                double[] lrValues = (double[]) values[0];  
                lr =  lrValues[1];
                lr1 = lrValues[0]; 

                       
                // ema indicator  
                Object[] values2 = _indicators.calculateIndicator(instrument, 
                                    _period, 
                                    new  OfferSide[] { OfferSide.BID }, 
                                    "EMA", 
                                    new IIndicators.AppliedPrice[] { IIndicators.AppliedPrice.CLOSE }, 
                                    new Object[] { _emaPeriod, MaType.EMA.ordinal() },  
                                    Filter.WEEKENDS,
                                    2,
                                    currentBarTime,
                                    0);
                
                double[] emaValues = (double[]) values2[0];
                ema = emaValues[1];
                ema1 = emaValues[0];
                                                
                double[][]     bbInner = _indicators.bbands(instrument, _period, OfferSide.BID, IIndicators.AppliedPrice.CLOSE, _bbPeriod, _bbDevInner, _bbDevInner, IIndicators.MaType.SMA, Filter.WEEKENDS, 1, currentBarTime, 0); // maType = 0 = SMA 
                double[][]     bbOuter = _indicators.bbands(instrument, _period, OfferSide.BID, IIndicators.AppliedPrice.CLOSE, _bbPeriod, _bbDevOuter, _bbDevOuter, IIndicators.MaType.SMA, Filter.WEEKENDS, 1, currentBarTime, 0); // maType = 0 = SMA 
             
                bbInnerUpper = bbInner[0][0];
                bbInnerLower = bbInner[2][0];
                bbOuterUpper = bbOuter[0][0];
                bbOuterLower = bbOuter[2][0];
     
                double bid  = bidBar.getOpen();
                
                debugPrint (INFO, instrument.toString() + ", " + sdf.format(new Date(bidBar.getTime())) +
                            ", Open = " + bidBar.getOpen() +
                            ", lr =" + df.format(lr) +
                            ", lr1 =" + df.format(lr1) +
                            ", ema =" + df.format(ema) +
                            ", ema1 =" + df.format(ema1) +
                            ", bbIU =" + df.format(bbInnerUpper) +
                            ", bbIL =" + df.format(bbInnerLower) +
                            ", bbOU =" + df.format(bbOuterUpper) +
                            ", bbOL =" + df.format(bbOuterLower));
                               
                InstrumentState instrState = _instrStates.get(instrument); // may need to put this back at the end of the onBar()
                instrState._trend = trend.NONE;
                
                if ( lr < ema)
                {
                    instrState._trend = trend.DOWN;
                }
                if ( lr > ema)
                {
                    instrState._trend = trend.UP;
                }
                    
                // dump out states
//                debugPrint (INFO, instrument.toString() + ", " + sdf.format(new Date(bidBar.getTime())) + 
//                        ",  trade = " + instrState._trade.toString() +
//                        ",  trend = " + instrState._trend.toString() );
//                
                if (instrState._trade == trade.NONE)
                {
                    if (instrState._trend == trend.DOWN) 
                    {            
                         enterShort(instrument, instrState, bidBar.getTime(), bid, 0 ); // TODO: check conditions for 60+ pip target
                    }
                    if (instrState._trend == trend.UP)  // setup for LONG entry
                    {
                       enterLong(instrument, instrState, bidBar.getTime(), bid, 0 );  // TODO: check conditions for 60+ pip target
                    }
                }
            }
            catch (JFException e) 
            {
                debugPrint (ERROR, instrument.toString() + " " + "Exception: " + e.toString());
                e.printStackTrace();
            }
        }
            
    }
}