OrderReliable

//=============================================================================
//                              OrderReliable.mqh
//
//         Copyright ? 2006, Derk Wehler     (derkwehler@gmail.com)
//
//  This file is simply LibOrderReliable as a header file instead of a library
//
//  In order to read this code most clearly in the Metaeditor, it is advised
//  that you set your tab settings to 4 (instead of the default 3): 
//  Tools->Options->General Tab, set Tab Size to 4, uncheck "Insert spaces"
//
// ***************************************************************************
// OrderReliable library MIT license
// 
// Copyright (c) 2006 Derk Wehler
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// LICENSE LIMITATION
// This MIT license is limited only for use of OrderReliable within 
// strategies generated by StrategyQuant. Any method from this library 
// that will be used outside of this source code will be governed 
// by GPL license.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// ***************************************************************************

// ***************************************************************************
//
//  A library for MT4 expert advisors, intended to give more reliable
//  order handling.	This library only concerns the mechanics of sending
//  orders to the Metatrader server, dealing with transient connectivity
//  problems better than the standard order sending functions.  It is
//  essentially an error-checking wrapper around the existing transaction
//  functions. This library provides nothing to help actual trade strategies,
//  but ought to be valuable for nearly all expert advisors which trade 'live'.
//
//
//=============================================================================
//
//  Contents:
//
//		OrderSendReliable()
//			This is intended to be a drop-in replacement for OrderSend()
//			which, one hopes is more resistant to various forms of errors
//			prevalent with MetaTrader.
//
//		OrderSendReliable2Step()
//			This function is intended to be used when brokers do not allow
//			initial stoploss and take-profit settings as part of the initial
//			market order. After successfully playing an order, it will then
//			Call OrderModifyReliable() to update the SL and TP settings.
//
//		OrderModifyReliable()
//			A replacement for OrderModify with more error handling.
//
//		OrderCloseReliable()
//			A replacement for OrderClose with more error handling.
//
//		OrderCloseReliableMKT()
//			This function is intended for closing orders ASAP; the
//			principal difference is that in its internal retry-loop,
//			it uses the new "Bid" and "Ask" real-time variables as opposed
//			to the OrderCloseReliable() which uses only the price given upon
//			entry to the routine.  More likely to get the order closed if 
//          price moves, but more likely to "slip"
//
//		OrderDeleteReliable()
//			A replacement for OrderDelete with more error handling.
//
//===========================================================================
//                      CHANGE LOG BEGUN 28 March, 2014
//         Prior to this, Source OffSite was used to save changes
//      Start with revision 32, which is what SOS had as last change
//
//  v32, 28 Mar 14: 
//  Small bug fixes for Build 600 changes
//
//  v33, 25 Apr 16: 
//  Tiny adjustment made to GetOrderDetails() for non-forex pairs
//
//  v34, 21 Jun 16: 
//  Changed SleepRandomTime() to just sleep 200ms
//
//  v35, 20 Jul 16: (important)
//  Added MySymbolConst2Val(), MySymbolVal2String(), necessary for correct
//  functioning of GetOrderDetails()
//
//  v36, 23 Apr 19: (Mark Fric, SQ)
//  Added separate retry_attempts_bad_price variable that can configure repeat 
//  attempts for ERR_INVALID_PRICE and ERR_INVALID_STOPS errors
//
//===========================================================================


