/*
+-----------------------------------------------------------------------------------+
| BBFlex.mq4                                                                        |
| Copyright © 2009, Forex Factory                                                   |
| Nanningbob 60x90 EA, post. 2465                                                   |
| Coded by Maxim Feinshtein mfdesier@gmail.com                                      |
| www.forexfactory.com                                                              |
|                                                                                   |
| Please, do not sell this EA because it's free                                     |
+-----------------------------------------------------------------------------------+
*/

#property copyright "Copyright © 2009, Forex Factory "
#property link      "www.forexfactory.com"

#include <stdlib.mqh>
#include <stderror.mqh>

#define ACCOUNT_TYPE_STANDARD 0
#define ACCOUNT_TYPE_MINI     1

#define OPERATIONAL_MODE_TRADING    0
#define OPERATIONAL_MODE_MONITORING 1
#define OPERATIONAL_MODE_TESTING    2

#define TP_MODE_MANUAL      0
#define TP_MODE_CENTER_BAND 1
#define TP_MODE_ATR         2

extern string s1 = "---  General settings  ---";
extern string s2 = "---  Operational mode  ---";
extern string s2_1 = "0-Trading, 1-Monitoring, 2-Testing";
extern int OperationalMode = OPERATIONAL_MODE_TRADING;
extern string s2_2 = "0-Standard, 1-Mini, 2-Micro";
extern int AccountType = ACCOUNT_TYPE_STANDARD;
extern string s2_3 = "The value, indicating whether account equity protection is enabled";
extern bool EquityProtectionEnabled = true;
extern string s2_4 = "The minimal account equity for which the EA should trade.";
extern double MinimalTradeEquity = 2000;
extern string s3 = "---  Money management  ---";
extern string s3_2 = "The size of level 1 trade";
extern double InitialTradeSize = 0.01;
extern string s3_3= "The trade size multiplier, applies from level 2 or higher";
extern double TradeSizeMultiplier = 1.6; 
extern string s3_2_1 = "Indicates whether profit target ability is enabled";
extern string s3_2_2 = "The EA will not open orders if the ability is enabled and";
extern string s3_2_3 = "the account reached the desired equity";
extern bool ProfitTargetAbilityEnabled = false;
extern string s3_2_4 = "The account profit target";
extern double AccountProfitTarget = 3000;
extern string s3_4 = "Maximal acceptable slippage";
extern int Slippage = 5;
extern string s4 = "---  Trade management  ---";
extern string s4_1 = "The timeframe for trading";
extern int TimeFrameMin = PERIOD_H4;
extern string s4_2 = "Indicates whether to trade on friday";
extern bool TradeOnFriday = true;
extern string s4_3= "The number of trading levels";
extern int TradingLevels = 3;
extern string s4_4 = "The size of level, in PIPs";
extern int LevelSizePIPs = 200;
extern string s4_5 = "Indicates whether to move trades to breakeven if possible";
extern bool MoveToBreakeven = false;
extern string s4_6 = "The amount of PIPs by which to update the stop loss when MoveToBreakeven is enabled";
extern int BreakevenPIPs = 5;
extern string s4_7 = "The amount of PIPs that price has to move in order to trigger moving stop loss. Applicable when MoveToBreakeven is enabled";
extern int BreakevenTriggerPIPs = 20;
extern string s4_8 = "The stop loss";
extern int StopLoss = 300;
extern string s4_9 = "0- Manual, 1- At the center of the band, 2- ATR based";
extern int TakeProfitMode = TP_MODE_MANUAL;
extern string s4_10 ="The trade''s profit goal in PIPs";
extern int TakeProfit = 100;
extern string s4_11_1 = "The percent of ATR to use for calculating the take profit";
extern string s4_11_2 = "Applicable when TakeProfitMode is ATR based";
extern int TakeProfitATRPercentage = 50;
extern string s4_12_1 = "Take profit increment. The EA increments";
extern string s4_12_2 = "the take profit by this value when entering";
extern string s4_12_3 = "trade at level 2 or higher when in manual mode";
extern int TakeProfitStep = 25;
extern string s4_13_1 = "The percent that the trade should move";
extern string s4_13_2 = "in trader''s favor before limited order";
extern string s4_13_3 = "becomes market order.";
extern string s4_13_4 = "The range for calculation is half bandwidth";
extern int SignalConfirmationPercent = 10;
extern string s4_14 = "Indicates whether trailing stop is enabled";
extern bool TrailingStopEnabled = false;
extern string s4_15 = "The trailing stop loss";
extern int TrailingStopLoss = 30;
extern string s5 = "---  BB and MA settings  ---";
extern string s5_1 = "Averaging period";
extern int AveragingPeriod = 40;
extern string s6 = "---  Trade definitions  ---";
extern string s6_1 = "The identifier of trades, opened by this instance";
extern int InstanceID = 12345;
extern string s6_2 = "Trade description";
extern string TradeDescription = "My trade";

