Files
ironsan2kk-pixel 6450d1efc6 Fix download links
2026-06-08 10:36:15 +02:00

1657 lines
80 KiB
Plaintext

//+------------------------------------------------------------------+
//| 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 strict
#include <stdlib.mqh>
#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."
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;
int UpperTicket, LowerTicket;
bool HaveBuyPending = false;
bool HaveSellPending = false;
bool HaveBuy = false;
bool HaveSell = false;
bool TCBusy = false;
bool PostBuySLAdjustmentDone = false, PostSellSLAdjustmentDone = false;
// For tick value adjustment:
string ProfitCurrency = "", account_currency = "", BaseCurrency = "", ReferenceSymbol = NULL, AdditionalReferenceSymbol = NULL;
bool ReferenceSymbolMode, AdditionalReferenceSymbolMode;
int ProfitCalcMode;
double TickSize;
// For error logging:
string filename;
void OnInit()
{
FindObjects();
if (ErrorLogging)
{
datetime tl = TimeLocal();
string mon = IntegerToString(TimeMonth(tl));
if (StringLen(mon) == 1) mon = "0" + mon;
string day = IntegerToString(TimeDay(tl));
if (StringLen(day) == 1) day = "0" + day;
string hour = IntegerToString(TimeHour(tl));
if (StringLen(hour) == 1) hour = "0" + hour;
string min = IntegerToString(TimeMinute(tl));
if (StringLen(min) == 1) min = "0" + min;
string sec = IntegerToString(TimeSeconds(tl));
if (StringLen(sec) == 1) sec = "0" + sec;
filename = "CPH-Errors-" + IntegerToString(TimeYear(tl)) + mon + day + hour + min + sec + ".log";
}
}
void OnDeinit(const int reason)
{
SetComment("");
}
void OnTick()
{
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(UpperEntryLine) > -1)
{
if ((ObjectType(UpperEntryLine) != OBJ_HLINE) && (ObjectType(UpperEntryLine) != OBJ_TREND))
{
Alert("Upper Entry Line should be either OBJ_HLINE or OBJ_TREND.");
return("\nWrong Upper Entry Line object type.");
}
if (ObjectType(UpperEntryLine) != OBJ_HLINE) UpperEntry = NormalizeDouble(ObjectGetValueByShift(UpperEntryLine, 0), Digits);
else UpperEntry = NormalizeDouble(ObjectGet(UpperEntryLine, OBJPROP_PRICE1), Digits); // Horizontal line value
if (UseSpreadAdjustment) UpperEntry = NormalizeDouble(UpperEntry + MarketInfo(Symbol(), MODE_SPREAD) * Point, Digits);
ObjectSet(UpperEntryLine, OBJPROP_RAY, true);
c = c + "\nUpper entry found. Level: " + DoubleToStr(UpperEntry, Digits);
}
else
{
if (ObjectFind(EntryChannel) > -1)
{
if (ObjectType(EntryChannel) != OBJ_CHANNEL)
{
Alert("Entry Channel should be OBJ_CHANNEL.");
return "\nWrong Entry Channel object type.";
}
UpperEntry = NormalizeDouble(FindUpperEntryViaChannel(), Digits);
if (UseSpreadAdjustment) UpperEntry = NormalizeDouble(UpperEntry + MarketInfo(Symbol(), MODE_SPREAD) * Point, Digits);
ObjectSet(EntryChannel, OBJPROP_RAY, true);
c = c + "\nUpper entry found (via channel). Level: " + DoubleToStr(UpperEntry, Digits);
}
else
{
c = c + "\nUpper entry not found. No new position will be entered.";
UseUpper = false;
}
}
// Border
if (ObjectFind(UpperBorderLine) > -1)
{
if ((ObjectType(UpperBorderLine) != OBJ_HLINE) && (ObjectType(UpperBorderLine) != 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();
ObjectSet(UpperBorderLine, OBJPROP_RAY, true);
c = c + "\nUpper border found. Upper stop-loss level: " + DoubleToStr(UpperSL, Digits);
}
else // Try to find a channel.
{
if (ObjectFind(BorderChannel) > -1)
{
if (ObjectType(BorderChannel) != OBJ_CHANNEL)
{
Alert("Border Channel should be OBJ_CHANNEL.");
return "\nWrong Border Channel object type.";
}
// Find upper SL
UpperSL = FindUpperSLViaChannel();
ObjectSet(BorderChannel, OBJPROP_RAY, true);
c = c + "\nUpper border found (via channel). Upper stop-loss level: " + DoubleToStr(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 ((OrderSelect(UpperTicket, SELECT_BY_TICKET)) && ((OrderType() == OP_BUYSTOP) || (OrderType() == OP_BUYLIMIT)))
{
UpperSL = OrderStopLoss();
}
}
}
}
// Adjust upper SL for tick size granularity.
TickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
UpperSL = NormalizeDouble(MathRound(UpperSL / TickSize) * TickSize, _Digits);
// Take-profit.
if (ObjectFind(UpperTPLine) > -1)
{
if ((ObjectType(UpperTPLine) != OBJ_HLINE) && (ObjectType(UpperTPLine) != OBJ_TREND))
{
Alert("Upper TP Line should be either OBJ_HLINE or OBJ_TREND.");
return "\nWrong Upper TP Line object type.";
}
if (ObjectType(UpperTPLine) != OBJ_HLINE) UpperTP = NormalizeDouble(ObjectGetValueByShift(UpperTPLine, 0), Digits);
else UpperTP = NormalizeDouble(ObjectGet(UpperTPLine, OBJPROP_PRICE1), Digits); // Horizontal line value
ObjectSet(UpperTPLine, OBJPROP_RAY, true);
c = c + "\nUpper take-profit found. Level: " + DoubleToStr(UpperTP, Digits);
}
else
{
if (ObjectFind(TPChannel) > -1)
{
if (ObjectType(TPChannel) != OBJ_CHANNEL)
{
Alert("TP Channel should be OBJ_CHANNEL.");
return "\nWrong TP Channel object type.";
}
UpperTP = FindUpperTPViaChannel();
ObjectSet(TPChannel, OBJPROP_RAY, true);
c = c + "\nUpper TP found (via channel). Level: " + DoubleToStr(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 ((OrderSelect(UpperTicket, SELECT_BY_TICKET)) && ((OrderType() == OP_BUYSTOP) || (OrderType() == OP_BUYLIMIT)))
{
UpperTP = OrderTakeProfit();
}
}
}
// 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(LowerEntryLine) > -1)
{
if ((ObjectType(LowerEntryLine) != OBJ_HLINE) && (ObjectType(LowerEntryLine) != OBJ_TREND))
{
Alert("Lower Entry Line should be either OBJ_HLINE or OBJ_TREND.");
return "\nWrong Lower Entry Line object type.";
}
if (ObjectType(LowerEntryLine) != OBJ_HLINE) LowerEntry = NormalizeDouble(ObjectGetValueByShift(LowerEntryLine, 0), Digits);
else LowerEntry = NormalizeDouble(ObjectGet(LowerEntryLine, OBJPROP_PRICE1), Digits); // Horizontal line value
ObjectSet(LowerEntryLine, OBJPROP_RAY, true);
c = c + "\nLower entry found. Level: " + DoubleToStr(LowerEntry, Digits);
}
else
{
if (ObjectFind(EntryChannel) > -1)
{
if (ObjectType(EntryChannel) != OBJ_CHANNEL)
{
Alert("Entry Channel should be OBJ_CHANNEL.");
return "\nWrong Entry Channel object type.";
}
LowerEntry = FindLowerEntryViaChannel();
ObjectSet(EntryChannel, OBJPROP_RAY, true);
c = c + "\nLower entry found (via channel). Level: " + DoubleToStr(LowerEntry, Digits);
}
else
{
c = c + "\nLower entry not found. No new position will be entered.";
UseLower = false;
}
}
// Border.
if (ObjectFind(LowerBorderLine) > -1)
{
if ((ObjectType(LowerBorderLine) != OBJ_HLINE) && (ObjectType(LowerBorderLine) != 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 + MarketInfo(Symbol(), MODE_SPREAD) * Point, Digits);
ObjectSet(LowerBorderLine, OBJPROP_RAY, true);
c = c + "\nLower border found. Lower stop-loss level: " + DoubleToStr(LowerSL, Digits);
}
else // Try to find a channel.
{
if (ObjectFind(BorderChannel) > -1)
{
if (ObjectType(BorderChannel) != 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 + MarketInfo(Symbol(), MODE_SPREAD) * Point, Digits);
ObjectSet(BorderChannel, OBJPROP_RAY, true);
c = c + "\nLower border found (via channel). Lower stop-loss level: " + DoubleToStr(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 ((OrderSelect(LowerTicket, SELECT_BY_TICKET)) && ((OrderType() == OP_SELLSTOP) || (OrderType() == OP_SELLLIMIT)))
{
LowerSL = OrderStopLoss();
}
}
}
}
// Adjust lower SL for tick size granularity.
TickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
LowerSL = NormalizeDouble(MathRound(LowerSL / TickSize) * TickSize, _Digits);
// Take-profit.
if (ObjectFind(LowerTPLine) > -1)
{
if ((ObjectType(LowerTPLine) != OBJ_HLINE) && (ObjectType(LowerTPLine) != OBJ_TREND))
{
Alert("Lower TP Line should be either OBJ_HLINE or OBJ_TREND.");
return "\nWrong Lower TP Line object type.";
}
if (ObjectType(LowerTPLine) != OBJ_HLINE) LowerTP = NormalizeDouble(ObjectGetValueByShift(LowerTPLine, 0), Digits);
else LowerTP = NormalizeDouble(ObjectGet(LowerTPLine, OBJPROP_PRICE1), Digits); // Horizontal line value
if (UseSpreadAdjustment) LowerTP = NormalizeDouble(LowerTP + MarketInfo(Symbol(), MODE_SPREAD) * Point, Digits);
ObjectSet(LowerTPLine, OBJPROP_RAY, true);
c = c + "\nLower take-profit found. Level: " + DoubleToStr(LowerTP, Digits);
}
else
{
if (ObjectFind(TPChannel) > -1)
{
if (ObjectType(TPChannel) != OBJ_CHANNEL)
{
Alert("TP Channel should be OBJ_CHANNEL.");
return "\nWrong TP Channel object type.";
}
LowerTP = NormalizeDouble(FindLowerTPViaChannel(), Digits);
if (UseSpreadAdjustment) LowerTP = NormalizeDouble(LowerTP + MarketInfo(Symbol(), MODE_SPREAD) * Point, Digits);
ObjectSet(TPChannel, OBJPROP_RAY, true);
c = c + "\nLower TP found (via channel). Level: " + DoubleToStr(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 ((OrderSelect(LowerTicket, SELECT_BY_TICKET)) && ((OrderType() == OP_SELLSTOP) || (OrderType() == OP_SELLLIMIT)))
{
LowerTP = OrderTakeProfit();
}
}
}
// 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 (ObjectType(LowerBorderLine) == OBJ_HLINE)
{
return NormalizeDouble(ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE1), Digits);
}
// Trend line.
else if (ObjectType(LowerBorderLine) == OBJ_TREND)
{
double price1 = ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE1);
double price2 = ObjectGetDouble(0, LowerBorderLine, OBJPROP_PRICE2);
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_PRICE1), Digits);
}
for (int i = 0; i < Bars; i++)
{
double Border, Entry;
if (ObjectType(UpperBorderLine) != OBJ_HLINE) Border = ObjectGetValueByShift(UpperBorderLine, i);
else Border = ObjectGet(UpperBorderLine, OBJPROP_PRICE1); // Horizontal line value
if (ObjectType(UpperEntryLine) != OBJ_HLINE) Entry = ObjectGetValueByShift(UpperEntryLine, i);
else Entry = ObjectGet(UpperEntryLine, OBJPROP_PRICE1); // 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 (ObjectType(UpperBorderLine) == OBJ_HLINE)
{
return NormalizeDouble(ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE1), Digits);
}
// Trend line.
else if (ObjectType(UpperBorderLine) == OBJ_TREND)
{
double price1 = ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE1);
double price2 = ObjectGetDouble(0, UpperBorderLine, OBJPROP_PRICE2);
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_PRICE1), Digits);
}
for (int i = 0; i < Bars; i++)
{
double Border, Entry;
if (ObjectType(LowerBorderLine) != OBJ_HLINE) Border = ObjectGetValueByShift(LowerBorderLine, i);
else Border = ObjectGet(LowerBorderLine, OBJPROP_PRICE1); // Horizontal line value
if (ObjectType(LowerEntryLine) != OBJ_HLINE) Entry = ObjectGetValueByShift(LowerEntryLine, i);
else Entry = ObjectGet(LowerEntryLine, OBJPROP_PRICE1); // 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_PRICE1), 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_PRICE1), 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;
for (int i = 0; i < OrdersTotal(); i++)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) continue;
if (((OrderType() == OP_BUYSTOP) || (OrderType() == OP_BUYLIMIT)) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == Magic))
{
HaveBuyPending = true;
UpperTicket = OrderTicket();
}
else if (((OrderType() == OP_SELLSTOP) || (OrderType() == OP_SELLLIMIT)) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == Magic))
{
HaveSellPending = true;
LowerTicket = OrderTicket();
}
else if ((OrderType() == OP_BUY) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == Magic)) HaveBuy = true;
else if ((OrderType() == OP_SELL) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == Magic)) HaveSell = true;
}
}
// 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, ".");
// Get object's type, price/time coordinates, style properties.
ENUM_OBJECT OT = (ENUM_OBJECT)ObjectGetInteger(0, Object, OBJPROP_TYPE);
double Price1 = ObjectGetDouble(0, Object, OBJPROP_PRICE, 0);
datetime Time1 = 0;
double Price2 = 0;
datetime Time2 = 0;
double Price3 = 0;
datetime Time3 = 0;
if ((OT == OBJ_TREND) || (OT == OBJ_CHANNEL))
{
Time1 = (datetime)ObjectGetInteger(0, Object, OBJPROP_TIME, 0);
Price2 = ObjectGetDouble(0, Object, OBJPROP_PRICE, 1);
Time2 = (datetime)ObjectGetInteger(0, Object, OBJPROP_TIME, 1);
if (OT == OBJ_CHANNEL)
{
Price3 = ObjectGetDouble(0, Object, OBJPROP_PRICE, 2);
Time3 = (datetime)ObjectGetInteger(0, Object, OBJPROP_TIME, 2);
}
}
color Color = (color)ObjectGetInteger(0, Object, OBJPROP_COLOR);
ENUM_LINE_STYLE Style = (ENUM_LINE_STYLE)ObjectGetInteger(0, Object, OBJPROP_STYLE);
int Width = (int)ObjectGetInteger(0, Object, OBJPROP_WIDTH);
// Delete object.
ObjectDelete(0, Object);
string NewObject = Object + IntegerToString(Magic);
// Create the same object with new name and set the old style properties.
ObjectCreate(0, NewObject, OT, 0, Time1, Price1, Time2, Price2, Time3, Price3);
ObjectSetInteger(0, NewObject, OBJPROP_COLOR, Color);
ObjectSetInteger(0, NewObject, OBJPROP_STYLE, Style);
ObjectSetInteger(0, NewObject, OBJPROP_WIDTH, Width);
ObjectSetInteger(0, NewObject, OBJPROP_RAY, true);
}
}
// The main trading procedure. Sends, Modifies and Deletes orders.
void AdjustUpperAndLowerOrders()
{
double NewVolume;
int last_error;
datetime expiration;
int order_type;
string order_type_string;
if ((!IsTradeAllowed()) || (IsTradeContextBusy()) || (!IsConnected()) || (!MarketInfo(Symbol(), MODE_TRADEALLOWED)))
{
if (!TCBusy) Output("Trading context is busy or disconnected.");
TCBusy = true;
return;
}
else if (TCBusy)
{
Output("Trading context is no longer busy or disconnected.");
TCBusy = false;
}
double StopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
double FreezeLevel = MarketInfo(Symbol(), MODE_FREEZELEVEL) * Point;
double LotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
int LotStep_digits = CountDecimalPlaces(LotStep);
if (UseExpiration)
{
// Set expiration to the end of the current bar.
expiration = Time[0] + Period() * 60;
// If expiration is less than 11 minutes from now, set it to at least 11 minutes from now.
// (Brokers have such limit.)
if (expiration - TimeCurrent() < 660) expiration = TimeCurrent() + 660;
}
else expiration = 0;
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_PRICE1), _Digits);
else BorderLevel = NormalizeDouble(ObjectGetValueByShift(LowerBorderLine, 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)
{
RefreshRates();
NewVolume = GetPositionSize(Bid, LowerSL);
LowerTicket = ExecuteMarketOrder(OP_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_PRICE1), _Digits);
else BorderLevel = NormalizeDouble(ObjectGetValueByShift(UpperBorderLine, 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)
{
RefreshRates();
NewVolume = GetPositionSize(Ask, UpperSL);
UpperTicket = ExecuteMarketOrder(OP_BUY, NewVolume, Ask, UpperSL, UpperTP);
}
}
return;
}
int OT = OrdersTotal();
for (int i = OT - 1; i >= 0; i--)
{
double prevOrderOpenPrice, prevOrderStopLoss, prevOrderTakeProfit;
double SL;
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
RefreshRates();
// BUY
if (((OrderType() == OP_BUYSTOP) || (OrderType() == OP_BUYLIMIT)) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == 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);
// Delete existing pending order
if ((HaveBuy) || ((HaveSell) && (OneCancelsOther)) || (!UseUpper))
{
if (!OrderDelete(OrderTicket()))
{
last_error = GetLastError();
Output("OrderDelete() error. Order ticket = " + IntegerToString(OrderTicket()) + ". Error = " + IntegerToString(last_error));
}
}
// 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(OrderLots() - NewVolume) > LotStep / 2))
{
if ((UpperEntry - Ask > StopLevel) || (Ask - UpperEntry > StopLevel)) // Order can be re-created.
{
if (!OrderDelete(OrderTicket()))
{
last_error = GetLastError();
Output("OrderDelete() error. Order ticket = " + IntegerToString(OrderTicket()) + ". Error = " + IntegerToString(last_error));
}
Sleep(5000); // Wait 5 seconds before opening a new order.
}
else continue;
// Ask could change after deletion, check if there is still no error 130 present.
RefreshRates();
if (UpperEntry - Ask > StopLevel) // Current price below entry.
{
order_type = OP_BUYSTOP;
order_type_string = "Stop";
}
else if (Ask - UpperEntry > StopLevel) // Current price above entry.
{
order_type = OP_BUYLIMIT;
order_type_string = "Limit";
}
else continue;
if (UseExpiration)
{
// Set expiration to the end of the current bar.
expiration = Time[0] + Period() * 60;
// If expiration is less than 11 minutes extra seconds from now, set it to at least 11 minutes from now.
// (Brokers have such limit.)
if (expiration - TimeCurrent() < 660) expiration = TimeCurrent() + 661;
}
else expiration = 0;
UpperTicket = OrderSend(Symbol(), order_type, NewVolume, UpperEntry, Slippage, UpperSL, UpperTP, "ChartPatternHelper", Magic, expiration);
last_error = GetLastError();
if ((UpperTicket == -1) && (last_error != 128)) // Ignore time-out errors.
{
Output("StopLevel = " + DoubleToStr(StopLevel, 8));
Output("FreezeLevel = " + DoubleToStr(FreezeLevel, 8));
Output("Error Recreating Buy " + order_type_string + ": " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("Volume = " + DoubleToStr(NewVolume, LotStep_digits) + " Entry = " + DoubleToStr(UpperEntry, Digits) + " SL = " + DoubleToStr(UpperSL, Digits) + " TP = " + DoubleToStr(UpperTP, Digits) + " Bid/Ask = " + DoubleToStr(Bid, Digits) + "/" + DoubleToStr(Ask, Digits) + " Exp: " + TimeToStr(expiration, TIME_DATE | TIME_SECONDS));
}
continue;
}
// Otherwise, update entry/SL/TP if at least one of them has changed.
else if ((MathAbs(OrderOpenPrice() - UpperEntry) > _Point / 2) || (MathAbs(OrderStopLoss() - UpperSL) > _Point / 2) || (MathAbs(OrderTakeProfit() - UpperTP) > _Point / 2))
{
// Avoid error 130 based on entry.
if (UpperEntry - Ask > StopLevel) // Current price below entry.
{
order_type_string = "Stop";
}
else if (Ask - UpperEntry > StopLevel) // Current price above entry.
{
order_type_string = "Limit";
}
else if (MathAbs(OrderOpenPrice() - UpperEntry) > _Point / 2) continue;
// Avoid error 130 based on stop-loss.
if (UpperEntry - UpperSL <= StopLevel)
{
Output("Skipping Modify Buy " + order_type_string + " because stop-loss is too close to entry. StopLevel = " + DoubleToStr(StopLevel, Digits) + " Entry = " + DoubleToStr(UpperEntry, Digits) + " SL = " + DoubleToStr(UpperSL, Digits));
continue;
}
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(OrderOpenPrice() - Ask) <= FreezeLevel))
{
Output("Skipping Modify Buy " + order_type_string + " because open price is too close to Ask. FreezeLevel = " + DoubleToStr(FreezeLevel, Digits) + " OpenPrice = " + DoubleToStr(OrderOpenPrice(), Digits) + " Ask = " + DoubleToStr(Ask, Digits));
continue;
}
if (UseExpiration)
{
expiration = OrderExpiration();
if (expiration - TimeCurrent() < 660) expiration = TimeCurrent() + 660;
}
else expiration = 0;
prevOrderOpenPrice = OrderOpenPrice();
prevOrderStopLoss = OrderStopLoss();
prevOrderTakeProfit = OrderTakeProfit();
if (!OrderModify(OrderTicket(), UpperEntry, UpperSL, UpperTP, expiration))
{
last_error = GetLastError();
if (last_error != 128) // Ignore time out errors.
{
if (last_error == 1)
{
Output("PREV: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " SL = " + DoubleToStr(OrderStopLoss(), Digits) + " TP = " + DoubleToStr(OrderTakeProfit(), Digits));
}
Output("StopLevel = " + DoubleToStr(StopLevel, 8));
Output("FreezeLevel = " + DoubleToStr(FreezeLevel, 8));
Output("Error Modifying Buy " + order_type_string + ": " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("FROM: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " SL = " + DoubleToStr(OrderStopLoss(), Digits) + " TP = " + DoubleToStr(OrderTakeProfit(), Digits) + " -> TO: Entry = " + DoubleToStr(UpperEntry, 8) + " SL = " + DoubleToStr(UpperSL, 8) + " TP = " + DoubleToStr(UpperTP, 8) + " Bid/Ask = " + DoubleToStr(Bid, Digits) + "/" + DoubleToStr(Ask, Digits) + " OrderTicket = " + IntegerToString(OrderTicket()) + " OrderExpiration = " + TimeToStr(OrderExpiration(), TIME_DATE | TIME_SECONDS) + " -> " + TimeToStr(expiration, TIME_DATE | TIME_SECONDS));
}
}
}
}
else if ((OrderType() == OP_BUY) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == Magic) && (!DisableBuyOrders))
{
// PostEntrySLAdjustment - a procedure to correct SL if breakout candle become too long and no longer qualifies for SL rule.
if ((OrderOpenTime() > Time[1]) && (OrderOpenTime() < Time[0]) && (PostEntrySLAdjustment) && (PostBuySLAdjustmentDone == false))
{
SL = AdjustPostBuySL();
if (SL != -1)
{
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(OrderOpenPrice() - Ask) <= FreezeLevel))
{
Output("Skipping Modify Buy Stop SL because open price is too close to Ask. FreezeLevel = " + DoubleToStr(FreezeLevel, Digits) + " OpenPrice = " + DoubleToStr(OrderOpenPrice(), 8) + " Ask = " + DoubleToStr(Ask, Digits));
continue;
}
if (NormalizeDouble(SL, Digits) == NormalizeDouble(OrderStopLoss(), Digits)) PostBuySLAdjustmentDone = true;
else
{
if (!OrderModify(OrderTicket(), OrderOpenPrice(), SL, OrderTakeProfit(), OrderExpiration()))
{
last_error = GetLastError();
if (last_error != 128) // Ignore time out errors.
{
Output("Error Modifying Buy SL: " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("FROM: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " SL = " + DoubleToStr(OrderStopLoss(), Digits) + " -> TO: Entry = " + DoubleToStr(OrderOpenPrice(), 8) + " SL = " + DoubleToStr(SL, 8) + " Ask = " + DoubleToStr(Ask, Digits));
}
}
else PostBuySLAdjustmentDone = true;
}
}
}
// Adjust TP only.
if (MathAbs(OrderTakeProfit() - UpperTP) > _Point / 2)
{
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(OrderOpenPrice() - Ask) <= FreezeLevel))
{
Output("Skipping Modify Buy Stop TP because open price is too close to Ask. FreezeLevel = " + DoubleToStr(FreezeLevel, Digits) + " OpenPrice = " + DoubleToStr(OrderOpenPrice(), 8) + " Ask = " + DoubleToStr(Ask, Digits));
continue;
}
if (!OrderModify(OrderTicket(), OrderOpenPrice(), OrderStopLoss(), UpperTP, OrderExpiration()))
{
last_error = GetLastError();
if (last_error != 128) // Ignore time out errors.
{
Output("Error Modifying Buy TP: " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("FROM: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " TP = " + DoubleToStr(OrderTakeProfit(), Digits) + " -> TO: Entry = " + DoubleToStr(OrderOpenPrice(), 8) + " TP = " + DoubleToStr(UpperTP, 8) + " Ask = " + DoubleToStr(Ask, Digits));
}
}
}
}
// SELL
else if (((OrderType() == OP_SELLSTOP) || (OrderType() == OP_SELLLIMIT)) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == Magic) && (!DisableSellOrders))
{
// Current price is above Buy entry - pending Buy Limit will be used instead of two stop orders.
if ((Ask - UpperEntry > StopLevel) && (UseUpper)) continue;
NewVolume = GetPositionSize(LowerEntry, LowerSL);
// Delete existing pending order.
if (((HaveBuy) && (OneCancelsOther)) || (HaveSell) || (!UseLower))
{
if (!OrderDelete(OrderTicket()))
{
last_error = GetLastError();
Output("OrderDelete() error. Order ticket = " + IntegerToString(OrderTicket()) + ". Error = " + IntegerToString(last_error));
}
}
// 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(OrderLots() - NewVolume) > LotStep / 2))
{
if ((Bid - LowerEntry > StopLevel) || (LowerEntry - Bid > StopLevel)) // Order can be re-created
{
if (!OrderDelete(OrderTicket()))
{
last_error = GetLastError();
Output("OrderDelete() error. Order ticket = " + IntegerToString(OrderTicket()) + ". Error = " + IntegerToString(last_error));
}
}
else continue;
// Bid could change after deletion, check if there is still no error 130 present.
RefreshRates();
if (Bid - LowerEntry > StopLevel) // Current price above entry.
{
order_type = OP_BUYSTOP;
order_type_string = "Stop";
}
else if (LowerEntry - Bid > StopLevel) // Current price below entry.
{
order_type = OP_BUYLIMIT;
order_type_string = "Limit";
}
else continue;
if (UseExpiration)
{
// Set expiration to the end of the current bar.
expiration = Time[0] + Period() * 60;
// If expiration is less than 11 minutes extra seconds from now, set it to at least 11 minutes from now.
// (Brokers have such limit.)
if (expiration - TimeCurrent() < 660) expiration = TimeCurrent() + 661;
}
else expiration = 0;
LowerTicket = OrderSend(Symbol(), order_type, NewVolume, LowerEntry, Slippage, LowerSL, LowerTP, "ChartPatternHelper", Magic, expiration);
last_error = GetLastError();
if ((LowerTicket == -1) && (last_error != 128)) // Ignore time-out errors.
{
Output("StopLevel = " + DoubleToStr(StopLevel, 8));
Output("FreezeLevel = " + DoubleToStr(FreezeLevel, 8));
Output("Error Recreating Sell " + order_type_string + ": " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("Volume = " + DoubleToStr(NewVolume, LotStep_digits) + " Entry = " + DoubleToStr(LowerEntry, Digits) + " SL = " + DoubleToStr(LowerSL, Digits) + " TP = " + DoubleToStr(LowerTP, Digits) + " Bid/Ask = " + DoubleToStr(Bid, Digits) + "/" + DoubleToStr(Ask, Digits) + " Exp: " + TimeToStr(expiration, TIME_DATE | TIME_SECONDS));
}
continue;
}
// Otherwise, just update what needs to be updated.
else if ((MathAbs(OrderOpenPrice() - LowerEntry) > _Point / 2) || (MathAbs(OrderStopLoss() - LowerSL) > _Point / 2) || (MathAbs(OrderTakeProfit() - LowerTP) > _Point / 2))
{
// Avoid error 130 based on entry.
if (Bid - LowerEntry > StopLevel) // Current price above entry.
{
order_type_string = "Stop";
}
else if (LowerEntry - Bid > StopLevel) // Current price below entry.
{
order_type_string = "Limit";
}
else if (MathAbs(OrderOpenPrice() - LowerEntry) > _Point / 2) continue;
// Avoid error 130 based on stop-loss.
if (LowerSL - LowerEntry <= StopLevel)
{
Output("Skipping Modify Sell " + order_type_string + " because stop-loss is too close to entry. StopLevel = " + DoubleToStr(StopLevel, Digits) + " Entry = " + DoubleToStr(LowerEntry, Digits) + " SL = " + DoubleToStr(LowerSL, Digits));
continue;
}
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(Bid - OrderOpenPrice()) <= FreezeLevel))
{
Output("Skipping Modify Sell " + order_type_string + " because open price is too close to Bid. FreezeLevel = " + DoubleToStr(FreezeLevel, Digits) + " OpenPrice = " + DoubleToStr(OrderOpenPrice(), Digits) + " Bid = " + DoubleToStr(Bid, Digits));
continue;
}
if (UseExpiration)
{
expiration = OrderExpiration();
if (expiration - TimeCurrent() < 660) expiration = TimeCurrent() + 660;
}
else expiration = 0;
prevOrderOpenPrice = OrderOpenPrice();
prevOrderStopLoss = OrderStopLoss();
prevOrderTakeProfit = OrderTakeProfit();
if (!OrderModify(OrderTicket(), LowerEntry, LowerSL, LowerTP, expiration))
{
last_error = GetLastError();
if (last_error != 128) // Ignore time out errors.
{
if (last_error == 1)
{
Output("PREV: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " SL = " + DoubleToStr(OrderStopLoss(), Digits) + " TP = " + DoubleToStr(OrderTakeProfit(), Digits));
}
Output("StopLevel = " + DoubleToStr(StopLevel, 8));
Output("FreezeLevel = " + DoubleToStr(FreezeLevel, 8));
Output("Error Modifying Sell " + order_type_string + ": " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("FROM: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " SL = " + DoubleToStr(OrderStopLoss(), Digits) + " TP = " + DoubleToStr(OrderTakeProfit(), Digits) + " -> TO: Entry = " + DoubleToStr(LowerEntry, 8) + " SL = " + DoubleToStr(LowerSL, 8) + " TP = " + DoubleToStr(LowerTP, 8) + " Bid/Ask = " + DoubleToStr(Bid, Digits) + "/" + DoubleToStr(Ask, Digits) + " OrderTicket = " + IntegerToString(OrderTicket()) + " OrderExpiration = " + TimeToStr(OrderExpiration(), TIME_DATE | TIME_SECONDS) + " -> " + TimeToStr(expiration, TIME_DATE | TIME_SECONDS));
}
}
}
}
else if ((OrderType() == OP_SELL) && (OrderSymbol() == Symbol()) && (OrderMagicNumber() == Magic) && (!DisableSellOrders))
{
// PostEntrySLAdjustment - a procedure to correct SL if breakout candle become too long and no longer qualifies for SL rule.
if ((OrderOpenTime() > Time[1]) && (OrderOpenTime() < Time[0]) && (PostEntrySLAdjustment) && (PostSellSLAdjustmentDone == false))
{
SL = AdjustPostSellSL();
if (SL != -1)
{
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(Bid - OrderOpenPrice()) <= FreezeLevel))
{
Output("Skipping Modify Sell Stop SL because open price is too close to Bid. FreezeLevel = " + DoubleToStr(FreezeLevel, Digits) + " OpenPrice = " + DoubleToStr(OrderOpenPrice(), 8) + " Bid = " + DoubleToStr(Bid, Digits));
continue;
}
if (NormalizeDouble(SL, Digits) == NormalizeDouble(OrderStopLoss(), Digits)) PostSellSLAdjustmentDone = true;
else
{
if (!OrderModify(OrderTicket(), OrderOpenPrice(), SL, OrderTakeProfit(), OrderExpiration()))
{
last_error = GetLastError();
if (last_error != 128) // Ignore time out errors.
{
Output("Error Modifying Sell SL: " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("FROM: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " SL = " + DoubleToStr(OrderStopLoss(), Digits) + " -> TO: Entry = " + DoubleToStr(OrderOpenPrice(), 8) + " SL = " + DoubleToStr(SL, 8) + " Bid = " + DoubleToStr(Bid, Digits));
}
}
else PostSellSLAdjustmentDone = true;
}
}
}
// Adjust TP only.
if (MathAbs(OrderTakeProfit() - LowerTP) > _Point / 2)
{
// Avoid frozen context. In all modification cases.
if ((FreezeLevel != 0) && (MathAbs(Bid - OrderOpenPrice()) <= FreezeLevel))
{
Output("Skipping Modify Sell Stop TP because open price is too close to Bid. FreezeLevel = " + DoubleToStr(FreezeLevel, Digits) + " OpenPrice = " + DoubleToStr(OrderOpenPrice(), 8) + " Bid = " + DoubleToStr(Bid, Digits));
continue;
}
if (!OrderModify(OrderTicket(), OrderOpenPrice(), OrderStopLoss(), LowerTP, OrderExpiration()))
{
last_error = GetLastError();
if (last_error != 128) // Ignore time out errors.
{
Output("Error Modifying Sell Stop TP: " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("FROM: Entry = " + DoubleToStr(OrderOpenPrice(), Digits) + " TP = " + DoubleToStr(OrderTakeProfit(), Digits) + " -> TO: Entry = " + DoubleToStr(OrderOpenPrice(), 8) + " TP = " + DoubleToStr(LowerTP, 8) + " Bid = " + DoubleToStr(Bid, Digits));
}
}
}
}
}
// BUY
// If we do not already have Long position or Long pending order and if we can enter Long
// and the current price is not below the Sell entry (in that case, only pending Sell Limit order will be used).
if ((!HaveBuy) && (!HaveBuyPending) && (UseUpper) && ((LowerEntry - Bid <= StopLevel) || (!UseLower)))
{
// Avoid error 130 based on stop-loss.
if (UpperEntry - UpperSL <= StopLevel)
{
Output("Skipping Send Pending Buy because stop-loss is too close to entry. StopLevel = " + DoubleToStr(StopLevel, Digits) + " Entry = " + DoubleToStr(UpperEntry, Digits) + " SL = " + DoubleToStr(UpperSL, Digits));
}
else
{
if (UpperEntry - Ask > StopLevel) // Current price below entry.
{
order_type = OP_BUYSTOP;
order_type_string = "Stop";
}
else if (Ask - UpperEntry > StopLevel) // Current price above entry.
{
order_type = OP_BUYLIMIT;
order_type_string = "Limit";
}
else
{
order_type = -1;
Output("Skipping Send Pending Buy because entry is too close to Ask. StopLevel = " + DoubleToStr(StopLevel, Digits) + " Entry = " + DoubleToStr(UpperEntry, Digits) + " Ask = " + DoubleToStr(Ask, Digits));
}
if (order_type > -1)
{
NewVolume = GetPositionSize(UpperEntry, UpperSL);
UpperTicket = OrderSend(Symbol(), order_type, NewVolume, UpperEntry, Slippage, UpperSL, UpperTP, "ChartPatternHelper", Magic, expiration);
last_error = GetLastError();
if ((UpperTicket == -1) && (last_error != 128)) // Ignore time-out errors
{
Output("Error Sending Buy " + order_type_string + ": " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("Volume = " + DoubleToStr(NewVolume, LotStep_digits) + " Entry = " + DoubleToStr(UpperEntry, Digits) + " SL = " + DoubleToStr(UpperSL, Digits) + " TP = " + DoubleToStr(UpperTP, Digits) + " Ask = " + DoubleToStr(Ask, Digits) + " Exp: " + TimeToStr(expiration, TIME_DATE | TIME_SECONDS));
}
}
}
}
// SELL
// If we do not already have Short position or Short pending order and if we can enter Short
// and the current price is not above the Buy entry (in that case, only pending Buy Limit order will be used).
if ((!HaveSell) && (!HaveSellPending) && (UseLower) && ((Ask - UpperEntry <= StopLevel) || (!UseUpper)))
{
// Avoid error 130 based on stop-loss.
if (LowerSL - LowerEntry <= StopLevel)
{
Output("Skipping Send Pending Sell because stop-loss is too close to entry. StopLevel = " + DoubleToStr(StopLevel, Digits) + " Entry = " + DoubleToStr(LowerEntry, Digits) + " SL = " + DoubleToStr(LowerSL, Digits));
}
else
{
if (Bid - LowerEntry > StopLevel) // Current price above entry.
{
order_type = OP_SELLSTOP;
order_type_string = "Stop";
}
else if (LowerEntry - Bid > StopLevel) // Current price below entry.
{
order_type = OP_SELLLIMIT;
order_type_string = "Limit";
}
else
{
order_type = -1;
Output("Skipping Send Pending Sell because entry is too close to Bid. StopLevel = " + DoubleToStr(StopLevel, Digits) + " Entry = " + DoubleToStr(LowerEntry, Digits) + " Bid = " + DoubleToStr(Bid, Digits));
}
if (order_type > -1)
{
NewVolume = GetPositionSize(LowerEntry, LowerSL);
LowerTicket = OrderSend(Symbol(), order_type, NewVolume, LowerEntry, Slippage, LowerSL, LowerTP, "ChartPatternHelper", Magic, expiration);
last_error = GetLastError();
if ((LowerTicket == -1) && (last_error != 128)) // Ignore time-out errors
{
Output("Error Sending Sell " + order_type_string + ": " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("Volume = " + DoubleToStr(NewVolume, LotStep_digits) + " Entry = " + DoubleToStr(LowerEntry, Digits) + " SL = " + DoubleToStr(LowerSL, Digits) + " TP = " + DoubleToStr(LowerTP, Digits) + " Bid = " + DoubleToStr(Bid, Digits) + " Exp: " + TimeToStr(expiration, TIME_DATE | TIME_SECONDS));
}
}
}
}
}
void SetComment(string c)
{
if (!Silent) Comment(c);
}
//+-----------------------------------------------------------------------------------+
//| Calculates necessary adjustments for cases when ProfitCurrency != AccountCurrency.|
//+-----------------------------------------------------------------------------------+
#define FOREX_SYMBOLS_ONLY 0
#define NONFOREX_SYMBOLS_ONLY 1
double CalculateAdjustment()
{
double add_coefficient = 1; // Might be necessary for correction coefficient calculation if two pairs are used for profit currency to account currency conversion. This is handled differently in MT5 version.
if (ReferenceSymbol == NULL)
{
ReferenceSymbol = GetSymbolByCurrencies(ProfitCurrency, account_currency, FOREX_SYMBOLS_ONLY);
if (ReferenceSymbol == NULL) ReferenceSymbol = GetSymbolByCurrencies(ProfitCurrency, account_currency, NONFOREX_SYMBOLS_ONLY);
ReferenceSymbolMode = true;
// Failed.
if (ReferenceSymbol == NULL)
{
// Reversing currencies.
ReferenceSymbol = GetSymbolByCurrencies(account_currency, ProfitCurrency, FOREX_SYMBOLS_ONLY);
if (ReferenceSymbol == NULL) ReferenceSymbol = GetSymbolByCurrencies(account_currency, ProfitCurrency, NONFOREX_SYMBOLS_ONLY);
ReferenceSymbolMode = false;
}
if (ReferenceSymbol == NULL)
{
// The condition checks whether we are caclulating conversion coefficient for the chart's symbol or for some other.
// The error output is OK for the current symbol only because it won't be repeated ad infinitum.
// It should be avoided for non-chart symbols because it will just flood the log.
Print("Couldn't detect proper currency pair for adjustment calculation. Profit currency: ", ProfitCurrency, ". Account currency: ", account_currency, ". Trying to find a possible two-symbol combination.");
if ((FindDoubleReferenceSymbol("USD")) // USD should work in 99.9% of cases.
|| (FindDoubleReferenceSymbol("EUR")) // For very rare cases.
|| (FindDoubleReferenceSymbol("GBP")) // For extremely rare cases.
|| (FindDoubleReferenceSymbol("JPY"))) // For extremely rare cases.
{
Print("Converting via ", ReferenceSymbol, " and ", AdditionalReferenceSymbol, ".");
}
else
{
Print("Adjustment calculation critical failure. Failed both simple and two-pair conversion methods.");
return 1;
}
}
}
if (AdditionalReferenceSymbol != NULL) // If two reference pairs are used.
{
// Calculate just the additional symbol's coefficient and then use it in final return's multiplication.
MqlTick tick;
SymbolInfoTick(AdditionalReferenceSymbol, tick);
add_coefficient = GetCurrencyCorrectionCoefficient(tick, AdditionalReferenceSymbolMode);
}
MqlTick tick;
SymbolInfoTick(ReferenceSymbol, tick);
return GetCurrencyCorrectionCoefficient(tick, ReferenceSymbolMode) * add_coefficient;
}
//+---------------------------------------------------------------------------+
//| Returns a currency pair with specified base currency and profit currency. |
//+---------------------------------------------------------------------------+
string GetSymbolByCurrencies(const string base_currency, const string profit_currency, const uint symbol_type)
{
// Cycle through all symbols.
for (int s = 0; s < SymbolsTotal(false); s++)
{
// Get symbol name by number.
string symbolname = SymbolName(s, false);
string b_cur;
// Normal case - Forex pairs:
if (MarketInfo(symbolname, MODE_PROFITCALCMODE) == 0)
{
if (symbol_type == NONFOREX_SYMBOLS_ONLY) continue; // Avoid checking symbols of a wrong type.
// Get its base currency.
b_cur = SymbolInfoString(symbolname, SYMBOL_CURRENCY_BASE);
if (b_cur == "RUR") b_cur = "RUB";
}
else // Weird case for brokers that set conversion pairs as CFDs.
{
if (symbol_type == FOREX_SYMBOLS_ONLY) continue; // Avoid checking symbols of a wrong type.
// Get its base currency as the initial three letters - prone to huge errors!
b_cur = StringSubstr(symbolname, 0, 3);
}
// Get its profit currency.
string p_cur = SymbolInfoString(symbolname, SYMBOL_CURRENCY_PROFIT);
if (p_cur == "RUR") p_cur = "RUB";
// If the currency pair matches both currencies, select it in Market Watch and return its name.
if ((b_cur == base_currency) && (p_cur == profit_currency))
{
// Select if necessary.
if (!(bool)SymbolInfoInteger(symbolname, SYMBOL_SELECT)) SymbolSelect(symbolname, true);
return symbolname;
}
}
return NULL;
}
//+----------------------------------------------------------------------------+
//| Finds reference symbols using 2-pair method. |
//| Results are returned via reference parameters. |
//| Returns true if found the pairs, false otherwise. |
//+----------------------------------------------------------------------------+
bool FindDoubleReferenceSymbol(const string cross_currency)
{
// A hypothetical example for better understanding:
// The trader buys CAD/CHF.
// account_currency is known = SEK.
// cross_currency = USD.
// profit_currency = CHF.
// I.e., we have to buy dollars with francs (using the Ask price) and then sell those for SEKs (using the Bid price).
ReferenceSymbol = GetSymbolByCurrencies(cross_currency, account_currency, FOREX_SYMBOLS_ONLY);
if (ReferenceSymbol == NULL) ReferenceSymbol = GetSymbolByCurrencies(cross_currency, account_currency, NONFOREX_SYMBOLS_ONLY);
ReferenceSymbolMode = true; // If found, we've got USD/SEK.
// Failed.
if (ReferenceSymbol == NULL)
{
// Reversing currencies.
ReferenceSymbol = GetSymbolByCurrencies(account_currency, cross_currency, FOREX_SYMBOLS_ONLY);
if (ReferenceSymbol == NULL) ReferenceSymbol = GetSymbolByCurrencies(account_currency, cross_currency, NONFOREX_SYMBOLS_ONLY);
ReferenceSymbolMode = false; // If found, we've got SEK/USD.
}
if (ReferenceSymbol == NULL)
{
Print("Error. Couldn't detect proper currency pair for 2-pair adjustment calculation. Cross currency: ", cross_currency, ". Account currency: ", account_currency, ".");
return false;
}
AdditionalReferenceSymbol = GetSymbolByCurrencies(cross_currency, ProfitCurrency, FOREX_SYMBOLS_ONLY);
if (AdditionalReferenceSymbol == NULL) AdditionalReferenceSymbol = GetSymbolByCurrencies(cross_currency, ProfitCurrency, NONFOREX_SYMBOLS_ONLY);
AdditionalReferenceSymbolMode = false; // If found, we've got USD/CHF. Notice that mode is swapped for cross/profit compared to cross/acc, because it is used in the opposite way.
// Failed.
if (AdditionalReferenceSymbol == NULL)
{
// Reversing currencies.
AdditionalReferenceSymbol = GetSymbolByCurrencies(ProfitCurrency, cross_currency, FOREX_SYMBOLS_ONLY);
if (AdditionalReferenceSymbol == NULL) AdditionalReferenceSymbol = GetSymbolByCurrencies(ProfitCurrency, cross_currency, NONFOREX_SYMBOLS_ONLY);
AdditionalReferenceSymbolMode = true; // If found, we've got CHF/USD. Notice that mode is swapped for profit/cross compared to acc/cross, because it is used in the opposite way.
}
if (AdditionalReferenceSymbol == NULL)
{
Print("Error. Couldn't detect proper currency pair for 2-pair adjustment calculation. Cross currency: ", cross_currency, ". Chart's pair currency: ", ProfitCurrency, ".");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Get profit correction coefficient based on current prices. |
//| Valid for loss calculation only. |
//+------------------------------------------------------------------+
double GetCurrencyCorrectionCoefficient(MqlTick &tick, const bool ref_symbol_mode)
{
if ((tick.ask == 0) || (tick.bid == 0)) return -1; // Data is not yet ready.
// Reverse quote.
if (ref_symbol_mode)
{
// Using Buy price for reverse quote.
return tick.ask;
}
// Direct quote.
else
{
// Using Sell price for direct quote.
return (1 / tick.bid);
}
}
// Taken from PositionSizeCalculator indicator.
double GetPositionSize(double Entry, double StopLoss)
{
double Size, RiskMoney, UnitCost, PositionSize = 0;
ProfitCurrency = SymbolInfoString(Symbol(), SYMBOL_CURRENCY_PROFIT);
BaseCurrency = SymbolInfoString(Symbol(), SYMBOL_CURRENCY_BASE);
ProfitCalcMode = (int)MarketInfo(Symbol(), MODE_PROFITCALCMODE);
account_currency = AccountCurrency();
// A rough patch for cases when account currency is set as RUR instead of RUB.
if (account_currency == "RUR") account_currency = "RUB";
if (ProfitCurrency == "RUR") ProfitCurrency = "RUB";
if (BaseCurrency == "RUR") BaseCurrency = "RUB";
double LotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
int LotStep_digits = CountDecimalPlaces(LotStep);
double SL = MathAbs(Entry - StopLoss);
if (!CalculatePositionSize) return FixedPositionSize;
if (AccountCurrency() == "") return 0;
if (FixedBalance > 0)
{
Size = FixedBalance;
}
else if (UseEquityInsteadOfBalance)
{
Size = AccountEquity();
}
else
{
Size = AccountBalance();
}
if (!UseMoneyInsteadOfPercentage) RiskMoney = Size * Risk / 100;
else RiskMoney = MoneyRisk;
// If Symbol is CFD.
if (ProfitCalcMode == 1)
UnitCost = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE) * SymbolInfoDouble(Symbol(), SYMBOL_TRADE_CONTRACT_SIZE); // Apparently, it is more accurate than taking TICKVALUE directly in some cases.
else UnitCost = MarketInfo(Symbol(), MODE_TICKVALUE); // Futures or Forex.
if (ProfitCalcMode != 0) // Non-Forex might need to be adjusted.
{
// If profit currency is different from account currency.
if (ProfitCurrency != account_currency)
{
double CCC = CalculateAdjustment(); // Valid only for loss calculation.
// Adjust the unit cost.
UnitCost *= CCC;
}
}
// If account currency == pair's base currency, adjust UnitCost to future rate (SL). Works only for Forex pairs.
if ((account_currency == BaseCurrency) && (ProfitCalcMode == 0))
{
double current_rate = 1, future_rate = StopLoss;
RefreshRates();
if (StopLoss < Entry)
{
current_rate = Ask;
}
else if (StopLoss > Entry)
{
current_rate = Bid;
}
UnitCost *= (current_rate / future_rate);
}
if ((SL != 0) && (UnitCost != 0) && (TickSize != 0)) PositionSize = NormalizeDouble(RiskMoney / (SL * UnitCost / TickSize), LotStep_digits);
if (PositionSize < MarketInfo(Symbol(), MODE_MINLOT)) PositionSize = MarketInfo(Symbol(), MODE_MINLOT);
else if (PositionSize > MarketInfo(Symbol(), MODE_MAXLOT)) PositionSize = MarketInfo(Symbol(), MODE_MAXLOT);
double steps = PositionSize / SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if (MathFloor(steps) < steps) PositionSize = MathFloor(steps) * MarketInfo(Symbol(), MODE_LOTSTEP);
return PositionSize;
}
// Prints and writes to file error info and context data.
void Output(string s)
{
Print(s);
if (!ErrorLogging) return;
int file = FileOpen(filename, FILE_CSV | FILE_READ | FILE_WRITE);
if (file == -1) Print("Failed to create an error log file: ", GetLastError(), ".");
else
{
FileSeek(file, 0, SEEK_END);
s = TimeToStr(TimeCurrent(), TIME_DATE | TIME_SECONDS) + " - " + s;
FileWrite(file, s);
FileClose(file);
}
}
// Runs only one time to adjust SL to appropriate bar's Low if breakout bar's part outside the pattern turned out to be longer than the one inside.
// Works only if PostEntrySLAdjustment = true.
double AdjustPostBuySL()
{
double SL = -1;
string smagic = IntegerToString(Magic);
double Border;
// Border.
if (ObjectFind(UpperBorderLine + smagic) > -1)
{
if ((ObjectType(UpperBorderLine + smagic) != OBJ_HLINE) && (ObjectType(UpperBorderLine + smagic) != OBJ_TREND)) return SL;
// Starting from 1 because it is new bar after breakout bar.
for (int i = 1; i < Bars; i++)
{
if (ObjectType(UpperBorderLine + smagic) != OBJ_HLINE) Border = ObjectGetValueByShift(UpperBorderLine + smagic, i);
else Border = ObjectGet(UpperBorderLine + smagic, OBJPROP_PRICE1); // Horizontal line value
// Major part inside pattern but and SL not closer than breakout bar's SL.
if ((Border - Low[i] > High[i] - Border) && (Low[i] <= Low[1])) return NormalizeDouble(Low[i], Digits);
}
}
else // Try to find a channel.
{
if (ObjectFind(BorderChannel + smagic) > -1)
{
if (ObjectType(BorderChannel + smagic) != OBJ_CHANNEL) return SL;
for (int i = 1; i < Bars; i++)
{
// Get the upper of main and auxiliary lines.
Border = MathMax(ObjectGetValueByTime(0, BorderChannel + smagic, Time[i], 0), ObjectGetValueByTime(0, BorderChannel + smagic, Time[i], 1));
// Major part inside pattern but and SL not closer than breakout bar's SL.
if ((Border - Low[i] > High[i] - Border) && (Low[i] <= Low[0])) return NormalizeDouble(Low[i], _Digits);
}
}
}
return SL;
}
// Runs only one time to adjust SL to appropriate bar's High if breakout bar's part outside the pattern turned out to be longer than the one inside.
// Works only if PostEntrySLAdjustment = true.
double AdjustPostSellSL()
{
double SL = -1;
string smagic = IntegerToString(Magic);
double Border;
// Border.
if (ObjectFind(LowerBorderLine + smagic) > -1)
{
if ((ObjectType(LowerBorderLine + smagic) != OBJ_HLINE) && (ObjectType(LowerBorderLine + smagic) != OBJ_TREND)) return SL;
// Starting from 1 because it is new bar after breakout bar.
for (int i = 1; i < Bars; i++)
{
if (ObjectType(LowerBorderLine + smagic) != OBJ_HLINE) Border = ObjectGetValueByShift(LowerBorderLine + smagic, i);
else Border = ObjectGet(LowerBorderLine + smagic, OBJPROP_PRICE1); // Horizontal line value
// Major part inside pattern but and SL not closer than breakout bar's SL.
if ((High[i] - Border > Border - Low[i]) && (High[i] >= High[0])) return NormalizeDouble(High[i], Digits);
}
}
else // Try to find a channel.
{
if (ObjectFind(BorderChannel + smagic) > -1)
{
if (ObjectType(BorderChannel + smagic) != OBJ_CHANNEL) return SL;
for (int i = 1; i < Bars; i++)
{
// Get the lower of main and auxiliary lines.
Border = MathMin(ObjectGetValueByTime(0, BorderChannel + smagic, Time[i], 0), ObjectGetValueByTime(0, BorderChannel + smagic, Time[i], 1));
// Major part inside pattern but and SL not closer than breakout bar's SL.
if ((High[i] - Border > Border - Low[i]) && (High[i] >= High[0])) return NormalizeDouble(High[i], _Digits);
}
}
}
return SL;
}
//+------------------------------------------------------------------+
//| Counts decimal places. |
//+------------------------------------------------------------------+
int CountDecimalPlaces(double number)
{
// 100 as maximum length of number.
for (int i = 0; i < 100; i++)
{
double pwr = MathPow(10, i);
if (MathRound(number * pwr) / pwr == number) return i;
}
return -1;
}
//+------------------------------------------------------------------+
//| Execute a markte order (depends on symbol's trade execution mode.|
//+------------------------------------------------------------------+
int ExecuteMarketOrder(const int order_type, const double volume, const double price, const double sl, const double tp)
{
double order_sl = sl;
double order_tp = tp;
double StopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
double FreezeLevel = MarketInfo(Symbol(), MODE_FREEZELEVEL) * Point;
double LotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
int LotStep_digits = CountDecimalPlaces(LotStep);
ENUM_SYMBOL_TRADE_EXECUTION Execution_Mode = (ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_EXEMODE);
// Market execution mode - preparation.
if (Execution_Mode == SYMBOL_TRADE_EXECUTION_MARKET)
{
// No SL/TP allowed on instant orders.
order_sl = 0;
order_tp = 0;
}
int ticket = OrderSend(Symbol(), order_type, volume, price, Slippage, order_sl, order_tp, "Chart Pattern Helper", Magic);
if (ticket == -1)
{
int last_error = GetLastError();
string order_string = "";
if (order_type == OP_BUY) order_string = "Buy";
else if (order_type == OP_SELL) order_string = "Sell";
Output("Error Sending " + order_string + ": " + IntegerToString(last_error) + " (" + ErrorDescription(last_error) + ")");
Output("Volume = " + DoubleToStr(volume, LotStep_digits) + " Entry = " + DoubleToStr(price, Digits) + " SL = " + DoubleToStr(order_sl, Digits) + " TP = " + DoubleToStr(order_tp, Digits));
}
else
{
Output("Order executed. Ticket: " + IntegerToString(ticket) + ".");
}
// Market execution mode - applying SL/TP.
if (Execution_Mode == SYMBOL_TRADE_EXECUTION_MARKET)
{
if (!OrderSelect(ticket, SELECT_BY_TICKET))
{
Output("Failed to find the order to apply SL/TP.");
return 0;
}
for (int i = 0; i < 10; i++)
{
bool result = OrderModify(ticket, OrderOpenPrice(), sl, tp, OrderExpiration());
if (result)
{
break;
}
else
{
Output("Error modifying the order: " + IntegerToString(GetLastError()));
}
}
}
return ticket;
}
//+------------------------------------------------------------------+