-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathChartPatternHelper.mq5
1726 lines (1600 loc) · 84 KB
/
ChartPatternHelper.mq5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//+------------------------------------------------------------------+
//| Chart Pattern Helper |
//| Copyright © 2024, EarnForex.com |
//| https://www.earnforex.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2024, EarnForex"
#property link "https://www.earnforex.com/metatrader-expert-advisors/ChartPatternHelper/"
#property version "1.15"
#property description "Uses graphic objects (horizontal/trend lines, channels) to enter trades."
#property description "Works in two modes:"
#property description "1. Price is below upper entry and above lower entry. Only one or two pending stop orders are used."
#property description "2. Price is above upper entry or below lower entry. Only one pending limit order is used."
#property description "If an object is deleted/renamed after the pending order was placed, order will be canceled."
#property description "Pending order is removed if opposite entry is triggered."
#property description "Generally, it is safe to turn off the EA at any point."
#include <Trade/Trade.mqh>
#include <Trade/OrderInfo.mqh>
input group "Objects"
input string UpperBorderLine = "UpperBorder";
input string UpperEntryLine = "UpperEntry";
input string UpperTPLine = "UpperTP";
input string LowerBorderLine = "LowerBorder";
input string LowerEntryLine = "LowerEntry";
input string LowerTPLine = "LowerTP";
// The pattern may be given as trend/horizontal lines or equidistant channels.
input string BorderChannel = "Border";
input string EntryChannel = "Entry";
input string TPChannel = "TP";
input group "Order management"
// In case Channel is used for Entry, pending orders will be removed even if OneCancelsOther = false.
input bool OneCancelsOther = true; // OneCancelsOther: Remove opposite orders once position is open?
// If true, spread will be added to Buy entry level and Sell SL/TP levels. It compensates the difference when Ask price is used, while all chart objects are drawn at Bid level.
input bool UseSpreadAdjustment = false; // UseSpreadAdjustment: Add spread to Buy entry and Sell SL/TP?
// Not all brokers support expiration.
input bool UseExpiration = true; // UseExpiration: Use expiration on pending orders?
input bool DisableBuyOrders = false; // DisableBuyOrders: Disable new and ignore existing buy trades?
input bool DisableSellOrders = false; // DisableSellOrders: Disable new and ignore existing sell trades?
// If true, the EA will try to adjust SL after breakout candle is complete as it may no longer qualify for SL; it will make SL more precise but will mess up the money management a bit.
input bool PostEntrySLAdjustment = false; // PostEntrySLAdjustment: Adjust SL after entry?
input bool UseDistantSL = false; // UseDistantSL: If true, set SL to pattern's farthest point.
input group "Trendline trading"
input bool OpenOnCloseAboveBelowTrendline = false; // Open trade on close above/below trendline.
input string SLLine = "SL"; // Stop-loss line name for trendline trading.
input int ThresholdSpreads = 10; // Threshold Spreads: number of spreads for minimum distance.
input group "Position sizing"
input bool CalculatePositionSize = true; // CalculatePositionSize: Use money management module?
input bool UpdatePendingVolume = true; // UpdatePendingVolume: If true, recalculate pending order volume.
input double FixedPositionSize = 0.01; // FixedPositionSize: Used if CalculatePositionSize = false.
input double Risk = 1; // Risk: Risk tolerance in percentage points.
input double MoneyRisk = 0; // MoneyRisk: Risk tolerance in base currency.
input bool UseMoneyInsteadOfPercentage = false;
input bool UseEquityInsteadOfBalance = false;
input double FixedBalance = 0; // FixedBalance: If > 0, trade size calc. uses it as balance.
input group "Miscellaneous"
input int Magic = 20200530;
input int Slippage = 30; // Slippage: Maximum slippage in broker's pips.
input bool Silent = false; // Silent: If true, does not display any output via chart comment.
input bool ErrorLogging = true; // ErrorLogging: If true, errors will be logged to file.
// Global variables:
bool UseUpper, UseLower;
double UpperSL, UpperEntry, UpperTP, LowerSL, LowerEntry, LowerTP;
ulong UpperTicket, LowerTicket;
bool HaveBuyPending = false;
bool HaveSellPending = false;
bool HaveBuy = false;
bool HaveSell = false;
bool TDisabled = false; // Trading disabled.
bool ExpirationEnabled = UseExpiration;
bool PostBuySLAdjustmentDone = false, PostSellSLAdjustmentDone = false;
// For tick value adjustment.
string AccountCurrency = "";
string ProfitCurrency = "";
string BaseCurrency = "";
ENUM_SYMBOL_CALC_MODE CalcMode;
string ReferencePair = NULL;
bool ReferenceSymbolMode;
// MT5 specific:
datetime Time[];
double High[], Low[], Close[];
int SecondsPerBar = 0;
CTrade *Trade;
COrderInfo OrderInfo;
// For error logging:
string filename;
void OnInit()
{
PrepareTimeseries();
FindObjects();
SecondsPerBar = PeriodSeconds();
Trade = new CTrade;
Trade.SetDeviationInPoints(Slippage);
Trade.SetExpertMagicNumber(Magic);
// Do not use expiration if broker does not allow it.
int exp_mode = (int)SymbolInfoInteger(_Symbol, SYMBOL_EXPIRATION_MODE);
if ((exp_mode & 4) != 4) ExpirationEnabled = false;
if (ErrorLogging)
{
// Creating filename for error logging
MqlDateTime dt;
TimeToStruct(TimeLocal(), dt);
filename = "CPH-Errors-" + IntegerToString(dt.year) + IntegerToString(dt.mon, 2, '0') + IntegerToString(dt.day, 2, '0') + IntegerToString(dt.hour, 2, '0') + IntegerToString(dt.min, 2, '0') + IntegerToString(dt.sec, 2, '0') + ".log";
}
}
void OnDeinit(const int reason)
{
SetComment("");
delete Trade;
}
void OnTick()
{
DoRoutines();
}
void OnChartEvent(const int id, // Event ID
const long& lparam, // Parameter of type long event
const double& dparam, // Parameter of type double event
const string& sparam // Parameter of type string events
)
{
// If not an object change.
if ((id != CHARTEVENT_OBJECT_DRAG) && (id != CHARTEVENT_OBJECT_CREATE) && (id != CHARTEVENT_OBJECT_CHANGE) && (id != CHARTEVENT_OBJECT_DELETE) && (id != CHARTEVENT_OBJECT_ENDEDIT)) return;
// If not EA's objects.
if ((sparam != UpperBorderLine) && (sparam != UpperEntryLine) && (sparam != UpperTPLine) && (sparam != LowerBorderLine) && (sparam != LowerEntryLine) && (sparam != LowerTPLine) && (sparam != BorderChannel) && (sparam != EntryChannel) && (sparam != TPChannel)) return;
DoRoutines();
}
//+------------------------------------------------------------------+
//| Main handling function |
//+------------------------------------------------------------------+
void DoRoutines()
{
PrepareTimeseries();
FindOrders();
FindObjects();
AdjustOrders(); // And delete the ones no longer needed.
}
// Finds Entry, Border and TP objects. Detects respective levels according to found objects. Outputs found values to chart comment.
void FindObjects()
{
string c1 = FindUpperObjects();
string c2 = FindLowerObjects();
SetComment(c1 + c2);
}
// Adjustment for Ask/Bid spread is made for entry level as Long positions are entered at Ask, while all objects are drawn at Bid.
string FindUpperObjects()
{
string c = ""; // Text for chart comment
if (DisableBuyOrders)
{
UseUpper = false;
return "\nBuy orders disabled via input parameters.";
}
UseUpper = true;
// Entry.
if (OpenOnCloseAboveBelowTrendline) // Simple trendline entry doesn't need an entry line.
{
c = c + "\nUpper entry unnecessary.";
}
else if (ObjectFind(0, UpperEntryLine) > -1)
{
if ((ObjectGetInteger(0, UpperEntryLine, OBJPROP_TYPE) != OBJ_HLINE) && (ObjectGetInteger(0, UpperEntryLine, OBJPROP_TYPE) != OBJ_TREND))
{
Alert("Upper Entry Line should be either OBJ_HLINE or OBJ_TREND.");
return("\nWrong Upper Entry Line object type.");
}
if (ObjectGetInteger(0, UpperEntryLine, OBJPROP_TYPE) != OBJ_HLINE) UpperEntry = NormalizeDouble(ObjectGetValueByTime(0, UpperEntryLine, Time[0], 0), _Digits);
else UpperEntry = NormalizeDouble(ObjectGetDouble(0, UpperEntryLine, OBJPROP_PRICE, 0), _Digits); // Horizontal line value
if (UseSpreadAdjustment) UpperEntry = NormalizeDouble(UpperEntry + SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point, _Digits);
ObjectSetInteger(0, UpperEntryLine, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, UpperEntryLine, OBJPROP_RAY_RIGHT, true);
c += "\nUpper entry found. Level: " + DoubleToString(UpperEntry, _Digits);
}
else
{
if (ObjectFind(0, EntryChannel) > -1)
{
if (ObjectGetInteger(0, EntryChannel, OBJPROP_TYPE) != OBJ_CHANNEL)
{
Alert("Entry Channel should be OBJ_CHANNEL.");
return "\nWrong Entry Channel object type.";
}
UpperEntry = NormalizeDouble(FindUpperEntryViaChannel(), _Digits);
if (UseSpreadAdjustment) UpperEntry = NormalizeDouble(UpperEntry + SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point, _Digits);
ObjectSetInteger(0, EntryChannel, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, EntryChannel, OBJPROP_RAY_RIGHT, true);
c = c + "\nUpper entry found (via channel). Level: " + DoubleToString(UpperEntry, _Digits);
}
else
{
c = c + "\nUpper entry not found. No new position will be entered.";
UseUpper = false;
}
}
// Border
if (ObjectFind(0, UpperBorderLine) > -1)
{
if ((ObjectGetInteger(0, UpperBorderLine, OBJPROP_TYPE) != OBJ_HLINE) && (ObjectGetInteger(0, UpperBorderLine, OBJPROP_TYPE) != OBJ_TREND))
{
Alert("Upper Border Line should be either OBJ_HLINE or OBJ_TREND.");
return "\nWrong Upper Border Line object type.";
}
// Find upper SL
UpperSL = FindUpperSL();
ObjectSetInteger(0, UpperBorderLine, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, UpperBorderLine, OBJPROP_RAY_RIGHT, true);
c = c + "\nUpper border found. Upper stop-loss level: " + DoubleToString(UpperSL, _Digits);
}
else // Try to find a channel
{
if (ObjectFind(0, BorderChannel) > -1)
{
if (ObjectGetInteger(0, BorderChannel, OBJPROP_TYPE) != OBJ_CHANNEL)
{
Alert("Border Channel should be OBJ_CHANNEL.");
return "\nWrong Border Channel object type.";
}
// Find upper SL
UpperSL = FindUpperSLViaChannel();
ObjectSetInteger(0, BorderChannel, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, BorderChannel, OBJPROP_RAY_RIGHT, true);
c = c + "\nUpper border found (via channel). Upper stop-loss level: " + DoubleToString(UpperSL, _Digits);
}
else
{
c = c + "\nUpper border not found.";
if ((CalculatePositionSize) && (!HaveBuy))
{
UseUpper = false;
c = c + " Cannot trade without stop-loss, while CalculatePositionSize set to true.";
}
else
{
c = c + " Stop-loss won\'t be applied to new positions.";
// Track current SL, possibly installed by user
if ((OrderInfo.Select(UpperTicket)) && (OrderInfo.State() == ORDER_STATE_PLACED))
{
UpperSL = OrderInfo.StopLoss();
}
}
}
}
// Adjust upper SL for tick size granularity.
double TickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
UpperSL = NormalizeDouble(MathRound(UpperSL / TickSize) * TickSize, _Digits);
// Take-profit
if (ObjectFind(0, UpperTPLine) > -1)
{
if ((ObjectGetInteger(0, UpperTPLine, OBJPROP_TYPE) != OBJ_HLINE) && (ObjectGetInteger(0, UpperTPLine, OBJPROP_TYPE) != OBJ_TREND))
{
Alert("Upper TP Line should be either OBJ_HLINE or OBJ_TREND.");
return("\nWrong Upper TP Line object type.");
}
if (ObjectGetInteger(0, UpperTPLine, OBJPROP_TYPE) != OBJ_HLINE) UpperTP = NormalizeDouble(ObjectGetValueByTime(0, UpperTPLine, Time[0], 0), _Digits);
else UpperTP = NormalizeDouble(ObjectGetDouble(0, UpperTPLine, OBJPROP_PRICE, 0), _Digits); // Horizontal line value
ObjectSetInteger(0, UpperTPLine, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, UpperTPLine, OBJPROP_RAY_RIGHT, true);
c = c + "\nUpper take-profit found. Level: " + DoubleToString(UpperTP, _Digits);
}
else
{
if (ObjectFind(0, TPChannel) > -1)
{
if (ObjectGetInteger(0, TPChannel, OBJPROP_TYPE) != OBJ_CHANNEL)
{
Alert("TP Channel should be OBJ_CHANNEL.");
return "\nWrong TP Channel object type.";
}
UpperTP = FindUpperTPViaChannel();
ObjectSetInteger(0, TPChannel, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, TPChannel, OBJPROP_RAY_RIGHT, true);
c = c + "\nUpper TP found (via channel). Level: " + DoubleToString(UpperTP, _Digits);
}
else
{
c = c + "\nUpper take-profit not found. Take-profit won\'t be applied to new positions.";
// Track current TP, possibly installed by user.
if ((OrderInfo.Select(UpperTicket)) && (OrderInfo.State() == ORDER_STATE_PLACED))
{
UpperTP = OrderInfo.TakeProfit();
}
}
}
// Adjust upper TP for tick size granularity.
UpperTP = NormalizeDouble(MathRound(UpperTP / TickSize) * TickSize, _Digits);
return c;
}
// Adjustment for Ask/Bid spread is made for exit levels (SL and TP) as Short positions are exited at Ask, while all objects are drawn at Bid.
string FindLowerObjects()
{
string c = ""; // Text for chart comment
if (DisableSellOrders)
{
UseLower = false;
return "\nSell orders disabled via input parameters.";
}
UseLower = true;
// Entry.
if (OpenOnCloseAboveBelowTrendline) // Simple trendline entry doesn't need an entry line.
{
c = c + "\nLower entry unnecessary.";
}
else if (ObjectFind(0, LowerEntryLine) > -1)
{
if ((ObjectGetInteger(0, LowerEntryLine, OBJPROP_TYPE) != OBJ_HLINE) && (ObjectGetInteger(0, LowerEntryLine, OBJPROP_TYPE) != OBJ_TREND))
{
Alert("Lower Entry Line should be either OBJ_HLINE or OBJ_TREND.");
return("\nWrong Lower Entry Line object type.");
}
if (ObjectGetInteger(0, LowerEntryLine, OBJPROP_TYPE) != OBJ_HLINE) LowerEntry = NormalizeDouble(ObjectGetValueByTime(0, LowerEntryLine, Time[0], 0), _Digits);
else LowerEntry = NormalizeDouble(ObjectGetDouble(0, LowerEntryLine, OBJPROP_PRICE, 0), _Digits); // Horizontal line value
ObjectSetInteger(0, LowerEntryLine, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, LowerEntryLine, OBJPROP_RAY_RIGHT, true);
c = c + "\nLower entry found. Level: " + DoubleToString(LowerEntry, _Digits);
}
else
{
if (ObjectFind(0, EntryChannel) > -1)
{
if (ObjectGetInteger(0, EntryChannel, OBJPROP_TYPE) != OBJ_CHANNEL)
{
Alert("Entry Channel should be OBJ_CHANNEL.");
return "\nWrong Entry Channel object type.";
}
LowerEntry = FindLowerEntryViaChannel();
ObjectSetInteger(0, EntryChannel, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, EntryChannel, OBJPROP_RAY_RIGHT, true);
c = c + "\nLower entry found (via channel). Level: " + DoubleToString(LowerEntry, _Digits);
}
else
{
c = c + "\nLower entry not found. No new position will be entered.";
UseLower = false;
}
}
// Border
if (ObjectFind(0, LowerBorderLine) > -1)
{
if ((ObjectGetInteger(0, LowerBorderLine, OBJPROP_TYPE) != OBJ_HLINE) && (ObjectGetInteger(0, LowerBorderLine, OBJPROP_TYPE) != OBJ_TREND))
{
Alert("Lower Border Line should be either OBJ_HLINE or OBJ_TREND.");
return "\nWrong Lower Border Line object type.";
}
// Find Lower SL
LowerSL = NormalizeDouble(FindLowerSL(), _Digits);
if (UseSpreadAdjustment) LowerSL = NormalizeDouble(LowerSL + SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point, _Digits);
ObjectSetInteger(0, LowerBorderLine, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, LowerBorderLine, OBJPROP_RAY_RIGHT, true);
c = c + "\nLower border found. Lower stop-loss level: " + DoubleToString(LowerSL, _Digits);
}
else // Try to find a channel
{
if (ObjectFind(0, BorderChannel) > -1)
{
if (ObjectGetInteger(0, BorderChannel, OBJPROP_TYPE) != OBJ_CHANNEL)
{
Alert("Border Channel should be OBJ_CHANNEL.");
return "\nWrong Border Channel object type.";
}
// Find Lower SL
LowerSL = NormalizeDouble(FindLowerSLViaChannel(), _Digits);
if (UseSpreadAdjustment) LowerSL = NormalizeDouble(LowerSL + SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point, _Digits);
ObjectSetInteger(0, BorderChannel, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, BorderChannel, OBJPROP_RAY_RIGHT, true);
c = c + "\nLower border found (via channel). Lower stop-loss level: " + DoubleToString(LowerSL, _Digits);
}
else
{
c = c + "\nLower border not found.";
if ((CalculatePositionSize) && (!HaveSell))
{
UseLower = false;
c = c + " Cannot trade without stop-loss, while CalculatePositionSize set to true.";
}
else
{
c = c + " Stop-loss won\'t be applied to new positions.";
// Track current SL, possibly installed by user
if ((OrderInfo.Select(LowerTicket)) && (OrderInfo.State() == ORDER_STATE_PLACED))
{
LowerSL = OrderInfo.StopLoss();
}
}
}
}
// Adjust lower SL for tick size granularity.
double TickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
LowerSL = NormalizeDouble(MathRound(LowerSL / TickSize) * TickSize, _Digits);
// Take-profit
if (ObjectFind(0, LowerTPLine) > -1)
{
if ((ObjectGetInteger(0, LowerTPLine, OBJPROP_TYPE) != OBJ_HLINE) && (ObjectGetInteger(0, LowerTPLine, OBJPROP_TYPE) != OBJ_TREND))
{
Alert("Lower TP Line should be either OBJ_HLINE or OBJ_TREND.");
return "\nWrong Lower TP Line object type.";
}
if (ObjectGetInteger(0, LowerTPLine, OBJPROP_TYPE) != OBJ_HLINE) LowerTP = NormalizeDouble(ObjectGetValueByTime(0, LowerTPLine, Time[0], 0), _Digits);
else LowerTP = NormalizeDouble(ObjectGetDouble(0, LowerTPLine, OBJPROP_PRICE, 0), _Digits); // Horizontal line value
if (UseSpreadAdjustment) LowerTP = NormalizeDouble(LowerTP + SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point, _Digits);
ObjectSetInteger(0, LowerTPLine, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, LowerTPLine, OBJPROP_RAY_RIGHT, true);
c = c + "\nLower take-profit found. Level: " + DoubleToString(LowerTP, _Digits);
}
else
{
if (ObjectFind(0, TPChannel) > -1)
{
if (ObjectGetInteger(0, TPChannel, OBJPROP_TYPE) != OBJ_CHANNEL)
{
Alert("TP Channel should be OBJ_CHANNEL.");
return "\nWrong TP Channel object type.";
}
LowerTP = NormalizeDouble(FindLowerTPViaChannel(), _Digits);
if (UseSpreadAdjustment) LowerTP = NormalizeDouble(LowerTP + SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point, _Digits);
ObjectSetInteger(0, TPChannel, OBJPROP_RAY_LEFT, true);
ObjectSetInteger(0, TPChannel, OBJPROP_RAY_RIGHT, true);
c = c + "\nLower TP found (via channel). Level: " + DoubleToString(LowerTP, _Digits);
}
else
{
c = c + "\nLower take-profit not found. Take-profit won\'t be applied to new positions.";
// Track current TP, possibly installed by user
if ((OrderInfo.Select(LowerTicket)) && (OrderInfo.State() == ORDER_STATE_PLACED))
{
LowerTP = OrderInfo.TakeProfit();
}
}
}
// Adjust lower TP for tick size granularity.
LowerTP = NormalizeDouble(MathRound(LowerTP / TickSize) * TickSize, _Digits);
return c;
}
// Find SL using a border line - the low of the first bar with major part below border.
double FindUpperSL()
{
// Invalid value will prevent order from executing in case something goes wrong.
double SL = -1;
// Everything becomes much easier if the EA just needs to find the farthest opposite point of the pattern.
if (UseDistantSL)
{
// Horizontal line.
if (ObjectGetInteger(0, LowerBorderLine, OBJPROP_TYPE) == OBJ_HLINE)
{
return NormalizeDouble(ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE, 0), _Digits);
}
// Trend line.
else if (ObjectGetInteger(0, LowerBorderLine, OBJPROP_TYPE) == OBJ_TREND)
{
double price1 = ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE, 0);
double price2 = ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE, 1);
if (price1 < price2) return(NormalizeDouble(price1, _Digits));
else return NormalizeDouble(price2, _Digits);
}
}
// Easy stop-loss via a separate horizontal line when using trendline trading.
if (OpenOnCloseAboveBelowTrendline)
{
if (ObjectFind(0, SLLine) < 0) return -1;
return NormalizeDouble(ObjectGetDouble(0, SLLine, OBJPROP_PRICE, 0), _Digits);
}
for (int i = 0; i < Bars(_Symbol, _Period); i++)
{
double Border, Entry;
if (ObjectGetInteger(0, UpperBorderLine, OBJPROP_TYPE) != OBJ_HLINE) Border = ObjectGetValueByTime(0, UpperBorderLine, Time[i], 0);
else Border = ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE, 0); // Horizontal line value
if (ObjectGetInteger(0, UpperEntryLine, OBJPROP_TYPE) != OBJ_HLINE) Entry = ObjectGetValueByTime(0, UpperEntryLine, Time[i], 0);
else Entry = ObjectGetDouble(0, UpperEntryLine, OBJPROP_PRICE, 0); // Horizontal line value
// Additional condition (Entry) checks whether _current_ candle may still have a bigger part within border before triggering entry.
// It is not possible if the current height inside border is not bigger than the distance from border to entry.
// It should not be checked for candles already completed.
// Additionally, if skipped the first bar because it could not potentially qualify, next bar's Low should be lower or equal to that of the first bar.
if ((Border - Low[i] > High[i] - Border) && ((Entry - Border < Border - Low[i]) || (i != 0)) && (Low[i] <= Low[0])) return NormalizeDouble(Low[i], _Digits);
}
return SL;
}
// Find SL using a border line - the high of the first bar with major part above border.
double FindLowerSL()
{
// Invalid value will prevent order from executing in case something goes wrong.
double SL = -1;
// Everything becomes much easier if the EA just needs to find the farthest opposite point of the pattern.
if (UseDistantSL)
{
// Horizontal line.
if (ObjectGetInteger(0, UpperBorderLine, OBJPROP_TYPE) == OBJ_HLINE)
{
return NormalizeDouble(ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE, 0), _Digits);
}
// Trend line.
else if (ObjectGetInteger(0, UpperBorderLine, OBJPROP_TYPE) == OBJ_TREND)
{
double price1 = ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE, 0);
double price2 = ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE, 1);
if (price1 > price2) return NormalizeDouble(price1, _Digits);
else return NormalizeDouble(price2, _Digits);
}
}
// Easy stop-loss via a separate horizontal line when using trendline trading.
if (OpenOnCloseAboveBelowTrendline)
{
if (ObjectFind(0, SLLine) < 0) return -1;
return NormalizeDouble(ObjectGetDouble(0, SLLine, OBJPROP_PRICE, 0), _Digits);
}
for (int i = 0; i < Bars(_Symbol, _Period); i++)
{
double Border, Entry;
if (ObjectGetInteger(0, LowerBorderLine, OBJPROP_TYPE) != OBJ_HLINE) Border = ObjectGetValueByTime(0, LowerBorderLine, Time[i], 0);
else Border = ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE, 0); // Horizontal line value
if (ObjectGetInteger(0, LowerEntryLine, OBJPROP_TYPE) != OBJ_HLINE) Entry = ObjectGetValueByTime(0, LowerEntryLine, Time[i], 0);
else Entry = ObjectGetDouble(0, LowerEntryLine, OBJPROP_PRICE, 0); // Horizontal line value
// Additional condition (Entry) checks whether _current_ candle may still have a bigger part within border before triggering entry.
// It is not possible if the current height inside border is not bigger than the distance from border to entry.
// It should not be checked for candles already completed.
// Additionally, if skipped the first bar because it could not potentially qualify, next bar's High should be higher or equal to that of the first bar.
if ((High[i] - Border > Border - Low[i]) && ((Border - Entry < High[i] - Border) || (i != 0)) && (High[i] >= High[0])) return NormalizeDouble(High[i], _Digits);
}
return SL;
}
// Find SL using a border channel - the low of the first bar with major part below upper line.
double FindUpperSLViaChannel()
{
// Invalid value will prevent order from executing in case something goes wrong.
double SL = -1;
// Easy stop-loss via a separate horizontal line when using trendline trading.
if (OpenOnCloseAboveBelowTrendline)
{
if (ObjectFind(0, SLLine) < 0) return -1;
return NormalizeDouble(ObjectGetDouble(0, SLLine, OBJPROP_PRICE, 0), _Digits);
}
for (int i = 0; i < Bars(_Symbol, _Period); i++)
{
// Get the upper of main and auxiliary lines
double Border = MathMax(ObjectGetValueByTime(0, BorderChannel, Time[i], 0), ObjectGetValueByTime(0, BorderChannel, Time[i], 1));
// Additional condition (Entry) checks whether _current_ candle may still have a bigger part within border before triggering entry.
// It is not possible if the current height inside border is not bigger than the distance from border to entry.
// It should not be checked for candles already completed.
// Additionally, if skipped the first bar because it could not potentially qualify, next bar's Low should be lower or equal to that of the first bar.
if ((Border - Low[i] > High[i] - Border) && ((UpperEntry - Border < Border - Low[i]) || (i != 0)) && (Low[i] <= Low[0])) return NormalizeDouble(Low[i], _Digits);
}
return SL;
}
// Find SL using a border channel - the high of the first bar with major part above upper line.
double FindLowerSLViaChannel()
{
// Invalid value will prevent order from executing in case something goes wrong.
double SL = -1;
// Easy stop-loss via a separate horizontal line when using trendline trading.
if (OpenOnCloseAboveBelowTrendline)
{
if (ObjectFind(0, SLLine) < 0) return -1;
return NormalizeDouble(ObjectGetDouble(0, SLLine, OBJPROP_PRICE, 0), _Digits);
}
for (int i = 0; i < Bars(_Symbol, _Period); i++)
{
// Get the lower of main and auxiliary lines
double Border = MathMin(ObjectGetValueByTime(0, BorderChannel, Time[i], 0), ObjectGetValueByTime(0, BorderChannel, Time[i], 1));
// Additional condition (Entry) checks whether _current_ candle may still have a bigger part within border before triggering entry.
// It is not possible if the current height inside border is not bigger than the distance from border to entry.
// It should not be checked for candles already completed.
// Additionally, if skipped the first bar because it could not potentially qualify, next bar's High should be higher or equal to that of the first bar.
if ((High[i] - Border > Border - Low[i]) && ((Border - LowerEntry < High[i] - Border) || (i != 0)) && (High[i] >= High[0])) return NormalizeDouble(High[i], _Digits);
}
return SL;
}
// Find entry point using the entry channel.
double FindUpperEntryViaChannel()
{
// Invalid value will prevent order from executing in case something goes wrong.
double Entry = -1;
// Get the upper of main and auxiliary lines
Entry = MathMax(ObjectGetValueByTime(0, EntryChannel, Time[0], 0), ObjectGetValueByTime(0, EntryChannel, Time[0], 1));
return NormalizeDouble(Entry, _Digits);
}
// Find entry point using the entry channel.
double FindLowerEntryViaChannel()
{
// Invalid value will prevent order from executing in case something goes wrong.
double Entry = -1;
// Get the lower of main and auxiliary lines
Entry = MathMin(ObjectGetValueByTime(0, EntryChannel, Time[0], 0), ObjectGetValueByTime(0, EntryChannel, Time[0], 1));
return NormalizeDouble(Entry, _Digits);
}
// Find TP using the TP channel.
double FindUpperTPViaChannel()
{
// Invalid value will prevent order from executing in case something goes wrong.
double TP = -1;
// Get the upper of main and auxiliary lines.
TP = MathMax(ObjectGetValueByTime(0, TPChannel, Time[0], 0), ObjectGetValueByTime(0, TPChannel, Time[0], 1));
return NormalizeDouble(TP, _Digits);
}
// Find TP using the TP channel.
double FindLowerTPViaChannel()
{
// Invalid value will prevent order from executing in case something goes wrong.
double TP = -1;
// Get the lower of main and auxiliary lines.
TP = MathMin(ObjectGetValueByTime(0, TPChannel, Time[0], 0), ObjectGetValueByTime(0, TPChannel, Time[0], 1));
return NormalizeDouble(TP, _Digits);
}
void AdjustOrders()
{
AdjustObjects(); // Rename objects if pending orders got executed.
AdjustUpperAndLowerOrders();
}
// Sets flags according to found pending orders and positions.
void FindOrders()
{
HaveBuyPending = false;
HaveSellPending = false;
HaveBuy = false;
HaveSell = false;
if (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) // Netting:
{
if (PositionSelect(_Symbol))
{
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) HaveBuy = true;
else HaveSell = true;
}
}
else // Hednging/exchange:
{
for (int i = 0; i < PositionsTotal(); i++)
{
if ((PositionGetString(POSITION_SYMBOL) != _Symbol) || (PositionGetInteger(POSITION_MAGIC) != Magic)) continue;
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) HaveBuy = true;
else HaveSell = true;
}
}
for (int i = 0; i < OrdersTotal(); i++)
{
OrderGetTicket(i);
if (((OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_BUY_STOP) || (OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_BUY_LIMIT)) && (OrderGetString(ORDER_SYMBOL) == _Symbol) && (OrderGetInteger(ORDER_MAGIC) == Magic))
{
HaveBuyPending = true;
UpperTicket = OrderGetTicket(i);
}
else if (((OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_SELL_STOP) || (OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_SELL_LIMIT)) && (OrderGetString(ORDER_SYMBOL) == _Symbol) && (OrderGetInteger(ORDER_MAGIC) == Magic))
{
HaveSellPending = true;
LowerTicket = OrderGetTicket(i);
}
}
}
// Renaming objects prevents new position opening.
void AdjustObjects()
{
if (((HaveBuy) && (!HaveBuyPending)))
{
if ((ObjectFind(0, UpperBorderLine) >= 0) || (ObjectFind(0, EntryChannel) >= 0))
{
Print("Buy position found, renaming chart objects...");
RenameObject(UpperBorderLine);
RenameObject(UpperEntryLine);
RenameObject(EntryChannel);
}
if (OneCancelsOther)
{
if ((ObjectFind(0, LowerBorderLine) >= 0) || (ObjectFind(0, BorderChannel) >= 0))
{
Print("OCO is on, renaming opposite chart objects...");
RenameObject(LowerBorderLine);
RenameObject(LowerEntryLine);
RenameObject(BorderChannel);
}
}
}
if (((HaveSell) && (!HaveSellPending)))
{
if ((ObjectFind(0, LowerEntryLine) >= 0) || (ObjectFind(0, EntryChannel) >= 0))
{
Print("Sell position found, renaming chart objects...");
RenameObject(LowerBorderLine);
RenameObject(LowerEntryLine);
RenameObject(EntryChannel);
}
if (OneCancelsOther)
{
if ((ObjectFind(0, UpperBorderLine) >= 0) || (ObjectFind(0, BorderChannel) >= 0))
{
Print("OCO is on, renaming opposite chart objects...");
RenameObject(UpperBorderLine);
RenameObject(UpperEntryLine);
RenameObject(BorderChannel);
}
}
}
}
void RenameObject(string Object)
{
if (ObjectFind(0, Object) > -1) // If exists.
{
Print("Renaming ", Object, ".");
ObjectSetString(0, Object, OBJPROP_NAME, Object + IntegerToString(Magic));
}
}
// The main trading procedure. Sends, Modifies and Deletes orders.
void AdjustUpperAndLowerOrders()
{
double NewVolume;
int last_error;
datetime expiration;
ENUM_ORDER_TYPE_TIME type_time;
ENUM_ORDER_TYPE order_type;
string order_type_string;
if ((!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) || (!TerminalInfoInteger(TERMINAL_CONNECTED)) || (SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_FULL))
{
if (!TDisabled) Output("Trading disabled or disconnected.");
TDisabled = true;
return;
}
else if (TDisabled)
{
Output("Trading is no longer disabled or disconnected.");
TDisabled = false;
}
double StopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
double FreezeLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL) * _Point;
double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double TickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double LotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
int LotStep_digits = CountDecimalPlaces(LotStep);
if (OpenOnCloseAboveBelowTrendline) // Simple case.
{
double BorderLevel;
if ((LowerTP > 0) && (!HaveSell) && (UseLower)) // SELL.
{
if (ObjectFind(0, LowerBorderLine) >= 0) // Line.
{
if (ObjectGetInteger(ChartID(), LowerBorderLine, OBJPROP_TYPE) == OBJ_HLINE) BorderLevel = NormalizeDouble(ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE, 0), _Digits);
else BorderLevel = NormalizeDouble(ObjectGetValueByTime(ChartID(), LowerBorderLine, Time[1]), _Digits);
}
else // Channel
{
BorderLevel = MathMin(ObjectGetValueByTime(0, BorderChannel, Time[1], 0), ObjectGetValueByTime(0, BorderChannel, Time[1], 1));
}
BorderLevel = NormalizeDouble(MathRound(BorderLevel / TickSize) * TickSize, _Digits);
// Previous candle close significantly lower than the border line.
if (BorderLevel - Close[1] >= SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) * _Point * ThresholdSpreads)
{
NewVolume = GetPositionSize(Bid, LowerSL, ORDER_TYPE_SELL);
LowerTicket = ExecuteMarketOrder(ORDER_TYPE_SELL, NewVolume, Bid, LowerSL, LowerTP);
}
}
else if ((UpperTP > 0) && (!HaveBuy) && (UseUpper)) // BUY.
{
if (ObjectFind(0, UpperBorderLine) >= 0) // Line.
{
if (ObjectGetInteger(ChartID(), UpperBorderLine, OBJPROP_TYPE) == OBJ_HLINE) BorderLevel = NormalizeDouble(ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE, 0), _Digits);
else BorderLevel = NormalizeDouble(ObjectGetValueByTime(ChartID(), UpperBorderLine, Time[1]), _Digits);
}
else // Channel
{
BorderLevel = MathMax(ObjectGetValueByTime(0, BorderChannel, Time[1], 0), ObjectGetValueByTime(0, BorderChannel, Time[1], 1));
}
BorderLevel = NormalizeDouble(MathRound(BorderLevel / TickSize) * TickSize, _Digits);
// Previous candle close significantly higher than the border line.
if (Close[1] - BorderLevel >= SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) * _Point * ThresholdSpreads)
{
NewVolume = GetPositionSize(Ask, UpperSL, ORDER_TYPE_BUY);
UpperTicket = ExecuteMarketOrder(ORDER_TYPE_BUY, NewVolume, Ask, UpperSL, UpperTP);
}
}
return;
}
// Have open position.
if (PositionSelect(_Symbol))
{
// PostEntrySLAdjustment - a procedure to correct SL if breakout candle become too long and no longer qualifies for SL rule.
if ((PositionGetInteger(POSITION_TIME) > Time[1]) && (PositionGetInteger(POSITION_TIME) < Time[0]) && (PostEntrySLAdjustment) && (PostBuySLAdjustmentDone == false))
{
double SL = AdjustPostBuySL();
if (SL != -1)
{
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(PositionGetDouble(POSITION_PRICE_OPEN) - Ask) <= FreezeLevel))
{
Output("Skipping Modify Buy Stop SL because open price is too close to Ask. FreezeLevel = " + DoubleToString(FreezeLevel, _Digits) + " OpenPrice = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " Ask = " + DoubleToString(Ask, _Digits));
}
else
{
if (NormalizeDouble(SL, _Digits) == NormalizeDouble(PositionGetDouble(POSITION_SL), _Digits)) PostBuySLAdjustmentDone = true;
else
{
if (!Trade.PositionModify(_Symbol, SL, PositionGetDouble(POSITION_TP)))
{
last_error = GetLastError();
Output("Error Modifying Buy SL: " + IntegerToString(last_error) + " (" + Trade.ResultRetcodeDescription() + ")");
Output("FROM: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) + " SL = " + DoubleToString(PositionGetDouble(POSITION_SL), _Digits) + " -> TO: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " SL = " + DoubleToString(SL, 8) + " Ask = " + DoubleToString(Ask, _Digits));
}
else PostBuySLAdjustmentDone = true;
}
}
}
}
// Adjust TP only.
if ((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) && (!DisableBuyOrders))
{
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(PositionGetDouble(POSITION_PRICE_OPEN) - Ask) <= FreezeLevel))
{
Output("Skipping Modify Buy TP because open price is too close to Ask. FreezeLevel = " + DoubleToString(FreezeLevel, _Digits) + " OpenPrice = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " Ask = " + DoubleToString(Ask, _Digits));
}
else if ((MathAbs(PositionGetDouble(POSITION_TP) - UpperTP) > _Point / 2) && (UpperTP != 0))
{
if (!Trade.PositionModify(_Symbol, PositionGetDouble(POSITION_SL), UpperTP))
{
last_error = GetLastError();
Output("Error Modifying Buy TP: " + IntegerToString(last_error) + " (" + Trade.ResultRetcodeDescription() + ")");
Output("FROM: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) + " TP = " + DoubleToString(PositionGetDouble(POSITION_TP), _Digits) + " -> TO: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " TP = " + DoubleToString(UpperTP, 8) + " Ask = " + DoubleToString(Ask, _Digits));
}
}
}
else if ((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) && (!DisableSellOrders))
{
// PostEntrySLAdjustment - a procedure to correct SL if breakout candle become too long and no longer qualifies for SL rule.
if ((PositionGetInteger(POSITION_TIME) > Time[1]) && (PositionGetInteger(POSITION_TIME) < Time[0]) && (PostEntrySLAdjustment) && (PostSellSLAdjustmentDone == false))
{
double SL = AdjustPostSellSL();
if (SL != -1)
{
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(Bid - PositionGetDouble(POSITION_PRICE_OPEN)) <= FreezeLevel))
{
Output("Skipping Modify Sell Stop SL because open price is too close to Bid. FreezeLevel = " + DoubleToString(FreezeLevel, _Digits) + " OpenPrice = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " Bid = " + DoubleToString(Bid, _Digits));
}
else
{
if (NormalizeDouble(SL, _Digits) == NormalizeDouble(PositionGetDouble(POSITION_SL), _Digits)) PostSellSLAdjustmentDone = true;
else
{
if (!Trade.PositionModify(_Symbol, SL, PositionGetDouble(POSITION_TP)))
{
last_error = GetLastError();
Output("Error Modifying Sell SL: " + IntegerToString(last_error) + " (" + Trade.ResultRetcodeDescription() + ")");
Output("FROM: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) + " SL = " + DoubleToString(PositionGetDouble(POSITION_SL), _Digits) + " -> TO: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " SL = " + DoubleToString(SL, 8) + " Bid = " + DoubleToString(Bid, _Digits));
}
else PostSellSLAdjustmentDone = true;
}
}
}
}
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(Bid - PositionGetDouble(POSITION_PRICE_OPEN)) <= FreezeLevel))
{
Output("Skipping Modify Sell TP because open price is too close to Bid. FreezeLevel = " + DoubleToString(FreezeLevel, _Digits) + " OpenPrice = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " Bid = " + DoubleToString(Bid, _Digits));
}
else if ((MathAbs(PositionGetDouble(POSITION_TP) - LowerTP) > _Point / 2) && (NormalizeDouble(LowerTP, _Digits) != 0))
{
if (!Trade.PositionModify(_Symbol, PositionGetDouble(POSITION_SL), LowerTP))
{
last_error = GetLastError();
Output("Error Modifying Sell TP: " + IntegerToString(last_error) + " (" + Trade.ResultRetcodeDescription() + ")");
Output("FROM: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) + " TP = " + DoubleToString(PositionGetDouble(POSITION_TP), _Digits) + " -> TO: Entry = " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), 8) + " TP = " + DoubleToString(LowerTP, 8) + " Bid = " + DoubleToString(Bid, _Digits));
}
}
}
}
for (int i = 0; i < OrdersTotal(); i++)
{
ulong ticket = OrderGetTicket(i);
// Refresh rates.
Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// BUY.
if (((OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_BUY_STOP) || (OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_BUY_LIMIT)) && (OrderGetString(ORDER_SYMBOL) == _Symbol) && (OrderGetInteger(ORDER_MAGIC) == Magic) && (!DisableBuyOrders))
{
// Current price is below Sell entry - pending Sell Limit will be used instead of two stop orders.
if ((LowerEntry - Bid > StopLevel) && (UseLower)) continue;
NewVolume = GetPositionSize(UpperEntry, UpperSL, ORDER_TYPE_BUY);
// Delete existing pending order
if ((HaveBuy) || ((HaveSell) && (OneCancelsOther)) || (!UseUpper)) Trade.OrderDelete(ticket);
// If volume needs to be updated - delete and recreate order with new volume. Also check if EA will be able to create new pending order at current price.
else if ((UpdatePendingVolume) && (MathAbs(OrderGetDouble(ORDER_VOLUME_CURRENT) - NewVolume) > LotStep / 2))
{
if ((UpperEntry - Ask > StopLevel) || (Ask - UpperEntry > StopLevel)) // Order can be re-created
{
Trade.OrderDelete(ticket);
}
else continue;
// Ask could change after deletion, check if there is still no error 130 present.
Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if (UpperEntry - Ask > StopLevel) // Current price below entry
{
order_type = ORDER_TYPE_BUY_STOP;
order_type_string = "Stop";
}
else if (Ask - UpperEntry > StopLevel) // Current price above entry
{
order_type = ORDER_TYPE_BUY_LIMIT;
order_type_string = "Limit";
}
else continue;
if (ExpirationEnabled)
{
type_time = ORDER_TIME_SPECIFIED;
// Set expiration to the end of the current bar.
expiration = Time[0] + GetSecondsPerBar();
// 2 minutes seem to be the actual minimum expiration time.
if (expiration - TimeCurrent() < 121) expiration = TimeCurrent() + 121;
}
else
{
expiration = 0;
type_time = ORDER_TIME_GTC;
}
if (!Trade.OrderOpen(_Symbol, order_type, NewVolume, 0, UpperEntry, UpperSL, UpperTP, type_time, expiration, "ChartPatternHelper"))
{
last_error = GetLastError();
Output("StopLevel = " + DoubleToString(StopLevel, 8));
Output("FreezeLevel = " + DoubleToString(FreezeLevel, 8));
Output("Error Recreating Buy " + order_type_string + ": " + IntegerToString(last_error) + " (" + Trade.ResultRetcodeDescription() + ")");
Output("Volume = " + DoubleToString(NewVolume, LotStep_digits) + " Entry = " + DoubleToString(UpperEntry, _Digits) + " SL = " + DoubleToString(UpperSL, _Digits) + " TP = " + DoubleToString(UpperTP, _Digits) + " Bid/Ask = " + DoubleToString(Bid, _Digits) + "/" + DoubleToString(Ask, _Digits) + " Exp: " + TimeToString(expiration, TIME_DATE | TIME_SECONDS));
}
else
{
UpperTicket = Trade.ResultOrder();
}
continue;
}
// Otherwise, just update what needs to be updated.
else if ((MathAbs(OrderGetDouble(ORDER_PRICE_OPEN) - UpperEntry) > _Point / 2) || (MathAbs(OrderGetDouble(ORDER_SL) - UpperSL) > _Point / 2) || (MathAbs(OrderGetDouble(ORDER_TP) - UpperTP) > _Point / 2))
{
// Avoid error 130 based on entry.
if (UpperEntry - Ask > StopLevel) // Current price below entry
{