//+----------------------------------------------------------------------------- //| MAStrongTrendDetectorEA.mq4 //| Lukasz Szelag (lukasz74nj@gmail.com) //| http://www.forexabyss.com //| //| An expert advisor that implements the FX-Ed Trend Technique. //| //| Notes: //| //| - only market orders are used (no pending orders) //| - orders that are not opened by this EA are ignored //| - EA doesn't open new orders if freeze level > 0 //| - no more than one order is opened for the same bar //| //| Date Version Comments //| 3/03/2009 1.0 Initial version. //| //| 3/11/2009 1.1 Made the EA work only on a daily timeframe. Changed the //| ORDER_MAGIC_NUMBER. //| //| 3/12/2009 1.2 Fixed a bug in the order accounting function. //| //| 3/13/2009 1.3 Added the ability to specify the maximum number of open //| orders. //| //| 3/14/2009 1.4 Stop loss levels are not updated if they didn't change. //| //| 3/22/2009 1.5 Fixed a condition that determines when the price //| approaches its 10-day EMA. Added the ability to specify //| the depth of the support or resistance zone. Fixed a //| bug that allowed opening more than one order for the //| same bar. //| //| 3/23/2009 1.6 Normalized calculated stop loss levels. //| //| 3/24/2009 1.7 Open price, stop loss and take profit values are //| normalized during order accounting. //| //| 3/27/2009 1.8 Fixed calculation of stop levels for new orders. //| //| 3/31/2009 1.9 If the trading thread is busy the tick is not //| processed. //| //| 4/19/2009 1.9 The condition to determine when the price approaches //| its 10-day EMA no longer uses the Bid. Instead, two //| separate conditions are used (one for an uptrend, one //| for a downtrend), which use the lowest or the highest //| price of the given bar. //| //| 4/21/2009 1.10 Fixed order accounting function to exclude orders that //| have been closed. //| //| 4/25/2009 1.11 Trading signal is calculated on every tick unless an //| order has been already opened for the given bar. Stop //| loss levels are trailed when a new bar is formed. //| //| 4/30/2009 1.12 Added adjustOrderLevel(). Calculation of stop loss //| levels for open orders takes into account the minimum //| distance from the market price allowed by the broker. //| //| 5/01/2009 1.13 Fixed a bug in adjustOrderLevel(). For long positions, //| the minimum distance is now calculated relative to the //| Bid price, and for short positions to the Ask price. //+----------------------------------------------------------------------------- #property copyright "Lukasz Szelag" #property link "http://www.forexabyss.com" //+----------------------------------------------------------------------------- //| Constants. //+----------------------------------------------------------------------------- /* * Magic number to be assigned to all orders opened by this EA. This unique * identifier is used to determine if the given order was opened by this EA. */ #define ORDER_MAGIC_NUMBER 0x7f000001 // constants for trade operations #define TRADE_OP_NONE 0 #define TRADE_OP_OPEN_BUY 1 #define TRADE_OP_OPEN_SELL 2 #define TRADE_OP_CLOSE_BUY 3 #define TRADE_OP_CLOSE_SELL 4 // constants for naming indexes of the "openOrders" array #define ORDER_TICKET 0 #define ORDER_TYPE 1 #define ORDER_OPEN_PRICE 2 #define ORDER_LOTS 3 #define ORDER_SL 4 #define ORDER_TP 5 #define ORDER_PROFIT 6 #define ORDER_COMMISSION 7 #define ORDER_SWAP 8 //+----------------------------------------------------------------------------- //| External variables. These can be changed in the program properties window. //+----------------------------------------------------------------------------- /* * The number of bars for which the price should stay above (in an uptrend) or * below (in a downtrend) 10-day EMA to show a strong trend signal. */ extern int signalMinBars = 1; /* * The maximum distance (in points) between the price and its 10-day EMA that * warrants opening a new position. A trade opportunity to buy or sell comes * when the price falls to, or rises towards its 10-day EMA. */ extern int maxMADist = 20; /* * The depth of the support or resistance zone in points. It is taken into * account when determining whether the support or resistance is broken. */ extern int suppResDepth = 2; /* * The maximum number of open orders. Multiple orders are opened on different * days. */ extern int maxOpenOrders = 1; // slippage for new orders extern int slippage = 1; /* * TRUE if the amount of lots in order is fixed, FALSE if it is calculated as a * percentage of free margin. */ extern bool lotsFixed = TRUE; // percentage of free margin to calculate amount of lots for new orders extern double lotMarginPC = 2; // amount of lots for new orders (it will be recalculated if not fixed) extern double lots = 1; //+----------------------------------------------------------------------------- //| Global variables. //+----------------------------------------------------------------------------- /* * An array that contains information about specific characteristics of all * market orders available at the current moment. The array is resized * dynamically to fit available orders and updated on every tick. Only orders * opened by this EA are accounted for. */ double openOrders[][9]; // the number of currently open market orders int openOrderCount = 0; /* * Stop loss levels for opening long and short positions (calculated * dynamically). */ double stopLossLong = 0, stopLossShort = 0; // actual freeze level in points (calculated dynamically) int actualFL = 0; // a flag used to prevent opening more than one order for the same bar static bool tradeOpenForBar = false; //+----------------------------------------------------------------------------- //| EA initialization special function. //+----------------------------------------------------------------------------- int init() { // resize the order array to accomodate the maximum number of open orders ArrayResize(openOrders, maxOpenOrders); return(0); } //+----------------------------------------------------------------------------- //| EA deinitialization special function. //+----------------------------------------------------------------------------- int deinit() { return(0); } //+----------------------------------------------------------------------------- //| EA start special function. //+----------------------------------------------------------------------------- int start() { // order accounting - exit in case of any errors if (!performOrderAccounting()) { return(0); } // data reduction if (!performDataReduction()) { return(0); } // if a new bar has been formed trail SL levels of open orders if (isNewBar()) { tradeOpenForBar = false; updateStopLossLevels(); } if (!tradeOpenForBar) { // calculate trading levels for new orders calculateTradingLevels(); // calculate position size for new orders calculateOrderLots(); // calculate a trade signal and act on it trade(calculateTradeSignal()); } return(0); } //+----------------------------------------------------------------------------- //| Data reduction function. It returns TRUE if the current tick should be //| processed, otherwise FALSE. //+----------------------------------------------------------------------------- bool performDataReduction() { // the indicator can be used on a daily timeframe only if (Period() != PERIOD_D1) { return(FALSE); } if (IsTradeContextBusy()) { return(FALSE); } // process the tick as long as there are enough bars to calculate EMA(200) return(Bars >= 200); } //+----------------------------------------------------------------------------- //| Order accounting function. It populates the global array "openOrders" with //| characteristics of all currently open (by this EA) market orders and returns //| TRUE if there were no errors while accounting orders, otherwise FALSE. The //| number of market orders is stored in the global variable "openOrderCount". //+----------------------------------------------------------------------------- bool performOrderAccounting() { int ordersTotal = OrdersTotal(); openOrderCount = 0; int j = 0; for (int i = 0; (i < ordersTotal) && (j < maxOpenOrders); i++) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if ((OrderSymbol() == Symbol()) && (OrderMagicNumber() == ORDER_MAGIC_NUMBER) && (OrderCloseTime() == 0) && ((OrderType() == OP_BUY) || (OrderType() == OP_SELL))) { openOrders[j][ORDER_TICKET] = OrderTicket(); openOrders[j][ORDER_TYPE] = OrderType(); openOrders[j][ORDER_OPEN_PRICE] = normalize(OrderOpenPrice()); openOrders[j][ORDER_LOTS] = OrderLots(); openOrders[j][ORDER_SL] = normalize(OrderStopLoss()); openOrders[j][ORDER_TP] = normalize(OrderTakeProfit()); openOrders[j][ORDER_PROFIT] = OrderProfit(); openOrders[j][ORDER_COMMISSION] = OrderCommission(); openOrders[j][ORDER_SWAP] = OrderSwap(); openOrderCount++; j++; } } else { // an error occured return(FALSE); } } return(TRUE); } //+----------------------------------------------------------------------------- //| Calculates trading levels for new orders and stores the result in global //| variables. //+----------------------------------------------------------------------------- void calculateTradingLevels() { actualFL = MarketInfo(Symbol(), MODE_FREEZELEVEL); double ema10 = iMA(NULL, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 0); double stopLossDist = iATR(NULL, 0, 14, 0) / 2; stopLossLong = adjustOrderLevel(ema10 - stopLossDist, true); stopLossShort = adjustOrderLevel(ema10 + stopLossDist, false); } //+----------------------------------------------------------------------------- //| Updates SL levels of open orders. //+----------------------------------------------------------------------------- void updateStopLossLevels() { for (int i = 0; i < openOrderCount; i++) { int ticket = openOrders[i][ORDER_TICKET]; int type = openOrders[i][ORDER_TYPE]; double openPrice = openOrders[i][ORDER_OPEN_PRICE]; double lots = openOrders[i][ORDER_LOTS]; double stopLoss = openOrders[i][ORDER_SL]; double takeProfit = openOrders[i][ORDER_TP]; double profit = openOrders[i][ORDER_PROFIT]; double commission = openOrders[i][ORDER_COMMISSION]; double swap = openOrders[i][ORDER_SWAP]; bool modifyOrder = FALSE; /* * Calculate a new stop loss level based on the previously closed bar. * Stop loss level is only updated in the direction of the open * position. It is never lowered for a long position or raised for a * short position. */ double ema10 = iMA(NULL, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 1); double stopLossDist = iATR(NULL, 0, 14, 1) / 2; double newStopLoss; switch(type) { case OP_BUY: newStopLoss = adjustOrderLevel(ema10 - stopLossDist, true); if (newStopLoss > stopLoss) { stopLoss = newStopLoss; modifyOrder = TRUE; } break; case OP_SELL: newStopLoss = adjustOrderLevel(ema10 + stopLossDist, false); if (newStopLoss < stopLoss) { stopLoss = newStopLoss; modifyOrder = TRUE; } break; default: return; } if (modifyOrder) { OrderModify(ticket, openPrice, stopLoss, takeProfit, 0); } } } //+----------------------------------------------------------------------------- //| Calculates the amount of lots for new orders or 0 if there is not enough //| free margin to open a position. Amount of lots is either fixed or calculated //| as a percentage of free margin. In both cases the final amount of lots is //| adjusted for conditions (restrictions) set by the broker. The result is //| stored in the global variable "lots". //+----------------------------------------------------------------------------- void calculateOrderLots() { double minLot = MarketInfo(Symbol(), MODE_MINLOT); double maxLot = MarketInfo(Symbol(), MODE_MAXLOT); double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP); double lotPrice = MarketInfo(Symbol(), MODE_MARGINREQUIRED); double freeMargin = AccountFreeMargin(); if (!lotsFixed) { lots = (freeMargin * lotMarginPC / 100) / lotPrice; lots = MathFloor(lots / lotStep) * lotStep; } if (lots < minLot) { lots = minLot; } else if (lots > maxLot) { lots = maxLot; } if (lots * lotPrice > freeMargin) { lots = 0; } } //+----------------------------------------------------------------------------- //| Defines trading criteria and calculates a trade signal. It returns one of //| the predefined trade operation types or TRADE_OP_NONE indicating no trade //| signal. //+----------------------------------------------------------------------------- int calculateTradeSignal() { double ema10 = iMA(NULL, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 0); double ema20 = iMA(NULL, 0, 20, 0, MODE_EMA, PRICE_CLOSE, 0); double ema50 = iMA(NULL, 0, 50, 0, MODE_EMA, PRICE_CLOSE, 0); double ema200 = iMA(NULL, 0, 200, 0, MODE_EMA, PRICE_CLOSE, 0); // don't trade if the freeze level is > 0 if (actualFL > 0) { return(TRADE_OP_NONE); } // don't open any new orders if the maximum is reached if (openOrderCount == maxOpenOrders) { return(TRADE_OP_NONE); } int i; // if moving averages are aligned in the proper order for an uptrend if ((ema10 > ema20) && (ema20 > ema50) && (ema50 > ema200)) { /* * Check if the price bounced off its 10-day EMA (the lowest price of * the bar must be close enough to the 10-day EMA). */ if (Low[0] - normalize(ema10) > maxMADist * Point) { return(TRADE_OP_NONE); } /* * An additional filter to make sure it is a strong uptrend. The 10-day * EMA must act as the support for a period of at least "signalMinBars" * bars. */ for (i = 0; i < signalMinBars; i++) { // if the support zone is broken if (Low[i] + (suppResDepth * Point) <= iMA(NULL, 0, 10, 0, MODE_EMA, PRICE_CLOSE, i)) { return(TRADE_OP_NONE); } } return(TRADE_OP_OPEN_BUY); } // if moving averages are aligned in the proper order for a downtrend else if ((ema10 < ema20) && (ema20 < ema50) && (ema50 < ema200)) { /* * Check if the price bounced off its 10-day EMA (the highest price of * the bar must be close enough to the 10-day EMA). */ if (normalize(ema10) - High[0] > maxMADist * Point) { return(TRADE_OP_NONE); } /* * An additional filter to make sure it is a strong downtrend. The * 10-day EMA must act as the resistance for a period of at least * "signalMinBars" bars. */ for (i = 0; i < signalMinBars; i++) { // if the resistance zone is broken if (High[i] - (suppResDepth * Point) >= iMA(NULL, 0, 10, 0, MODE_EMA, PRICE_CLOSE, i)) { return(TRADE_OP_NONE); } } return(TRADE_OP_OPEN_SELL); } return(TRADE_OP_NONE); } //+----------------------------------------------------------------------------- //| Realizes the trading strategy given one of the predefined trade operation //| types. It updates the global variable "tradeOpenForBar" if a position is //| opened successfully. //+----------------------------------------------------------------------------- void trade(int tradeOp) { switch(tradeOp) { case TRADE_OP_OPEN_BUY: // if there is enough money if (lots > 0) { if (OrderSend(Symbol(), OP_BUY, lots, Ask, slippage, stopLossLong, 0, "Comment", ORDER_MAGIC_NUMBER, 0, Green) != -1) { tradeOpenForBar = true; } } break; case TRADE_OP_OPEN_SELL: // if there is enough money if (lots > 0) { if (OrderSend(Symbol(), OP_SELL, lots, Bid, slippage, stopLossShort, 0, "Comment", ORDER_MAGIC_NUMBER, 0, Red) != -1) { tradeOpenForBar = true; } } break; default: break; } } //+----------------------------------------------------------------------------- //| Returns TRUE if a new bar has been formed, otherwise FALSE. //+----------------------------------------------------------------------------- bool isNewBar() { static datetime lastTime = 0; if (Time[0] != lastTime) { lastTime = Time[0]; return(TRUE); } return(FALSE); } //+----------------------------------------------------------------------------- //| Normalizes the given floating point value to the precision corresponding //| to the number of digits after decimal point for the current symbol prices //| and returns the result. //+----------------------------------------------------------------------------- double normalize(double value) { return(NormalizeDouble(value, Digits)); } //+----------------------------------------------------------------------------- //| Adjusts the given stop loss or take profit level for the minimum distance //| from the market price allowed by the broker. Returns a new (normalized) //| level according to the position type (long or short). //+----------------------------------------------------------------------------- double adjustOrderLevel(double orderLevel, bool longPosition) { int minDist = MarketInfo(Symbol(), MODE_STOPLEVEL); double result; if (longPosition) { result = MathMin(orderLevel, Bid - (minDist * Point)); } else { result = MathMax(orderLevel, Ask + (minDist * Point)); } return(normalize(result)); }