//=============================================================================
//							 OrderSendReliable()
//
//  This is intended to be a drop-in replacement for OrderSend() which,
//  one hopes, is more resistant to various forms of errors prevalent
//  with MetaTrader.
//
//	RETURN VALUE:
//     Ticket number or -1 under some error conditions.  
//
//  FEATURES:
//     * Re-trying under some error conditions, sleeping a random
//       time defined by an exponential probability distribution.
//
//     * Automatic normalization of Digits
//
//     * Automatically makes sure that stop levels are more than
//       the minimum stop distance, as given by the server. If they
//       are too close, they are adjusted.
//
//     * Automatically converts stop orders to market orders
//       when the stop orders are rejected by the server for
//       being to close to market.  NOTE: This intentionally
//       applies only to OP_BUYSTOP and OP_SELLSTOP,
//       OP_BUYLIMIT and OP_SELLLIMIT are not converted to market
//       orders and so for prices which are too close to current
//       this function is likely to loop a few times and return
//       with the "invalid stops" error message.
//       Note, the commentary in previous versions erroneously said
//       that limit orders would be converted.  Note also
//       that entering a BUYSTOP or SELLSTOP new order is distinct
//       from setting a stoploss on an outstanding order; use
//       OrderModifyReliable() for that.
//
//     * Displays various error messages on the log for debugging.
//
//  ORIGINAL AUTHOR AND DATE:
//     Matt Kennel, 2006-05-28
//
//=============================================================================
int OrderSendReliable(string symbol, int cmd, double volume, double price,
                      int slippage, double stoploss, double takeprofit,
                      string comment="", int magic=0, datetime expiration=0,
                      color arrow_color=CLR_NONE)
{
	OrderReliable_Fname = "OrderSendReliable";
	int ticket = -1;
  
  price = NormalizeDouble(price, Digits);
	takeprofit = NormalizeDouble(takeprofit, Digits);
	stoploss = NormalizeDouble(stoploss, Digits);
  
	// ========================================================================
	// If testing or optimizing, there is no need to use this lib, as the 
	// orders are not real-world, and always get placed optimally.  By 
	// refactoring this option to be in this library, one no longer needs 
	// to create similar code in each EA.
	if (!UseForTesting)
	{
		if (IsOptimization()  ||  IsTesting())
		{
			ticket = OrderSend(symbol, cmd, volume, price, slippage, stoploss,
			                   takeprofit, comment, magic, expiration, arrow_color);
			return(ticket);
		}
	}
	// ========================================================================
	
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// Get information about this order
	double realPoint = MarketInfo(symbol, MODE_POINT);
	double adjPoint = realPoint;
	if (adjPoint == 0.00001  ||  adjPoint == 0.001)
		adjPoint *= 10;
	int digits;
	double point, M=0;
	double bid, ask;
	double sl, tp;
	double priceNow=0;
	double hasSlippedBy=0;
	
	GetOrderDetails(0, symbol, cmd, digits, point, sl, tp, bid, ask, false);
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	OrderReliablePrint("Attempted " + OrderTypeToString(cmd) + " " + symbol + ": " + DoubleToStr(volume, 3) + " lots @" + 
	                   DoubleToStr(price, digits+1) + " sl:" + DoubleToStr(stoploss, digits+1) + " tp:" + DoubleToStr(takeprofit, digits+1));


	// Normalize all price / stoploss / takeprofit to the proper # of digits.
	price = NormalizeDouble(price, digits);
	stoploss = NormalizeDouble(stoploss, digits);
	takeprofit = NormalizeDouble(takeprofit, digits);

	// Check stop levels, adjust if necessary
	EnsureValidStops(symbol, cmd, price, stoploss, takeprofit);

	int cnt, cnt_bad_price;
	GetLastError(); // clear the global variable.
	int err = 0;
	bool exit_loop = false;
	bool limit_to_market = false;
	bool fixed_invalid_price = false;

	// Concatenate to comment if enabled
	double symSpr = MarketInfo(symbol, MODE_ASK) - MarketInfo(symbol, MODE_BID);
	if (AddSpreadToComment)
		comment = comment + " (Spr: " + DoubleToStr(symSpr / adjPoint, 1) + ")";
		
	// Limit/Stop order...............................................................
	if (cmd > OP_SELL)
	{
		cnt = 0;
		cnt_bad_price = 0;
		while (!exit_loop)
		{
			// = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : =
			// Calculating our own slippage internally should not need to be done for pending orders; see market orders below
			// = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : =

			OrderReliablePrint("About to call OrderSend(), comment = " + comment);
			ticket = OrderSend(symbol, cmd, volume, price, slippage, stoploss,
			                   takeprofit, comment, magic, expiration, arrow_color);
			err = GetLastError();

			switch (err)
			{
				case ERR_NO_ERROR:
					exit_loop = true;
					break;

				// retryable errors
				case ERR_SERVER_BUSY:
				case ERR_NO_CONNECTION:
				case ERR_OFF_QUOTES:
				case ERR_BROKER_BUSY:
				case ERR_TRADE_CONTEXT_BUSY:
				case ERR_TRADE_TIMEOUT:
				case ERR_TRADE_DISABLED:
				case ERR_PRICE_CHANGED:
				case ERR_REQUOTE:
					cnt++;
					break;

				case ERR_INVALID_PRICE:
				case ERR_INVALID_STOPS:
					cnt++;
					cnt_bad_price++;
					break;

				case ERR_INVALID_TRADE_PARAMETERS:
				default:
					// an apparently serious error.
					exit_loop = true;
					break;

			}  // end switch

			if (cnt > retry_attempts)
				exit_loop = true;

			if (cnt_bad_price > retry_attempts_bad_price)
				exit_loop = true;


			if (exit_loop)
			{
				if (!limit_to_market)
				{
					if (err != ERR_NO_ERROR  &&  err != ERR_NO_RESULT)
						OrderReliablePrint("Non-retryable error: " + OrderReliableErrTxt(err));
					else if (cnt > retry_attempts)
						OrderReliablePrint("Retry attempts maxed at " + (string)retry_attempts +"("+DoubleToStr(retry_attempts_bad_price)+")");
				}
			}
			else
			{
				OrderReliablePrint("Result of attempt " + (string)cnt + " of " + (string)retry_attempts+"("+DoubleToStr(retry_attempts_bad_price)+")" + ": Retryable error: " + OrderReliableErrTxt(err));
				
				SleepRandomTime(sleep_time, sleep_maximum);
				RefreshRates();
			}
		}

		// We have now exited from loop.
		if (err == ERR_NO_ERROR  ||  err == ERR_NO_RESULT)
		{
			OrderReliablePrint("Ticket #" + (string)ticket + ": Successful " + OrderTypeToString(cmd) + " order placed with comment = " + comment + ", details follow.");
			if (!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
				OrderReliablePrint("Could Not Select Ticket #" + (string)ticket);
			sqOrderPrint();
			return(ticket); // SUCCESS!
		}
		if (!limit_to_market)
		{
			OrderReliablePrint("Failed to execute stop or limit order after " + (string)retry_attempts + " retries");
			OrderReliablePrint("Failed trade: " + OrderTypeToString(cmd) + " " + DoubleToStr(volume, 2) + " lots  " + symbol +
			                   "@" + DoubleToStr(price) + " tp@" + DoubleToStr(takeprofit) + " sl@" + DoubleToStr(stoploss));
			OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
			
			OrderReliablePrint("");
			return(-1);
		}
	}  // end

	if (limit_to_market)
	{
		OrderReliablePrint("Going from stop/limit order to market order because market is too close.");
		cmd %= 2;
		if (cmd == OP_BUY)	price = ask;
		else 				price = bid;
	}

	// We now have a market order.
	err = GetLastError(); // so we clear the global variable.
	err = 0;
	ticket = -1;
	exit_loop = false;


	// Market order..........................................................
	if (cmd == OP_BUY  ||  cmd == OP_SELL)
	{
		cnt = 0;
		cnt_bad_price = 0;
		while (!exit_loop)
		{
			// = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : =
			// Get current price and calculate slippage
			RefreshRates();
			if (cmd == OP_BUY)
			{
				M = 1.0;
				priceNow = NormalizeDouble(MarketInfo(symbol, MODE_ASK), (int)MarketInfo(symbol, MODE_DIGITS));	// Open @ Ask
				hasSlippedBy = (priceNow - price) / point;	// (Adjusted Point)
			}
			else if (cmd == OP_SELL)
			{
				M = -1.0;
				priceNow = NormalizeDouble(MarketInfo(symbol, MODE_BID), (int)MarketInfo(symbol, MODE_DIGITS));	// Open @ Bid
				hasSlippedBy = (price - priceNow) / point;	// (Adjusted Point)
			}

			// Check if slippage is more than caller's maximum allowed
			if (priceNow != price  &&  hasSlippedBy > slippage)
			{
				// Actual price has slipped against us more than user allowed
				// Log error message, sleep, and try again
				OrderReliablePrint("Actual Price (Ask for buy, Bid for sell) = " + DoubleToStr(priceNow, Digits+1) + "; Slippage from Requested Price = " + DoubleToStr(hasSlippedBy, 1) + " pips.  Retrying...");
				err = ERR_PRICE_CHANGED;
			}
			else
			{
				if (priceNow != price)
				{
					// If the price has slipped "acceptably" (either negative or within 
					// "Slippage" param), then we need to adjust the SL and TP accordingly
					if (stoploss != 0)		stoploss += M * hasSlippedBy;
					if (takeprofit != 0)	takeprofit += M * hasSlippedBy;
					OrderReliablePrint("Actual Price (Ask for buy, Bid for sell) = " + DoubleToStr(priceNow, Digits+1) + "; Requested Price = " + DoubleToStr(price, Digits) + "; Slippage from Requested Price = " + DoubleToStr(hasSlippedBy, 1) + " pips (\'positive slippage\').  Attempting order at market");
				}
				OrderReliablePrint("About to call OrderSend(), comment = " + comment);
				ticket = OrderSend(symbol, cmd, volume, priceNow, (slippage - (int)hasSlippedBy), 
								   stoploss, takeprofit, comment, magic,	expiration, arrow_color);
				err = GetLastError();
			}
			// = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : =

			switch (err)
			{
				case ERR_NO_ERROR:
					exit_loop = true;
					break;

				case ERR_INVALID_PRICE:
					if (cmd == OP_BUY)
						OrderReliablePrint("INVALID PRICE ERROR - Requested Price: " + DoubleToStr(price, Digits) + "; Ask = " + DoubleToStr(MarketInfo(symbol, MODE_ASK), Digits));
					else
						OrderReliablePrint("INVALID PRICE ERROR - Requested Price: " + DoubleToStr(price, Digits) + "; Bid = " + DoubleToStr(MarketInfo(symbol, MODE_BID), Digits));
					cnt++; // a retryable error
					cnt_bad_price++;
					break;
					
				case ERR_INVALID_STOPS:
					OrderReliablePrint("INVALID STOPS on attempted " + OrderTypeToString(cmd) + " : " + DoubleToStr(volume, 2) + " lots " + " @ " + DoubleToStr(price, Digits) + ", SL = " + DoubleToStr(stoploss, Digits) + ", TP = " + DoubleToStr(takeprofit, Digits));
					cnt++; // a retryable error
					cnt_bad_price++;
					break;
					
				case ERR_SERVER_BUSY:
				case ERR_NO_CONNECTION:
				case ERR_OFF_QUOTES:
				case ERR_BROKER_BUSY:
				case ERR_TRADE_CONTEXT_BUSY:
				case ERR_TRADE_TIMEOUT:
				case ERR_TRADE_DISABLED:
				case ERR_PRICE_CHANGED:
				case ERR_REQUOTE:
					cnt++; // a retryable error
					break;

				default:
					// an apparently serious, unretryable error.
					exit_loop = true;
					break;
			}  

			if (cnt > retry_attempts)
				exit_loop = true;

			if (cnt_bad_price > retry_attempts_bad_price)
				exit_loop = true;

			if (exit_loop)
			{
				if (err != ERR_NO_ERROR  &&  err != ERR_NO_RESULT)
					OrderReliablePrint("Non-retryable error: " + OrderReliableErrTxt(err));
				if (cnt > retry_attempts)
					OrderReliablePrint("Retry attempts maxed at " + (string)retry_attempts +"("+DoubleToStr(retry_attempts_bad_price)+")");
			}
			else
			{
				OrderReliablePrint("Result of attempt " + (string)cnt + " of " + (string)retry_attempts +"("+DoubleToStr(retry_attempts_bad_price)+")" + ": Retryable error: " + OrderReliableErrTxt(err));
				
				SleepRandomTime(sleep_time, sleep_maximum);
				RefreshRates();
			}
		}

		// We have now exited from loop; if successful, return ticket #
		if (err == ERR_NO_ERROR  ||  err == ERR_NO_RESULT)
		{
			OrderReliablePrint("Ticket #" + (string)ticket + ": Successful " + OrderTypeToString(cmd) + " order placed with comment = " + comment + ", details follow.");
			if (!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
				OrderReliablePrint("Could Not Select Ticket #" + (string)ticket);
			sqOrderPrint();
			return(ticket); // SUCCESS!
		}
		
		// If not successful, log and return -1
		OrderReliablePrint("Failed to execute OP_BUY/OP_SELL, after " + (string)retry_attempts + " retries");
		OrderReliablePrint("Failed trade: " + OrderTypeToString(cmd) + " " + DoubleToStr(volume, 2) + " lots  " + symbol +
		                   "@" + DoubleToStr(price) + " tp@" + DoubleToStr(takeprofit) + " sl@" + DoubleToStr(stoploss));
		OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
	}
	return(-1);
}


//=============================================================================
//							 OrderSendReliable2Step()
//
//  Some brokers don't allow the SL and TP settings as part of the initial
//  market order (Water House Capital).  Therefore, this routine will first
//  place the market order with no stop-loss and take-profit but later
//  update the order accordingly
//
//	RETURN VALUE:
//     Same as OrderSendReliable; the ticket number
//
//  NOTES:
//     Order will not be updated if an error continues during
//     OrderSendReliableMKT.  No additional information will be logged
//     since OrderSendReliableMKT would have already logged the error
//     condition
//
//  ORIGINAL AUTHOR AND DATE:
//     Jack Tomlinson, 2007-05-29
//
//=============================================================================
int OrderSendReliable2Step(string symbol, int cmd, double volume, double price,
                           int slippage, double stoploss, double takeprofit,
                           string comment="", int magic=0, datetime expiration=0,
                           color arrow_color=CLR_NONE)
{
	OrderReliable_Fname = "OrderSendReliable2Step";
	int ticket = -1;
	double slipped = 0;
  
  price = NormalizeDouble(price, Digits);
	takeprofit = NormalizeDouble(takeprofit, Digits);
	stoploss = NormalizeDouble(stoploss, Digits);
    
	// ========================================================================
	// If testing or optimizing, there is no need to use this lib, as the 
	// orders are not real-world, and always get placed optimally.  By 
	// refactoring this option to be in this library, one no longer needs 
	// to create similar code in each EA.
	if (!UseForTesting)
	{
		if (IsOptimization()  ||  IsTesting())
		{
			ticket = OrderSend(symbol, cmd, volume, price, slippage, 0, 0, 
							   comment, magic, 0, arrow_color);

			if (!OrderModify(ticket, price, stoploss, takeprofit, expiration, arrow_color))
				OrderReliablePrint("Order Modify of Ticket #" + (string)ticket + " FAILED");
			
			return(ticket);
		}
	}
	// ========================================================================
	
	
	OrderReliablePrint("Doing OrderSendReliable, followed by OrderModifyReliable:");

	ticket = OrderSendReliable(symbol, cmd, volume, price, slippage,
								0, 0, comment, magic, expiration, arrow_color);

	if (stoploss != 0 || takeprofit != 0)
	{
		if (ticket >= 0)
		{
			double theOpenPrice = price;
			if (OrderSelect(ticket, SELECT_BY_TICKET))
			{
				slipped = OrderOpenPrice() - price;
				theOpenPrice = OrderOpenPrice();
			}
			else
				OrderReliablePrint("Failed to select ticket #" + (string)ticket + " after successful 2step placement; cannot recalculate SL & TP");
			if (slipped > 0)
			{
				OrderReliablePrint("2step order slipped by: " + DoubleToStr(slipped, Digits) + "; SL & TP modified by same amount");
				if (takeprofit != 0)	takeprofit += slipped;
				if (stoploss != 0)		stoploss += slipped;
			}
			OrderModifyReliable(ticket, theOpenPrice, stoploss, takeprofit, expiration, arrow_color);
		}
	}
	else
		OrderReliablePrint("Skipping OrderModifyReliable because no SL or TP specified.");

	return(ticket);
}


//=============================================================================
//							 OrderModifyReliable()
//
//  This is intended to be a drop-in replacement for OrderModify() which,
//  one hopes, is more resistant to various forms of errors prevalent
//  with MetaTrader.
//
//  RETURN VALUE:
//     TRUE if successful, FALSE otherwise
//
//  FEATURES:
//     * Re-trying under some error conditions, sleeping a random
//       time defined by an exponential probability distribution.
//
//     * Displays various error messages on the log for debugging.
//
//
//  ORIGINAL AUTHOR AND DATE:
//     Matt Kennel, 2006-05-28
//
//=============================================================================
bool OrderModifyReliable(int ticket, double price, double stoploss,
                         double takeprofit, datetime expiration,
                         color arrow_color=CLR_NONE)
{
	OrderReliable_Fname = "OrderModifyReliable";
	bool result = false;
	bool non_retryable_error = false;

	// ========================================================================
	// If testing or optimizing, there is no need to use this lib, as the 
	// orders are not real-world, and always get placed optimally.  By 
	// refactoring this option to be in this library, one no longer needs 
	// to create similar code in each EA.
	if (!UseForTesting)
	{
		if (IsOptimization()  ||  IsTesting())
		{
			result = OrderModify(ticket, price, stoploss,
			                     takeprofit, expiration, arrow_color);
			return(result);
		}
	}
	// ========================================================================
	
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// Get information about this order
	string symbol = "ALLOCATE";		// This is so it has memory space allocated
	int type;
	int digits;
	double point;
	double bid, ask;
	double sl, tp;
	GetOrderDetails(ticket, symbol, type, digits, point, sl, tp, bid, ask);
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		
	
	OrderReliablePrint("Attempted modify of #" + (string)ticket + " price:" + DoubleToStr(price, digits+1) +
	                   " sl:" + DoubleToStr(stoploss, digits+1) + " tp:" + DoubleToStr(takeprofit, digits+1) + 
	                   " exp:" + TimeToStr(expiration));

	// Below, we call "EnsureValidStops".  If the order we are modifying 
	// is a pending order, then we should use the price passed in.  But 
	// if it's an open order, the price passed in is irrelevant; we need 
	// to use the appropriate bid or ask, so get those...
	double prc = price;
	if (type == OP_BUY)			prc = bid;
	else if (type == OP_SELL)	prc = ask;

	// With the requisite info, we can do error checking on SL & TP
	prc = NormalizeDouble(prc, digits);
	price = NormalizeDouble(price, digits);
	stoploss = NormalizeDouble(stoploss, digits);
	takeprofit = NormalizeDouble(takeprofit, digits);
	
	// If SL/TP are not changing then send in zeroes to EnsureValidStops(),
	// so that it does not bother to try to change them
	double newSL = stoploss;
	double newTP = takeprofit;
	if (stoploss == sl)		newSL = 0;
	if (takeprofit == tp)	newTP = 0;
	EnsureValidStops(symbol, type, prc, newSL, newTP, false);
	if (stoploss != sl)		stoploss = newSL;
	if (takeprofit != tp)	takeprofit = newTP;


	int cnt = 0;
	int cnt_bad_price = 0;
	int err = GetLastError(); // so we clear the global variable.
	err = 0;
	bool exit_loop = false;

	while (!exit_loop)
	{
		result = OrderModify(ticket, price, stoploss,
		                     takeprofit, expiration, arrow_color);
		err = GetLastError();

		if (result == true)
			exit_loop = true;
		else
		{
			switch (err)
			{
				case ERR_NO_ERROR:
					exit_loop = true;
					OrderReliablePrint("ERR_NO_ERROR received, but OrderClose() returned false; exiting");
					break;

				case ERR_NO_RESULT:
					// Modification to same value as before
					// See below for reported result
					exit_loop = true;
					break;

				// Shouldn't be any reason stops are invalid (and yet I've seen it); try again
				case ERR_INVALID_PRICE:
				case ERR_INVALID_STOPS:	
					OrderReliablePrint("OrderModifyReliable, ERR_INVALID_STOPS, Broker\'s Min Stop Level (in pips) = " + DoubleToStr(MarketInfo(symbol, MODE_STOPLEVEL) * Point / AdjPoint(symbol), 1));
//					EnsureValidStops(symbol, price, stoploss, takeprofit);
               cnt_bad_price++;
               
				case ERR_COMMON_ERROR:
				case ERR_SERVER_BUSY:
				case ERR_NO_CONNECTION:
				case ERR_TOO_FREQUENT_REQUESTS:
				case ERR_TRADE_TIMEOUT:		// for modify this is a retryable error, I hope.
				case ERR_OFF_QUOTES:
				case ERR_BROKER_BUSY:
				case ERR_TOO_MANY_REQUESTS:
				case ERR_TRADE_CONTEXT_BUSY:
				case ERR_TRADE_DISABLED:
					cnt++; 	// a retryable error
					break;

				case ERR_TRADE_MODIFY_DENIED:
					// This one may be important; have to Ensure Valid Stops AND valid price (for pends)
					break;
				
				case ERR_PRICE_CHANGED:
				case ERR_REQUOTE:
					RefreshRates();
					continue; 	// we can apparently retry immediately according to MT docs.

				default:
					// an apparently serious, unretryable error.
					exit_loop = true;
					non_retryable_error = true;
					break;

			}  // end switch
		}

		if (cnt > retry_attempts)
			exit_loop = true;

		if (cnt_bad_price > retry_attempts_bad_price)
			exit_loop = true;

		if (!exit_loop)
		{
			OrderReliablePrint("Result of attempt " + (string)cnt + " of " + (string)retry_attempts+"("+DoubleToStr(retry_attempts_bad_price)+")" + ": Retryable error: " + OrderReliableErrTxt(err));
			
			SleepRandomTime(sleep_time, sleep_maximum);
			RefreshRates();
		}
		else
		{
			if (cnt > retry_attempts)
				OrderReliablePrint("Retry attempts maxed at " + (string)retry_attempts);
			else if (non_retryable_error)
				OrderReliablePrint("Non-retryable error: "  + OrderReliableErrTxt(err));
		}
	}

	// we have now exited from loop.
	if (err == ERR_NO_RESULT)
	{
		OrderReliablePrint("Server reported modify order did not actually change parameters.");
		OrderReliablePrint("Redundant modification: " + (string)ticket + " " + symbol +
		                   "@" + DoubleToStr(price) + " tp@" + DoubleToStr(takeprofit) + " sl@" + DoubleToStr(stoploss));
		OrderReliablePrint("Suggest modifying code logic to avoid.");
	}
	
	if (result)
	{
		OrderReliablePrint("Ticket #" + (string)ticket + ": Modification successful, updated trade details follow.");
		if (!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
			OrderReliablePrint("Could Not Select Ticket #" + (string)ticket);
		sqOrderPrint();
	}
	else
	{
		OrderReliablePrint("Failed to execute modify after " + (string)retry_attempts + " retries");
		OrderReliablePrint("Failed modification: "  + (string)ticket + " " + symbol +
	                   	"@" + DoubleToStr(price) + " tp@" + DoubleToStr(takeprofit) + " sl@" + DoubleToStr(stoploss));
		OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
	}
	
	return(result);
}


//=============================================================================
//                            OrderCloseReliable()
//
//  This is intended to be a drop-in replacement for OrderClose() which,
//  one hopes, is more resistant to various forms of errors prevalent
//  with MetaTrader.
//
//  RETURN VALUE:
//     TRUE if successful, FALSE otherwise
//
//  FEATURES:
//     * Re-trying under some error conditions, sleeping a random
//       time defined by an exponential probability distribution.
//
//     * Displays various error messages on the log for debugging.
//
//  ORIGINAL AUTHOR AND DATE:
//     Derk Wehler, 2006-07-19
//
//=============================================================================
bool OrderCloseReliable(int ticket, double volume, double price,
						int slippage, color arrow_color=CLR_NONE)
{
	OrderReliable_Fname = "OrderCloseReliable";
	bool result = false;
	bool non_retryable_error = false;
	
	// ========================================================================
	// If testing or optimizing, there is no need to use this lib, as the 
	// orders are not real-world, and always get placed optimally.  By 
	// refactoring this option to be in this library, one no longer needs 
	// to create similar code in each EA.
	if (!UseForTesting)
	{
		if (IsOptimization()  ||  IsTesting())
		{
			result = OrderClose(ticket, volume, price, slippage, arrow_color);
			return(result);
		}
	}
	// ========================================================================
	

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// Get information about this order
	string symbol = "ALLOCATE";		// This is so it has memory space allocated
	int type;
	int digits;
	double point;
	double bid, ask;
	double sl, tp;
	GetOrderDetails(ticket, symbol, type, digits, point, sl, tp, bid, ask);
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	
	
	OrderReliablePrint("Attempted close of #" + (string)ticket + " initial price:" + DoubleToStr(price, digits+1) +
	                   " lots:" + DoubleToStr(volume, 3) + " slippage:" + (string)slippage);


	if (type != OP_BUY && type != OP_SELL)
	{
		OrderReliablePrint("Error: Trying to close ticket #" + (string)ticket + ", which is " + OrderTypeToString(type) + ", not OP_BUY or OP_SELL");
		return(false);
	}


	int cnt = 0;
	int err = GetLastError(); // so we clear the global variable.
	err = 0;
	bool exit_loop = false;
	double priceNow=0;
	double hasSlippedBy=0;

	while (!exit_loop)
	{
		// = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : =
		// Get current price and calculate slippage
		RefreshRates();
		if (type == OP_BUY)
		{
			priceNow = NormalizeDouble(MarketInfo(symbol, MODE_BID), (int)MarketInfo(symbol, MODE_DIGITS));	// Close @ Bid
			hasSlippedBy = (price - priceNow) / point;	// (Adjusted Point)
		}
		else if (type == OP_SELL)
		{
			priceNow = NormalizeDouble(MarketInfo(symbol, MODE_ASK), (int)MarketInfo(symbol, MODE_DIGITS));	// Close @ Ask
			hasSlippedBy = (priceNow - price) / point;	// (Adjusted Point)
		}

		// Check if slippage is more than caller's maximum allowed
		if (priceNow != price  &&  hasSlippedBy > slippage)
		{
			// Actual price has slipped against us more than user allowed
			// Log error message, sleep, and try again
			OrderReliablePrint("Actual Price (Bid for buy, Ask for sell) Value = " + DoubleToStr(priceNow, Digits) + "; Slippage from Requested Price = " + DoubleToStr(hasSlippedBy, 1) + " pips.  Retrying...");
			result = false;
			err = ERR_PRICE_CHANGED;
		}
		else
		{
			result = OrderClose(ticket, volume, priceNow, (slippage - (int)hasSlippedBy), arrow_color);
			err = GetLastError();
		}
		// = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : = : =

		if (result == true)
			exit_loop = true;
		else
		{
			switch (err)
			{
				case ERR_NO_ERROR:
					exit_loop = true;
					OrderReliablePrint("ERR_NO_ERROR received, but OrderClose() returned false; exiting");
					OrderReliablePrint("If order did not actually close, error code is apparently wrong");
					break;

				case ERR_NO_RESULT:
					exit_loop = true;
					OrderReliablePrint("ERR_NO_RESULT received, but OrderClose() returned false; exiting");
					OrderReliablePrint("If order did not actually close, error code is apparently wrong");
					break;

				case ERR_INVALID_PRICE:
					OrderReliablePrint("ERR_INVALID_PRICE received, but should not occur since we are refreshing rates");
					cnt++; 	// a retryable error
					break;

				case ERR_PRICE_CHANGED:
				case ERR_COMMON_ERROR:
				case ERR_SERVER_BUSY:
				case ERR_NO_CONNECTION:
				case ERR_TOO_FREQUENT_REQUESTS:
				case ERR_TRADE_TIMEOUT:		// for close this is a retryable error, I hope.
				case ERR_TRADE_DISABLED:
				case ERR_OFF_QUOTES:
				case ERR_BROKER_BUSY:
				case ERR_REQUOTE:
				case ERR_TOO_MANY_REQUESTS:	
				case ERR_TRADE_CONTEXT_BUSY:
					cnt++; 	// a retryable error
					break;

				default:
					// Any other error is an apparently serious, unretryable error.
					exit_loop = true;
					non_retryable_error = true;
					break;

			}  // end switch
		}

		if (cnt > retry_attempts)
			exit_loop = true;

		if (exit_loop)
		{
			if (cnt > retry_attempts)
				OrderReliablePrint("Retry attempts maxed at " + (string)retry_attempts);
			else if (non_retryable_error)
				OrderReliablePrint("Non-retryable error: " + OrderReliableErrTxt(err));
		}
		else
		{
			OrderReliablePrint("Result of attempt " + (string)cnt + " of " + (string)retry_attempts + ": Retryable error: " + OrderReliableErrTxt(err));
			
			SleepRandomTime(sleep_time, sleep_maximum);
		}
	}

	// We have now exited from loop
	if (result  ||  err == ERR_NO_RESULT  ||  err == ERR_NO_ERROR)
	{
		if (!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
			OrderReliablePrint("Successful close of Ticket #" + (string)ticket + "     [ Last error: " + OrderReliableErrTxt(err) + " ]");
		else if (OrderCloseTime() > 0)	// Then it closed ok
			OrderReliablePrint("Successful close of Ticket #" + (string)ticket + "     [ Last error: " + OrderReliableErrTxt(err) + " ]");
		else
		{
			OrderReliablePrint("Close result reported success (or failure, but w/ERR_NO_ERROR); yet order remains!  Must re-try close from EA logic!");
			OrderReliablePrint("Close Failed: Ticket #" + (string)ticket + ", Price: " +
		                   		DoubleToStr(price) + ", Slippage: " + (string)slippage);
			OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
			result = false;
		}
	}
	else
	{
		OrderReliablePrint("Failed to execute close after " + (string)(cnt-1) + " retries");
		OrderReliablePrint("Failed close: Ticket #" + (string)ticket + " @ Price: " + DoubleToStr(priceNow) + 
	                   	   " (Requested Price: " + DoubleToStr(price) + "), Slippage: " + (string)slippage);
		OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
	}
	
	
	return(result);
}


//=============================================================================
//                           OrderCloseReliableMKT()
//
//	This function is intended for closing orders ASAP; the principal 
//  difference is that in its internal retry-loop, it uses the new "Bid" 
//  and "Ask" real-time variables as opposed to the OrderCloseReliable(), 
//  which uses only the price given upon entry to the routine.  More likely 
//  to get the order closed if price moves, but more likely to "slip"
//
//  RETURN VALUE:
//     TRUE if successful, FALSE otherwise
//
//  FEATURES:
//     * Re-trying under some error conditions, sleeping a random
//       time defined by an exponential probability distribution.
//
//     * Displays various error messages on the log for debugging.
//
//  ORIGINAL AUTHOR AND DATE:
//     Derk Wehler, 2009-04-03
//
//=============================================================================
bool OrderCloseReliableMKT(int ticket, double volume, double price,
						   int slippage, color arrow_color=CLR_NONE)
{
	OrderReliable_Fname = "OrderCloseReliableMKT";
	bool result = false;
	bool non_retryable_error = false;
	
	// ========================================================================
	// If testing or optimizing, there is no need to use this lib, as the 
	// orders are not real-world, and always get placed optimally.  By 
	// refactoring this option to be in this library, one no longer needs 
	// to create similar code in each EA.
	if (!UseForTesting)
	{
		if (IsOptimization()  ||  IsTesting())
		{
			result = OrderClose(ticket, volume, price, slippage, arrow_color);
			return(result);
		}
	}
	// ========================================================================
	

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// Get information about this order
	string symbol = "ALLOCATE";		// This is so it has memory space allocated
	int type;
	int digits;
	double point;
	double bid, ask;
	double sl, tp;
	GetOrderDetails(ticket, symbol, type, digits, point, sl, tp, bid, ask);
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	
	
	OrderReliablePrint("Attempted close of #" + (string)ticket + " initial price:" + DoubleToStr(price, digits+1) +
	                   " lots:" + DoubleToStr(price, 3) + " slippage:" + (string)slippage);


	if (type != OP_BUY && type != OP_SELL)
	{
		OrderReliablePrint("Error: Trying to close ticket #" + (string)ticket + ", which is " + OrderTypeToString(type) + ", not OP_BUY or OP_SELL");
		return(false);
	}


	int cnt = 0;
	int err = GetLastError(); // so we clear the global variable.
	err = 0;
	bool exit_loop = false;
	double pnow=0;
	int slippagenow=0;

	while (!exit_loop)
	{
		if (type == OP_BUY)
		{
			pnow = NormalizeDouble(MarketInfo(symbol, MODE_ASK), (int)MarketInfo(symbol, MODE_DIGITS)); // we are buying at Ask
			if (pnow > price)
			{
				// Do not allow slippage to go negative; will cause error
				slippagenow = (int)MathMax(0, slippage - (pnow - price) / point);
			}
		}
		else if (type == OP_SELL)
		{
			pnow = NormalizeDouble(MarketInfo(symbol, MODE_BID), (int)MarketInfo(symbol, MODE_DIGITS)); // we are buying at Ask
			if (pnow < price)
			{
				// Do not allow slippage to go negative; will cause error
				slippagenow = (int)MathMax(0, slippage - (price - pnow) / point);
			}
		}

		result = OrderClose(ticket, volume, pnow, slippagenow, arrow_color);
		err = GetLastError();

		if (result == true)
			exit_loop = true;
		else
		{
			switch (err)
			{
				case ERR_NO_ERROR:
					exit_loop = true;
					OrderReliablePrint("ERR_NO_ERROR received, but OrderClose() returned false; exiting");
					break;

				case ERR_NO_RESULT:
					exit_loop = true;
					OrderReliablePrint("ERR_NO_RESULT received, but OrderClose() returned false; exiting");
					break;

				case ERR_COMMON_ERROR:
				case ERR_SERVER_BUSY:
				case ERR_NO_CONNECTION:
				case ERR_TOO_FREQUENT_REQUESTS:
				case ERR_TRADE_TIMEOUT:		// for close this is a retryable error, I hope.
				case ERR_TRADE_DISABLED:
				case ERR_PRICE_CHANGED:
				case ERR_INVALID_PRICE:
				case ERR_OFF_QUOTES:
				case ERR_BROKER_BUSY:
				case ERR_REQUOTE:
				case ERR_TOO_MANY_REQUESTS:	
				case ERR_TRADE_CONTEXT_BUSY:
					cnt++; 	// a retryable error
					break;

				default:
					// Any other error is an apparently serious, unretryable error.
					exit_loop = true;
					non_retryable_error = true;
					break;

			}  // end switch
		}

		if (cnt > retry_attempts)
			exit_loop = true;

		if (!exit_loop)
		{
			OrderReliablePrint("Result of attempt " + (string)cnt + " of " + (string)retry_attempts + ": Retryable error: " + OrderReliableErrTxt(err));
			
			SleepRandomTime(sleep_time, sleep_maximum);
		}

		if (exit_loop)
		{
			if (cnt > retry_attempts)
				OrderReliablePrint("Retry attempts maxed at " + (string)retry_attempts);
			else if (non_retryable_error)
				OrderReliablePrint("Non-retryable error: " + OrderReliableErrTxt(err));
		}
	}

	// we have now exited from loop.
	if (result)
	{
		if (!OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
			OrderReliablePrint("Successful close of Ticket #" + (string)ticket + "     [ Last error: " + OrderReliableErrTxt(err) + " ]");
		else if (OrderCloseTime() > 0)	// Then it closed ok
			OrderReliablePrint("Successful close of Ticket #" + (string)ticket + "     [ Last error: " + OrderReliableErrTxt(err) + " ]");
		else
		{
			OrderReliablePrint("Close result reported success, but order remains!  Must re-try close from EA logic!");
			OrderReliablePrint("Close Failed: Ticket #" + (string)ticket + ", Price: " +
		                   		DoubleToStr(price) + ", Slippage: " + (string)slippage);
			OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
			result = false;
		}
	}
	else
	{
		OrderReliablePrint("Failed to execute close after " + (string)retry_attempts + " retries");
		OrderReliablePrint("Failed close: Ticket #" + (string)ticket + " @ Price: " +
	                   		DoubleToStr(pnow) + " (Initial Price: " + DoubleToStr(price) + "), Slippage: " + 
	                   		(string)slippagenow + " (Initial Slippage: " + (string)slippage + ")");
		OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
	}
	
	
	return(result);
}


//=============================================================================
//                            OrderDeleteReliable()
//
//  This is intended to be a drop-in replacement for OrderDelete() which,
//  one hopes, is more resistant to various forms of errors prevalent
//  with MetaTrader.
//
//  RETURN VALUE:
//     TRUE if successful, FALSE otherwise
//
//
//  FEATURES:
//     * Re-trying under some error conditions, sleeping a random
//       time defined by an exponential probability distribution.
//
//     * Displays various error messages on the log for debugging.
//
//  ORIGINAL AUTHOR AND DATE:
//     Derk Wehler, 2006-12-21
//
//=============================================================================
bool OrderDeleteReliable(int ticket, color clr=CLR_NONE)
{
	OrderReliable_Fname = "OrderDeleteReliable";
	bool result = false;
	bool non_retryable_error = false;

	// ========================================================================
	// If testing or optimizing, there is no need to use this lib, as the 
	// orders are not real-world, and always get placed optimally.  By 
	// refactoring this option to be in this library, one no longer needs 
	// to create similar code in each EA.
	if (!UseForTesting)
	{
		if (IsOptimization()  ||  IsTesting())
		{
			result = OrderDelete(ticket, clr);
			return(result);
		}
	}
	// ========================================================================
	
	
	
	OrderReliablePrint("Attempted deletion of pending order, #" + (string)ticket);
	

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// Get information about this order
	string symbol = "ALLOCATE";		// This is so it has memory space allocated
	int type;
	int digits;
	double point;
	double bid, ask;
	double sl, tp;
	GetOrderDetails(ticket, symbol, type, digits, point, sl, tp, bid, ask);
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		
	if (type == OP_BUY || type == OP_SELL)
	{
		OrderReliablePrint("error: Trying to close ticket #" + (string)ticket +
		                   ", which is " + OrderTypeToString(type) +
		                   ", not OP_BUYSTOP, OP_SELLSTOP, OP_BUYLIMIT, or OP_SELLLIMIT");
		return(false);
	}


	int cnt = 0;
	int err = GetLastError(); // so we clear the global variable.
	err = 0;
	bool exit_loop = false;

	while (!exit_loop)
	{
		result = OrderDelete(ticket, clr);
		err = GetLastError();

		if (result == true)
			exit_loop = true;
		else
		{
			switch (err)
			{
				case ERR_NO_ERROR:
					exit_loop = true;
					OrderReliablePrint("ERR_NO_ERROR received, but OrderDelete() returned false; exiting");
					break;

				case ERR_NO_RESULT:
					exit_loop = true;
					OrderReliablePrint("ERR_NO_RESULT received, but OrderDelete() returned false; exiting");
					break;

				case ERR_COMMON_ERROR:
				case ERR_SERVER_BUSY:
				case ERR_NO_CONNECTION:
				case ERR_TOO_FREQUENT_REQUESTS:
				case ERR_TRADE_TIMEOUT:		// for delete this is a retryable error, I hope.
				case ERR_TRADE_DISABLED:
				case ERR_OFF_QUOTES:
				case ERR_PRICE_CHANGED:
				case ERR_BROKER_BUSY:
				case ERR_REQUOTE:
				case ERR_TOO_MANY_REQUESTS:
				case ERR_TRADE_CONTEXT_BUSY:
					cnt++; 	// a retryable error
					break;

				default:	// Any other error is an apparently serious, unretryable error.
					exit_loop = true;
					non_retryable_error = true;
					break;

			}  // end switch
		}

		if (cnt > retry_attempts)
			exit_loop = true;

		if (!exit_loop)
		{
			OrderReliablePrint("Result of attempt " + (string)cnt + " of " + (string)retry_attempts + ": Retryable error: " + OrderReliableErrTxt(err));
			SleepRandomTime(sleep_time, sleep_maximum);
		}
		else
		{
			if (cnt > retry_attempts)
				OrderReliablePrint("Retry attempts maxed at " + (string)retry_attempts);
			else if (non_retryable_error)
				OrderReliablePrint("Non-retryable error: " + OrderReliableErrTxt(err));
		}
	}

	// we have now exited from loop.
	if (result)
	{
		OrderReliablePrint("Successful deletion of Ticket #" + (string)ticket);
		
		return(true); // SUCCESS!
	}
	else
	{
		OrderReliablePrint("Failed to execute delete after " + (string)retry_attempts + " retries");
		OrderReliablePrint("Failed deletion: Ticket #" + (string)ticket);
		OrderReliablePrint("Last error: " + OrderReliableErrTxt(err));
	}
	
	
	return(result);
}
本博客所有文章如无特别注明均为原创。作者:天泓评测
分享到:更多

相关推荐

发表评论

路人甲 表情
Ctrl+Enter快速提交

网友评论(0)