1657 lines
80 KiB
Plaintext
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;
|
|
}
|
|
//+------------------------------------------------------------------+ |