//+------------------------------------------------------------------+ //| Copyright © 2020, Gehtsoft USA LLC | //| http://fxcodebase.com | //+------------------------------------------------------------------+ //| Developed by : Mario Jemic | //| mario.jemic@gmail.com | //| https://AppliedMachineLearning.systems | //+------------------------------------------------------------------+ //| Support our efforts by donating | //| Paypal : https://goo.gl/9Rj74e | //| Patreon : https://goo.gl/GdXWeN | //+------------------------------------------------------------------+ #property copyright "Copyright © 2020, Gehtsoft USA LLC" #property link "http://fxcodebase.com" #property version "1.0" #property strict input double Amount = 0.1; // Lots input double stop = 30; // Stop loss input double limit = 30; // Take profit input int magic_number = 42; // Magic number // Trades monitor v.2.0 // Action v2.0 #ifndef IAction_IMP #define IAction_IMP interface IAction { public: virtual void AddRef() = 0; virtual void Release() = 0; virtual bool DoAction(const int period, const datetime date) = 0; }; #endif #ifndef TradingMonitor_IMP #define TradingMonitor_IMP class TradingMonitor { int active_ticket[1000]; double active_type[1000]; double active_price[1000]; double active_stoploss[1000]; double active_takeprofit[1000]; bool active_still_active[1000]; int active_total; IAction* _closedTradeAction; IAction* _tradeChangedAction; IAction* _newTradeAction; public: TradingMonitor() { _closedTradeAction = NULL; _tradeChangedAction = NULL; _newTradeAction = NULL; } ~TradingMonitor() { if (_closedTradeAction != NULL) _closedTradeAction.Release(); if (_tradeChangedAction != NULL) _tradeChangedAction.Release(); if (_newTradeAction != NULL) _newTradeAction.Release(); } void SetClosedTradeAction(IAction* action) { if (_closedTradeAction != NULL) _closedTradeAction.Release(); _closedTradeAction = action; if (_closedTradeAction != NULL) _closedTradeAction.AddRef(); } void SetOnTradeChanged(IAction* action) { if (_tradeChangedAction != NULL) _tradeChangedAction.Release(); _tradeChangedAction = action; if (_tradeChangedAction != NULL) _tradeChangedAction.AddRef(); } void SetOnNewTrade(IAction* action) { if (_newTradeAction != NULL) _newTradeAction.Release(); _newTradeAction = action; if (_newTradeAction != NULL) _newTradeAction.AddRef(); } void DoWork() { bool changed = false; int total = OrdersTotal(); for (int i = 0; i < total; i++) { if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; int ticket = OrderTicket(); int index = getOrderCacheIndex(ticket); if (index == -1) { changed = true; if (_newTradeAction != NULL) _newTradeAction.DoAction(0, 0); } else { active_still_active[index] = true; // order is still there if (OrderOpenPrice() != active_price[index] || OrderStopLoss() != active_stoploss[index] || OrderTakeProfit() != active_takeprofit[index] || OrderType() != active_type[index]) { changed = true; if (_tradeChangedAction != NULL) _tradeChangedAction.DoAction(0, 0); } } } for (int index = 0; index < active_total; index++) { if (active_still_active[index] == false) { changed = true; if (_closedTradeAction != NULL && OrderSelect(active_ticket[index], MODE_HISTORY)) _closedTradeAction.DoAction(0, 0); } active_still_active[index] = false; } if (changed) updateActiveOrders(); } private: int getOrderCacheIndex(const int ticket) { for (int i = 0; i < active_total; i++) { if (active_ticket[i] == ticket) return i; } return -1; } /** * read in the current state of all open orders * and trades so we can track any changes in the next tick */ void updateActiveOrders() { active_total = OrdersTotal(); for (int i = 0; i < active_total; i++) { if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; active_ticket[i] = OrderTicket(); active_type[i] = OrderType(); active_price[i] = OrderOpenPrice(); active_stoploss[i] = OrderStopLoss(); active_takeprofit[i] = OrderTakeProfit(); active_still_active[i] = false; // filled in the next tick } } }; #endif // Trade calculator v2.3 // More templates and snippets on https://github.com/sibvic/mq4-templates // Order side enum v1.0 #ifndef OrderSide_IMP #define OrderSide_IMP enum OrderSide { BuySide, // Buy/long SellSide // Sell/short }; #endif // Supported stop loss/take profit types (outdated) v1.0 #ifndef StopLimitType_IMP #define StopLimitType_IMP enum StopLimitType { StopLimitDoNotUse, // Do not use StopLimitPercent, // Set in % StopLimitPips, // Set in Pips StopLimitDollar, // Set in $, StopLimitRiskReward, // Set in % of stop loss (take profit only) StopLimitAbsolute // Set in absolite value (rate) }; #endif // Supported position size types v1.0 #ifndef PositionSizeType_IMP #define PositionSizeType_IMP enum PositionSizeType { PositionSizeAmount, // $ PositionSizeContract, // In contracts PositionSizeEquity, // % of equity PositionSizeRisk, // Risk in % of equity PositionSizeRiskCurrency // Risk in $ }; #endif // Orders iterator v 1.12 // More templates and snippets on https://github.com/sibvic/mq4-templates #ifndef OrdersIterator_IMP #define OrdersIterator_IMP enum CompareType { CompareLessThan, CompareMoreThan }; class OrdersIterator { bool _useMagicNumber; int _magicNumber; bool _useOrderType; int _orderType; bool _trades; bool _useSide; bool _isBuySide; int _lastIndex; bool _useSymbol; string _symbol; bool _useProfit; double _profit; bool _useComment; string _comment; CompareType _profitCompare; bool _orders; public: OrdersIterator() { _useOrderType = false; _useMagicNumber = false; _useSide = false; _lastIndex = INT_MIN; _trades = false; _useSymbol = false; _useProfit = false; _orders = false; _useComment = false; } OrdersIterator *WhenSymbol(const string symbol) { _useSymbol = true; _symbol = symbol; return &this; } OrdersIterator *WhenProfit(const double profit, const CompareType compare) { _useProfit = true; _profit = profit; _profitCompare = compare; return &this; } OrdersIterator *WhenTrade() { _trades = true; return &this; } OrdersIterator *WhenOrder() { _orders = true; return &this; } OrdersIterator *WhenSide(const OrderSide side) { _useSide = true; _isBuySide = side == BuySide; return &this; } OrdersIterator *WhenOrderType(const int orderType) { _useOrderType = true; _orderType = orderType; return &this; } OrdersIterator *WhenMagicNumber(const int magicNumber) { _useMagicNumber = true; _magicNumber = magicNumber; return &this; } OrdersIterator *WhenComment(const string comment) { _useComment = true; _comment = comment; return &this; } int GetOrderType() { return OrderType(); } double GetProfit() { return OrderProfit(); } double IsBuy() { return OrderType() == OP_BUY; } double IsSell() { return OrderType() == OP_SELL; } int GetTicket() { return OrderTicket(); } datetime GetOpenTime() { return OrderOpenTime(); } double GetOpenPrice() { return OrderOpenPrice(); } double GetStopLoss() { return OrderStopLoss(); } double GetTakeProfit() { return OrderTakeProfit(); } string GetSymbol() { return OrderSymbol(); } int Count() { int count = 0; for (int i = OrdersTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && PassFilter()) count++; } return count; } bool Next() { if (_lastIndex == INT_MIN) _lastIndex = OrdersTotal() - 1; else _lastIndex = _lastIndex - 1; while (_lastIndex >= 0) { if (OrderSelect(_lastIndex, SELECT_BY_POS, MODE_TRADES) && PassFilter()) return true; _lastIndex = _lastIndex - 1; } return false; } bool Any() { for (int i = OrdersTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && PassFilter()) return true; } return false; } int First() { for (int i = OrdersTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && PassFilter()) return OrderTicket(); } return -1; } void Reset() { _lastIndex = INT_MIN; } private: bool PassFilter() { if (_useMagicNumber && OrderMagicNumber() != _magicNumber) return false; if (_useOrderType && OrderType() != _orderType) return false; if (_trades && !IsTrade()) return false; if (_orders && IsTrade()) return false; if (_useSymbol && OrderSymbol() != _symbol) return false; if (_useProfit) { switch (_profitCompare) { case CompareLessThan: if (OrderProfit() >= _profit) { return false; } break; case CompareMoreThan: if (OrderProfit() <= _profit) { return false; } break; } } if (_useSide) { if (_trades) { if (_isBuySide && !IsBuy()) return false; if (!_isBuySide && !IsSell()) return false; } else { //TODO: IMPLEMENT!!!! } } if (_useComment && OrderComment() != _comment) return false; return true; } bool IsTrade() { return (OrderType() == OP_BUY || OrderType() == OP_SELL) && OrderCloseTime() == 0.0; } }; #endif // Instrument info v.1.7 // More templates and snippets on https://github.com/sibvic/mq4-templates #ifndef InstrumentInfo_IMP #define InstrumentInfo_IMP class InstrumentInfo { string _symbol; double _mult; double _point; double _pipSize; int _digits; double _tickSize; public: InstrumentInfo(const string symbol) { _symbol = symbol; _point = MarketInfo(symbol, MODE_POINT); _digits = (int)MarketInfo(symbol, MODE_DIGITS); _mult = _digits == 3 || _digits == 5 ? 10 : 1; _pipSize = _point * _mult; _tickSize = MarketInfo(_symbol, MODE_TICKSIZE); } // Return < 0 when lot1 < lot2, > 0 when lot1 > lot2 and 0 owtherwise int CompareLots(double lot1, double lot2) { double lotStep = SymbolInfoDouble(_symbol, SYMBOL_VOLUME_STEP); if (lotStep == 0) { return lot1 < lot2 ? -1 : (lot1 > lot2 ? 1 : 0); } int lotSteps1 = (int)floor(lot1 / lotStep + 0.5); int lotSteps2 = (int)floor(lot2 / lotStep + 0.5); int res = lotSteps1 - lotSteps2; return res; } static double GetBid(const string symbol) { return MarketInfo(symbol, MODE_BID); } double GetBid() { return GetBid(_symbol); } static double GetAsk(const string symbol) { return MarketInfo(symbol, MODE_ASK); } double GetAsk() { return GetAsk(_symbol); } static double GetPipSize(const string symbol) { double point = MarketInfo(symbol, MODE_POINT); double digits = (int)MarketInfo(symbol, MODE_DIGITS); double mult = digits == 3 || digits == 5 ? 10 : 1; return point * mult; } double GetPipSize() { return _pipSize; } double GetPointSize() { return _point; } string GetSymbol() { return _symbol; } double GetSpread() { return (GetAsk() - GetBid()) / GetPipSize(); } int GetDigits() { return _digits; } double GetTickSize() { return _tickSize; } double GetMinLots() { return SymbolInfoDouble(_symbol, SYMBOL_VOLUME_MIN); }; double AddPips(const double rate, const double pips) { return RoundRate(rate + pips * _pipSize); } double RoundRate(const double rate) { return NormalizeDouble(MathFloor(rate / _tickSize + 0.5) * _tickSize, _digits); } double RoundLots(const double lots) { double lotStep = SymbolInfoDouble(_symbol, SYMBOL_VOLUME_STEP); if (lotStep == 0) { return 0.0; } return floor(lots / lotStep) * lotStep; } double LimitLots(const double lots) { double minVolume = GetMinLots(); if (minVolume > lots) { return 0.0; } double maxVolume = SymbolInfoDouble(_symbol, SYMBOL_VOLUME_MAX); if (maxVolume < lots) { return maxVolume; } return lots; } double NormalizeLots(const double lots) { return LimitLots(RoundLots(lots)); } }; #endif #ifndef TradingCalculator_IMP #define TradingCalculator_IMP class TradingCalculator { InstrumentInfo *_symbol; TradingCalculator(const string symbol) { _symbol = new InstrumentInfo(symbol); } public: static TradingCalculator *Create(const string symbol) { ResetLastError(); double temp = MarketInfo(symbol, MODE_POINT); if (GetLastError() != 0) return NULL; return new TradingCalculator(symbol); } ~TradingCalculator() { delete _symbol; } double GetPipSize() { return _symbol.GetPipSize(); } string GetSymbol() { return _symbol.GetSymbol(); } double GetBid() { return _symbol.GetBid(); } double GetAsk() { return _symbol.GetAsk(); } int GetDigits() { return _symbol.GetDigits(); } double GetSpread() { return _symbol.GetSpread(); } double GetMinLots() { return _symbol.GetMinLots(); } static bool IsBuyOrder() { switch (OrderType()) { case OP_BUY: case OP_BUYLIMIT: case OP_BUYSTOP: return true; } return false; } double GetBreakevenPrice(OrdersIterator &it1, const OrderSide side, double &totalAmount) { totalAmount = 0.0; double lotStep = SymbolInfoDouble(_symbol.GetSymbol(), SYMBOL_VOLUME_STEP); double price = side == BuySide ? _symbol.GetBid() : _symbol.GetAsk(); double totalPL = 0; while (it1.Next()) { double orderLots = OrderLots(); totalAmount += orderLots / lotStep; if (side == BuySide) totalPL += (price - OrderOpenPrice()) * (OrderLots() / lotStep); else totalPL += (OrderOpenPrice() - price) * (OrderLots() / lotStep); } if (totalAmount == 0.0) return 0.0; double shift = -(totalPL / totalAmount); return side == BuySide ? price + shift : price - shift; } double GetBreakevenPrice(const int side, const int magicNumber, double &totalAmount) { totalAmount = 0.0; OrdersIterator it1(); it1.WhenMagicNumber(magicNumber); it1.WhenSymbol(_symbol.GetSymbol()); it1.WhenOrderType(side); return GetBreakevenPrice(it1, side == OP_BUY ? BuySide : SellSide, totalAmount); } double CalculateTakeProfit(const bool isBuy, const double takeProfit, const StopLimitType takeProfitType, const double amount, double basePrice) { int direction = isBuy ? 1 : -1; switch (takeProfitType) { case StopLimitPercent: return RoundRate(basePrice + basePrice * takeProfit / 100.0 * direction); case StopLimitPips: return RoundRate(basePrice + takeProfit * _symbol.GetPipSize() * direction); case StopLimitDollar: return RoundRate(basePrice + CalculateSLShift(amount, takeProfit) * direction); case StopLimitAbsolute: return takeProfit; } return 0.0; } double CalculateStopLoss(const bool isBuy, const double stopLoss, const StopLimitType stopLossType, const double amount, double basePrice) { int direction = isBuy ? 1 : -1; switch (stopLossType) { case StopLimitPercent: return RoundRate(basePrice - basePrice * stopLoss / 100.0 * direction); case StopLimitPips: return RoundRate(basePrice - stopLoss * _symbol.GetPipSize() * direction); case StopLimitDollar: return RoundRate(basePrice - CalculateSLShift(amount, stopLoss) * direction); case StopLimitAbsolute: return stopLoss; } return 0.0; } double GetLots(const PositionSizeType lotsType, const double lotsValue, const double stopDistance) { switch (lotsType) { case PositionSizeAmount: return GetLotsForMoney(lotsValue); case PositionSizeContract: return _symbol.NormalizeLots(lotsValue); case PositionSizeEquity: return GetLotsForMoney(AccountEquity() * lotsValue / 100.0); case PositionSizeRisk: { double affordableLoss = AccountEquity() * lotsValue / 100.0; double unitCost = MarketInfo(_symbol.GetSymbol(), MODE_TICKVALUE); double tickSize = _symbol.GetTickSize(); double possibleLoss = unitCost * stopDistance / tickSize; if (possibleLoss <= 0.01) { return 0; } return _symbol.NormalizeLots(affordableLoss / possibleLoss); } case PositionSizeRiskCurrency: { double unitCost = MarketInfo(_symbol.GetSymbol(), MODE_TICKVALUE); double tickSize = _symbol.GetTickSize(); double possibleLoss = unitCost * stopDistance / tickSize; if (possibleLoss <= 0.01) { return 0; } return _symbol.NormalizeLots(lotsValue / possibleLoss); } } return lotsValue; } bool IsLotsValid(const double lots, PositionSizeType lotsType, string &error) { switch (lotsType) { case PositionSizeContract: return IsContractLotsValid(lots, error); } return true; } double NormalizeLots(const double lots) { return _symbol.NormalizeLots(lots); } double RoundRate(const double rate) { return _symbol.RoundRate(rate); } private: bool IsContractLotsValid(const double lots, string &error) { double minVolume = _symbol.GetMinLots(); if (minVolume > lots) { error = "Min. allowed lot size is " + DoubleToString(minVolume); return false; } double maxVolume = SymbolInfoDouble(_symbol.GetSymbol(), SYMBOL_VOLUME_MAX); if (maxVolume < lots) { error = "Max. allowed lot size is " + DoubleToString(maxVolume); return false; } return true; } double GetLotsForMoney(const double money) { double marginRequired = MarketInfo(_symbol.GetSymbol(), MODE_MARGINREQUIRED); if (marginRequired <= 0.0) { Print("Margin is 0. Server misconfiguration?"); return 0.0; } return _symbol.NormalizeLots(money / marginRequired); } double CalculateSLShift(const double amount, const double money) { double unitCost = MarketInfo(_symbol.GetSymbol(), MODE_TICKVALUE); double tickSize = _symbol.GetTickSize(); return (money / (unitCost / tickSize)) / amount; } }; #endif // Order builder v2.1 // Trading commands v.2.14 // More templates and snippets on https://github.com/sibvic/mq4-templates #ifndef TradingCommands_IMP #define TradingCommands_IMP class TradingCommands { public: static bool MoveSLTP(const int ticketId, const double newStopLoss, const double newTakeProfit, string &error) { if (!OrderSelect(ticketId, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0) { error = "Trade not found"; return false; } double rate = OrderOpenPrice(); ResetLastError(); int res = OrderModify(ticketId, rate, newStopLoss, newTakeProfit, 0, CLR_NONE); int errorCode = GetLastError(); switch (errorCode) { case ERR_NO_ERROR: break; case ERR_NO_RESULT: error = "Broker returned no error but no confirmation as well"; break; case ERR_INVALID_TICKET: error = "Trade not found"; return false; case ERR_INVALID_STOPS: { string symbol = OrderSymbol(); InstrumentInfo instrument(symbol); double point = instrument.GetPointSize(); int minStopDistancePoints = (int)MarketInfo(symbol, MODE_STOPLEVEL); if (newStopLoss != 0.0 && MathRound(MathAbs(rate - newStopLoss) / point) < minStopDistancePoints) error = "Your stop loss level is too close. The minimal distance allowed is " + IntegerToString(minStopDistancePoints) + " points"; else if (newTakeProfit != 0.0 && MathRound(MathAbs(rate - newTakeProfit) / point) < minStopDistancePoints) error = "Your take profit level is too close. The minimal distance allowed is " + IntegerToString(minStopDistancePoints) + " points"; else { int orderType = OrderType(); bool isBuyOrder = orderType == OP_BUY || orderType == OP_BUYLIMIT || orderType == OP_BUYSTOP; double rateDistance = orderType ? MathAbs(rate - instrument.GetAsk()) / point : MathAbs(rate - instrument.GetBid()) / point; if (rateDistance < minStopDistancePoints) error = "Distance to the pending order rate is too close: " + DoubleToStr(rateDistance, 1) + ". Min. allowed distance: " + IntegerToString(minStopDistancePoints); else error = "Invalid stop loss or take profit in the request"; } } return false; default: error = "Last error: " + IntegerToString(errorCode); return false; } return true; } static bool MoveSL(const int ticketId, const double newStopLoss, string &error) { if (!OrderSelect(ticketId, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0) { error = "Trade not found"; return false; } return MoveSLTP(ticketId, newStopLoss, OrderTakeProfit(), error); } static bool MoveTP(const int ticketId, const double newTakeProfit, string &error) { if (!OrderSelect(ticketId, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0) { error = "Trade not found"; return false; } return MoveSLTP(ticketId, OrderStopLoss(), newTakeProfit, error); } static void DeleteOrders(const int magicNumber) { OrdersIterator it1(); it1.WhenMagicNumber(magicNumber); it1.WhenOrder(); while (it1.Next()) { int ticket = OrderTicket(); if (!OrderDelete(ticket)) Print("Failed to delete the order " + IntegerToString(ticket)); } } static bool DeleteCurrentOrder(string &error) { int ticket = OrderTicket(); if (!OrderDelete(ticket)) { error = "Failed to delete the order " + IntegerToString(ticket); return false; } return true; } static bool CloseCurrentOrder(const int slippage, const double amount, string &error) { int orderType = OrderType(); if (orderType == OP_BUY) return CloseCurrentOrder(InstrumentInfo::GetBid(OrderSymbol()), slippage, amount, error); if (orderType == OP_SELL) return CloseCurrentOrder(InstrumentInfo::GetAsk(OrderSymbol()), slippage, amount, error); return false; } static bool CloseCurrentOrder(const int slippage, string &error) { return CloseCurrentOrder(slippage, OrderLots(), error); } static bool CloseCurrentOrder(const double price, const int slippage, string &error) { return CloseCurrentOrder(price, slippage, OrderLots(), error); } static bool CloseCurrentOrder(const double price, const int slippage, const double amount, string &error) { bool closed = OrderClose(OrderTicket(), amount, price, slippage); if (closed) return true; int lastError = GetLastError(); switch (lastError) { case ERR_NOT_ENOUGH_MONEY: error = "Not enough money"; break; case ERR_TRADE_NOT_ALLOWED: error = "Trading is not allowed"; break; case ERR_INVALID_PRICE: error = "Invalid closing price: " + DoubleToStr(price); break; case ERR_INVALID_TRADE_VOLUME: error = "Invalid trade volume: " + DoubleToStr(amount); break; case ERR_TRADE_PROHIBITED_BY_FIFO: error = "Prohibited by FIFO"; break; case ERR_MARKET_CLOSED: error = "The market is closed"; break; default: error = "Last error: " + IntegerToString(lastError); break; } return false; } static int CloseTrades(OrdersIterator &it, const int slippage) { int failed = 0; return CloseTrades(it, slippage, failed); } static int CloseTrades(OrdersIterator &it, const int slippage, int& failed) { int closedPositions = 0; failed = 0; while (it.Next()) { string error; if (!CloseCurrentOrder(slippage, error)) { ++failed; Print("Failed to close positoin. ", error); } else ++closedPositions; } return closedPositions; } }; #endif // Action on condition logic v2.0 // Action on condition v3.0 // ICondition v3.1 // More templates and snippets on https://github.com/sibvic/mq4-templates interface ICondition { public: virtual void AddRef() = 0; virtual void Release() = 0; virtual bool IsPass(const int period, const datetime date) = 0; virtual string GetLogMessage(const int period, const datetime date) = 0; }; // Action on condition controller interface v1.0 #ifndef IActionOnConditionController_IMP #define IActionOnConditionController_IMP class IActionOnConditionController { public: virtual bool Set(IAction* action, ICondition *condition) = 0; virtual void DoLogic(const int period, datetime date) = 0; }; #endif #ifndef ActionOnConditionController_IMP #define ActionOnConditionController_IMP class ActionOnConditionController : public IActionOnConditionController { bool _finished; ICondition *_condition; IAction* _action; public: ActionOnConditionController() { _action = NULL; _condition = NULL; _finished = true; } ~ActionOnConditionController() { _action.Release(); _condition.Release(); } bool Set(IAction* action, ICondition *condition) { if (!_finished || action == NULL) return false; if (_action != NULL) _action.Release(); _action = action; _action.AddRef(); _finished = false; if (_condition != NULL) _condition.Release(); _condition = condition; _condition.AddRef(); return true; } void DoLogic(const int period, datetime date) { if (_finished) return; if (_condition.IsPass(period, date) && _action.DoAction(period, date)) { _finished = true; } } }; #endif // Multi action on condition v1.0 #ifndef MultiActionOnConditionController_IMP #define MultiActionOnConditionController_IMP class MultiActionOnConditionController : public IActionOnConditionController { ICondition *_condition; IAction* _action; public: MultiActionOnConditionController() { _action = NULL; _condition = NULL; } ~MultiActionOnConditionController() { _action.Release(); _condition.Release(); } bool Set(IAction* action, ICondition *condition) { if (action == NULL) { return false; } if (_action != NULL) { _action.Release(); } _action = action; _action.AddRef(); if (_condition != NULL) { _condition.Release(); } _condition = condition; _condition.AddRef(); return true; } void DoLogic(const int period, datetime date) { if (_condition.IsPass(period, date)) { _action.DoAction(period, date); } } }; #endif #ifndef ActionOnConditionLogic_IMP #define ActionOnConditionLogic_IMP class ActionOnConditionLogic { IActionOnConditionController* _controllers[]; public: ~ActionOnConditionLogic() { int count = ArraySize(_controllers); for (int i = 0; i < count; ++i) { delete _controllers[i]; } } void DoLogic(const int period, datetime date) { int count = ArraySize(_controllers); for (int i = 0; i < count; ++i) { _controllers[i].DoLogic(period, date); } } bool AddActionOnCondition(IAction* action, ICondition* condition) { int count = ArraySize(_controllers); for (int i = 0; i < count; ++i) { if (_controllers[i].Set(action, condition)) return true; } ArrayResize(_controllers, count + 1); _controllers[count] = new ActionOnConditionController(); return _controllers[count].Set(action, condition); } bool AddMultiActionOnCondition(IAction* action, ICondition* condition) { int count = ArraySize(_controllers); for (int i = 0; i < count; ++i) { if (_controllers[i].Set(action, condition)) return true; } ArrayResize(_controllers, count + 1); _controllers[count] = new MultiActionOnConditionController(); return _controllers[count].Set(action, condition); } }; #endif // No stop loss or take profit condition v1.0 // Abstract condition v1.1 #ifndef AConditionBase_IMP #define AConditionBase_IMP class AConditionBase : public ICondition { int _references; public: AConditionBase() { _references = 1; } virtual void AddRef() { ++_references; } virtual void Release() { --_references; if (_references == 0) delete &this; } virtual string GetLogMessage(const int period, const datetime date) { return ""; } }; #endif #ifndef NoStopLossOrTakeProfitCondition_IMP #define NoStopLossOrTakeProfitCondition_IMP class NoStopLossOrTakeProfitCondition : public AConditionBase { int _currentTicket; public: NoStopLossOrTakeProfitCondition(int currentTicket) { _currentTicket = currentTicket; } virtual bool IsPass(const int period, const datetime date) { if (!OrderSelect(_currentTicket, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0.0) return true; return OrderStopLoss() == 0 || OrderTakeProfit() == 0; } }; #endif // Set stop loss and/or take profit action v2.0 // AAction v1.0 #ifndef AAction_IMP #define AAction_IMP class AAction : public IAction { protected: int _references; AAction() { _references = 1; } public: void AddRef() { ++_references; } void Release() { --_references; if (_references == 0) delete &this; } }; #endif #ifndef SetStopLossAndTakeProfitAction_IMP #define SetStopLossAndTakeProfitAction_IMP class SetStopLossAndTakeProfitAction : public AAction { double _stopLoss; double _takeProfit; int _currentTicket; public: SetStopLossAndTakeProfitAction(double stopLoss, double takeProfit, int currentTicket) { _stopLoss = stopLoss; _takeProfit = takeProfit; _currentTicket = currentTicket; } ~SetStopLossAndTakeProfitAction() { } virtual bool DoAction(const int period, const datetime date) { if (!OrderSelect(_currentTicket, SELECT_BY_TICKET, MODE_TRADES) || OrderCloseTime() != 0.0) return true; if ((OrderStopLoss() != 0 || _stopLoss == 0) && (OrderTakeProfit() != 0 || _takeProfit == 0)) return true; string errorMessage; bool success = TradingCommands::MoveSLTP(_currentTicket, _stopLoss, _takeProfit, errorMessage); return success && (errorMessage == NULL || errorMessage == ""); } }; #endif #ifndef OrderBuilder_IMP #define OrderBuilder_IMP class OrderBuilder { OrderSide _orderSide; string _instrument; double _amount; double _rate; int _slippage; double _stopLoss; double _takeProfit; int _magicNumber; string _comment; bool _ecnBroker; ActionOnConditionLogic* _actions; public: OrderBuilder(ActionOnConditionLogic* actions) { _actions = actions; _ecnBroker = false; } // Sets ECN broker flag OrderBuilder* SetECNBroker(bool isEcn) { _ecnBroker = isEcn; return &this; } OrderBuilder *SetSide(const OrderSide orderSide) { _orderSide = orderSide; return &this; } OrderBuilder *SetInstrument(const string instrument) { _instrument = instrument; return &this; } OrderBuilder *SetAmount(const double amount) { _amount = amount; return &this; } OrderBuilder *SetRate(const double rate) { _rate = rate; return &this; } OrderBuilder *SetSlippage(const int slippage) { _slippage = slippage; return &this; } OrderBuilder *SetStopLoss(const double stop) { _stopLoss = stop; return &this; } OrderBuilder *SetTakeProfit(const double limit) { _takeProfit = limit; return &this; } OrderBuilder *SetMagicNumber(const int magicNumber) { _magicNumber = magicNumber; return &this; } OrderBuilder *SetComment(const string comment) { _comment = comment; return &this; } int Execute(string &errorMessage) { InstrumentInfo instrument(_instrument); double rate = instrument.RoundRate(_rate); double sl = instrument.RoundRate(_stopLoss); double tp = instrument.RoundRate(_takeProfit); int orderType; if (_orderSide == BuySide) orderType = rate > instrument.GetAsk() ? OP_BUYSTOP : OP_BUYLIMIT; else orderType = rate < instrument.GetBid() ? OP_SELLSTOP : OP_SELLLIMIT; int order; if (_ecnBroker) order = OrderSend(_instrument, orderType, _amount, rate, _slippage, 0, 0, _comment, _magicNumber); else order = OrderSend(_instrument, orderType, _amount, rate, _slippage, sl, tp, _comment, _magicNumber); if (order == -1) { int error = GetLastError(); switch (error) { case ERR_OFF_QUOTES: errorMessage = "No quotes"; return -1; case ERR_NOT_ENOUGH_MONEY: errorMessage = "Not enough money"; break; case ERR_TRADE_NOT_ALLOWED: errorMessage = "Trading is not allowed"; break; case ERR_TRADE_TOO_MANY_ORDERS: errorMessage = "Too many orders opened"; break; case ERR_INVALID_STOPS: { double point = SymbolInfoDouble(_instrument, SYMBOL_POINT); int minStopDistancePoints = (int)SymbolInfoInteger(_instrument, SYMBOL_TRADE_STOPS_LEVEL); if (_stopLoss != 0.0) { if (MathRound(MathAbs(rate - _stopLoss) / point) < minStopDistancePoints) errorMessage = "Your stop loss level is too close. The minimal distance allowed is " + IntegerToString(minStopDistancePoints) + " points"; else errorMessage = "Invalid stop loss in the request. Do you have ECN broker and forget to enable ECN?"; } else if (_takeProfit != 0.0) { if (MathRound(MathAbs(rate - _takeProfit) / point) < minStopDistancePoints) errorMessage = "Your take profit level is too close. The minimal distance allowed is " + IntegerToString(minStopDistancePoints) + " points"; else errorMessage = "Invalid take profit in the request. Do you have ECN broker and forget to enable ECN?"; } else errorMessage = "Invalid stop loss or take profit in the request. Do you have ECN broker and forget to enable ECN?"; } break; case ERR_INVALID_TRADE_PARAMETERS: errorMessage = "Incorrect trade parameters. Symbol: " + _instrument + " Order type: " + IntegerToString(orderType) + " Amount: " + DoubleToString(_amount) + " Rate: " + DoubleToString(rate) + " Slippage: " + DoubleToString(_slippage) + " SL: " + DoubleToString(sl) + " TP: " + DoubleToString(tp) + " Comment: " + _comment == NULL ? "" : _comment + " Magic number: " + IntegerToString(_magicNumber); break; default: errorMessage = "Failed to create order: " + IntegerToString(error); break; } } else if (_ecnBroker && (_stopLoss != 0 || _takeProfit != 0)) { NoStopLossOrTakeProfitCondition* condition = new NoStopLossOrTakeProfitCondition(order); SetStopLossAndTakeProfitAction* action = new SetStopLossAndTakeProfitAction(_stopLoss, _takeProfit, order); _actions.AddActionOnCondition(action, condition); condition.Release(); action.Release(); } return order; } }; #endif // Market order builder v 2.3 // More templates and snippets on https://github.com/sibvic/mq4-templates #ifndef MarketOrderBuilder_IMP #define MarketOrderBuilder_IMP class MarketOrderBuilder { OrderSide _orderSide; string _instrument; double _amount; double _rate; int _slippage; double _stopLoss; double _takeProfit; int _magicNumber; string _comment; bool _ecnBroker; ActionOnConditionLogic* _actions; int _retries; public: MarketOrderBuilder(ActionOnConditionLogic* actions) { _retries = 1; _actions = actions; _ecnBroker = false; } MarketOrderBuilder* SetRetries(int retries) { _retries = retries; return &this; } MarketOrderBuilder *SetSide(const OrderSide orderSide) { _orderSide = orderSide; return &this; } // Sets ECN broker flag MarketOrderBuilder* SetECNBroker(bool isEcn) { _ecnBroker = isEcn; return &this; } MarketOrderBuilder *SetInstrument(const string instrument) { _instrument = instrument; return &this; } MarketOrderBuilder *SetAmount(const double amount) { _amount = amount; return &this; } MarketOrderBuilder *SetSlippage(const int slippage) { _slippage = slippage; return &this; } MarketOrderBuilder *SetStopLoss(const double stop) { _stopLoss = NormalizeDouble(stop, Digits); return &this; } MarketOrderBuilder *SetTakeProfit(const double limit) { _takeProfit = NormalizeDouble(limit, Digits); return &this; } MarketOrderBuilder *SetMagicNumber(const int magicNumber) { _magicNumber = magicNumber; return &this; } MarketOrderBuilder *SetComment(const string comment) { _comment = comment; return &this; } int Execute(string &errorMessage) { int orderType = _orderSide == BuySide ? OP_BUY : OP_SELL; double minstoplevel = MarketInfo(_instrument, MODE_STOPLEVEL); double rate = _orderSide == BuySide ? MarketInfo(_instrument, MODE_ASK) : MarketInfo(_instrument, MODE_BID); for (int i = 0; i < _retries; ++i) { int order; if (_ecnBroker) order = OrderSend(_instrument, orderType, _amount, rate, _slippage, 0, 0, _comment, _magicNumber); else order = OrderSend(_instrument, orderType, _amount, rate, _slippage, _stopLoss, _takeProfit, _comment, _magicNumber); if (order == -1) { int error = GetLastError(); switch (error) { case ERR_REQUOTE: RefreshRates(); continue; case ERR_NOT_ENOUGH_MONEY: errorMessage = "Not enougth money"; return -1; case ERR_INVALID_TRADE_VOLUME: { double minVolume = SymbolInfoDouble(_instrument, SYMBOL_VOLUME_MIN); if (_amount < minVolume) { errorMessage = "Volume of the lot is too low: " + DoubleToStr(_amount) + " Min lot is: " + DoubleToStr(minVolume); return -1; } double maxVolume = SymbolInfoDouble(_instrument, SYMBOL_VOLUME_MAX); if (_amount > maxVolume) { errorMessage = "Volume of the lot is too high: " + DoubleToStr(_amount) + " Max lot is: " + DoubleToStr(maxVolume); return -1; } errorMessage = "Invalid volume: " + DoubleToStr(_amount); } return -1; case ERR_OFF_QUOTES: errorMessage = "No quotes"; return -1; case ERR_TRADE_NOT_ALLOWED: errorMessage = "Trading is not allowed"; return -1; case ERR_TRADE_HEDGE_PROHIBITED: errorMessage = "Trade hedge prohibited"; return -1; case ERR_TRADE_TOO_MANY_ORDERS: errorMessage = "Too many orders opened"; return -1; case ERR_INVALID_STOPS: { double point = SymbolInfoDouble(_instrument, SYMBOL_POINT); int minStopDistancePoints = (int)SymbolInfoInteger(_instrument, SYMBOL_TRADE_STOPS_LEVEL); if (_stopLoss != 0.0) { if (MathRound(MathAbs(rate - _stopLoss) / point) < minStopDistancePoints) errorMessage = "Your stop loss level is too close. The minimal distance allowed is " + IntegerToString(minStopDistancePoints) + " points"; else errorMessage = "Invalid stop loss in the request. Do you have ECN broker and forget to enable ECN?"; } else if (_takeProfit != 0.0) { if (MathRound(MathAbs(rate - _takeProfit) / point) < minStopDistancePoints) errorMessage = "Your take profit level is too close. The minimal distance allowed is " + IntegerToString(minStopDistancePoints) + " points"; else errorMessage = "Invalid take profit in the request. Do you have ECN broker and forget to enable ECN?"; } else errorMessage = "Invalid stop loss or take profit in the request. Do you have ECN broker and forget to enable ECN?"; } return -1; case ERR_INVALID_PRICE: errorMessage = "Invalid price"; return -1; default: errorMessage = "Failed to create order: " + IntegerToString(error); return -1; } } else if (_ecnBroker && (_stopLoss != 0 || _takeProfit != 0)) { NoStopLossOrTakeProfitCondition* condition = new NoStopLossOrTakeProfitCondition(order); SetStopLossAndTakeProfitAction* action = new SetStopLossAndTakeProfitAction(_stopLoss, _takeProfit, order); _actions.AddActionOnCondition(action, condition); condition.Release(); action.Release(); } return order; } errorMessage = "Requote"; return -1; } }; #endif class OnTradeClosedAction : public AAction { public: virtual bool DoAction(const int period, const datetime date) { string comment = OrderComment(); double rate = OrderOpenPrice(); if (comment != NULL && comment != "") { rate = StrToDouble(comment); } if (OrderClosePrice() == OrderStopLoss() || StringFind(OrderComment(), "[sl]", 0) != -1) { TradingCalculator* calc = TradingCalculator::Create(OrderSymbol()); double amount = OrderLots() * 4; double takeProfit = calc.CalculateTakeProfit(OrderType() == OP_BUY, limit, StopLimitPips, amount, OrderOpenPrice()); double stopLoss = calc.CalculateStopLoss(OrderType() == OP_BUY, stop, StopLimitPips, amount, OrderOpenPrice()); delete calc; string error; OrderBuilder order(actions); int ticket = order.SetSide(OrderType() == OP_BUY ? BuySide : SellSide) .SetInstrument(OrderSymbol()) .SetAmount(OrderLots() * 4) .SetRate(OrderOpenPrice()) .SetMagicNumber(OrderMagicNumber()) .SetStopLoss(stopLoss) .SetTakeProfit(takeProfit) .Execute(error); if (ticket <= 0) { Print(error); } } if (OrderClosePrice() == OrderTakeProfit() || StringFind(OrderComment(), "[tp]", 0) != -1) { Open(); } return false; } }; TradingMonitor tradingMonitor; IAction* closeTradeAction; ActionOnConditionLogic* actions; int OnInit() { closeTradeAction = new OnTradeClosedAction(); tradingMonitor.SetClosedTradeAction(closeTradeAction); actions = new ActionOnConditionLogic(); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { delete actions; actions = NULL; closeTradeAction.Release(); closeTradeAction = NULL; } void Open() { string error; TradingCommands::DeleteOrders(magic_number); MarketOrderBuilder marketorder(actions); OrderBuilder order(actions); InstrumentInfo instrument(_Symbol); double rate; double marketrate; bool isBuy = Close[0] > Open[0]; if (isBuy) { marketorder.SetSide(BuySide); order.SetSide(SellSide); rate = instrument.AddPips(instrument.GetBid(), -stop); marketrate = instrument.GetAsk(); } else { marketorder.SetSide(SellSide); order.SetSide(BuySide); rate = instrument.AddPips(instrument.GetAsk(), stop); marketrate = instrument.GetBid(); } TradingCalculator* calc = TradingCalculator::Create(_Symbol); order.SetStopLoss(calc.CalculateStopLoss(!isBuy, stop, StopLimitPips, Amount, rate)); order.SetTakeProfit(calc.CalculateTakeProfit(!isBuy, limit, StopLimitPips, Amount, rate)); marketorder.SetStopLoss(calc.CalculateStopLoss(isBuy, stop, StopLimitPips, Amount, marketrate)); marketorder.SetTakeProfit(calc.CalculateTakeProfit(isBuy, limit, StopLimitPips, Amount, marketrate)); delete calc; order.SetRate(rate); order.SetInstrument(_Symbol); order.SetAmount(Amount * 2); int ticket = order.Execute(error); if (ticket <= 0) { Print(error); return; } marketorder.SetInstrument(_Symbol); marketorder.SetAmount(Amount); int res = marketorder.Execute(error); if (res <= 0) { Print(error); return; } } bool executed = false; void OnTick() { tradingMonitor.DoWork(); if (!executed) { Open(); executed = true; } }