bleh
[mqlkit.git] / include / common_functions.mqh
blob025a749dad494c8eb46c2c0b7b0fd6f6a8cd98a6
1 //+------------------------------------------------------------------+\r
2 //|                                             common_functions.mqh |\r
3 //|                                 Copyright (c) 2010, Bernd Kreuss |\r
4 //|                                          prof7bit@googlemail.com |\r
5 //|                                            Version: 2010.11.30.1 |\r
6 //+------------------------------------------------------------------+\r
7 \r
8 /** @file\r
9     This file contains most often used utility functions that are\r
10     commonly needed in EAs, especially functions for robust order handling,\r
11     the latest version of this file is available at\r
12     http://sites.google.com/site/prof7bit/\r
14     This program is free software: you can redistribute it and/or modify\r
15     it under the terms of the GNU General Public License as published by\r
16     the Free Software Foundation, either version 3 of the License, or\r
17     (at your option) any later version.\r
19     This program is distributed in the hope that it will be useful,\r
20     but WITHOUT ANY WARRANTY; without even the implied warranty of\r
21     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
22     GNU General Public License for more details.\r
24     You should have received a copy of the GNU General Public License\r
25     along with this program. If not, see <http://www.gnu.org/licenses/>.\r
26 */\r
28 /** @mainpage\r
29     This documentation is generated by Doxygen. Click on "Files" to see\r
30     the list of documented files and go to http://sites.google.com/site/prof7bit/\r
31     for downloading the actual .mqh files and for additional information.\r
32 */\r
34 #include <stderror.mqh>\r
35 #include <stdlib.mqh>\r
36                        \r
37 #import "shell32.dll"\r
38 int ShellExecuteA(int hWnd, string Verb, string File, string Parameter, string Path, int ShowCommand);\r
39 #import "kernel32.dll"\r
40 void OutputDebugStringA(string msg);\r
41 #import\r
43 #define SW_SHOWNORMAL 1\r
45 static color CLR_BUY_ARROW = Blue;\r
46 static color CLR_SELL_ARROW = Red;\r
47 static color CLR_CROSSLINE_ACTIVE = Magenta;\r
48 static color CLR_CROSSLINE_TRIGGERED = Gray;\r
49 static int STYLE_CROSSLINE_TRIGGERED = STYLE_SOLID;\r
50 static int STYLE_CROSSLINE_ACTIVE = STYLE_DASH;\r
51 static int WIDTH_CROSSLINE_TRIGGERED = 1;\r
52 static int WIDTH_CROSSLINE_ACTIVE = 1;\r
53 static bool IS_ECN_BROKER = false;\r
55 /**\r
56 * start an external program but DON'T wait for it to finish\r
57 */\r
58 void shell(string file, string parameters = "") {\r
59    ShellExecuteA(0, "open", file, parameters, NULL, SW_SHOWNORMAL);\r
60 }\r
62 /**\r
63 * Replacement for the built-in Print(), output to the debug monitor.\r
64 * Send information to OutputDebugString() to be viewed and logged\r
65 * by SysInternals DebugView (free download from microsoft)\r
66 * This is ideal for debugging as an alternative to Print().\r
67 * The function will take up to 8 string (or numeric) arguments\r
68 * to be concatenated into one debug message.\r
69 */\r
70 void log(\r
71    string s1,\r
72    string s2 = "",\r
73    string s3 = "",\r
74    string s4 = "",\r
75    string s5 = "",\r
76    string s6 = "",\r
77    string s7 = "",\r
78    string s8 = ""\r
79 ) {\r
80    string out = StringTrimRight(StringConcatenate(\r
81                                    WindowExpertName(), ".mq4 ", Symbol(),\r
82                                    " ", s1,\r
83                                    " ", s2,\r
84                                    " ", s3,\r
85                                    " ", s4,\r
86                                    " ", s5,\r
87                                    " ", s6,\r
88                                    " ", s7,\r
89                                    " ", s8\r
90                                 ));\r
91    OutputDebugStringA(out);\r
92 }\r
94 /**\r
95 * Replacement for the built-in Print(), output to the chart window.\r
96 * use the Comments() display to simulate the behaviour of\r
97 * the good old print command, useful for debugging.\r
98 * text will be appended as a new line on every call\r
99 * and if it has reached 20 lines it will start to scroll.\r
100 * if clear is set to True the buffer will be cleared.\r
101 */\r
102 void print(string text, bool clear = False) {\r
103    static string print_lines[20];\r
104    static int print_line_position = 0;\r
105    if (IsOptimization()) {\r
106       return(0);\r
107    }\r
108    string output = "\n";\r
109    string space = "                        ";\r
110    int max_lines = 20;\r
111    int i;\r
112    if (clear) {\r
113       for (i = 0; i < max_lines; i++) {\r
114          print_lines[i] = "";\r
115          print_line_position = 0;\r
116       }\r
117    }\r
119    if (print_line_position == max_lines) {\r
120       for (i = 0; i < max_lines; i++) {\r
121          print_lines[i] = print_lines[i+1];\r
122       }\r
123       print_line_position--;\r
124    }\r
126    print_lines[print_line_position] = text;\r
127    print_line_position++;\r
129    for (i = 0; i < print_line_position; i++) {\r
130       output = output + print_lines[i] + "\n";\r
131    }\r
133    output = stringReplace(output, "\n", "\n" + space);\r
134    Comment(output);\r
137 /**\r
138 * search for the string needle in the string haystack and replace all\r
139 * occurrecnes with replace.\r
140 */\r
141 string stringReplace(string haystack, string needle, string replace = "") {\r
142    string left, right;\r
143    int start = 0;\r
144    int rlen = StringLen(replace);\r
145    int nlen = StringLen(needle);\r
146    while (start > -1) {\r
147       start = StringFind(haystack, needle, start);\r
148       if (start > -1) {\r
149          if (start > 0) {\r
150             left = StringSubstr(haystack, 0, start);\r
151          } else {\r
152             left = "";\r
153          }\r
154          right = StringSubstr(haystack, start + nlen);\r
155          haystack = left + replace + right;\r
156          start = start + rlen;\r
157       }\r
158    }\r
159    return (haystack);\r
163 /**\r
164 * parse a string containing delimiters into an array of strings\r
165 * and return the number of elements in the resulting array.\r
167 * if compact is set to true (default) then it will trim all\r
168 * substrigs and only include non-empty strings in the result.\r
170 * if max is non-zero then this is the maximum number of results\r
171 * that should be returned. If for example max = 2 and str is\r
172 * "42 foo bar" and delimiter = " " then it will stop after the\r
173 * first delimiter and return only two elements: "42" and "foo bar"\r
174 * max = 0 (default) will search until the end.\r
175 */\r
176 int stringExplode(string str, string delimiter, string &result[], bool compact = true, int max = 0) {\r
177    int count = 0;\r
178    int pos = 0;\r
179    int pos_next = 0;\r
180    int l_sub;\r
181    string sub;\r
182    bool end = false;\r
183    int l_d = StringLen(delimiter);\r
184    while (!end) {\r
185       ArrayResize(result, count + 1);\r
186       pos_next = StringFind(str, delimiter, pos);\r
187       if (max > 0 && count == max - 1) {\r
188          pos_next = -1;\r
189       }\r
190       if (pos_next > -1) {\r
191          // found delimiter\r
192          l_sub = pos_next - pos;\r
193          if (l_sub == 0) {\r
194             sub = "";\r
195          } else {\r
196             sub = StringSubstr(str, pos, l_sub);\r
197          }\r
198          pos = pos_next + l_d;\r
199       } else {\r
200          sub = StringSubstr(str, pos, 0); // until the end\r
201          end = true;\r
202       }\r
203       if (compact) {\r
204          sub = StringTrimLeft(StringTrimRight(sub));\r
205       }\r
206       if (sub != "" || !compact) {\r
207          result[count] = sub;\r
208          count++;\r
209       }\r
210    }\r
211    return(count);\r
215 /**\r
216 * convert a 32 bit integer into its hexadecimal representation\r
217 */\r
218 string intToHex(int x, int digits = 8) {\r
219    int i, mask, nibble;\r
220    string n, hex;\r
221    mask = 15;\r
222    for (i = 0; i < digits; i++) {\r
223       nibble = (x & mask) >> (i << 2);\r
224       if (nibble < 10) n = CharToStr(48 + nibble);\r
225       if (nibble >  9) n = CharToStr(55 + nibble);\r
226       mask = mask << 4;\r
227       hex = n + hex;\r
228    }\r
229    return(hex);\r
233 /**\r
234 * create a positive integer for the use as a magic number.\r
236 * The function takes a string as argument and calculates\r
237 * an 31 bit hash value from it. The hash does certainly not\r
238 * have the strength of a real cryptographic hash function\r
239 * but it should be more than sufficient for generating a\r
240 * unique ID from a string and collissions should not occur.\r
242 * use it in your init() function like this:\r
243 *    magic = makeMagicNumber(WindowExpertName() + Symbol() + Period());\r
245 * where name would be the name of your EA. Your EA will then\r
246 * get a unique magic number for each instrument and timeframe\r
247 * and this number will always be the same, whenever you put\r
248 * the same EA onto the same chart.\r
250 * Numbers generated during testing mode will differ from those\r
251 * numbers generated on a live chart.\r
252 */\r
253 int makeMagicNumber(string key) {\r
254    int i, k;\r
255    int h = 0;\r
257    if (IsTesting()) {\r
258       key = "_" + key;\r
259    }\r
261    for (i = 0; i < StringLen(key); i++) {\r
262       k = StringGetChar(key, i);\r
263       h = h + k;\r
264       h = bitRotate(h, 5); // rotate 5 bits\r
265    }\r
267    for (i = 0; i < StringLen(key); i++) {\r
268       k = StringGetChar(key, i);\r
269       h = h + k;\r
270       // rotate depending on character value\r
271       h = bitRotate(h, k & 0x0000000F);\r
272    }\r
274    // now we go backwards in our string\r
275    for (i = StringLen(key); i > 0; i--) {\r
276       k = StringGetChar(key, i - 1);\r
277       h = h + k;\r
278       // rotate depending on the last 4 bits of h\r
279       h = bitRotate(h, h & 0x0000000F);\r
280    }\r
282    return(h & 0x7fffffff);\r
285 /**\r
286 * Rotate a 32 bit integer value bit-wise\r
287 * the specified number of bits to the right.\r
288 * This function is needed for calculations\r
289 * in the hash function makeMacicNumber()\r
290 */\r
291 int bitRotate(int value, int count) {\r
292    int i, tmp, mask;\r
293    mask = (0x00000001 << count) - 1;\r
294    tmp = value & mask;\r
295    value = value >> count;\r
296    value = value | (tmp << (32 - count));\r
297    return(value);\r
300 /**\r
301 * place a market buy with stop loss, target, magic and Comment\r
302 * keeps trying in an infinite loop until the position is open.\r
303 */\r
304 int buy(double lots, double sl, double tp, int magic = 42, string comment = "") {\r
305    int ticket;\r
306    if (!IS_ECN_BROKER) {\r
307       return(orderSendReliable(Symbol(), OP_BUY, lots, Ask, 100, sl, tp, comment, magic, 0, CLR_BUY_ARROW));\r
308    } else {\r
309       ticket = orderSendReliable(Symbol(), OP_BUY, lots, Ask, 100, 0, 0, comment, magic, 0, CLR_BUY_ARROW);\r
310       if (sl + tp > 0) {\r
311          orderModifyReliable(ticket, 0, sl, tp, 0);\r
312       }\r
313       return(ticket);\r
314    }\r
317 /**\r
318 * place a market sell with stop loss, target, magic and comment\r
319 * keeps trying in an infinite loop until the position is open.\r
320 */\r
321 int sell(double lots, double sl, double tp, int magic = 42, string comment = "") {\r
322    int ticket;\r
323    if (!IS_ECN_BROKER) {\r
324       return(orderSendReliable(Symbol(), OP_SELL, lots, Bid, 100, sl, tp, comment, magic, 0, CLR_SELL_ARROW));\r
325    } else {\r
326       ticket = orderSendReliable(Symbol(), OP_SELL, lots, Bid, 100, 0, 0, comment, magic, 0, CLR_SELL_ARROW);\r
327       if (sl + tp > 0) {\r
328          orderModifyReliable(ticket, 0, sl, tp, 0);\r
329       }\r
330       return(ticket);\r
331    }\r
334 /**\r
335 * place a buy limit order\r
336 */\r
337 int buyLimit(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {\r
338    return(orderSendReliable(Symbol(), OP_BUYLIMIT, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));\r
341 /**\r
342 * place a sell limit order\r
343 */\r
344 int sellLimit(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {\r
345    return(orderSendReliable(Symbol(), OP_SELLLIMIT, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));\r
348 /**\r
349 * place a buy stop order\r
350 */\r
351 int buyStop(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {\r
352    return(orderSendReliable(Symbol(), OP_BUYSTOP, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));\r
355 /**\r
356 * place a sell stop order\r
357 */\r
358 int sellStop(double lots, double price, double sl, double tp, int magic = 42, string comment = "") {\r
359    return(orderSendReliable(Symbol(), OP_SELLSTOP, lots, price, 1, sl, tp, comment, magic, 0, CLR_NONE));\r
363 /**\r
364 * calculate unrealized P&L, belonging to all open trades with this magic number\r
365 */\r
366 double getProfit(int magic) {\r
367    int cnt;\r
368    double profit = 0;\r
369    int total = OrdersTotal();\r
370    for (cnt = 0; cnt < total; cnt++) {\r
371       OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
372       if (OrderMagicNumber() == magic) {\r
373          profit += OrderProfit() + OrderSwap() + OrderCommission();\r
374       }\r
375    }\r
376    return (profit);\r
379 /**\r
380 * calculate realized P&L resulting from all closed trades with this magic number\r
381 */\r
382 double getProfitRealized(int magic) {\r
383    int cnt;\r
384    double profit = 0;\r
385    int total = OrdersHistoryTotal();\r
386    for (cnt = 0; cnt < total; cnt++) {\r
387       OrderSelect(cnt, SELECT_BY_POS, MODE_HISTORY);\r
388       if (OrderMagicNumber() == magic) {\r
389          profit += OrderProfit() + OrderSwap() + OrderCommission();\r
390       }\r
391    }\r
392    return (profit);\r
395 /**\r
396 * get the number of currently open trades of specified type\r
397 */\r
398 int getNumOpenOrders(int type, int magic) {\r
399    int cnt;\r
400    int num = 0;\r
401    int total = OrdersTotal();\r
402    for (cnt = 0; cnt < total; cnt++) {\r
403       OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
404       if ((magic == -1 || OrderMagicNumber() == magic) && (type == -1 || OrderType() == type)) {\r
405          num++;\r
406       }\r
407    }\r
408    return (num);\r
412 /**\r
413 * Trail the take-profit if price moves against the trade.\r
415 * Loop through all matching open positions and trail the target\r
416 * towards price if the price moves AGAINST the trade direction (away\r
417 * from the target). This may even trail the target into negative territory.\r
419 * If trailtarget is 0 then nothing will be done, a positive number\r
420 * means the maximum distance between price and target.\r
422 * If max_negative is set to -1 (default) the there is no limitation\r
423 * how far into negative territory the target can be trailed, if\r
424 * max_negative is set to 0 then it will only trail down to break even\r
425 * and then stop at this point, if it is set to a positive number\r
426 * then it will use this number as a limit of how far into loss the\r
427 * target may be trailed.\r
428 */\r
429 void trailTargets(double trailtarget, int magic, double max_negative = -1) {\r
430    int total, cnt, type;\r
431    double op, tp, old_tp, dist;\r
432    bool change;\r
434    if (trailtarget != 0) {\r
435       total = OrdersTotal();\r
436       for (cnt = 0; cnt < total; cnt++) {\r
437          OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
438          if (OrderMagicNumber() == magic) {\r
439             op = OrderOpenPrice();\r
440             old_tp = OrderTakeProfit();\r
441             type = OrderType();\r
442             change = False;\r
444             if (type == OP_BUY) {\r
445                tp = Bid + trailtarget;\r
446                dist = tp - op;\r
447                if (max_negative >= 0 && -dist > max_negative) {\r
448                   tp = op - max_negative;\r
449                }\r
450                if (tp < old_tp) {\r
451                   change = True;\r
452                }\r
453             }\r
455             if (type == OP_SELL) {\r
456                tp = Ask - trailtarget;\r
457                dist = op - tp;\r
458                if (max_negative >= 0 && -dist > max_negative) {\r
459                   tp = op + max_negative;\r
460                }\r
461                if (tp > old_tp) {\r
462                   change = True;\r
463                }\r
464             }\r
466             if (change) {\r
467                orderModifyReliable(\r
468                   OrderTicket(),\r
469                   op,\r
470                   OrderStopLoss(),\r
471                   tp,\r
472                   OrderExpiration()\r
473                );\r
474             }\r
475          }\r
476       }\r
477    }\r
480 /**\r
481 * Trail the stoploss.\r
483 * Loop through all matching open positions and trail their stops.\r
485 * If trailstops is 0 then nothing will be done, a positive number is\r
486 * the maximum distance between price and stop, the distance at which\r
487 * the stop is trailed behind price.\r
489 * trailstop_slow is a factor that will increase trailing distance\r
490 * when the profit grows. default 1 is normal trailing stop behaviour\r
491 */\r
492 void trailStops(double trailstop, int magic, double trailstop_slow = 1) {\r
493    int total, cnt, type;\r
494    double op, sl, old_sl;\r
495    bool change;\r
497    if (trailstop != 0) {\r
498       total = OrdersTotal();\r
499       for (cnt = 0; cnt < total; cnt++) {\r
500          OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
501          if (OrderMagicNumber() == magic) {\r
502             op = OrderOpenPrice();\r
503             old_sl = OrderStopLoss();\r
504             type = OrderType();\r
505             change = False;\r
507             if (type == OP_BUY) {\r
508                sl = op + (Bid - trailstop - op) / trailstop_slow;\r
509                if (sl > op && sl > old_sl) {\r
510                   change = True;\r
511                }\r
512             }\r
514             if (type == OP_SELL) {\r
515                sl = op - (op - Ask - trailstop) / trailstop_slow;\r
516                if (sl < op && (sl < old_sl || old_sl == 0)) {\r
517                   change = True;\r
518                }\r
519             }\r
521             if (change) {\r
522                orderModifyReliable(\r
523                   OrderTicket(),\r
524                   op,\r
525                   sl,\r
526                   OrderTakeProfit(),\r
527                   OrderExpiration()\r
528                );\r
529             }\r
530          }\r
531       }\r
532    }\r
535 /**\r
536 * Lock in pofit after a crtain amount of profit is reached.\r
538 * This will place (or move) a stoploss min_profit away from\r
539 * the open price as soon as price has moved distance away from\r
540 * this point. If distance is 0 or smaller than the minimum\r
541 * allowed stop distance then it will still behave as if you had\r
542 * specified the minimum allowed stop distance.\r
543 */\r
544 void lockProfit(double min_profit, int magic, double distance = 0) {\r
545    int total, cnt, type;\r
546    double op, sl, old_sl;\r
547    bool change;\r
549    total = OrdersTotal();\r
550    for (cnt = 0; cnt < total; cnt++) {\r
551       OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
552       if (OrderMagicNumber() == magic) {\r
553          old_sl = OrderStopLoss();\r
554          op = OrderOpenPrice();\r
555          type = OrderType();\r
556          change = False;\r
558          if (type == OP_BUY) {\r
559             sl = op + min_profit;\r
560             if (Bid > sl + distance && sl > old_sl) {\r
561                change = True;\r
562             }\r
563          }\r
565          if (type == OP_SELL) {\r
566             sl = op - min_profit;\r
567             if (Ask < sl - distance && (sl < old_sl || old_sl == 0)) {\r
568                change = True;\r
569             }\r
570          }\r
572          if (change) {\r
573             orderModifyReliable(\r
574                OrderTicket(),\r
575                op,\r
576                sl,\r
577                OrderTakeProfit(),\r
578                OrderExpiration()\r
579             );\r
580          }\r
581       }\r
582    }\r
585 /**\r
586 * Return true if there exists an order of this type at exactly this price.\r
587 */\r
588 bool isOrder(int type, double price, int magic) {\r
589    int cnt, num;\r
590    int total = OrdersTotal();\r
591    for (cnt = 0; cnt < total; cnt++) {\r
592       OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
593       if (OrderMagicNumber() == magic && (type == -1 || OrderType() == type)) {\r
594          if (isEqualPrice(OrderOpenPrice(), price)) {\r
595             num++;\r
596          }\r
597       }\r
598    }\r
599    return (num > 0);\r
602 /**\r
603 * Close all open orders or trades that match type and magic number.\r
604 * This function won't return until all positions are closed\r
605 * type = -1 means all types, magic = -1 means all magic numbers\r
606 */\r
607 void closeOpenOrders(int type, int magic) {\r
608    int total, cnt;\r
609    double price;\r
610    color clr;\r
611    int order_type;\r
613    Print("closeOpenOrders(" + type + "," + magic + ")");\r
615    while (getNumOpenOrders(type, magic) > 0) {\r
616       while (IsTradeContextBusy()) {\r
617          Print("closeOpenOrders(): waiting for trade context.");\r
618          Sleep(MathRand() / 10);\r
619       }\r
620       total = OrdersTotal();\r
621       RefreshRates();\r
622       if (type == OP_BUY) {\r
623          price = Bid;\r
624          clr = CLR_SELL_ARROW;\r
625       } else {\r
626          price = Ask;\r
627          clr = CLR_BUY_ARROW;\r
628       }\r
629       for (cnt = 0; cnt < total; cnt++) {\r
630          OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
631          if ((type == -1 || OrderType() == type) && (magic == -1 || OrderMagicNumber() == magic)) {\r
632             if (IsTradeContextBusy()) {\r
633                break; // something else is trading too, back to the while loop.\r
634             }\r
635             order_type = OrderType();\r
636             if (order_type == OP_BUYSTOP || order_type == OP_SELLSTOP || order_type == OP_BUYLIMIT || order_type == OP_SELLLIMIT) {\r
637                orderDeleteReliable(OrderTicket());\r
638             } else {\r
639                orderCloseReliable(OrderTicket(), OrderLots(), price, 999, clr);\r
640             }\r
641             break; // restart the loop from 0 (hello FIFO!)\r
642          }\r
643       }\r
644    }\r
647 /**\r
648 * Scale out of a position the specified amounts of lots and return\r
649 * the number of lots that could *not* be closed.\r
651 * This function will try to close out the needed lots out of any\r
652 * combination of open orders, using partial closes or complete closes\r
653 * until there is nothing left to close or the needed lot size has been\r
654 * closed. It will start closing the oldest orders first (FIFO).\r
655 * The return value will be the amount that could not be closed, this\r
656 * can then be directly used to place a buy or sell into the other\r
657 * direction. If the requested amount could be closed it will return 0.\r
659 * This function is not yet fully tested and debugged. It may change\r
660 * in future releases, it may not work at all. Dont use it (yet)!\r
661 */\r
662 double reducePosition(int type, double lots, int magic) {\r
663    int i;\r
664    double price;\r
665    int clr;\r
666    bool loop_again = true;\r
668    Print("reducePosition()");\r
670    while (loop_again) {\r
671       RefreshRates();\r
672       if (type == OP_BUY) {\r
673          price = Bid;\r
674          clr = CLR_SELL_ARROW;\r
675       }\r
676       if (type == OP_SELL) {\r
677          price = Ask;\r
678          clr = CLR_BUY_ARROW;\r
679       }\r
680       int total_orders = OrdersTotal();\r
681       loop_again = false;\r
682       for (i = 0; i < total_orders; i++) {\r
683          OrderSelect(i, SELECT_BY_POS, MODE_TRADES);\r
684          if (OrderMagicNumber() == magic) {\r
685             if (OrderType() == type) {\r
686                if (NormalizeDouble(OrderLots() - lots, 2) >= 0) {\r
687                   // found big enough order to do it in one step\r
688                   Print("reducePosition(): now trying to close order");\r
689                   if (orderCloseReliable(OrderTicket(), lots, price, 100, clr) == true) {\r
690                      Print("reducePosition(): success!");\r
691                      return(0);\r
692                   } else {\r
693                      Print("reducePosition(): order found but failed to close: " + GetLastError());\r
694                      // permanent error occured\r
695                      return(lots);\r
696                   }\r
697                } else {\r
698                   // order is smaller. close it comppetely\r
699                   if (orderCloseReliable(OrderTicket(), OrderLots(), price, 100, clr) == true) {\r
700                      lots -= OrderLots();\r
701                      Print("reducePosition(): closed " + OrderLots() + " remaining: " + lots);\r
702                      loop_again = True;\r
703                      break; // number of orders has now changed, restart the for loop\r
704                   } else {\r
705                      Print("reducePosition(): order found but failed to close: " + GetLastError());\r
706                      // permanent error occured\r
707                      return(lots);\r
708                   }\r
709                }\r
711                // are we already done?\r
712                if (NormalizeDouble(lots, 2) == 0) {\r
713                   return(0);\r
714                }\r
715             }\r
716          }\r
717       }//for (all orders)\r
719       // whenever the for loop has completed\r
720       // loop_again would indicate that an order has been closed\r
721       // and we need to start the for loop again from 0\r
723    }// while (loop_again)\r
724    Print("reducePosition(): nothing more to close. " + lots + " could not be closed");\r
725    return(lots);\r
728 /**\r
729 * move the stop (stops) to the specified price.\r
730 */\r
731 void moveStop(int type, int magic, double stoploss = -1) {\r
732    int total, cnt;\r
733    total = OrdersTotal();\r
734    for (cnt = 0; cnt < total; cnt++) {\r
735       OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
736       if (OrderType() == type && OrderMagicNumber() == magic) {\r
737          if (stoploss == -1) {\r
738             stoploss = OrderOpenPrice();\r
739          }\r
740          if (!isEqualPrice(stoploss, OrderStopLoss())) {\r
741             orderModifyReliable(\r
742                OrderTicket(),\r
743                OrderOpenPrice(),\r
744                stoploss,\r
745                OrderTakeProfit(),\r
746                OrderExpiration()\r
747             );\r
748          }\r
749       }\r
750    }\r
753 /**\r
754 * Move the pending order to the specified price.\r
755 * This will also take care of moving associated\r
756 * stop and target the same amount.\r
757 */\r
758 void moveOrder(int type, int magic, double price) {\r
759    int total, cnt;\r
760    double d, sl, tp;\r
761    total = OrdersTotal();\r
762    for (cnt = 0; cnt < total; cnt++) {\r
763       OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
764       if (OrderType() == type && OrderMagicNumber() == magic) {\r
765          if (!isEqualPrice(d, OrderOpenPrice())) {\r
766             d = price - OrderOpenPrice();\r
767             if (OrderStopLoss() == 0) {\r
768                sl = 0;\r
769             } else {\r
770                sl = OrderStopLoss() + d;\r
771             }\r
772             if (OrderTakeProfit() == 0) {\r
773                tp = 0;\r
774             } else {\r
775                tp = OrderTakeProfit() + d;\r
776             }\r
777             orderModifyReliable(\r
778                OrderTicket(),\r
779                price,\r
780                sl,\r
781                tp,\r
782                OrderExpiration()\r
783             );\r
784          }\r
785       }\r
786    }\r
789 /**\r
790 * Move the take-profit to the specified price.\r
791 */\r
792 void moveTarget(int type, int magic, double target) {\r
793    int total, cnt;\r
794    total = OrdersTotal();\r
795    for (cnt = 0; cnt < total; cnt++) {\r
796       OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);\r
797       if (OrderType() == type && OrderMagicNumber() == magic) {\r
798          if (!isEqualPrice(target, OrderTakeProfit())) {\r
799             orderModifyReliable(\r
800                OrderTicket(),\r
801                OrderOpenPrice(),\r
802                OrderStopLoss(),\r
803                target,\r
804                OrderExpiration()\r
805             );\r
806          }\r
807       }\r
808    }\r
811 /**\r
812 * Get the number of (effective) lots that are curretly open.\r
813 * This will return the effective exposure. Offsetting trades\r
814 * will be subtracted. The return value will always be positive.\r
815 * See also getLotsOnTableSigned()\r
816 */\r
817 double getLotsOnTable(int magic) {\r
818    double total_lots = 0;\r
819    int i;\r
820    int total_orders = OrdersTotal();\r
821    for (i = 0; i < total_orders; i++) {\r
822       OrderSelect(i, SELECT_BY_POS, MODE_TRADES);\r
823       if (OrderMagicNumber() == magic) {\r
824          if (OrderType() == OP_BUY) {\r
825             total_lots += OrderLots();\r
826          }\r
827          if (OrderType() == OP_SELL) {\r
828             total_lots -= OrderLots();\r
829          }\r
830       }\r
831    }\r
832    total_lots = MathAbs(total_lots);\r
833    return(total_lots);\r
836 /**\r
837 * Get the number of (effective) lots that are curretly open.\r
838 * This will return the effective exposure. Offsetting trades\r
839 * will be subtracted. Positive means long, negative is short.\r
840 * See also getLotsOnTable()\r
841 */\r
842 double getLotsOnTableSigned(int magic) {\r
843    double total_lots = 0;\r
844    int i;\r
845    int total_orders = OrdersTotal();\r
846    for (i = 0; i < total_orders; i++) {\r
847       OrderSelect(i, SELECT_BY_POS, MODE_TRADES);\r
848       if (OrderMagicNumber() == magic) {\r
849          if (OrderType() == OP_BUY) {\r
850             total_lots += OrderLots();\r
851          }\r
852          if (OrderType() == OP_SELL) {\r
853             total_lots -= OrderLots();\r
854          }\r
855       }\r
856    }\r
857    return(total_lots);\r
860 /**\r
861 * Get the open price of the average position.\r
862 */\r
863 double getAveragePositionPrice(int magic) {\r
864    double total_lots = getLotsOnTable(magic);\r
865    double average_price = 0;\r
866    int i;\r
867    int total_orders = OrdersTotal();\r
868    for (i = 0; i < total_orders; i++) {\r
869       OrderSelect(i, SELECT_BY_POS, MODE_TRADES);\r
870       if (OrderMagicNumber() == magic) {\r
871          average_price += OrderOpenPrice() * OrderLots() / total_lots;\r
872       }\r
873    }\r
874    return(average_price);\r
877 /**\r
878 * Get the profit of the last closed order (sl, tp or manually closed).\r
879 */\r
880 double getLastProfit(int magic) {\r
881    selectLastClosedTrade(magic);\r
882    return(OrderProfit());\r
885 /**\r
886 * Select the last closed order (sl, tp or manually closed).\r
887 */\r
888 void selectLastClosedTrade(int magic) {\r
889    int total, cnt, type;\r
890    total = OrdersHistoryTotal();\r
891    for (cnt = total - 1; cnt >= 0; cnt--) {\r
892       OrderSelect(cnt, SELECT_BY_POS, MODE_HISTORY);\r
893       type = OrderType();\r
894       if (OrderMagicNumber() == magic && (type == OP_BUY || type == OP_SELL)) {\r
895          return(0);\r
896       }\r
897    }\r
900 /**\r
901 * Plot the opening trade arrow.\r
902 * This is part of a re-implementation of what metatrader does when dragging\r
903 * a trade from the history to the chart. Metatrader won't do this automatically\r
904 * for manual trading and for pending order fills so we have to do it ourselves.\r
905 * See also plotNewOpenTrades() and plotNewClosedTrades() defined below.\r
906 */\r
907 void plotOpenedTradeArrow(int ticket, bool remove = false) {\r
908    string name, name_wrong;\r
909    color clr;\r
910    if (IsOptimization()) {\r
911       return(0);\r
912    }\r
913    OrderSelect(ticket, SELECT_BY_TICKET);\r
915    name = "#" + ticket + " ";\r
916    if (OrderType() == OP_BUY) {\r
917       name = name + "buy ";\r
918       clr = CLR_BUY_ARROW;\r
919    }\r
920    if (OrderType() == OP_SELL) {\r
921       name = name + "sell ";\r
922       clr = CLR_SELL_ARROW;\r
923    }\r
924    name = name + DoubleToStr(OrderLots(), 2) + " ";\r
925    name = name + OrderSymbol() + " ";\r
927    // sometimes mt4 will have created an arrow with open price = 0.\r
928    // this is wrong, we will delete it.\r
929    name_wrong = name + "at " + DoubleToStr(0, MarketInfo(OrderSymbol(), MODE_DIGITS));\r
931    name = name + "at " + DoubleToStr(OrderOpenPrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));\r
933    ObjectDelete(name_wrong);\r
934    if (remove) {\r
935       ObjectDelete(name);\r
936    } else {\r
937       ObjectCreate(name, OBJ_ARROW, 0, OrderOpenTime(), OrderOpenPrice());\r
938       ObjectSet(name, OBJPROP_ARROWCODE, 1);\r
939       ObjectSet(name, OBJPROP_COLOR, clr);\r
940       ObjectSetText(name, formatOrderArrowInfo());\r
941    }\r
944 /**\r
945 * Plot the closing trade arrow.\r
946 * This is part of a re-implementation of what metatrader does when dragging\r
947 * a trade from the history to the chart. Metatrader won't do this automatically\r
948 * for manual trading and for pending order fills so we have to do it ourselves.\r
949 * See also plotNewOpenTrades() and plotNewClosedTrades() defined below.\r
950 */\r
951 void plotClosedTradeArrow(int ticket, bool remove = false) {\r
952    string name;\r
953    color clr;\r
954    if (IsOptimization()) {\r
955       return(0);\r
956    }\r
957    OrderSelect(ticket, SELECT_BY_TICKET);\r
958    name = "#" + ticket + " ";\r
959    if (OrderType() == OP_BUY) {\r
960       name = name + "buy ";\r
961       clr = CLR_SELL_ARROW; // closing a buy is a sell, so make it red\r
962    }\r
963    if (OrderType() == OP_SELL) {\r
964       name = name + "sell ";\r
965       clr = CLR_BUY_ARROW; // closing a sell is a buy, so make it blue\r
966    }\r
967    name = name + DoubleToStr(OrderLots(), 2) + " ";\r
968    name = name + OrderSymbol() + " ";\r
969    name = name + "at " + DoubleToStr(OrderOpenPrice(), MarketInfo(OrderSymbol(), MODE_DIGITS)) + " ";\r
970    name = name + "close at " + DoubleToStr(OrderClosePrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));\r
972    if (remove) {\r
973       ObjectDelete(name);\r
974    } else {\r
975       ObjectCreate(name, OBJ_ARROW, 0, OrderCloseTime(), OrderClosePrice());\r
976       ObjectSet(name, OBJPROP_ARROWCODE, 3);\r
977       ObjectSet(name, OBJPROP_COLOR, clr);\r
978       ObjectSetText(name, formatOrderArrowInfo());\r
979    }\r
982 /**\r
983 * Plot the line connecting open and close of a history trade.\r
984 * This is part of a re-implementation of what metatrader does when dragging\r
985 * a trade from the history to the chart. Metatrader won't do this automatically\r
986 * for manual trading and for pending order fills so we have to do it ourselves.\r
987 * See also plotNewOpenTrades() and plotNewClosedTrades() defined below.\r
988 */\r
989 void plotClosedTradeLine(int ticket, bool remove = false) {\r
990    string name;\r
991    color clr;\r
992    if (IsOptimization()) {\r
993       return(0);\r
994    }\r
995    OrderSelect(ticket, SELECT_BY_TICKET);\r
996    name = "#" + ticket + " ";\r
997    if (OrderType() == OP_BUY) {\r
998       clr = CLR_BUY_ARROW;\r
999    }\r
1000    if (OrderType() == OP_SELL) {\r
1001       clr = CLR_SELL_ARROW;\r
1002    }\r
1003    name = name + DoubleToStr(OrderOpenPrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));\r
1004    name = name + " -> ";\r
1005    name = name + DoubleToStr(OrderClosePrice(), MarketInfo(OrderSymbol(), MODE_DIGITS));\r
1007    if (remove) {\r
1008       ObjectDelete(name);\r
1009    } else {\r
1010       ObjectCreate(name, OBJ_TREND, 0, OrderOpenTime(), OrderOpenPrice(), OrderCloseTime(), OrderClosePrice());\r
1011       ObjectSet(name, OBJPROP_RAY, false);\r
1012       ObjectSet(name, OBJPROP_STYLE, STYLE_DOT);\r
1013       ObjectSet(name, OBJPROP_COLOR, clr);\r
1014    }\r
1017 /**\r
1018 * Create the info-string for opened and closed order arrows.\r
1019 */\r
1020 string formatOrderArrowInfo() {\r
1021    // order is already selected\r
1022    return(StringConcatenate(\r
1023              OrderComment(),\r
1024              " (",\r
1025              OrderMagicNumber(),\r
1026              ")\nP/L: ",\r
1027              DoubleToStr(OrderProfit() + OrderSwap() + OrderCommission(), 2),\r
1028              " ",\r
1029              AccountCurrency(),\r
1030              " (",\r
1031              DoubleToStr(MathAbs(OrderOpenPrice() - OrderClosePrice()) / (MarketInfo(OrderSymbol(), MODE_POINT) * pointsPerPip()), 2),\r
1032              " pips)"\r
1033           ));\r
1036 /**\r
1037 * Plot all newly opened trades into the chart.\r
1038 * Check if the open trade list has changed and plot\r
1039 * arrows for opened trades into the chart.\r
1040 * Metatrader won't do this automatically for manual trading.\r
1041 * Use this function for scanning for new trades and plotting them.\r
1042 */\r
1043 void plotNewOpenTrades(int magic = -1) {\r
1044    //static int last_ticket=0;\r
1045    int total, i;\r
1046    if (IsTesting()) {\r
1047       return(0);\r
1048    }\r
1050    total = OrdersTotal();\r
1051    //OrderSelect(total - 1, SELECT_BY_POS, MODE_TRADES);\r
1052    //if (OrderTicket() != last_ticket){\r
1053    //   last_ticket = OrderTicket();\r
1055    // FIXME! find something to detect changes as cheap as possible!\r
1057    // order list has changed, so plot all arrows\r
1058    for (i = 0; i < total; i++) {\r
1059       OrderSelect(i, SELECT_BY_POS, MODE_TRADES);\r
1060       if ((magic == -1 || OrderMagicNumber() == magic) && OrderSymbol() == Symbol()) {\r
1061          if (OrderType() == OP_BUY || OrderType() == OP_SELL) {\r
1062             plotOpenedTradeArrow(OrderTicket());\r
1063          }\r
1064       }\r
1065    }\r
1066    //}\r
1069 /**\r
1070 * Plot all newly closed trades into the chart.\r
1071 * Check for changes in the trading history and plot the\r
1072 * trades into the chart with arrows and lines connectimg them.\r
1073 * Metatrader won't do this automatically for manual trading.\r
1074 * Use this function for scanning for closed trades and plotting them.\r
1075 */\r
1076 void plotNewClosedTrades(int magic = -1) {\r
1077    if (IsTesting()) {\r
1078       return(0);\r
1079    }\r
1081    static int last_change = 0;\r
1082    bool max_age_defined = GlobalVariableCheck("ARROW_MAX_AGE");\r
1083    int max_age = GlobalVariableGet("ARROW_MAX_AGE") * 24 * 60 * 60;\r
1084    datetime tc = TimeCurrent();\r
1085    bool remove;\r
1086    int total, i;\r
1088    total = OrdersHistoryTotal();\r
1089    OrderSelect(total - 1, SELECT_BY_POS, MODE_HISTORY);\r
1090    if (OrderTicket() + max_age + max_age_defined != last_change) {\r
1091       last_change = OrderTicket() + max_age + max_age_defined;\r
1093       // order list has changed, so plot all arrows\r
1094       for (i = 0; i < total; i++) {\r
1095          OrderSelect(i, SELECT_BY_POS, MODE_HISTORY);\r
1096          if ((magic == -1 || OrderMagicNumber() == magic) && OrderSymbol() == Symbol()) {\r
1097             if (OrderType() == OP_BUY || OrderType() == OP_SELL) {\r
1098                if (max_age_defined && tc - OrderCloseTime() > max_age) {\r
1099                   remove = true;\r
1100                } else {\r
1101                   remove = false;\r
1102                }\r
1103                plotOpenedTradeArrow(OrderTicket(), remove);\r
1104                plotClosedTradeArrow(OrderTicket(), remove);\r
1105                plotClosedTradeLine(OrderTicket(), remove);\r
1106             }\r
1107          }\r
1108       }\r
1109    }\r
1112 /**\r
1113 * Create a line connecting 2 points.\r
1114 */\r
1115 string line(string name, datetime t1, double p1, datetime t2, double p2, color clr = Red, string label = "", bool ray = False) {\r
1116    if (!IsOptimization()) {\r
1117       if (name == "") {\r
1118          name = "line_" + Time[0];\r
1119       }\r
1120       if (ObjectFind(name) == -1) {\r
1121          ObjectCreate(name, OBJ_TREND, 0, t1, p1, t2, p2);\r
1122       }\r
1123       ObjectSet(name, OBJPROP_RAY, ray);\r
1124       ObjectSet(name, OBJPROP_COLOR, clr);\r
1125       ObjectSet(name, OBJPROP_TIME1, t1);\r
1126       ObjectSet(name, OBJPROP_TIME2, t2);\r
1127       ObjectSet(name, OBJPROP_PRICE1, p1);\r
1128       ObjectSet(name, OBJPROP_PRICE2, p2);\r
1129       ObjectSetText(name, label);\r
1130    }\r
1131    return(name);\r
1134 /**\r
1135 * Create a horizontal line.\r
1136 */\r
1137 string horizLine(string name, double price, color clr = Red, string label = "") {\r
1138    if (!IsOptimization()) {\r
1139       if (name == "") {\r
1140          name = "line_" + Time[0];\r
1141       }\r
1142       if (ObjectFind(name) == -1) {\r
1143          ObjectCreate(name, OBJ_HLINE, 0, 0, price);\r
1144       } else {\r
1145          ObjectSet(name, OBJPROP_PRICE1, price);\r
1146       }\r
1147       ObjectSet(name, OBJPROP_COLOR, clr);\r
1148       ObjectSetText(name, label);\r
1149    }\r
1150    return(name);\r
1153 /**\r
1154 * Create a vertical line.\r
1155 */\r
1156 string vertLine(string name, datetime time, color clr = Red, string label = "") {\r
1157    if (!IsOptimization()) {\r
1158       if (name == "") {\r
1159          name = "line_" + Time[0];\r
1160       }\r
1161       if (ObjectFind(name) == -1) {\r
1162          ObjectCreate(name, OBJ_VLINE, 0, time, 0);\r
1163       } else {\r
1164          ObjectSet(name, OBJPROP_TIME1, time);\r
1165       }\r
1166       ObjectSet(name, OBJPROP_COLOR, clr);\r
1167       ObjectSetText(name, label);\r
1168    }\r
1169    return(name);\r
1172 /**\r
1173 * Create a text object.\r
1174 */\r
1175 string text(string name, string text, datetime time = 0, double price = 0, color clr = Red, int size = 8) {\r
1176    if (time == 0) {\r
1177       time = Time[0];\r
1178    }\r
1179    if (price == 0) {\r
1180       price = Close[iBarShift(NULL, 0, time)];\r
1181    }\r
1182    if (name == "") {\r
1183       name = "text_" + time;\r
1184    }\r
1185    if (ObjectFind(name) == -1) {\r
1186       ObjectCreate(name, OBJ_TEXT, 0, time, price);\r
1187    }\r
1188    ObjectSet(name, OBJPROP_TIME1, time);\r
1189    ObjectSet(name, OBJPROP_PRICE1, price);\r
1190    ObjectSet(name, OBJPROP_COLOR, clr);\r
1191    ObjectSet(name, OBJPROP_FONTSIZE, size);\r
1192    ObjectSetText(name, text);\r
1193    return(name);\r
1196 /**\r
1197 * Create a text label.\r
1198 */\r
1199 string label(string name, int x, int y, int corner, string text, color clr = Gray) {\r
1200    if (!IsOptimization()) {\r
1201       if (name == "") {\r
1202          name = "label_" + Time[0];\r
1203       }\r
1204       if (ObjectFind(name) == -1) {\r
1205          ObjectCreate(name, OBJ_LABEL, 0, 0, 0);\r
1206       }\r
1207       ObjectSet(name, OBJPROP_COLOR, clr);\r
1208       ObjectSet(name, OBJPROP_CORNER, corner);\r
1209       ObjectSet(name, OBJPROP_XDISTANCE, x);\r
1210       ObjectSet(name, OBJPROP_YDISTANCE, y);\r
1211       ObjectSetText(name, text);\r
1212    }\r
1213    return(name);\r
1216 /**\r
1217 * Show a button and check if it has been actuated.\r
1218 * Emulate a button with a label that must be moved by the user.\r
1219 * Return true if the label has been moved and move it back.\r
1220 * create it if it does not already exist.\r
1221 */\r
1222 bool labelButton(string name, int x, int y, int corner, string text, color clr = Gray) {\r
1223    if (IsOptimization()) {\r
1224       return(false);\r
1225    }\r
1226    if (ObjectFind(name) != -1) {\r
1227       if (ObjectGet(name, OBJPROP_XDISTANCE) != x || ObjectGet(name, OBJPROP_YDISTANCE) != y) {\r
1228          ObjectDelete(name);\r
1229          return(true);\r
1230       }\r
1231    }\r
1232    label(name, x, y, corner, "[" + text + "]", clr);\r
1233    return(false);\r
1237 /**\r
1238 * Check if a line has been crossed, the line may contain a command string.\r
1240 * return "1" or the the argument if price (Bid) just has crossed\r
1241 * a line with a decription starting with this command or "" otherwise.\r
1243 * for example if a line has the description\r
1244 * buy 0.1\r
1245 * then it will return the string "0.1" when price has just crossed it\r
1246 * and it will return "" if this was not the case.\r
1248 * If the parameter one_shot is true (default) it will add the word\r
1249 * "triggered: " to the command so it can't be triggered a second time,\r
1250 * the line description will then look like\r
1251 * triggered: buy 0.1\r
1252 * and it will not be active anymore.\r
1254 * if there is bo argument behind the command then it will simply\r
1255 * return the string "1" if it is triggered.\r
1257 * This function is intentionally returning strings and not doubles\r
1258 * to enable you to do such funny things like for example define a command\r
1259 * "delete" and then have lines like "delete buy" or "delete sell" and have\r
1260 * it delete other lines once price triggers this line or do even more\r
1261 * complex "visual programming" in the chart.\r
1262 */\r
1263 string crossedLineS(string command, bool one_shot = true, color clr_active = CLR_NONE, color clr_triggered = CLR_NONE) {\r
1264    double last_bid; // see below!\r
1265    int i;\r
1266    double price;\r
1267    string name;\r
1268    string command_line;\r
1269    string command_argument;\r
1270    int type;\r
1273    if (clr_active == CLR_NONE) {\r
1274       clr_active = CLR_CROSSLINE_ACTIVE;\r
1275    }\r
1276    if (clr_triggered == CLR_NONE) {\r
1277       clr_triggered = CLR_CROSSLINE_TRIGGERED;\r
1278    }\r
1280    for (i = 0; i < ObjectsTotal(); i++) {\r
1281       name = ObjectName(i);\r
1283       // is this an object without description (newly created by the user)?\r
1284       if (ObjectDescription(name) == "") {\r
1285          // Sometimes the user draws a new line and the default color is\r
1286          // accidentially the color and style of an active line. If we\r
1287          // simply reset all lines without decription but with the active\r
1288          // color and style we can almost completely eliminate this problem.\r
1289          // The color does not influence the functionality in any way but\r
1290          // we simply don't WANT to confuse the USER with lines that have\r
1291          // the active color and style that are not active lines.\r
1292          if (ObjectGet(name, OBJPROP_COLOR) == clr_active\r
1293                && ObjectGet(name, OBJPROP_WIDTH) == WIDTH_CROSSLINE_ACTIVE\r
1294                && ObjectGet(name, OBJPROP_STYLE) == STYLE_CROSSLINE_ACTIVE) {\r
1295             ObjectSet(name, OBJPROP_COLOR, clr_triggered);\r
1296             ObjectSet(name, OBJPROP_STYLE, STYLE_CROSSLINE_TRIGGERED);\r
1297             ObjectSet(name, OBJPROP_WIDTH, WIDTH_CROSSLINE_TRIGGERED);\r
1298             ObjectSet(name, OBJPROP_PRICE3, 0);\r
1299          }\r
1300       }\r
1302       // is this an object that contains our command?\r
1303       if (StringFind(ObjectDescription(name), command) == 0) {\r
1304          price = 0;\r
1305          type = ObjectType(name);\r
1307          // we only care about certain types of objects\r
1308          if (type == OBJ_HLINE) {\r
1309             price = ObjectGet(name, OBJPROP_PRICE1);\r
1310          }\r
1311          if (type == OBJ_TREND) {\r
1312             price = ObjectGetValueByShift(name, 0);\r
1313          }\r
1315          if (price > 0) { // we found a line\r
1317             // ATTENTION! DIRTY HACK! MAY BREAK IN FUTURE VERSIONS OF MT4\r
1318             // ==========================================================\r
1319             // We store the last bid price in the unused PRICE3 field\r
1320             // of every line, so we can call this function more than once\r
1321             // per tick for multiple lines. A static variable would not work here\r
1322             // since we could not call the functon a second time during the same tick\r
1323             last_bid = ObjectGet(name, OBJPROP_PRICE3);\r
1325             // visually mark the line as an active line\r
1326             ObjectSet(name, OBJPROP_COLOR, clr_active);\r
1327             ObjectSet(name, OBJPROP_STYLE, STYLE_CROSSLINE_ACTIVE);\r
1328             ObjectSet(name, OBJPROP_WIDTH, WIDTH_CROSSLINE_ACTIVE);\r
1330             // we have a last_bid value for this line\r
1331             if (last_bid > 0) {\r
1333                // did price cross this line since the last time we checked this line?\r
1334                if ((Close[0] >= price && last_bid <= price) || (Close[0] <= price && last_bid >= price)) {\r
1336                   // extract the argument\r
1337                   command_line = ObjectDescription(name);\r
1338                   command_argument = StringSubstr(command_line, StringLen(command) + 1);\r
1339                   if (command_argument == "") {\r
1340                      command_argument = "1"; // default argument is "1"\r
1341                   }\r
1343                   // make the line triggered if it is a one shot command\r
1344                   if (one_shot) {\r
1345                      ObjectSetText(name, "triggered: " + command_line);\r
1346                      ObjectSet(name, OBJPROP_COLOR, clr_triggered);\r
1347                      ObjectSet(name, OBJPROP_STYLE, STYLE_CROSSLINE_TRIGGERED);\r
1348                      ObjectSet(name, OBJPROP_WIDTH, WIDTH_CROSSLINE_TRIGGERED);\r
1349                      ObjectSet(name, OBJPROP_PRICE3, 0);\r
1350                   } else {\r
1351                      ObjectSet(name, OBJPROP_PRICE3, Close[0]);\r
1352                   }\r
1354                   return(command_argument);\r
1355                }\r
1356             }\r
1358             // store current price in the line itself\r
1359             ObjectSet(name, OBJPROP_PRICE3, Close[0]);\r
1360          }\r
1361       }\r
1363    }\r
1365    return(""); // command line not crossed, return empty string (false)\r
1368 /**\r
1369 * Check if a line has been crossed, the line may contain a numeric value.\r
1370 * Call crossedLineS() (see crossedLineS() for more documentation)\r
1371 * and cast it into a number (will return 1 if the line has no argument)\r
1372 */\r
1373 double crossedLineD(string command, bool one_shot = true, color clr_active = CLR_NONE, color clr_triggered = CLR_NONE) {\r
1374    string arg = crossedLineS(string command, one_shot, clr_active, clr_triggered);\r
1375    return(StrToDouble(arg));\r
1379 /**\r
1380 * Check if a line has been crossed.\r
1381 * Call crossedLineS() (see crossedLineS() for more documentation)\r
1382 * and cast it into a bool (true if crossed, otherwise false)\r
1383 */\r
1384 bool crossedLine(string command, bool one_shot = true, color clr_active = CLR_NONE, color clr_triggered = CLR_NONE) {\r
1385    string arg = crossedLineS(string command, one_shot, clr_active, clr_triggered);\r
1386    return(arg != "");\r
1389 /**\r
1390 * Drop-in replacement for OrderModify().\r
1391 * Try to handle all errors and locks and return only if successful\r
1392 * or if the error can not be handled or waited for.\r
1393 */\r
1394 bool orderModifyReliable(\r
1395    int ticket,\r
1396    double price,\r
1397    double stoploss,\r
1398    double takeprofit,\r
1399    datetime expiration,\r
1400    color arrow_color = CLR_NONE\r
1401 ) {\r
1402    bool success;\r
1403    int err;\r
1404    Print("OrderModifyReliable(" + ticket + "," + price + "," + stoploss + "," + takeprofit + "," + expiration + "," + arrow_color + ")");\r
1405    while (True) {\r
1406       while (IsTradeContextBusy()) {\r
1407          Print("OrderModifyReliable(): Waiting for trade context.");\r
1408          Sleep(MathRand() / 10);\r
1409       }\r
1410       success = OrderModify(\r
1411                    ticket,\r
1412                    NormalizeDouble(price, Digits),\r
1413                    NormalizeDouble(stoploss, Digits),\r
1414                    NormalizeDouble(takeprofit, Digits),\r
1415                    expiration,\r
1416                    arrow_color);\r
1418       if (success) {\r
1419          Print("OrderModifyReliable(): Success!");\r
1420          return(True);\r
1421       }\r
1423       err = GetLastError();\r
1424       if (isTemporaryError(err)) {\r
1425          Print("orderModifyReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");\r
1426       } else {\r
1427          Print("orderModifyReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");\r
1428          return(false);\r
1429       }\r
1430       Sleep(MathRand() / 10);\r
1431    }\r
1434 /**\r
1435 * Drop-in replacement for OrderSend().\r
1436 * Try to handle all errors and locks and return only if successful\r
1437 * or if the error can not be handled or waited for.\r
1438 */\r
1439 int orderSendReliable(\r
1440    string symbol,\r
1441    int cmd,\r
1442    double volume,\r
1443    double price,\r
1444    int slippage,\r
1445    double stoploss,\r
1446    double takeprofit,\r
1447    string comment = "",\r
1448    int magic = 0,\r
1449    datetime expiration = 0,\r
1450    color arrow_color = CLR_NONE\r
1451 ) {\r
1452    int ticket;\r
1453    int err;\r
1454    Print("orderSendReliable("\r
1455          + symbol + ","\r
1456          + cmd + ","\r
1457          + volume + ","\r
1458          + price + ","\r
1459          + slippage + ","\r
1460          + stoploss + ","\r
1461          + takeprofit + ","\r
1462          + comment + ","\r
1463          + magic + ","\r
1464          + expiration + ","\r
1465          + arrow_color + ")");\r
1467    while (true) {\r
1468       if (IsStopped()) {\r
1469          Print("orderSendReliable(): Trading is stopped!");\r
1470          return(-1);\r
1471       }\r
1472       RefreshRates();\r
1473       if (cmd == OP_BUY) {\r
1474          price = Ask;\r
1475       }\r
1476       if (cmd == OP_SELL) {\r
1477          price = Bid;\r
1478       }\r
1479       if (!IsTradeContextBusy()) {\r
1480          ticket = OrderSend(\r
1481                      symbol,\r
1482                      cmd,\r
1483                      volume,\r
1484                      NormalizeDouble(price, MarketInfo(symbol, MODE_DIGITS)),\r
1485                      slippage,\r
1486                      NormalizeDouble(stoploss, MarketInfo(symbol, MODE_DIGITS)),\r
1487                      NormalizeDouble(takeprofit, MarketInfo(symbol, MODE_DIGITS)),\r
1488                      comment,\r
1489                      magic,\r
1490                      expiration,\r
1491                      arrow_color\r
1492                   );\r
1493          if (ticket > 0) {\r
1494             Print("orderSendReliable(): Success! Ticket: " + ticket);\r
1495             return(ticket); // the normal exit\r
1496          }\r
1498          err = GetLastError();\r
1499          if (isTemporaryError(err)) {\r
1500             Print("orderSendReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");\r
1501          } else {\r
1502             Print("orderSendReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");\r
1503             return(-1);\r
1504          }\r
1505       } else {\r
1506          Print("orderSendReliable(): Must wait for trade context");\r
1507       }\r
1508       Sleep(MathRand() / 10);\r
1509    }\r
1512 /**\r
1513 * Drop-in replacement for OrderClose().\r
1514 * Try to handle all errors and locks and return only if successful\r
1515 * or if the error can not be handled or waited for.\r
1516 */\r
1517 bool orderCloseReliable(\r
1518    int ticket,\r
1519    double lots,\r
1520    double price,\r
1521    int slippage,\r
1522    color arrow_color = CLR_NONE\r
1523 ) {\r
1524    bool success;\r
1525    int err;\r
1526    Print("orderCloseReliable()");\r
1527    OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES);\r
1528    while (true) {\r
1529       if (IsStopped()) {\r
1530          Print("orderCloseReliable(): Trading is stopped!");\r
1531          return(false);\r
1532       }\r
1533       RefreshRates();\r
1534       if (OrderType() == OP_BUY) {\r
1535          price = Bid; // close long at bid\r
1536       }\r
1537       if (OrderType() == OP_SELL) {\r
1538          price = Ask; // close short at ask\r
1539       }\r
1540       if (!IsTradeContextBusy()) {\r
1541          success = OrderClose(\r
1542                       ticket,\r
1543                       lots,\r
1544                       NormalizeDouble(price, MarketInfo(OrderSymbol(), MODE_DIGITS)),\r
1545                       slippage,\r
1546                       arrow_color\r
1547                    );\r
1548          if (success) {\r
1549             Print("orderCloseReliable(): Success!");\r
1550             return(true); // the normal exit\r
1551          }\r
1553          err = GetLastError();\r
1554          if (isTemporaryError(err)) {\r
1555             Print("orderCloseReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");\r
1556          } else {\r
1557             Print("orderCloseReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");\r
1558             return(false);\r
1559          }\r
1560       } else {\r
1561          Print("orderCloseReliable(): Must wait for trade context");\r
1562       }\r
1563       Sleep(MathRand() / 10);\r
1564    }\r
1567 /**\r
1568 * Drop-in replacement for OrderDelete().\r
1569 * Try to handle all errors and locks and return only if successful\r
1570 * or if the error can not be handled or waited for.\r
1571 */\r
1572 bool orderDeleteReliable(int ticket) {\r
1573    bool success;\r
1574    int err;\r
1575    Print("orderDeleteReliable(" + ticket + ")");\r
1576    while (true) {\r
1577       while (IsTradeContextBusy()) {\r
1578          Print("OrderDeleteReliable(): Waiting for trade context.");\r
1579          Sleep(MathRand() / 10);\r
1580       }\r
1582       success = OrderDelete(ticket);\r
1584       if (success) {\r
1585          Print("orderDeleteReliable(): success.");\r
1586          return(true);\r
1587       }\r
1589       err = GetLastError();\r
1590       if (isTemporaryError(err)) {\r
1591          Print("orderDeleteReliable(): Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting.");\r
1592       } else {\r
1593          Print("orderDeleteReliable(): Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up.");\r
1594          return(false);\r
1595       }\r
1596       Sleep(MathRand() / 10);\r
1597    }\r
1600 /**\r
1601 * Compare two prices.\r
1602 * The floating point precision of MT4 can make two seemingly identical values\r
1603 * still differ from each other. This function will compare two prices after\r
1604 * rounding them to the precision that is used for prices (value of Digits).\r
1605 */\r
1606 bool isEqualPrice(double a, double b) {\r
1607    return(NormalizeDouble(a, Digits) == NormalizeDouble(b, Digits));\r
1610 /**\r
1611 * Is the error temporary (does it make sense to wait).\r
1612 */\r
1613 bool isTemporaryError(int error) {\r
1614    return(\r
1615             error == ERR_NO_ERROR ||\r
1616             error == ERR_COMMON_ERROR ||\r
1617             error == ERR_SERVER_BUSY ||\r
1618             error == ERR_NO_CONNECTION ||\r
1619             error == ERR_MARKET_CLOSED ||\r
1620             error == ERR_PRICE_CHANGED ||\r
1621             error == ERR_INVALID_PRICE ||  //happens sometimes\r
1622             error == ERR_OFF_QUOTES ||\r
1623             error == ERR_BROKER_BUSY ||\r
1624             error == ERR_REQUOTE ||\r
1625             error == ERR_TRADE_TIMEOUT ||\r
1626             error == ERR_TRADE_CONTEXT_BUSY\r
1627          );\r
1631 /**\r
1632 * current symbol without broker initials.\r
1633 * Return only the first 6 letters of Symbol()\r
1634 * Some Brokers add their initials at the end\r
1635 * of their symbol names and some of my EAs and\r
1636 * indicators want a 6-character symbol name.\r
1637 */\r
1638 string Symbol6() {\r
1639    return(StringSubstr(Symbol(), 0, 6));\r
1643 /**\r
1644 * Get the list of symbols available on this platform.\r
1645 * The function will resize and fill the supplied array with\r
1646 * the symbol names and return the number of symbols.\r
1647 */\r
1648 int getSymbols(string &sym[]) {\r
1649    int i;\r
1650    string symbol;\r
1651    int f = FileOpenHistory("symbols.raw", FILE_BIN | FILE_READ);\r
1652    int count = FileSize(f) / 1936;\r
1653    ArrayResize(sym, count);\r
1654    for (i = 0; i < count; i++) {\r
1655       symbol = FileReadString(f, 12);\r
1656       sym[i] = symbol;\r
1657       FileSeek(f, 1924, SEEK_CUR);\r
1658    }\r
1659    FileClose(f);\r
1660    return(count);\r
1664 /**\r
1665 * Determine the pip multiplier.\r
1666 * Determine the pip multiplier (1 or 10) depending on how many\r
1667 * digits the EURUSD symbol has. This is done by first\r
1668 * finding the exact name of this symbol in the symbols.raw\r
1669 * file (it could be EURUSDm or EURUSDiam or any other stupid name\r
1670 * the broker comes up with only to break other people's code)\r
1671 * and then usig MarketInfo() for determining the digits.\r
1672 */\r
1673 double pointsPerPip() {\r
1674    int i;\r
1675    int digits;\r
1676    double ppp = 1;\r
1677    string symbol;\r
1678    int f = FileOpenHistory("symbols.raw", FILE_BIN | FILE_READ);\r
1679    int count = FileSize(f) / 1936;\r
1680    for (i = 0; i < count; i++) {\r
1681       symbol = FileReadString(f, 12);\r
1682       if (StringFind(symbol, "EURUSD") != -1) {\r
1683          digits = MarketInfo(symbol, MODE_DIGITS);\r
1684          if (digits == 4) {\r
1685             ppp = 1;\r
1686          } else {\r
1687             ppp = 10;\r
1688          }\r
1689          break;\r
1690       }\r
1691       FileSeek(f, 1924, SEEK_CUR);\r
1692    }\r
1693    FileClose(f);\r
1694    return (ppp);\r
1698 /**\r
1699 * return false only if this is called the first \r
1700 * time after a new bar has opened, true otherwise.\r
1701 * Common usage pattern:\r
1702 \r
1703 * int start(){\r
1704 *   if (isOldBar()) return(0);\r
1705 *   ...\r
1707 * this will extit start() immediately if\r
1708 * we are not at the opening of a new bar.\r
1709 */\r
1710 bool isOldBar(){\r
1711    static int time;\r
1712    if (time == Time[0]){\r
1713       return(true);\r
1714    }else{\r
1715       time = Time[0];\r
1716       return(false);\r
1717    }\r