// For double comparisons
#define EPSILON 0.0000001

// Allows for switching shifts in indicator functions
// in a simple and convinient way.
#define BASE_SHIFT 1

#define BREAKEVEN_COLOR    DarkOrange
#define BUY_COLOR          DodgerBlue
#define BUY_CLOSE_COLOR    Blue
#define ORDER_DELETE_COLOR MediumTurquoise
#define SELL_COLOR         DeepPink
#define SELL_CLOSE_COLOR   Red
#define TRAILING_COLOR     LightGreen

string g_symbol;
double g_pipValue;
double g_ATR;
double g_MinimalStopLoss;
double g_TrailingStop;
double g_TrailingPercentageATR = 125;
int g_limitOrdersCount  = 0,
    g_marketOrdersCount = 0;

int g_brokerDigits;
int g_minimalStopLossPIPs,
	 g_trailingStopPIPs;

// The value at index 'i' indicates
// whether breakeven was applied to level i.
bool g_levelToBreakEvenStatusMapping[];

// The value at index 'i' contains
// that trade's ticket.
int g_levelToTicketMapping[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
	
int init()
{
	g_symbol = Symbol();
	g_pipValue = Point;
	g_brokerDigits = Digits;
	
	// Retrieve the minimum stop loss in PIPs
	g_minimalStopLossPIPs = MarketInfo( g_symbol, MODE_STOPLEVEL );

	ArrayResize( g_levelToBreakEvenStatusMapping, TradingLevels );
	ArrayResize( g_levelToTicketMapping,          TradingLevels );

	resetMappings();

//	double orderSize_1 = calculateOrderSize(1);
//	Print( "orderSize_1: " + orderSize_1 );
//	double orderSize_2 = calculateOrderSize(2);
//	Print( "orderSize_2: " + orderSize_2 );
//	double orderSize_3 = calculateOrderSize(3);
//	Print( "orderSize_3: " + orderSize_3 );
//	Print( "Min stop loss pips: " + g_MinimalStopLoss );
//	Print( "TrailingEnabled: " + TrailingEnabled );

	return (0);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
{
	// Nothing to do
	return (0);
}

//+------------------------------------------------------------------+
//| Tick handling function                                           |
//+------------------------------------------------------------------+
int start()
{
	string message;
 	int marketOrders = 0,
 		 limitOrders  = 0,
 		 openedOrders = 0;

	int orderType = -1;

	calculateATR();
	calculateTrailingStopLoss();

   // Handle already opened trades
	for( int cnt = 0; cnt < OrdersTotal(); cnt++ )   
   {
		if( ! OrderSelect( cnt, SELECT_BY_POS, MODE_TRADES ) )
     	{
     		Print( ErrorDescription( GetLastError() ) );
     		continue;
		}

		// Filter orders, that were not opened by this instance
		if( ! ( ( OrderSymbol() == Symbol() ) && ( OrderMagicNumber() == InstanceID ) ) )
		{
			continue;
		}

		int tradeLevel = retrieveTradeLevel();
		handleTrade( limitOrders, marketOrders, tradeLevel );

		if( marketOrders > g_marketOrdersCount )
		{
			Print( "Market orders count increased from ", g_marketOrdersCount, " to ", marketOrders );
			g_marketOrdersCount = marketOrders;
		}
   }

	if( marketOrders < g_marketOrdersCount )
	{
		// Take the closed trade into account,
		// otherwise g_marketOrdersCount will be 1
		// after closeMarketTrades.
		g_marketOrdersCount = marketOrders;
		closeMarketTrades();
		resetMappings();
	}

	//  Don't open new orders if account equity is too small
	if( EquityProtectionEnabled && ( AccountEquity() <= MinimalTradeEquity ) )
	{
		message = "Equity protection activated: account equity is: " + AccountEquity() + " minimal trade equity is: " + MinimalTradeEquity;
		Print( message );

		return(0);
	}
   
	// Handle friday trading
	if( ( 5 == DayOfWeek() ) && ( ! TradeOnFriday ) ) 
	{
		return (0);
	}

	// Check whether symbol profit target achieved
	double symbolProfit = calculateSymbolProfit( InstanceID );
	if( ProfitTargetAbilityEnabled && ( AccountProfitTarget >= symbolProfit ) )
	{
		message = "Profit target achieved: symbol profit is: " + symbolProfit;
		Print( message );
		return (0);
	}

	if( marketOrders > TradingLevels )
	{
		return (0);
	}

	// Limit orders count will be greater than zero when the trade
	// has already been opened but still did not trigger buy/sell.
	if( limitOrders > 0 )
		return (0);

	if( marketOrders > 0 )
	{
		// Multi level is handled by handleBuy/Sell trade
		return (0);		
	}

	int orderToOpen = checkTradingSignal();
	switch( orderToOpen ) {
	case OP_BUYSTOP:
		openBuyOrder( 0 );
	break;
	case OP_SELLSTOP:
		openSellOrder( 0 );
	break;
								 } // switch( orderToOpen )
	return (0);
}

void openBuyOrder( int orderLevel )
{
	// The order level starts from zero.
	if( ! ( IsTradeAllowed() || IsTradeContextBusy() ) )
	{
		Print( "openBuyOrder: Either trading is not allowed or trade context is busy..." );
		return;
	}

	if( g_levelToTicketMapping[ orderLevel ] > 0 )
	{
		// The order from that level has already been opened.
		return;
	}

	int tradeCommand = -1;
	if( orderLevel == 0 )
		tradeCommand = OP_BUYSTOP;
	else
		tradeCommand = OP_BUY;

	double tradeSize      = calculateTradeSize( orderLevel ),
			 tradeOpenPrice = calculateTradeOpenPrice( tradeCommand, orderLevel );

	double stopLossPrice     = calculateStopLossPrice(   tradeCommand, tradeOpenPrice ),
			 takeProfitPrice   = calculateTakeProfitPrice( tradeCommand, orderLevel, tradeOpenPrice );

	if( tradeOpenPrice > takeProfitPrice )
	{
		// Treat case when the bar crosses the
		// Bollinger band center line.
		Print( "openBuyOrder: Cancelling: tradeOpenPrice > takeProfitPrice: order price: " + tradeOpenPrice + " takeProfitPrice: " + takeProfitPrice );
		return;
	}
	
	// Support ECN brokerage
	int orderTicket = OrderSend(
									g_symbol,
									tradeCommand,
									tradeSize,
									tradeOpenPrice,
									Slippage,
									0,
									0,
									TradeDescription,
									InstanceID,
									0,
									BUY_COLOR
							  		   );
	logOrderSendInfo( "openBuyOrder:OrderSend: ", g_symbol, tradeSize, tradeOpenPrice, Slippage, stopLossPrice, takeProfitPrice, GetLastError() );
	if( -1 == orderTicket )
		return;

	bool res = OrderModify( orderTicket, tradeOpenPrice, stopLossPrice, takeProfitPrice, 0, BUY_COLOR );
	if( res )
		g_levelToTicketMapping[ orderLevel ] = orderTicket;

	logOrderModifyInfo( "openBuyOrder:Modify: ", g_symbol, orderTicket, tradeOpenPrice, stopLossPrice, takeProfitPrice, GetLastError() );
}

void openSellOrder( int orderLevel )
{
	// The order level starts from zero.
	if( ! ( IsTradeAllowed() || IsTradeContextBusy() ) )
	{
		Print( "openSellOrder: Either trading is not allowed or trade context is busy..." );
		return;
	}

	if( g_levelToTicketMapping[ orderLevel ] > 0 )
	{
		// The order from that level has already been opened.
		return;
	}

	int tradeCommand = -1;
	if( orderLevel == 0 )
		tradeCommand = OP_SELLSTOP;
	else
		tradeCommand = OP_SELL;

	double tradeSize      = calculateTradeSize( orderLevel ),
			 tradeOpenPrice = calculateTradeOpenPrice( tradeCommand, orderLevel );

	double priceAtBandCenter = calculateBandCenterPrice(),
			 stopLossPrice     = calculateStopLossPrice( tradeCommand, tradeOpenPrice ),
			 takeProfitPrice   = calculateTakeProfitPrice( tradeCommand, orderLevel, tradeOpenPrice );
 
	if( tradeOpenPrice < priceAtBandCenter )
	{
		// Treat case when the bar crosses the
		// Bollinger band center line.
		Print( "openSellOrder: Cancelling: tradeOpenPrice < priceAtBandCenter: order price: " + tradeOpenPrice + " priceAtBandCenter: " + priceAtBandCenter );
		return;
	}

//	Print( "Opening sell order; order level is: " + orderLevel + ", take profit is: " + priceToPIPs( takeProfitPrice ) );

	// Support ECN brokerage
	int orderTicket = OrderSend(
									g_symbol,
									tradeCommand,
									tradeSize,
									tradeOpenPrice,
									Slippage,
									0,
									0,
									TradeDescription,
									InstanceID,
									0,
									SELL_COLOR
							  		   );
	logOrderSendInfo( "OP_SELL-Send: ", g_symbol, tradeSize, tradeOpenPrice, Slippage, stopLossPrice, takeProfitPrice, GetLastError() );
	if( -1 == orderTicket )
		return;

	bool res = OrderModify( orderTicket, tradeOpenPrice, stopLossPrice, takeProfitPrice, 0, SELL_COLOR );
	if( res )
		g_levelToTicketMapping[ orderLevel ] = orderTicket;

	logOrderModifyInfo( "OP_SELL-Modify: ", g_symbol, orderTicket, tradeOpenPrice, stopLossPrice, takeProfitPrice, GetLastError() );
}

void handleTrade( int& limitOrders, int& marketOrders, int tradeLevel )
{
	switch( OrderType() ) {
	case OP_BUY:
		marketOrders++;
		handleBuyTrade( tradeLevel );
	break;
	case OP_SELL:
		marketOrders++;
		handleSellTrade( tradeLevel );
	break;
	case OP_BUYSTOP:
		limitOrders++;
		handleBuyStopTrade();
	break;
	case OP_SELLSTOP:
		limitOrders++;
		handleSellStopTrade();
	break;
								 } // switch( OrderType() )
}

void handleBuyTrade( int tradeLevel )
{
	if( MoveToBreakeven )
	{
		// Apply only once
		if( ! g_levelToBreakEvenStatusMapping[ tradeLevel ] )
			applyBreakevenToBuyTrade( tradeLevel );
	}

	if( TrailingStopEnabled )
		applyTrailingToBuyTrade();
		
	// Check whether to open additional level trade
	
	// Check whether to open additional level trade if:
	// - Open price - Ask is greater than level size
	// - Not all levels used
	// - The level (tradeLevel + 1) trade had not been opened yet
	if( ( ( OrderOpenPrice() - Ask ) > pipsToPrice( LevelSizePIPs ) ) &&
		 ( g_marketOrdersCount < TradingLevels ) &&
		 ( g_levelToTicketMapping[ tradeLevel + 1 ] == 0 )
	  )
	{
		Print( "handleBuyTrade: Opening level ", tradeLevel + 1, " trade, open price: ", OrderOpenPrice(), " Ask: ", Ask, " LevelSizePIPs * tradeLevel: ", LevelSizePIPs * ( tradeLevel + 1 ) );
		openBuyOrder( tradeLevel + 1 );
	}
}

void applyBreakevenToBuyTrade( int tradeLevel )
{
	double tradeOpenPrice = OrderOpenPrice();
	int    triggerPIPs    = calculateBreakevenTriggerPIPs();

	// Check whether the trade moved BreakevenTriggerPIPs in our favor;
	// Prevent the "OrderModify error 1".
	if( ( Bid - pipsToPrice( triggerPIPs + BreakevenPIPs ) ) < tradeOpenPrice )
		return;

	double newStopLossPrice = tradeOpenPrice + pipsToPrice( BreakevenPIPs );
	bool res = updateTrade( "Applying breakeven to buy trade: ", newStopLossPrice, OrderTakeProfit(), BREAKEVEN_COLOR );
	if( res )
		g_levelToBreakEvenStatusMapping[ tradeLevel ] = true;
}

void applyTrailingToBuyTrade()
{
	double tradeOpenPrice       = OrderOpenPrice(),
			 currentStopLossPrice = OrderStopLoss(),
			 trailingStopPrice    = pipsToPrice( g_trailingStopPIPs );

	// Check whether the trade moved g_trailingStopPIPs in our favor;
	// Prevent the "OrderModify error 1".
	if( ( Bid - trailingStopPrice - pipsToPrice( g_minimalStopLossPIPs ) ) < tradeOpenPrice )
		return;

	double newStopLossPrice = Bid - trailingStopPrice - pipsToPrice( g_minimalStopLossPIPs );
	if( ( newStopLossPrice - currentStopLossPrice ) > g_pipValue )
		updateTrade( "Applying trailing to buy trade: ", newStopLossPrice, 0.0, TRAILING_COLOR );
}

void handleSellTrade( int tradeLevel )
{
	if( MoveToBreakeven )
	{
		// Apply only once
		if( ! g_levelToBreakEvenStatusMapping[ tradeLevel ] )
			applyBreakevenToSellTrade( tradeLevel );
	}

	if( TrailingStopEnabled )
		applyTrailingToSellTrade();

	// Check whether to open additional level trade if:
	// - Bid - Open price is greater than level size
	// - Not all levels used
	// - The level (tradeLevel + 1) trade had not been opened yet
	if( ( ( Bid - OrderOpenPrice() ) > pipsToPrice( LevelSizePIPs ) ) &&
		 ( g_marketOrdersCount < TradingLevels ) &&
		 ( g_levelToTicketMapping[ tradeLevel + 1 ] == 0 )
	  )
	{
		Print( "handleSellTrade: Opening level ", tradeLevel + 1, " trade" );
		openSellOrder( tradeLevel + 1 );
	}
}

void handleBuyStopTrade()
{
	// Cancel the order if
	// -The current bar is under the band
	// -The price made 80% of it's way to the stop loss
	double lowBBValue = calculateLowBand( 0 ),
			 barHigh = iHigh( g_symbol, TimeFrameMin, BASE_SHIFT );
	if( barHigh < lowBBValue )
	{
		int orderTicket = OrderTicket();
		OrderDelete( orderTicket, ORDER_DELETE_COLOR );
		resetMappings();
		logOrderDeleteInfo( "Buy stop order deleted: ", g_symbol, orderTicket, GetLastError() );
	}
}

void handleSellStopTrade()
{
	// Cancel the order if:
	// -The current bar is above the band
	// -The price made 80% of it's way to the stop loss
	double upperBBValue = calculateUpperBand( 0 ),
			 barLow       = iLow( g_symbol, TimeFrameMin, 0 );
//			 barLow       = iLow( g_symbol, BBTimeFrame, 1 );
	if( barLow > upperBBValue )
	{
		int orderTicket = OrderTicket();
		OrderDelete( orderTicket, ORDER_DELETE_COLOR );
		resetMappings();
		logOrderDeleteInfo( "Sell stop order deleted: ", g_symbol, orderTicket, GetLastError() );
	}
}

void applyBreakevenToSellTrade( int tradeLevel )
{
	double tradeOpenPrice = OrderOpenPrice();
	int    triggerPIPs    = calculateBreakevenTriggerPIPs();

	// Check whether the trade moved BreakevenTriggerPIPs in our favor;
	// Prevent the "OrderModify error 1".
	if( ( Ask + pipsToPrice( triggerPIPs + BreakevenPIPs + g_minimalStopLossPIPs ) ) > tradeOpenPrice )
		return;

	double newStopLossPrice = tradeOpenPrice - pipsToPrice( BreakevenPIPs );
	bool res = updateTrade( "Applying breakeven to sell trade: ", newStopLossPrice, OrderTakeProfit(), BREAKEVEN_COLOR );
	if( res )
		g_levelToBreakEvenStatusMapping[ tradeLevel ] = true;
}

void applyTrailingToSellTrade()
{
	double tradeOpenPrice       = OrderOpenPrice(),
			 currentStopLossPrice = OrderStopLoss(),
			 trailingStopPrice    = pipsToPrice( g_trailingStopPIPs );

	// Check whether the trade moved g_trailingStopPIPs in our favor;
	// Prevent the "OrderModify error 1".
	if( ( Ask + trailingStopPrice + pipsToPrice( g_minimalStopLossPIPs ) ) >= tradeOpenPrice )
		return;

	double newStopLossPrice = Ask + trailingStopPrice + pipsToPrice( g_minimalStopLossPIPs );
	if( ( currentStopLossPrice - newStopLossPrice ) > g_pipValue )
		updateTrade( "Applying trailing to sell trade: ", newStopLossPrice, 0.0, TRAILING_COLOR );
}

double calculateSymbolProfit( int instanceID )
{
	int ordersCount = OrdersTotal() - 1;
	double symbolProfit = 0.0;
	for( int curOrder = ordersCount; curOrder >= 0 ; curOrder-- )
	{
		OrderSelect( curOrder, SELECT_BY_POS, MODE_TRADES );
		if( OrderMagicNumber() == instanceID )
		{
			symbolProfit += OrderProfit();
		}
	}

	return(symbolProfit);
}

double calculateTradeSize( int orderLevel )
{
	Print( "calculateTradeSize: orderLevel is ", orderLevel );
//	double tradeSize = InitialTradeSize * MathPow( TradeSizeMultiplier, orderLevel - 1 );
	double tradeSize = InitialTradeSize * MathPow( TradeSizeMultiplier, orderLevel );
	tradeSize = roundDouble( tradeSize );
	return (tradeSize);
}

double calculateTradeOpenPrice( int tradeType, int tradeLevel )
{
	double price = -1.0;

	int bandWidthPIPs = calculateBandWidthInPIPs(),
		 confirmationPIPs = ( ( bandWidthPIPs / 2 ) * SignalConfirmationPercent ) / 100;
			 
	Print( "confirmationPIPs: ", confirmationPIPs );
		
	switch( tradeType ) {
	case OP_BUY:
		price = Ask;
	break;
	case OP_BUYSTOP:
		price = Ask + pipsToPrice( confirmationPIPs );
	break;
	case OP_SELL:
		price = Bid;
	break;
	case OP_SELLSTOP:
		price = Bid - pipsToPrice( confirmationPIPs );
	break;
							  } // switch( orderType )

	return (NormalizeDouble( price, g_brokerDigits ));
}

double calculateStopLossPrice( int orderType, double openPrice )
{
	// Start from manual stop loss value
	int stopLossPIPs = StopLoss;
	if( stopLossPIPs < g_minimalStopLossPIPs )
	{
		stopLossPIPs = g_minimalStopLossPIPs;
	}

	double price = 0;
	switch( orderType ) {
	case OP_BUY:
	case OP_BUYSTOP:
		price = openPrice - pipsToPrice( stopLossPIPs );
	break;
	case OP_SELL:
	case OP_SELLSTOP:
		price = openPrice + pipsToPrice( stopLossPIPs );
	break;
							  } // switch( orderType )

	price = NormalizeDouble( price, Digits );

	return (price);
}

double calculateTakeProfitPrice( int tradeType, int orderLevel, double openPrice )
{
	int tpMode = TakeProfitMode;
//	if( orderLevel > 1 )
//		tpMode = TP_MODE_MANUAL;

	int takeProfitPIPs = 0;
	double price = 0;
	switch( tpMode ) {
	case TP_MODE_MANUAL :
//		takeProfitPIPs = TakeProfit + TakeProfitStep * ( orderLevel - 1 );
		takeProfitPIPs = TakeProfit + TakeProfitStep * orderLevel;
		switch( tradeType ) {
		case OP_BUY:
		case OP_BUYSTOP:
			price = openPrice + pipsToPrice( takeProfitPIPs );
		break;
		case OP_SELLSTOP:
			price = openPrice - pipsToPrice( takeProfitPIPs );
		break;
							  	} // switch( tradeType )
	break;
	case TP_MODE_CENTER_BAND :
		takeProfitPIPs = calculateBandWidthInPIPs() / 2;
		Print( "Calculated TP:", takeProfitPIPs );
		price = calculateBandCenterPrice();
	break;
	case TP_MODE_ATR :
		takeProfitPIPs = TakeProfitATRPercentage * 100 * g_ATR;
		switch( tradeType ) {
		case OP_BUYSTOP:
		case OP_BUY:
			price = openPrice + pipsToPrice( takeProfitPIPs );
		break;
		case OP_SELLSTOP:
			price = openPrice - pipsToPrice( takeProfitPIPs );
		break;
							  	} // switch( tradeType )
	break;
						  } // switch( tpMode )

	price = NormalizeDouble( price, Digits );

	return (price);
}

double calculateBandCenterPrice()
{
	int bandWidthPIPs = calculateBandWidthInPIPs();
	double price = calculateLowBand( 0 ) + pipsToPrice( bandWidthPIPs / 2 );
	
	return (price);
}

bool updateTrade(
			 string reason,
			 double newStopLossPrice,
			 double newTakeProfitPrice,
			  color arrowColor
			 		 )
{
	Print( "updateTrade: reason: ", reason );
	double tradeOpenPrice = OrderOpenPrice();
	int tradeTicket = OrderTicket();
	bool res = OrderModify( tradeTicket, tradeOpenPrice, newStopLossPrice, newTakeProfitPrice, 0, arrowColor );
	if( res )
		return (true);

	logOrderModifyInfo( reason, g_symbol, tradeTicket, tradeOpenPrice, newStopLossPrice, newTakeProfitPrice, GetLastError() );
	return (false);
}

int checkTradingSignal()
{
	double prevLowBBValue         = calculateLowBand( BASE_SHIFT ),
			 beforePrevLowBBValue   = calculateLowBand( BASE_SHIFT + 1 ),
			 prevUpperBBValue       = calculateUpperBand( BASE_SHIFT ),
			 beforePrevUpperBBValue = calculateUpperBand( BASE_SHIFT + 1 ),

			 prevBarClose = iClose( g_symbol, TimeFrameMin, BASE_SHIFT     ),
			 prevBarLow   = iLow(   g_symbol, TimeFrameMin, BASE_SHIFT     ),
			 prevBarHigh  = iHigh(  g_symbol, TimeFrameMin, BASE_SHIFT     ),

			 beforePrevBarClose = iClose( g_symbol, TimeFrameMin, BASE_SHIFT + 1 ),
			 beforePrevBarLow   = iLow(   g_symbol, TimeFrameMin, BASE_SHIFT + 1 ),
			 beforePrevBarHigh  = iHigh(  g_symbol, TimeFrameMin, BASE_SHIFT + 1 );

	int orderType = -1;

	// Signal sell stop if:
	// -The bar before previous bar is above the upper band or
	// -The bar before previous bar crosses the upper band
	// AND
	// -Previous bar is inside the band.
	//
	// Signal buy stop if:
	// -The bar before previous bar is under the low band or
	// -The bar before previous bar crosses the low band
	// AND
	// -Previous bar is inside the band.
	bool beforePrevBarCrossesLowBand   = false,
		  beforePrevBarCrossesUpperBand = false,
		  beforePrevBarUnderLowBand     = false,
		  beforePrevBarAboveUpperBand   = false,
		  prevBarInsideBand             = false;

datetime currentBarTime = iTime( g_symbol, PERIOD_H4, 0 );
//Print( "currentBarTime: " + TimeToStr( currentBarTime ) + " Prev bar time: " + TimeToStr( prevBarTime ) + " lowBBValue: " + lowBBValue + " upperBBValue: " + upperBBValue );

	// Check whether previous bar is inside the band
	if( ( prevBarLow  > prevLowBBValue   ) &&
		 ( prevBarHigh < prevUpperBBValue )
	  )
	{
//		Print( "Previous bar inside band, barLow: " + barLow + " bar high: " + barHigh + " Time: " + TimeToStr( TimeCurrent() ) );
		prevBarInsideBand = true;
	}

	// Check whether previous bar crosses lower band
	if( ( beforePrevBarLow  < beforePrevLowBBValue ) &&
		 ( beforePrevBarHigh > beforePrevLowBBValue )
	  )
	{
//		Print( TimeToStr( TimeCurrent() ) + " prev bar crosses low band, prev low " + prevBarLow + " prev high: " + prevBarHigh + " prevLowBBValue: " + prevLowBBValue );
		beforePrevBarCrossesLowBand = true;
	}
	
	// Check whether before previous bar crosses upper band
	if( ( beforePrevBarLow  < beforePrevUpperBBValue ) &&
		 ( beforePrevBarHigh > beforePrevUpperBBValue )
	  )
	{
		beforePrevBarCrossesUpperBand = true;
	}

	// Check whether before previous bar is under the lower band
	if( beforePrevBarHigh < beforePrevLowBBValue )
	{
//		Print( "Time: " + TimeToStr( TimeCurrent() ) + " previous bar under low band" );
		beforePrevBarUnderLowBand = true;
	}

	// Check whether before previous bar is above the upper band
	if( beforePrevBarLow > beforePrevUpperBBValue )
	{
		beforePrevBarAboveUpperBand = true;
	}
	
	if( ( beforePrevBarCrossesUpperBand || beforePrevBarAboveUpperBand ) && prevBarInsideBand )
	{
		return (OP_SELLSTOP);
	}
		
	if( ( beforePrevBarCrossesLowBand || beforePrevBarUnderLowBand ) && prevBarInsideBand )
	{
//		Print( "BUYSTOP generated" );
//		Print( TimeToStr( TimeCurrent() ) + " :BUYSTOP generated: beforePrevBarCrossesLowBand: " + beforePrevBarCrossesLowBand + " beforePrevBarUnderLowBand: " + beforePrevBarUnderLowBand + " currentBarInsideBand: " + prevBarInsideBand + " prevBarInsideBand" );
		return (OP_BUYSTOP);
	}

	return (-1);
}

int calculateBreakevenTriggerPIPs()
{
	int triggerPIPS = BreakevenTriggerPIPs;
//	if( MMEnabled )
//	{
//		// Calculate trigger pips according to ATR
//		triggerPIPS = BreakEvenATRPercentage * 100 * g_ATR;
//	}
	
	return (triggerPIPS);
}

int calculateBandWidthInPIPs()
{
	double bandWidthPriceRange = ( calculateUpperBand( 0 ) - calculateLowBand( 0 ) );
	int bandWidthInPIPS = priceToPIPs( bandWidthPriceRange );
	
	return (bandWidthInPIPS);
}

void closeMarketTrades()
{
	color closeColor = CLR_NONE;
	for( int i = 0; i < TradingLevels; i++ )
	{
		if( g_levelToTicketMapping[ i ] == 0 )
			continue;

		bool selected = OrderSelect( g_levelToTicketMapping[ i ], SELECT_BY_TICKET );
		if( ! selected )
			continue;

		// Check whether the order has already been closed
		datetime closeTime = OrderCloseTime();
		if( closeTime != 0 )
			continue;

		int type = OrderType();
		double closePrice;
		switch( type ) {
		case OP_BUY :
			closePrice = Bid;
			closeColor = BUY_CLOSE_COLOR;
		break;
		case OP_SELL:
			closePrice = Ask;
			closeColor = SELL_CLOSE_COLOR;
		break;
								} // switch( type )

		bool closed = OrderClose( g_levelToTicketMapping[ i ], OrderLots(), closePrice, Slippage, closeColor );
		Print( "Trade ", g_levelToTicketMapping[ i ], " level ", i, " closed; result: ", closed );
		if( closed )
		{
			g_marketOrdersCount--;
			g_levelToTicketMapping[ i ] = 0;
		}
	}
	
	Print( "closeMarketTrades: g_marketOrdersCount after loop is ", g_marketOrdersCount );
}

void resetMappings()
{
	for( int i = 0; i < TradingLevels; i++ )
	{
		g_levelToBreakEvenStatusMapping[ i ] = false;
		g_levelToTicketMapping[ i ]          = 0;
	}
}

int priceToPIPs( double price )
{
	int priceInPIPS = (int)price / g_pipValue;
	if( 5 == g_brokerDigits )
		priceInPIPS *= 10;
	
	return (priceInPIPS);
}

double pipsToPrice( int pips )
{
	int calculationPIPs = pips;
	if( ( 5 == g_brokerDigits ) ||
		 ( 3 == g_brokerDigits )
	  )
		calculationPIPs *= 10;

	return (calculationPIPs * g_pipValue);
}

double calculateLowBand( int shift )
{
	double lowBand = iBands( g_symbol, TimeFrameMin, AveragingPeriod, 2, 0, PRICE_CLOSE, MODE_LOWER, shift );
	return (lowBand);
}

double calculateUpperBand( int shift )
{
	double upperBand = iBands( g_symbol, TimeFrameMin, AveragingPeriod, 2, 0, PRICE_CLOSE, MODE_UPPER, shift );
	return (upperBand);
}

double calculateATR()
{
	g_ATR = iATR( g_symbol, PERIOD_D1, 14, 0 );
	if( g_ATR > 0.1 )
	{
		// Prevent lot size problem with instruments,
		// having only 2 decimal places (like USD/JPY).
		Print( "g_ATR divided by 100..." );
		g_ATR /= 100;
	}
}

int retrieveTradeLevel()
{
	int level = 0;
	for( int i = 0; i < TradingLevels; i++ )
	{
		if( g_levelToTicketMapping[ i ] == OrderTicket() )
		{
//			level = i + 1;
			level = i;
			break;
		}
	}

	return (level);
}

double calculateTrailingStopLoss()
{
	g_TrailingStop = g_TrailingPercentageATR * 100 * g_ATR;
//	Print( "g_TrailingStop is: " + g_TrailingStop );
}

double roundDouble( double value  )
{
	double roundedValue = 0.0;
	int roundingDigits = 0;
	switch( OperationalMode ) {
	case OPERATIONAL_MODE_TRADING:
		roundingDigits = 2;
	break;
	case OPERATIONAL_MODE_MONITORING:
		Print( "roundDouble when in monitoring mode: should not get here" );
		roundingDigits = 0;
	break;
	case OPERATIONAL_MODE_TESTING:
		roundingDigits = 1;
	break;
									  } // switch( OperationalMode )
									  
	roundedValue = NormalizeDouble( value, roundingDigits );
	return (roundedValue);
}

void logOrderSendInfo(
               string commonInfo,
               string instrument,
               double orderSize,
               double openPrice,
                  int slippage,
               double stopLoss,
               double takeProfit,
                  int errorCode
                     )
{
   string info = StringConcatenate(
                      commonInfo,
                      "instrument: ",   instrument,
                      " order size: ",  orderSize,
                      " open price: ",  openPrice,
                      " slippage: ",    slippage,
                      " stop loss: ",   stopLoss,
                      " take profit: ", takeProfit,
                      " Ask: ", Ask,
                      " Bid: ", Bid
                                  );
   Print( info );
   if( ERR_NO_ERROR == errorCode )
      return;
    
   Print( "Error info: ", errorCode, " description: ", ErrorDescription( errorCode ) );
}

void logOrderModifyInfo(
                 string commonInfo,
                 string instrument,
                    int orderTicket,
                 double openPrice,
                 double stopLoss,
                 double takeProfit,
                    int errorCode
                       )
{
   string info = StringConcatenate(
                      commonInfo,
                      "instrument: ",   instrument,
                      " ticket: ",      orderTicket,
                      " open price: ",  openPrice,
                      " stop loss: ",   stopLoss,
                      " take profit: ", takeProfit
                                  );
   Print( info );
   if( ERR_NO_ERROR == errorCode )
      return;
    
   Print( "Error info: ", errorCode, " description: ", ErrorDescription( errorCode ) );
}

void logOrderDeleteInfo(
					  string commonInfo,
					  string instrument,
						  int orderTicket,
						  int errorCode
							  )
{
   string info = StringConcatenate(
                      commonInfo,
                      "Instrument: ", instrument,
                      " ticket: ",    orderTicket
                                  );
   Print( info );
   if( ERR_NO_ERROR == errorCode )
      return;
    
   Print( "Error info: ", errorCode, " description: ", ErrorDescription( errorCode ) );
}

