diff --git a/Account.mqh b/Account.mqh index de985cb5d..58089bb56 100644 --- a/Account.mqh +++ b/Account.mqh @@ -198,6 +198,18 @@ class Account { return (float)Account::AccountFreeMargin(); } + /** + * Returns free margin value of the current account in percentage. + * + * @return + * Account's free margin in percentage. + */ + double GetMarginFreeInPct() { + double _margin_free = GetMarginFree(); + double _margin_avail = GetMarginAvail(); + return _margin_avail > 0 ? 100 / _margin_avail * _margin_free : 0; + } + /** * Returns the current account number. */ @@ -574,16 +586,28 @@ class Account { // @todo return false; */ + case ACCOUNT_COND_MARGIN_FREE_IN_PC: + // Arguments: + // arg[0] - Specify value in percentage. + // arg[1] - Math comparison operators (@see: ENUM_MATH_CONDITION). + return Math::Compare(GetMarginFreeInPct(), _args[0].ToValue(), + (ENUM_MATH_CONDITION)_args[1].ToValue()); case ACCOUNT_COND_MARGIN_USED_10PC: - return AccountMargin() >= AccountEquity() / 100 * 10; + return GetMarginUsedInPct() >= 10; case ACCOUNT_COND_MARGIN_USED_20PC: - return AccountMargin() >= AccountEquity() / 100 * 20; + return GetMarginUsedInPct() >= 20; case ACCOUNT_COND_MARGIN_USED_50PC: - return AccountMargin() >= AccountEquity() / 100 * 50; + return GetMarginUsedInPct() >= 50; case ACCOUNT_COND_MARGIN_USED_80PC: - return AccountMargin() >= AccountEquity() / 100 * 80; + return GetMarginUsedInPct() >= 80; case ACCOUNT_COND_MARGIN_USED_99PC: - return AccountMargin() >= AccountEquity() / 100 * 99; + return GetMarginUsedInPct() >= 99; + case ACCOUNT_COND_MARGIN_USED_IN_PC: + // Arguments: + // arg[0] - Specify value in percentage. + // arg[1] - Math comparison operators (@see: ENUM_MATH_CONDITION). + return Math::Compare(GetMarginUsedInPct(), _args[0].ToValue(), + (ENUM_MATH_CONDITION)_args[1].ToValue()); default: // logger.Error(StringFormat("Invalid account condition: %s!", EnumToString(_cond), __FUNCTION_LINE__)); #ifdef __debug__ @@ -592,14 +616,22 @@ class Account { return false; } } + bool CheckCondition(ENUM_ACCOUNT_CONDITION _cond) { + ARRAY(DataParamEntry, _args); + return Account::CheckCondition(_cond, _args); + } bool CheckCondition(ENUM_ACCOUNT_CONDITION _cond, long _arg1) { ARRAY(DataParamEntry, _args); DataParamEntry _param1 = _arg1; ArrayPushObject(_args, _param1); return Account::CheckCondition(_cond, _args); } - bool CheckCondition(ENUM_ACCOUNT_CONDITION _cond) { + bool CheckCondition(ENUM_ACCOUNT_CONDITION _cond, long _arg1, long _arg2) { ARRAY(DataParamEntry, _args); + DataParamEntry _param1 = _arg1; + DataParamEntry _param2 = _arg2; + ArrayPushObject(_args, _param1); + ArrayPushObject(_args, _param2); return Account::CheckCondition(_cond, _args); } diff --git a/Condition.enum.h b/Condition.enum.h index 1a57b7759..84ddb0b47 100644 --- a/Condition.enum.h +++ b/Condition.enum.h @@ -149,14 +149,16 @@ enum ENUM_ACCOUNT_CONDITION { ACCOUNT_COND_EQUITY_IN_LOSS, // Equity in loss ACCOUNT_COND_EQUITY_IN_PROFIT, // Equity in profit /* @todo - ACCOUNT_COND_MARGIN_CALL_10PC, // Margin Call (10% margin left) - ACCOUNT_COND_MARGIN_CALL_20PC, // Margin Call (20% margin left) + ACCOUNT_COND_MARGIN_CALL_10PC, // Margin call (10% margin left) + ACCOUNT_COND_MARGIN_CALL_20PC, // Margin call (20% margin left) */ - ACCOUNT_COND_MARGIN_USED_10PC, // Margin Used in 10% - ACCOUNT_COND_MARGIN_USED_20PC, // Margin Used in 20% - ACCOUNT_COND_MARGIN_USED_50PC, // Margin Used in 50% - ACCOUNT_COND_MARGIN_USED_80PC, // Margin Used in 80% - ACCOUNT_COND_MARGIN_USED_99PC, // Margin Used in 99% + ACCOUNT_COND_MARGIN_FREE_IN_PC, // Margin used in % (args) + ACCOUNT_COND_MARGIN_USED_10PC, // Margin used in 10% + ACCOUNT_COND_MARGIN_USED_20PC, // Margin used in 20% + ACCOUNT_COND_MARGIN_USED_50PC, // Margin used in 50% + ACCOUNT_COND_MARGIN_USED_80PC, // Margin used in 80% + ACCOUNT_COND_MARGIN_USED_99PC, // Margin used in 99% + ACCOUNT_COND_MARGIN_USED_IN_PC, // Margin used in % (args) FINAL_ACCOUNT_CONDITION_ENTRY }; diff --git a/EA.mqh b/EA.mqh index f09a5f7b3..c8129561d 100644 --- a/EA.mqh +++ b/EA.mqh @@ -95,6 +95,7 @@ class EA { logger.Link(_trade.GetLogger()); // trade.GetByKey(_Symbol).GetLogger().Error("Test"); // logger.Flush(); + // Loads existing trades by magic number. } /** @@ -135,7 +136,7 @@ class EA { void Set(ENUM_STRATEGY_PARAM _param, T _value) { for (DictStructIterator> iter = strats.Begin(); iter.IsValid(); ++iter) { Strategy *_strat = iter.Value().Ptr(); - _strat.Set Set(_param, _value); + _strat.Set(_param, _value); } } @@ -212,8 +213,8 @@ class EA { if (_sig_f == 0 || GetSignalOpenFiltered(_signal, _sig_f) >= 0.5f) { _strat.Set(TRADE_PARAM_ORDER_COMMENT, _strat.GetOrderOpenComment("B:")); // Buy order open. - TradeRequest(ORDER_TYPE_BUY, _Symbol, _strat); - if (eparams.CheckSignalFilter(STRUCT_ENUM(EAParams, EA_PARAM_SIGNAL_FILTER_FIRST))) { + _result &= TradeRequest(ORDER_TYPE_BUY, _Symbol, _strat); + if (_result && eparams.CheckSignalFilter(STRUCT_ENUM(EAParams, EA_PARAM_SIGNAL_FILTER_FIRST))) { _signal.AddSignals(STRAT_SIGNAL_PROCESSED); break; } @@ -225,8 +226,8 @@ class EA { if (_sig_f == 0 || GetSignalOpenFiltered(_signal, _sig_f) <= -0.5f) { _strat.Set(TRADE_PARAM_ORDER_COMMENT, _strat.GetOrderOpenComment("S:")); // Sell order open. - TradeRequest(ORDER_TYPE_SELL, _Symbol, _strat); - if (eparams.CheckSignalFilter(STRUCT_ENUM(EAParams, EA_PARAM_SIGNAL_FILTER_FIRST))) { + _result &= TradeRequest(ORDER_TYPE_SELL, _Symbol, _strat); + if (_result && eparams.CheckSignalFilter(STRUCT_ENUM(EAParams, EA_PARAM_SIGNAL_FILTER_FIRST))) { _signal.AddSignals(STRAT_SIGNAL_PROCESSED); break; } @@ -250,7 +251,7 @@ class EA { if (_last_error > 0) { logger.Warning(StringFormat("Processing signals failed! Code: %d", _last_error), __FUNCTION_LINE__); } - return _last_error == 0; + return _result && _last_error == 0; } /** @@ -659,11 +660,13 @@ class EA { * Returns true if the strategy has been initialized correctly, otherwise false. */ template - bool StrategyAdd(ENUM_TIMEFRAMES _tf, long _magic_no = 0) { + bool StrategyAdd(ENUM_TIMEFRAMES _tf, long _magic_no = 0, int _type = 0) { bool _result = true; _magic_no = _magic_no > 0 ? _magic_no : rand(); Ref _strat = ((SClass *)NULL).Init(_tf); _strat.Ptr().Set(STRAT_PARAM_ID, _magic_no); + _strat.Ptr().Set(STRAT_PARAM_TF, _tf); + _strat.Ptr().Set(STRAT_PARAM_TYPE, _type); if (!strats.KeyExists(_magic_no)) { _result &= strats.Set(_magic_no, _strat); } else { @@ -691,16 +694,24 @@ class EA { * Returns true if all strategies has been initialized correctly, otherwise false. */ template - bool StrategyAdd(unsigned int _tfs, long _init_magic = 0) { + bool StrategyAdd(unsigned int _tfs, long _init_magic = 0, int _type = 0) { bool _result = true; for (int _tfi = 0; _tfi < sizeof(int) * 8; ++_tfi) { if ((_tfs & (1 << _tfi)) != 0) { - _result &= StrategyAdd(ChartTf::IndexToTf((ENUM_TIMEFRAMES_INDEX)_tfi), _init_magic + _tfi); + _result &= StrategyAdd(ChartTf::IndexToTf((ENUM_TIMEFRAMES_INDEX)_tfi), _init_magic + _tfi, _type); } } return _result; } + /** + * Loads existing trades for the given strategy. + */ + bool StrategyLoadTrades(Strategy *_strat) { + Trade *_trade = trade.GetByKey(_Symbol); + return _trade.OrdersLoadByMagic(_strat.Get(STRAT_PARAM_ID)); + } + /* Update methods */ /** @@ -735,11 +746,14 @@ class EA { * Updates strategy lot size. */ bool UpdateLotSize() { + bool _result = false; if (eparams.CheckFlag(EA_PARAM_FLAG_LOTSIZE_AUTO)) { - // Auto calculate lot size for each strategy. - return ExecuteAction(EA_ACTION_STRATS_EXE_ACTION, STRAT_ACTION_TRADE_EXE, TRADE_ACTION_CALC_LOT_SIZE); + // Auto calculate lot size for all strategies. + Trade *_trade = trade.GetByKey(_Symbol); + _result &= _trade.ExecuteAction(TRADE_ACTION_CALC_LOT_SIZE); + Set(STRAT_PARAM_LS, _trade.Get(TRADE_PARAM_LOT_SIZE)); } - return false; + return _result; } /* Conditions and actions */ @@ -852,6 +866,43 @@ class EA { /* Getters */ + /** + * Gets strategy based on the property value. + * + * @return + * Returns first found strategy instance on success. + * Otherwise, it returns NULL. + */ + template + Strategy *GetStrategyViaProp(ENUM_STRATEGY_PARAM _prop, T _value, ENUM_MATH_CONDITION _op = MATH_COND_EQ) { + for (DictStructIterator> iter = strats.Begin(); iter.IsValid(); ++iter) { + Strategy *_strat = iter.Value().Ptr(); + if (Math::Compare(_strat.Get(_prop), _value, _op)) { + return _strat; + } + } + return NULL; + } + + /** + * Gets strategy based on the two property values. + * + * @return + * Returns first found strategy instance on success. + * Otherwise, it returns NULL. + */ + template + Strategy *GetStrategyViaProp2(ENUM_STRATEGY_PARAM _prop1, T1 _value1, ENUM_STRATEGY_PARAM _prop2, T2 _value2, + ENUM_MATH_CONDITION _op = MATH_COND_EQ) { + for (DictStructIterator> iter = strats.Begin(); iter.IsValid(); ++iter) { + Strategy *_strat = iter.Value().Ptr(); + if (Math::Compare(_strat.Get(_prop1), _value1, _op) && Math::Compare(_strat.Get(_prop2), _value2, _op)) { + return _strat; + } + } + return NULL; + } + /** * Returns pointer to Terminal object. */ @@ -955,6 +1006,8 @@ class EA { _strat.Set(TRADE_PARAM_RISK_MARGIN, _margin_risk); // Link a logger instance. logger.Link(_strat.GetLogger()); + // Load existing strategy trades. + StrategyLoadTrades(_strat); } /* Printer methods */ diff --git a/Math.h b/Math.h index db7e4b5c2..4edd25801 100644 --- a/Math.h +++ b/Math.h @@ -90,8 +90,8 @@ class Math { /** * Checks condition for 2 values based on the given comparison operator. */ - template - static bool Compare(V1 _v1, V2 _v2, ENUM_MATH_CONDITION _op = MATH_COND_EQ) { + template + static bool Compare(T1 _v1, T2 _v2, ENUM_MATH_CONDITION _op = MATH_COND_EQ) { switch (_op) { case MATH_COND_EQ: return _v1 == _v2; diff --git a/Strategy.enum.h b/Strategy.enum.h index 29de1b9d9..c9c3b36b7 100644 --- a/Strategy.enum.h +++ b/Strategy.enum.h @@ -89,7 +89,9 @@ enum ENUM_STRATEGY_PARAM { STRAT_PARAM_SOFT, // Signal open filter time STRAT_PARAM_SOL, // Signal open level STRAT_PARAM_SOM, // Signal open method + STRAT_PARAM_TF, // Timeframe STRAT_PARAM_TFM, // Tick filter method + STRAT_PARAM_TYPE, // Type STRAT_PARAM_WEIGHT, // Weight FINAL_ENUM_STRATEGY_PARAM }; diff --git a/Strategy.mqh b/Strategy.mqh index 1a4bcadf1..1ff10865a 100644 --- a/Strategy.mqh +++ b/Strategy.mqh @@ -553,8 +553,6 @@ class Strategy : public Object { /* Setters */ - /* Getters */ - /** * Sets a strategy parameter value. */ @@ -888,8 +886,9 @@ class Strategy : public Object { _sargs[i] = _args[i + 1]; } _result = trade.ExecuteAction((ENUM_TRADE_ACTION)_args[0].integer_value, _sargs); + /* @fixme if (_result) { - Order _order = trade.GetOrderLast(); + Order *_order = trade.GetOrderLast(); switch ((ENUM_TRADE_ACTION)_args[0].integer_value) { case TRADE_ACTION_ORDER_OPEN: // @fixme: Operation on the structure copy. @@ -897,6 +896,7 @@ class Strategy : public Object { break; } } + */ } return _result; case STRAT_ACTION_UNSUSPEND: diff --git a/Strategy.struct.h b/Strategy.struct.h index 20b37e912..472fab1be 100644 --- a/Strategy.struct.h +++ b/Strategy.struct.h @@ -71,9 +71,11 @@ struct StgParams { float max_spread; // Maximum spread to trade (in pips). int tp_max; // Hard limit on maximum take profit (in pips). int sl_max; // Hard limit on maximum stop loss (in pips). + int type; // Strategy type (@see: ENUM_STRATEGY). long id; // Unique identifier of the strategy. datetime refresh_time; // Order refresh frequency (in sec). short shift; // Shift (relative to the current bar, 0 - default) + ChartTf tf; // Main timeframe where strategy operates on. DictStruct> indicators_managed; // Indicators list keyed by id. Dict indicators_unmanaged; // Indicators list keyed by id. // Constructor. @@ -106,6 +108,7 @@ struct StgParams { max_spread(0.0), tp_max(0), sl_max(0), + type(0), refresh_time(0) {} StgParams(int _som, int _sofm, float _sol, int _sob, int _scm, int _scf, float _scl, int _psm, float _psl, int _tfm, float _ms, short _s = 0) @@ -136,6 +139,7 @@ struct StgParams { max_spread(0.0), tp_max(0), sl_max(0), + type(0), refresh_time(0) {} StgParams(StgParams &_stg_params) { DeleteObjects(); @@ -190,8 +194,12 @@ struct StgParams { return (T)price_profit_method; case STRAT_PARAM_PSM: return (T)price_stop_method; + case STRAT_PARAM_TF: + return (T)tf.GetTf(); case STRAT_PARAM_TFM: return (T)tick_filter_method; + case STRAT_PARAM_TYPE: + return (T)type; case STRAT_PARAM_WEIGHT: return (T)weight; } @@ -279,9 +287,17 @@ struct StgParams { case STRAT_PARAM_PSM: // Price stop method price_stop_method = (int)_value; return; + case STRAT_PARAM_TF: + // Main timeframe where strategy operates on. + tf = (ENUM_TIMEFRAMES)_value; + return; case STRAT_PARAM_TFM: // Tick filter method tick_filter_method = (int)_value; return; + case STRAT_PARAM_TYPE: + // Strategy type. + type = (int)_value; + return; case STRAT_PARAM_WEIGHT: // Weight weight = (float)_value; return; diff --git a/Trade.mqh b/Trade.mqh index 1c00f50a2..11d5018bd 100644 --- a/Trade.mqh +++ b/Trade.mqh @@ -66,12 +66,12 @@ class Trade { */ Trade() : chart(new Chart()), order_last(NULL) { SetName(); - OrdersLoadByMagic(); + OrdersLoadByMagic(tparams.magic_no); }; Trade(TradeParams &_tparams, ChartParams &_cparams) : chart(new Chart(_cparams)), tparams(_tparams), order_last(NULL) { SetName(); - OrdersLoadByMagic(); + OrdersLoadByMagic(tparams.magic_no); }; /** @@ -276,6 +276,14 @@ class Trade { * Check if trading is allowed. */ bool IsTradeAllowed() { + UpdateStates(); + return !tstates.CheckState(TRADE_STATE_TRADE_CANNOT); + } + + /** + * Check if trading is recommended. + */ + bool IsTradeRecommended() { UpdateStates(); return !tstates.CheckState(TRADE_STATE_TRADE_WONT); } @@ -398,16 +406,6 @@ class Trade { */ bool HasState(ENUM_TRADE_STATE _state) { return tstates.CheckState(_state); } - /** - * Check the limit on the number of active pending orders. - * - * Validate whether the amount of open and pending orders - * has reached the limit set by the broker. - * - * @see: https://www.mql5.com/en/articles/2555#account_limit_pending_orders - */ - bool IsOrderAllowed() { return (OrdersTotal() < account.GetLimitOrders()); } - /* Calculation methods */ /** @@ -654,14 +652,23 @@ HistorySelect(0, TimeCurrent()); // Select history for access. bool RequestSend(MqlTradeRequest &_request, OrderParams &_oparams) { bool _result = false; switch (_request.action) { + case TRADE_ACTION_CLOSE_BY: + break; case TRADE_ACTION_DEAL: - if (!IsOrderAllowed()) { - logger.Error("Limit of open and pending orders has reached the limit!", __FUNCTION_LINE__); + if (!IsTradeRecommended()) { + // logger.Warning("Trade not recommended!", __FUNCTION_LINE__, (string)tstates.GetStates()); return _result; + } else if (account.GetAccountFreeMarginCheck(_request.type, _request.volume) == 0) { + logger.Error("No free margin to open a new trade!", __FUNCTION_LINE__); } - if (account.GetAccountFreeMarginCheck(_request.type, _request.volume) == 0) { - logger.Error("No free margin to open more orders!", __FUNCTION_LINE__); - } + break; + case TRADE_ACTION_MODIFY: + break; + case TRADE_ACTION_PENDING: + break; + case TRADE_ACTION_REMOVE: + break; + case TRADE_ACTION_SLTP: break; } Order *_order = new Order(_request, _oparams); @@ -676,15 +683,30 @@ HistorySelect(0, TimeCurrent()); // Select history for access. return RequestSend(_request, _oparams); } + /** + * Loads an existing order. + */ + bool OrderLoad(Order *_order) { + bool _result = false; + Ref _order_ref = _order; + if (_order.IsOpen()) { + // @todo: _order.IsPending()? + _result &= orders_active.Set(_order.Get(ORDER_PROP_TICKET), _order_ref); + } else { + _result &= orders_history.Set(_order.Get(ORDER_PROP_TICKET), _order_ref); + } + return _result && GetLastError() == ERR_NO_ERROR; + } + /** * Loads active orders by magic number. */ - bool OrdersLoadByMagic() { + bool OrdersLoadByMagic(unsigned long _magic_no) { ResetLastError(); int _total_active = TradeStatic::TotalActive(); for (int pos = 0; pos < _total_active; pos++) { if (OrderStatic::SelectByPosition(pos)) { - if (OrderStatic::MagicNumber() == tparams.magic_no) { + if (OrderStatic::MagicNumber() == _magic_no) { unsigned long _ticket = OrderStatic::Ticket(); Ref _order = new Order(_ticket); orders_active.Set(_ticket, _order); @@ -1228,9 +1250,13 @@ HistorySelect(0, TimeCurrent()); // Select history for access. // Check if maximum margin allowed to use is reached. && account.GetMarginUsedInPct() > tparams.GetRiskMargin()); /* Money checks */ - // tstates.SetState(TRADE_STATE_MONEY_NOT_ENOUGH, @todo); + tstates.SetState(TRADE_STATE_MONEY_NOT_ENOUGH, account.GetMarginFreeInPct() <= 0.1); /* Orders checks */ tstates.SetState(TRADE_STATE_ORDERS_ACTIVE, orders_active.Size() > 0); + // Check the limit on the number of active pending orders has reached the limit set by the broker. + // @see: https://www.mql5.com/en/articles/2555#account_limit_pending_orders + tstates.SetState(TRADE_STATE_ORDERS_MAX_HARD, OrdersTotal() == account.GetLimitOrders()); + // @todo: TRADE_STATE_ORDERS_MAX_SOFT tstates.SetState(TRADE_STATE_TRADE_NOT_POSSIBLE, // Check if the EA trading is enabled. (account.IsExpertEnabled() || !Terminal::IsRealtime()) @@ -1248,20 +1274,21 @@ HistorySelect(0, TimeCurrent()); // Select history for access. && !Account::IsTradeAllowed()); tstates.SetState(TRADE_STATE_TRADE_TERMINAL_BUSY, Terminal::IsTradeContextBusy()); _last_check = TimeCurrent(); - } - /* Terminal checks */ - // Check if terminal is connected. - tstates.SetState(TRADE_STATE_TRADE_TERMINAL_OFFLINE, Terminal::IsRealtime() && !Terminal::IsConnected()); - // Check if terminal is stopping. - tstates.SetState(TRADE_STATE_TRADE_TERMINAL_SHUTDOWN, IsStopped()); - if (tstates.GetStates() != _states_prev) { - for (int _bi = 0; _bi < sizeof(int) * 8; _bi++) { - bool _enabled = tstates.CheckState(1 << _bi) > TradeStates::CheckState(1 << _bi, _states_prev); - if (_enabled && (ENUM_TRADE_STATE)(1 << _bi) != TRADE_STATE_ORDERS_ACTIVE) { - logger.Warning(TradeStates::GetStateMessage((ENUM_TRADE_STATE)(1 << _bi)), GetName()); + /* Terminal checks */ + // Check if terminal is connected. + tstates.SetState(TRADE_STATE_TRADE_TERMINAL_OFFLINE, Terminal::IsRealtime() && !Terminal::IsConnected()); + // Check if terminal is stopping. + tstates.SetState(TRADE_STATE_TRADE_TERMINAL_SHUTDOWN, IsStopped()); + // Check for new states. + if (tstates.GetStates() != _states_prev) { + for (int _bi = 0; _bi < sizeof(int) * 8; _bi++) { + bool _enabled = tstates.CheckState(1 << _bi) > TradeStates::CheckState(1 << _bi, _states_prev); + if (_enabled && (ENUM_TRADE_STATE)(1 << _bi) != TRADE_STATE_ORDERS_ACTIVE) { + logger.Warning(TradeStates::GetStateMessage((ENUM_TRADE_STATE)(1 << _bi)), GetName()); + } } + _states_prev = tstates.GetStates(); } - _states_prev = tstates.GetStates(); } return GetLastError() == ERR_NO_ERROR; }