//============================================================================= // LibDerksUtils.mq4 // // Copyright © 2007, Derk Wehler // derkwehler@gmail.com // // *************************************************************************** // *************************************************************************** // LICENSING: This is free, open source software, licensed under // Version 2 of the GNU General Public License (GPL). // // In particular, this means that distribution of this software in a binary // format, e.g. as compiled in as part of a .ex4 format, must be accompanied // by the non-obfuscated source code of both this file, AND the .mq4 source // files which it is compiled with, or you must make such files available at // no charge to binary recipients. If you do not agree with such terms you // must not use this code. Detailed terms of the GPL are widely available // on the Internet. The Library GPL (LGPL) was intentionally not used, // therefore the source code of files which link to this are subject to // terms of the GPL if binaries made from them are publicly distributed or // sold. // // ANY USE OF THIS CODE NOT CONFORMING TO THIS LICENSE MUST FIRST RECEIVE // PRIOR AUTHORIZATION FROM THE AUTHOR(S). ANY COMMERCIAL USE MUST FIRST // OBTAIN A COMMERCIAL LICENSE FROM THE AUTHOR(S). // // Copyright (2007), Derk Wehler, derkwehler@gmail.com // *************************************************************************** // *************************************************************************** // //============================================================================= //| $Workfile:: LibDerksUtils.mq4 $| //| $Revision:: 11 $| //| $Author:: Derk $| //| $Date:: 8/01/07 6:21p $| //+------------------------------------------------------------------+ #property copyright "Copyright © 2007, Derk Wehler" #property link "http://www.ArrogantFxBastards.com" #property library // DerksUtils included only for #defines at top #include #include int StaticBars = -1; bool IsDualWinCorrectional = false; int DU_ErrorLevel = 3; string DU_Fname = "N/A"; string DU_Version = "9"; //---------------------------------------------------------------------------- // Money Management Helper Functions //---------------------------------------------------------------------------- //============================================================================= // // PURPOSE: // Using information about this account acquired from MarketInfo() calls, // and given the amount to risk, calculate how many lots to risk. // // PARAMETERS: // TradeSizePercent: The ticket number of the order that starts this run // MinLots: The base number of lots (minimum to use) // MaxLots: The max number of lots the caller will allow // // RETURN VALUE: // lots: The number of lots to use in the trade // // SAMPLE: // double lotMM = Lots; // if (MoneyManagement) // lotMM = CalcLotsMM(TradeSizePercent, Lots, MaxLots); // //============================================================================= double CalcLotsMM(double TradeSizePercent, double MinLots, double MaxLots) { double lots; DU_Fname = "CalcLotsMM"; // Turn percent into an actual decimal number TradeSizePercent /= 100; // Get various attributes of this account double minLot = MarketInfo(Symbol(), MODE_MINLOT); double maxLot = MarketInfo(Symbol(), MODE_MAXLOT); double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP); double lotSize = MarketInfo(Symbol(), MODE_LOTSIZE); double margReq = MarketInfo(Symbol(), MODE_MARGINREQUIRED); // Get the amount of money available in this account double freeMargin = AccountFreeMargin(); DU_Log(5, "CalcLotsMM: Free Margin is " + freeMargin); // Get the account leverage int leverage = AccountLeverage(); DU_Log(5, "CalcLotsMM: Leverage is " + leverage); // Multiply the amount of free margin we have times the risk percentage double riskAmount = freeMargin * TradeSizePercent; DU_Log(5, "CalcLotsMM: We are willing to risk " + riskAmount); // Calculate the cost of one lot (leverage dependent) double lotCost = lotSize / leverage; DU_Log(5, "CalcLotsMM: Cost of one lot is " + lotCost); lots = NormalizeDouble((riskAmount / lotCost), GetLotDigits()); // DU_Log(5, "CalcLotsMM: ...which is " + lots + " lots (BEFORE min/max calcs)"); // Print section for debugging or just to see values DU_Log(5, "INPUTS: TradeSizePercent = " + TradeSizePercent + " MinLots = " + MinLots + " MaxLots = " + MaxLots); DU_Log(5, "MODE_MINLOT = " + minLot); DU_Log(5, "MODE_MAXLOT = " + maxLot); DU_Log(5, "MODE_LOTSTEP = " + lotStep); DU_Log(5, "MODE_LOTSIZE = " + lotSize); DU_Log(5, "MODE_MARGINREQUIRED = " + margReq); DU_Log(5, "freeMargin = " + freeMargin); DU_Log(5, "leverage = " + leverage); DU_Log(5, "riskAmount = " + riskAmount); DU_Log(5, "lotCost (at " + leverage + ":1) = " + lotCost); DU_Log(5, "digits = " + Digits); DU_Log(5, "Normalized lots = " + lots); // Check against broker- and caller-defined mins and maxs lots = MathMin(maxLot, MathMin(lots, MaxLots)); lots = MathMax(minLot, MathMax(lots, MinLots)); DU_Log(5, "CalcLotsMM: With min and max, we end up with " + lots + " lots"); return(lots); } //============================================================================= // // PURPOSE: // Using information about this account acquired from MarketInfo() calls, // and given the amount to risk, calculate how many lots to risk. // // PARAMETERS: // RiskPercent: The ticket number of the order that starts this run // MinLots: The base number of lots (minimum to use) // MaxLots: The max number of lots the caller will allow // StopLoss: The SL number of pips for this trade // // RETURN VALUE: // lots: The number of lots to use in the trade // // SAMPLE: // double lotMM = Lots; // if (MoneyManagement) // lotMM = CalcLotsGivenRisk(RiskPercent, Lots, MaxLots, StopLoss); // //============================================================================= double CalcLotsGivenRisk(double RiskPercent, double MinLots, double MaxLots, int SL_Pips) { double lots; DU_Fname = "CalcLotsGivenRisk"; if (SL_Pips == 0) { DU_Log(5, "CalcLotsGivenRisk: SL_Pips == 0; returning CalcLotsMM"); return(CalcLotsMM(RiskPercent, MinLots, MaxLots)); } // Turn percent into an actual decimal number RiskPercent /= 100; // Get various attributes of this account double minLot = MarketInfo(Symbol(), MODE_MINLOT); double maxLot = MarketInfo(Symbol(), MODE_MAXLOT); double lotSize = MarketInfo(Symbol(), MODE_LOTSIZE); // Get the value of one pip (per full (1.0) lot) double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE); if (tickValue == 0) { DU_Log(5, "CalcLotsGivenRisk: tickValue == 0; returning MinLots ("+ MinLots+ ")"); return(MinLots); } // Get the amount of money available in this account double freeMargin = AccountFreeMargin(); DU_Log(5, "CalcLotsGivenRisk: We have " + freeMargin + " of free margin"); // Multiply the amount of free margin we have times the risk percentage double riskAmount = freeMargin * RiskPercent; DU_Log(5, "CalcLotsGivenRisk: We are willing to risk " + riskAmount); // Calculate the amount per pip for our max risk double amtPerPip = riskAmount / SL_Pips; DU_Log(5, "CalcLotsGivenRisk: With a " + SL_Pips + " pip stoploss, at $" + tickValue + " per tick (at 1 full contract)..."); // Calculate number of pips to all double lotEst = amtPerPip / tickValue; DU_Log(5, "CalcLotsGivenRisk: Min Lot is " + minLot + ", so we can bet " + lotEst + " lots"); lots = NormalizeDouble(lotEst, GetLotDigits()); // Check against broker- and caller-defined mins and maxs lots = MathMin(maxLot, MathMin(lots, MaxLots)); lots = MathMax(minLot, MathMax(lots, MinLots)); DU_Log(5, "CalcLotsGivenRisk: With min and max + we end up with " + lots + " lots"); return(lots); } //============================================================================= // // PURPOSE: // Used for calculating the number Lots with the Price per lot is // known and Fixed independent of leverage // // PARAMETERS: // TradeSizePercent: The ticket number of the order that starts this run // MinLots: The base number of lots (minimum to use) // MaxLots: The max number of lots the caller will allow // FixedPricePerLot: The SL number of pips for this trade // // RETURN VALUE: // lots: The number of lots to use in the trade // //============================================================================= double CalcLotsFixedLotCost(double TradeSizePercent, double MinLots, double MaxLots, double FixedPricePerLot) { double lots; double minLot = MarketInfo(Symbol(), MODE_MINLOT); double maxLot = MarketInfo(Symbol(), MODE_MAXLOT); DU_Fname = "CalcLotsFixedLotCost"; if (FixedPricePerLot == 0) { DU_Log(5, "CalcLotsFixedLotCost: FixedPricePerLot == 0; returning CalcLotsMM"); return(CalcLotsMM(TradeSizePercent, MinLots, MaxLots)); } TradeSizePercent /= 100; double freeMargin = AccountFreeMargin(); DU_Log(5, "CalcLotsFixedLotCost: Free Margin is " + freeMargin); // Multiply the amount of free margin we have times the risk percentage // Note that we don't have a SL so we are actually risking the entire freeMargin double TradeAmount = freeMargin * TradeSizePercent; DU_Log(5, "CalcLotsFixedLotCost: We are willing to risk " + TradeAmount); lots = NormalizeDouble((TradeAmount / FixedPricePerLot), GetLotDigits()); DU_Log(5, "CalcLotsFixedLotCost: FixedPricePerLot is " + FixedPricePerLot); lots = MathMin(maxLot, MathMin(lots, MaxLots)); lots = MathMax(minLot, MathMax(lots, MinLots)); DU_Log(5, "CalcLotsFixedLotCost: With min and max, we end up with " + lots + " lots"); return(lots); } //============================================================================= // // PURPOSE: // Given magic number, determine the lot amount to use for the // "Dual Win" MM system. // // PARAMETERS: // begLots: The base number of lots to start with // magic: The Magic number the calling EA uses // TP_Pips: The TakeProfit setting the calling EA is using // SL_Pips: The StopLoss setting the calling EA is using // // RETURN VALUE: // lots: The number of lots to use in the trade // //============================================================================= double GetDualWinLots(double begLots, int magic, int TP_Pips, int SL_Pips) { datetime date[20]; double profit[20]; double lots[20]; double TP[20]; double SL[20]; double SumSL; double SumTP; datetime tmpD; double tmpP; double tmpL; double ordersSL; double Sum, SumModified; int cnt, i, j, n; int ordersTotal = OrdersHistoryTotal(); DU_Fname = "GetDualWinLots"; // Set this global that EAs might reference it's value. IsDualWinCorrectional = false; // If there have been no trades, return the minimum if (ordersTotal == 0) return(begLots); // Resize arrays to hold all orders present // WARNING: "OrdersHistoryTotal" only gives you the // orders that are SHOWING in the Account History tab!! ArrayResize(date, ordersTotal); ArrayResize(profit, ordersTotal+1); ArrayResize(lots, ordersTotal); ArrayResize(TP, ordersTotal); ArrayResize(SL, ordersTotal); // Gather info about historical trades for (i=0, cnt=ordersTotal-1; cnt >= 0; cnt--) { OrderSelect(cnt, SELECT_BY_POS, MODE_HISTORY); if (OrderSymbol() != Symbol() || (OrderMagicNumber() != magic && magic != 0)) continue; ordersSL = MathAbs(OrderOpenPrice() - OrderStopLoss()) / Point; // if (ordersSL != SL_Pips) if (MathAbs(ordersSL - SL_Pips) > 1) { DU_Log(5, ".^.^.^.^.^.^.^. GetDualWinLots, MathAbs(ordersSL - SL_Pips) = " + MathAbs(ordersSL - SL_Pips)); DU_Log(5, ".^.^.^.^.^.^.^. GetDualWinLots, skipping order #" + OrderTicket() + "; SL was " + ordersSL + " pips. SL_Pips = " + SL_Pips); continue; } date[i] = OrderCloseTime(); profit[i] = OrderProfit(); lots[i] = OrderLots(); i++; } // set n to the last good index n = i - 1; DU_Log(5, ".^.^.^.^.^.^.^. GetDualWinLots, i = " + i + ", n = " + n); // Sort the arrays by close date (bubble sort) for (i=0; i < n-1; i++) { for (j=0; j < n-1-i; j++) { if (date[j+1] > date[j]) { tmpD = date[j]; date[j] = date[j+1]; date[j+1] = tmpD; tmpP = profit[j]; profit[j] = profit[j+1]; profit[j+1] = tmpP; tmpL = lots[j]; lots[j] = lots[j+1]; lots[j+1] = tmpL; } } } // Now the orders are sorted, most recent [0] to least recent [n-1]... for (i=0; i <= n; i++) { if (profit[i] < 0) DU_Log(5, ".^.^.^.^.^.^.^. Closed date: " + date[i] + "...had loss of: $" + profit[i]); else DU_Log(5, ".^.^.^.^.^.^.^. Closed date: " + date[i] + "...had profit of: $" + profit[i]); } // Set a phantom trade profit to zero so we have one extra for loop below profit[i] = 0; // If the last trade was a loss, then trade the same lots if (profit[0] < 0) { DU_Log(5, ".^.^.^.^.^.^.^. GetDualWinLots: Added up; first one back was loss; betting same: " + lots[0] + " lots."); return (lots[0]); } // If the last trade was a winner then check if it was preceeded by // loser(s) and adjust lot value if so....... // Two methods here. The first: // If we do not know the TP value for this EA, then we need to just sum up the // lots of the loser trades. if (TP_Pips == 0) { // If we got here, then the most recent trade was a win, so // count back to the previous win before that, summing the lots Sum = 0; for (i=0; i <= n && !(profit[i] >= 0 && profit[i+1] >= 0); i++) { // Skip trades that were not negative if (profit[i] >= 0) continue; IsDualWinCorrectional = true; Sum += lots[i]; // Calculate the sum of the SL's and TP's SumSL += SL[i]; SumTP += TP[i]; } // Calc the modified sum of lots (e.g. if ave SL // was 80, and ave TP was 20, we need to multiply lots by 4) if (SumTP == 0 || SumSL == 0) { DU_Log(5, ".^.^.^.^.^.^.^. TakeProfit sum is zero (or SL sum is 0); cannot divide by it... Using Sum(" + Sum + ")"); SumModified = NormalizeDouble(Sum, GetLotDigits()); } else { SumModified = NormalizeDouble(Sum * SumSL / SumTP, GetLotDigits()); } DU_Log(5, ".^.^.^.^.^.^.^. GetDualWinLots (TP_Pips == 0): Added up " + (i - 1) + " trades; Sum is " + Sum + "; After SL/TP factor, returning sum of: " + SumModified); } // The second: // Sum up the dollar amounts of the past string of losers and divide that // by the TakeProfit pips, to get the amount per pip we need to make in // order to make back all the money. Figure lots from this. else { // If we got here, then the most recent trade was a win, so // count back to the previous win before that, summing the monetary losses Sum = 0; for (i=0; i <= n && !(profit[i] >= 0 && profit[i+1] >= 0); i++) { DU_Log(5, ".^.^.^.^.^.^.^. GetDualWinLots (TP_Pips != 0): Counting up losers: i = " + i + "; profit[i] = " + profit[i]); // Skip trades that were not negative if (profit[i] >= 0) continue; IsDualWinCorrectional = true; Sum += (profit[i] * (-1)); } // Calc the value if 1 pip with 1 contract double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE); // Calc the modified sum of lots (e.g. if ave SL // was 80, and ave TP was 20, we need to multiply lots by 4) SumModified = NormalizeDouble((Sum / TP_Pips) / tickValue, GetLotDigits()); DU_Log(5, ".^.^.^.^.^.^.^. GetDualWinLots (TP_Pips != 0): Added up " + (i-1) + " trades; Sum of losses was " + Sum + "; After TP factor, returning sum of: " + SumModified); } return (MathMax(SumModified, begLots)); } int GetLotDigits() { // Find out how many decimal places in the smallest lot size // e.g. if 0.1, then digits is 1; if 0.01, digits is 2 int digits = 0; double minLot = MarketInfo(Symbol(), MODE_MINLOT); while (minLot < 1.0) { minLot *= 10; digits++; } return(digits); } void DU_Log(int level, string s) { // Print to log prepended with stuff; // Print("IN DU_Log: DU_ErrorLevel = " + DU_ErrorLevel + " level = " + level + " string = " + s); if (!IsOptimization()) { if (DU_ErrorLevel >= level) Print(DU_Fname + " " + DU_Version + ": " + s); } } // Use level = 5 to Print all MM-Related Messages // Use level = 0 to Print nothing void DU_SetErrorLevel(int level) { DU_ErrorLevel = level; } //============================================================================= // // PURPOSE: // Function for EA to call to see if last lot value returned by // call to GetDualWinLots was indeed increased to make up for // prior losing trades // // RETURN VALUE: // IsDualWinCorrectional ("static" global) // //============================================================================= bool IsMakeUpTrade() { return (IsDualWinCorrectional); } //---------------------------------------------------------------------------- // Conversion/other Helper Functions //---------------------------------------------------------------------------- int TimeFrameConst2Val(int TF_In_Minutes) { switch(TF_In_Minutes) { case 1: return(1); // M1 case 5: return(2); // M5 case 15: return(3); // M15 case 30: return(4); // M30 case 60: return(5); // H1 case 240: return(6); // H4 case 1440: return(7); // D1 case 10080: return(8); // W1 case 43200: return(9); // MN } } string TimeFrameVal2String(int TF_val) { switch(TF_val) { case 1: return("M1"); case 2: return("M5"); case 3: return("M15"); case 4: return("M30"); case 5: return("H1"); case 6: return("H4"); case 7: return("D1"); case 8: return("W1"); case 9: return("MN"); } } int SymbolConst2Val(string symbol) { // Handle problem of trailing chars on mini accounts. string mySymbol = StringSubstr(symbol,0,6); if (mySymbol == "AUDCAD") return(1); if (mySymbol == "AUDJPY") return(2); if (mySymbol == "AUDNZD") return(3); if (mySymbol == "AUDUSD") return(4); if (mySymbol == "CHFJPY") return(5); if (mySymbol == "EURAUD") return(6); if (mySymbol == "EURCAD") return(7); if (mySymbol == "EURCHF") return(8); if (mySymbol == "EURGBP") return(9); if (mySymbol == "EURJPY") return(10); if (mySymbol == "EURUSD") return(11); if (mySymbol == "GBPCHF") return(12); if (mySymbol == "GBPJPY") return(13); if (mySymbol == "GBPUSD") return(14); if (mySymbol == "NZDUSD") return(15); if (mySymbol == "USDCAD") return(16); if (mySymbol == "USDCHF") return(17); if (mySymbol == "USDJPY") return(18); return(19); } string OrderType2String(int type) { if (type == OP_BUY) return("BUY"); if (type == OP_SELL) return("SELL"); if (type == OP_BUYSTOP) return("BUY STOP"); if (type == OP_SELLSTOP) return("SELL STOP"); if (type == OP_BUYLIMIT) return("BUY LIMIT"); if (type == OP_SELLLIMIT) return("SELL LIMIT"); if (type == ALL_PENDING_BUYS) return("ALL PENDING BUYS"); if (type == ALL_PENDING_SELLS) return("ALL PENDING SELLS"); if (type == ALL_PENDING) return("ALL OPEN OR ALL PENDING"); } int OrderString2Num(string type) { if (type == "BUY" || type == "buy" || type == "Buy") return(OP_BUY); else if (type == "SELL" || type == "sell" || type == "Sell") return(OP_SELL); else if (type == "BUYSTOP" || type == "buystop" || type == "BuyStop") return(OP_BUYSTOP); else if (type == "SELLSTOP" || type == "sellstop" || type == "SellStop" || type == "SELL STOP" || type == "sell stop" || type == "Sell Stop") return(OP_SELLSTOP); else if (type == "BUYLIMIT" || type == "buylimit" || type == "BuyLimit" || type == "BUY LIMIT" || type == "buy limit" || type == "Buy Limit") return(OP_BUYLIMIT); else if (type == "SELLLIMIT" || type == "selllimit" || type == "SellLimit" || type == "SELL LIMIT" || type == "sell limit" || type == "Sell Limit") return(OP_SELLLIMIT); } string GetBoolText(bool value) { if (value == true) return("True"); return("False"); } //---------------------------------------------------------------------------- // Trading Functions //---------------------------------------------------------------------------- //============================================================================= // // PURPOSE: // To discern whether a new candle has *just* formed. // // NOTE: This function does not perform any magic; it has to be called // eack tick (or literally, has to be called at least once each candle) // in order to fuction properly. // ALSO: It loses it's state once it is called so you cannot call it twice per tick // // RETURN VALUE: // TRUE: This is the first tick of a new candle // FALSE: This is NOT a new canldle // //============================================================================= bool IsNewCandle() { if (Bars == StaticBars) return (false); StaticBars = Bars; return (true); } //============================================================================= // // PURPOSE: // Return the number of open positions (buy, sell, or both) // // PARAMETERS: // magic: The magic number to match // dir: The direction sought; either // OP_BOTH (-1) for both // -or- // OP_BUY for ALL buys // -or- // OP_SELL for ALL sells // // RETURN VALUE: // The number of open orders as requested // //============================================================================= int NumOpenPositions(int magic, int dir) { int cnt; int NumBuyTrades, NumSellTrades; // Number of buy and sell trades in this symbol NumBuyTrades = 0; NumSellTrades = 0; for (cnt=OrdersTotal()-1; cnt >= 0; cnt--) { OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES); if (OrderSymbol() != Symbol() || (OrderMagicNumber() != magic && magic != 0)) continue; // We only want open orders; if we wanted pending too, we could use OrdersTotal() if (OrderType() != OP_BUY && OrderType() != OP_SELL) continue; if (OrderType() == OP_BUY) NumBuyTrades++; else if (OrderType() == OP_SELL) NumSellTrades++; } if (dir == OP_BOTH) return (NumBuyTrades + NumSellTrades); else if (dir == OP_BUY) return (NumBuyTrades); else if (dir == OP_SELL) return (NumSellTrades); } //============================================================================= // // PURPOSE: // Return the number of pending orders (buy, sell, or both) // // PARAMETERS: // magic: The magic number to match // dir: The direction sought; either // ALL_PENDING (-1) for all: // -or- // ALL_PENDING_BUYS (-2) for all pending Buy orders: // -or- // ALL_PENDING_SELLS (-3) for all pending Sell orders: // -or- // OP_BUYSTOP for all pending Buy Stops // -or- // OP_BUYLIMIT for all pending Buy Limits // -or- // OP_SELLSTOP for all pending Sell Stops // -or- // OP_SELLLIMIT for all pending Sell Limits // // RETURN VALUE: // The number of pending orders as requested // //============================================================================= int NumPendingOrders(int magic, int dir) { int cnt; int NumBuyStops = 0; int NumSellStops = 0; int NumBuyLimits = 0; int NumSellLimits = 0; for (cnt=OrdersTotal()-1; cnt >= 0; cnt--) { OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES); if (OrderSymbol() != Symbol() || OrderMagicNumber() != magic) continue; int type = OrderType(); if (type == OP_BUY || type == OP_SELL) continue; else if (type == OP_BUYSTOP) NumBuyStops++; else if (type == OP_SELLSTOP) NumSellStops++; else if (type == OP_BUYLIMIT) NumBuyLimits++; else if (type == OP_SELLLIMIT) NumSellLimits++; } if (dir == ALL_PENDING) return (NumBuyStops + NumSellStops + NumBuyLimits + NumSellLimits); else if (dir == ALL_PENDING_BUYS) return (NumBuyStops + NumBuyLimits); else if (dir == ALL_PENDING_SELLS) return (NumSellStops + NumSellLimits); else if (dir == OP_BUYSTOP) return (NumBuyStops); else if (dir == OP_BUYLIMIT) return (NumBuyLimits); else if (dir == OP_SELLSTOP) return (NumSellStops); else if (dir == OP_SELLLIMIT) return (NumSellLimits); } //============================================================================= // // PURPOSE: // Close all pending orders, with the following restrictions. // // PARAMETERS: // magic: The magic number to match // dir: The direction sought; either // ALL_PENDING (-1) for all: // -or- // ALL_PENDING_BUYS (-2) for all pending Buy orders: // -or- // ALL_PENDING_SELLS (-3) for all pending Sell orders: // -or- // OP_BUYSTOP for all pending Buy Stops // -or- // OP_BUYLIMIT for all pending Buy Limits // -or- // OP_SELLSTOP for all pending Sell Stops // -or- // OP_SELLLIMIT for all pending Sell Limits // // RETURN VALUE: // The number of orders deleted // //============================================================================= int ClosePendingOrders(int magic, int dir) { int retVal = 0; for (int cnt=OrdersTotal()-1; cnt >= 0; cnt--) { OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES); if (OrderSymbol() != Symbol() || OrderMagicNumber() != magic) continue; int type = OrderType(); if (type == OP_BUY || type == OP_SELL) continue; else if (type == OP_BUYSTOP && dir != OP_BUYSTOP && dir != ALL_PENDING_BUYS && dir != ALL_PENDING) continue; else if (type == OP_SELLSTOP && dir != OP_SELLSTOP && dir != ALL_PENDING_SELLS && dir != ALL_PENDING) continue; else if (type == OP_BUYLIMIT && dir != OP_BUYLIMIT && dir != ALL_PENDING_BUYS && dir != ALL_PENDING) continue; else if (type == OP_SELLLIMIT && dir != OP_SELLLIMIT && dir != ALL_PENDING_SELLS && dir != ALL_PENDING) continue; // So now we have filtered out the undesirables. // If we got this far, we need to delete the order bool ret = OrderDeleteReliable(OrderTicket()); if (ret) retVal++; } return (retVal); } //============================================================================= // // PURPOSE: // Close all pending orders, with the following restrictions. // // PARAMETERS: // symbol: The symbol to trade // buyPrice: The starting long price if doing BuyStops // sellPrice: The starting short price if doing SellStops // magic: The magic number to match // dir: The direction sought; either // OP_BUYSTOP to place pending Buy Stops // -or- // OP_SELLSTOP to place pending Sell Stops // -or- // OP_BOTH to place buys and sells // allSameSL: Use the same SL for all orders // allSameTP: Use the same TP for all orders // SL: The Stoploss to use (pips) // TP: The Takeprofit to use (pips) // qtyOrders: How many orders to place (in each direction) // distBetween: How many pips apart to place orders // lots: How many lots to trade per order // slippage: Sippage value to pass to order // comment: Comment string to pass to order // clr: Color var to pass to order // // RETURN VALUE: // The number of orders deleted // //============================================================================= int PlacePendingBreakouts( string symbol, double buyPrice, double sellPrice, int magic, int dir, bool allSameSL, bool allSameTP, int SL, int TP, int qtyOrders, int distBetween, double lots, int slippage, string comment, color clr) { int i; double price; double sl = 0; double tp = 0; int tkt, totalOrders = 0; color myClr = NULL; if (dir == OP_BUYSTOP || dir == OP_BOTH) { if (buyPrice == NULL) buyPrice = Ask; price = buyPrice; if (TP > 0) tp = price + (TP * Point); if (SL > 0) sl = price - (SL * Point); for (i=0; i < qtyOrders; i++) { if (!allSameTP && TP > 0) tp = price + (TP * Point); if (!allSameSL && SL > 0) sl = price - (SL * Point); if (clr == NULL) myClr = Blue; else myClr = clr; tkt = OrderSendReliable2Step(symbol, OP_BUYSTOP, lots, price, slippage, sl, tp, "PlacePendingBreakouts", magic, 0, myClr); if (tkt > 0) totalOrders++; else { Print("O.R. ERROR: ", OrderReliableErrTxt(OrderReliableLastErr())); } price += (distBetween * Point); } } sl = 0; tp = 0; if (dir == OP_SELLSTOP || dir == OP_BOTH) { if (sellPrice == NULL) sellPrice = Bid; price = sellPrice; if (TP > 0) tp = price - TP * Point; if (SL > 0) sl = price + SL * Point; for (i=0; i < qtyOrders; i++) { if (!allSameTP && TP > 0) tp = price - TP * Point; if (!allSameSL && SL > 0) sl = price + SL * Point; if (clr == NULL) myClr = Red; else myClr = clr; tkt = OrderSendReliable2Step(symbol, OP_SELLSTOP, lots, price, slippage, sl, tp, "PlacePendingBreakouts", magic, 0, myClr); if (tkt > 0) totalOrders++; else { Print("O.R. ERROR: ", OrderReliableErrTxt(OrderReliableLastErr())); } price -= (distBetween * Point); } } return(totalOrders); } //============================================================================= // // PURPOSE: // Given a matrix of orders (using arrays), loop through current open and // pending orders, placing any orders from the matrix not already there. // // PARAMETERS: // quantity: The number of orders in the matrix to be placed (we could // use the length of the arrays(s), but this way the caller // does not have to worry about resizing the arrays, or // initializing unused indices). // // symbol: The currency pair for which to place the trades // // dir: An array containing the order type to use for each entry // in the matrix (e.g. OP_BUY, OP_SELLSTOP) // // lots: The lot size to use for each order // // price: An array containing the order price for each entry in the // matrix // // slippage: The slippage value to send (used for all trades) // // sls: An array contianing the initial stop loss to set for each // entry in the matrix // // tps: An array contianing the initial take profit to set for // each entry in the matrix // // comment: The comment to send in with each order // // magic: The magic number to match // // orderPlaced: An array passed in which we modify so that the caller // can check to see which orders were placed. // // RETURN VALUE: // The number of orders successfully placed // //============================================================================= int ReplenishOrders( int quantity, string symbol, int dir[], double lots, double price[], int slippage, double sls[], double tps[], string comment, int magic, datetime expir, color arrowClr, bool& orderPlaced[]) { // Initialize the orderPlaced array to all true. Then loop through, // checking if each order already exists; if it does, set to false. // Then, for each of those still set to true, place the order. // If the order fails to be opened, set to false. Then when this // array is returned to the caller, the indicies still set to true // will be the orders successfully placed. for (int i=0; i < quantity; i++) orderPlaced[i] = true; // Print(" - - - - - - - - - - - ENTERING ReplenishOrders()"); // for (i=0; i < quantity; i++) // { // Print("Proposed Order #", i, ": dir = ", OrderType2String(dir[i]), " ; Price = ", price[i]); // } // Loop through every open order to see if the ones in the // matrix are already open or pending for (int cnt=OrdersTotal()-1; cnt >= 0; cnt--) { OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES); if (OrderSymbol() != Symbol() || OrderMagicNumber() != magic) { // Print("SYMBOL OR MAGIC DOES NOT MATCH... SKIPPING THIS OPEN ORDER"); continue; } // Get various info about this open order int type = OrderType(); double openPrice = OrderOpenPrice(); // Loop through each order in the matrix // Print(" Quantity ==== ", quantity); for (i=0; i < quantity; i++) { // Check if the order types match; i.e. if a Buy exists at a certain // price point, and an order in the matrix for that price is either // OP_BUY or OP_BUYSTOP or OP_BUYLIMIT, then we consider these the same, // and thus skip placing that order. NOTE: If a stop order already // exists at a given price, and the matrix has a limit order to place // at that price (or vice versa), the order will still be placed. string sDir = OrderType2String(dir[i]); string sType = OrderType2String(type); // Print("ReplenishOrders: OrderTypes ----> Existing: ", sType, ", Proposed: ", sDir); bool typesMatch = false; if (StringFind(sDir, sType) != -1 || StringFind(sType, sDir) != -1) typesMatch = true; // Print(" i = ", i, " ; --------> ReplenishOrders: OrderOpenPrice = ", openPrice, " price[", i, "] = ", price[i], " typesMatch = ", typesMatch); // if (MathFloor(openPrice / Point) == MathFloor(price[i] / Point)) // Print(" Comparing Open: ", MathFloor(openPrice / Point), " ..with New: ", MathFloor(price[i] / Point), " =====> PRICES MATCH!!"); // else // Print(" Comparing Open: ", MathFloor(openPrice / Point), " ..with New: ", MathFloor(price[i] / Point), " -----> PRICES DO NOT MATCH!!"); // check if this order is already placed if (typesMatch) { // In order to check if the price of the existing trade is the same as the // price of the proposed order, we had to do this kludge where I check if // the price is within 2 pips. This is because I witnessed this code // saying that two equal prices were not equal! I even tried converting // the prices to integers and it still judged them as not equal! // Then I tried checking if the difference was less than Point, and it // *still* failed. But (2*Point) seems to work. The downside of this // of course is that you cannot use this function to place orders within // 2 pips of each other. if (openPrice == price[i] || (openPrice > price[i] && openPrice - price[i] < 2*Point) || (openPrice < price[i] && price[i] - openPrice < 2*Point)) { // Print(" i = ", i, " ; Comparing Open: ", MathFloor(openPrice / Point), " ..with New: ", MathFloor(price[i] / Point), " -----> PRICES MATCH!!"); orderPlaced[i] = false; // Print("ReplenishOrders: Nixing order #", i, ", price = ", price[i]); } } // Print("- - - - - - - - - - - - - - - - - - - - - -"); } // Print("- <> - <> - <> - <> - <> - <> - <> - <> - <> - <> - <> - <> - <> - <> - <> - <> - <> -"); } string outStr = "Nix\'d Orders "; for (i=0; i < quantity; i++) { if (orderPlaced[i] == false) outStr = outStr + "#" + DoubleToStr(i, 0) + ", "; } // Print(outStr); // Get the minimum number of pips from price an order can be placed int minDist = MarketInfo(Symbol(), MODE_STOPLEVEL); int numPlaced = 0; // Now we know which orders NOT to place, because they are already active; // we have these marked in the orderPlaced[] array as false for (i=0; i < quantity; i++) { // Skip if an order already exists at this price if (orderPlaced[i] == false) continue; double curPrice = Ask; if (dir[i] == OP_SELL || dir[i] == OP_SELLSTOP || dir[i] == OP_SELLLIMIT) curPrice = Bid; // Check if this order is too close to the current price if (MathAbs(curPrice - price[i]) / Point < minDist) { Print(" PRICE TOO CLOSE TO PLACE ORDER. i = ", i, " ; OP = ", OrderType2String(dir[i]), " ; PRICE = ", price[i]); continue; } // If we got this far, time to place the order orderPlaced[i] = OrderSendReliable(symbol, dir[i], lots, price[i], slippage, sls[i], tps[i], comment, magic, expir, arrowClr); if (orderPlaced[i]) numPlaced++; } return (numPlaced); } //============================================================================= // // PURPOSE: // Loops through all open orders and adjust trailing stop accordingly // // // PARAMETERS: // // TrailType: The type of trailing stop to use: // // 1: Moves the stoploss without delay (pip for pip). // // 2: Waits for price to move the amount of the trailing stop // (TrailPips) before moving stop loss then moves like type 1. // The only difference between this and type 1 is that this // one will not initially move the SL until the it would be // set at breakeven. // // 3: Uses up to 3 levels for trailing stop // Level 1 Move stop to 1st level // Level 2 Move stop to 2nd level // Level 3 Trail like type 1 pip for pip // // 4: Ratchets the SL up: // e.g. if SL = 20, and open price is 1.2400, then // when price reaches 1.2420, SL is set to 1.2400; when // it gets to 1.2440, SL is moved to 1.2420, etc // // TrailPips: The trailing stop value in pips // // Magic: The magic number to check // // Dir: OP_BUY: Modify only OP_BUY orders // OP_SELL: Modify only OP_SELL orders // OP_BOTH: Modify both OP_BUY & OP_SELL orders // // // The remainder of the params are used only for TrailType 3: // // FirstMove: When the trade is in profit this much... // FirstStopLoss: Move SL this far from the current price // // SecondMove: When the trade is in profit this much... // SecondStopLoss: Move SL this far from the current price // // ThirdMove: When the trade is in profit this much... // TrailPips: Use this value & trail like TrailType 1 // // // RETURN VALUE: // // True: All OrderModify calls returned successfully // False: One or more calls failed // // // Calling examples: // // AdjTrailOnAllOrders( 3, 30, 19999, OP_SELL, 30, 20, 40, 30, 60); // AdjTrailOnAllOrders( 1, 25, MagicNumber, OP_BOTH, 0, 0, 0, 0, 0); // // NOTE: OP_BOTH is defined in LibDerkUtils.mqh, as well as some others // // For Copy & Paste usage: // AdjTrailOnAllOrders(type, pips, magic, dir, Mv1, SL1, Mv2, SL2, Mv3); //============================================================================= bool AdjTrailOnAllOrders( int TrailType, int TrailPips, int Magic, int Direction, int FirstMove, int FirstStopLoss, int SecondMove, int SecondStopLoss, int ThirdMove) { double SL = 0; double openPrice, curSL, curTP; bool retValue = true; int ticket; double dTrailVal = Point * TrailPips; double dFirstMove = Point * FirstMove; double dFirstStopLoss = Point * FirstStopLoss; double dSecondMove = Point * SecondMove; double dSecondStopLoss = Point * SecondStopLoss; double dThirdMove = Point * ThirdMove; for (int cnt=OrdersTotal()-1; cnt >= 0; cnt--) { OrderSelect(cnt, SELECT_BY_POS); int type = OrderType(); if (OrderSymbol() != Symbol() || OrderMagicNumber() != Magic || (type != OP_BUY && type != OP_SELL)) continue; curSL = OrderStopLoss(); curTP = OrderTakeProfit(); ticket = OrderTicket(); openPrice = OrderOpenPrice(); if (type == OP_BUY && (Direction == OP_BUY || Direction == OP_BOTH)) { switch (TrailType) { case 1: if (curSL < Bid - dTrailVal) // was: (Bid - curSL > dTrailVal), which is the same if (!OrderModifyReliable(ticket, openPrice, Bid - dTrailVal, curTP, 0, Aqua)) retValue = false; break; case 2: if (Bid - openPrice >= dTrailVal && (curSL < Bid - dTrailVal || curSL == 0)) if (!OrderModifyReliable(ticket, openPrice, Bid - dTrailVal, curTP, 0, Aqua)) { retValue = false; Comment("OrderModifyReliable Failed, Price = ", openPrice, " SL = ", Bid - dTrailVal); Print("OrderModifyReliable Failed, Price = ", openPrice, " SL = ", Bid - dTrailVal); } break; case 3: if (Bid - openPrice >= dFirstMove && dFirstMove != 0) { SL = openPrice + dFirstMove - dFirstStopLoss; if (curSL < SL) if (!OrderModifyReliable(ticket, openPrice, SL, curTP, 0, Aqua)) retValue = false; } if (Bid - openPrice >= dSecondMove && dSecondMove != 0) { SL = openPrice + dSecondMove - dSecondStopLoss; if (curSL < SL) if (!OrderModifyReliable(ticket, openPrice, SL, curTP, 0, Aqua)) retValue = false; } if (Bid - openPrice >= dThirdMove) { SL = Bid - dTrailVal; if (curSL < SL) if (!OrderModifyReliable(ticket, openPrice, SL, curTP, 0, Aqua)) retValue = false; } break; case 4: if (Bid - curSL >= dTrailVal * 2) if (!OrderModifyReliable(ticket, openPrice, Bid - dTrailVal, curTP, 0, Aqua)) retValue = false; break; } } if (type == OP_SELL && (Direction == OP_SELL || Direction == OP_BOTH)) { switch (TrailType) { case 1: if (curSL > Ask + dTrailVal) // was: (curSL - Ask > dTrailVal), which is the same if (!OrderModifyReliable(ticket, openPrice, Ask + dTrailVal, curTP, 0, Aqua)) retValue = false; break; case 2: if (openPrice - Ask >= dTrailVal && (curSL > Ask + dTrailVal || curSL == 0)) if (!OrderModifyReliable(ticket, openPrice, Ask + dTrailVal, curTP, 0, Aqua)) { retValue = false; Comment("OrderModifyReliable Failed, Price = ", openPrice, " SL = ", Ask + dTrailVal); Print("OrderModifyReliable Failed, Price = ", openPrice, " SL = ", Ask + dTrailVal); } break; case 3: if (openPrice - Ask >= dFirstMove && dFirstMove != 0) { SL = openPrice - dFirstMove + dFirstStopLoss; if (curSL > SL) if (!OrderModifyReliable(ticket, openPrice, SL, curTP, 0, Aqua)) retValue = false; } if (openPrice - Ask >= dSecondMove && dSecondMove != 0) { SL = openPrice - dSecondMove + dSecondStopLoss; if (curSL > SL) if (!OrderModifyReliable(ticket, openPrice, SL, curTP, 0, Aqua)) retValue = false; } if (openPrice - Ask >= dThirdMove) { SL = Ask + dTrailVal; if (curSL > SL) if (!OrderModifyReliable(ticket, openPrice, SL, curTP, 0, Aqua)) retValue = false; } break; case 4: if (curSL - Ask >= dTrailVal * 2) if (!OrderModifyReliable(ticket, openPrice, Ask + dTrailVal, curTP, 0, Aqua)) retValue = false; break; } } } return(retValue); } //============================================================================= // // PURPOSE: // Find the adjusted SL value to use for a given order. Unlike // AdjTrailOnAllOrders above, this is used by EAs that keep the // SL value internally. // // // PARAMETERS: // // Ticket: The ticket number of the order being modified // // TrailType: The type of trailing stop to use: // // 1: Moves the stoploss without delay (pip for pip). // // 2: Waits for price to move the amount of the trailing stop // (TrailPips) before moving stop loss then moves like type 1. // The only difference between this and type 1 is that this // one will not initially move the SL until the it would be // set at breakeven. // // 3: Uses up to 3 levels for trailing stop // Level 1 Move stop to 1st level // Level 2 Move stop to 2nd level // Level 3 Trail like type 1 pip for pip // // 4: Ratchets the SL up: // e.g. if SL = 20, and open price is 1.2400, then // when price reaches 1.2420, SL is set to 1.2400; when // it gets to 1.2440, SL is moved to 1.2420, etc // // TrailPips: The trailing stop value in pips // // // The remainder of the params are used only for TrailType 3: // // FirstMove: When the trade is in profit this much... // FirstStopLoss: Move SL this far from the current price // // SecondMove: When the trade is in profit this much... // SecondStopLoss: Move SL this far from the current price // // ThirdMove: When the trade is in profit this much... // TrailPips: Use this value & trail like TrailType 1 // // // RETURN VALUE: // The new price at which the internal SL value should be set // Returns zero if the SL does not need modification // // For Copy & Paste usage: // GetTrailingSLValue(ticket, type, pips, curSL, Mv1, SL1, Mv2, SL2, Mv3); //============================================================================= double GetTrailingSLValue( int Ticket, int TrailType, int TrailPips, double CurSL, int FirstMove, int FirstStopLoss, int SecondMove, int SecondStopLoss, int ThirdMove) { double SL = 0; double dTrailVal = Point * TrailPips; double dFirstMove = Point * FirstMove; double dFirstStopLoss = Point * FirstStopLoss; double dSecondMove = Point * SecondMove; double dSecondStopLoss = Point * SecondStopLoss; double dThirdMove = Point * ThirdMove; OrderSelect(Ticket, SELECT_BY_TICKET); int type = OrderType(); double openPrice = OrderOpenPrice(); if (type == OP_BUY) { switch (TrailType) { case 1: if (CurSL < Bid - dTrailVal) SL = Bid - dTrailVal; break; case 2: if (Bid - openPrice >= dTrailVal && (CurSL < Bid - dTrailVal || CurSL == 0)) SL = Bid - dTrailVal; break; case 3: if (Bid - openPrice >= dFirstMove && dFirstMove != 0) SL = openPrice + dFirstMove - dFirstStopLoss; if (Bid - openPrice >= dSecondMove && dSecondMove != 0) SL = openPrice + dSecondMove - dSecondStopLoss; if (Bid - openPrice >= dThirdMove) SL = Bid - dTrailVal; break; case 4: if (Bid - CurSL >= dTrailVal * 2) SL = Bid - dTrailVal; break; } } if (type == OP_SELL) { switch (TrailType) { case 1: if (CurSL > Ask + dTrailVal) SL = Ask + dTrailVal; break; case 2: if (openPrice - Ask >= dTrailVal && (CurSL > Ask + dTrailVal || CurSL == 0)) SL = Ask + dTrailVal; break; case 3: if (openPrice - Ask >= dFirstMove && dFirstMove != 0) SL = openPrice - dFirstMove + dFirstStopLoss; if (openPrice - Ask >= dSecondMove && dSecondMove != 0) SL = openPrice - dSecondMove + dSecondStopLoss; if (openPrice - Ask >= dThirdMove) SL = Ask + dTrailVal; break; case 4: if (CurSL - Ask >= dTrailVal * 2) SL = Ask + dTrailVal; break; } } return(SL); } //---------------------------------------------------------------------------- // Error Handling Functions //---------------------------------------------------------------------------- string ErrorDescription(int error_code) { string error_string; switch (error_code) { //---- codes returned from trade server case 0: case 1: error_string = "no error"; break; case 2: error_string = "common error"; break; case 3: error_string = "invalid trade parameters"; break; case 4: error_string = "trade server is busy"; break; case 5: error_string = "old version of the client terminal"; break; case 6: error_string = "no connection with trade server"; break; case 7: error_string = "not enough rights"; break; case 8: error_string = "too frequent requests"; break; case 9: error_string = "malfunctional trade operation"; break; case 64: error_string = "account disabled"; break; case 65: error_string = "invalid account"; break; case 128: error_string = "trade timeout"; break; case 129: error_string = "invalid price"; break; case 130: error_string = "invalid stops"; break; case 131: error_string = "invalid trade volume"; break; case 132: error_string = "market is closed"; break; case 133: error_string = "trade is disabled"; break; case 134: error_string = "not enough money"; break; case 135: error_string = "price changed"; break; case 136: error_string = "off quotes"; break; case 137: error_string = "broker is busy"; break; case 138: error_string = "requote"; break; case 139: error_string = "order is locked"; break; case 140: error_string = "long positions only allowed"; break; case 141: error_string = "too many requests"; break; case 145: error_string = "modification denied because order too close to market"; break; case 146: error_string = "trade context is busy"; break; //---- mql4 errors case 4000: error_string = "no error"; break; case 4001: error_string = "wrong function pointer"; break; case 4002: error_string = "array index is out of range"; break; case 4003: error_string = "no memory for function call stack"; break; case 4004: error_string = "recursive stack overflow"; break; case 4005: error_string = "not enough stack for parameter"; break; case 4006: error_string = "no memory for parameter string"; break; case 4007: error_string = "no memory for temp string"; break; case 4008: error_string = "not initialized string"; break; case 4009: error_string = "not initialized string in array"; break; case 4010: error_string = "no memory for array\' string"; break; case 4011: error_string = "too long string"; break; case 4012: error_string = "remainder from zero divide"; break; case 4013: error_string = "zero divide"; break; case 4014: error_string = "unknown command"; break; case 4015: error_string = "wrong jump (never generated error)"; break; case 4016: error_string = "not initialized array"; break; case 4017: error_string = "dll calls are not allowed"; break; case 4018: error_string = "cannot load library"; break; case 4019: error_string = "cannot call function"; break; case 4020: error_string = "expert function calls are not allowed"; break; case 4021: error_string = "not enough memory for temp string returned from function"; break; case 4022: error_string = "system is busy (never generated error)"; break; case 4050: error_string = "invalid function parameters count"; break; case 4051: error_string = "invalid function parameter value"; break; case 4052: error_string = "string function internal error"; break; case 4053: error_string = "some array error"; break; case 4054: error_string = "incorrect series array using"; break; case 4055: error_string = "custom indicator error"; break; case 4056: error_string = "arrays are incompatible"; break; case 4057: error_string = "global variables processing error"; break; case 4058: error_string = "global variable not found"; break; case 4059: error_string = "function is not allowed in testing mode"; break; case 4060: error_string = "function is not confirmed"; break; case 4061: error_string = "send mail error"; break; case 4062: error_string = "string parameter expected"; break; case 4063: error_string = "integer parameter expected"; break; case 4064: error_string = "double parameter expected"; break; case 4065: error_string = "array as parameter expected"; break; case 4066: error_string = "requested history data in update state"; break; case 4099: error_string = "end of file"; break; case 4100: error_string = "some file error"; break; case 4101: error_string = "wrong file name"; break; case 4102: error_string = "too many opened files"; break; case 4103: error_string = "cannot open file"; break; case 4104: error_string = "incompatible access to a file"; break; case 4105: error_string = "no order selected"; break; case 4106: error_string = "unknown symbol"; break; case 4107: error_string = "invalid price parameter for trade function"; break; case 4108: error_string = "invalid ticket"; break; case 4109: error_string = "trade is not allowed"; break; case 4110: error_string = "longs are not allowed"; break; case 4111: error_string = "shorts are not allowed"; break; case 4200: error_string = "object is already exist"; break; case 4201: error_string = "unknown object property"; break; case 4202: error_string = "object is not exist"; break; case 4203: error_string = "unknown object type"; break; case 4204: error_string = "no object name"; break; case 4205: error_string = "object coordinates error"; break; case 4206: error_string = "no specified subwindow"; break; default: error_string = "unknown error"; } return(error_string); } /*$History: LibDerksUtils.mq4 $ * * ***************** Version 11 ***************** * User: Derk Date: 8/01/07 Time: 6:21p * Updated in $/experts/libraries * Added new trail function for internal stops * * ***************** Version 10 ***************** * User: Jackt Date: 7/27/07 Time: 11:35a * Updated in $/experts/libraries * * ***************** Version 8 ***************** * User: Derk Date: 6/07/07 Time: 10:21p * Updated in $/experts/libraries * Changes / improvements to PlacePendingBreakouts * * ***************** Version 7 ***************** * User: Derk Date: 6/05/07 Time: 11:20p * Updated in $/experts/libraries * PlacePendingBreakouts * * ***************** Version 5 ***************** * User: Jackt Date: 5/30/07 Time: 6:44p * Updated in $/experts/libraries * Updated Keyword Expanion */