Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXIV): classe básica de negociação, correção automática de parâmetros errados
Sumário
- Ideia
- Corrigindo a definição de eventos de negociação
- Manipulador de erros nos parâmetros da solicitação de negociação
- Teste
- O que vem agora?
No último artigo, adicionamos um controle de parâmetros errados à classe de negociação. Tudo o que é feito ao controlar os valores de uma ordem de negociação é verificar que os valores transferidos para os métodos de negociação estejam corretos e, se forem detectados valores incorretos em qualquer um dos parâmetros, a partir do método de negociação será retornada uma mensagem de erro. Esse comportamento não permitirá que o EA carregue o servidor de negociação com pedidos deliberadamente errados, mas também não dará controle total sobre o comportamento do EA. Afinal, é possível verificar valores incorretos que podem ser corrigidos e corrigi-los, se possível, e enviar uma ordem de negociação já ajustada ao servidor.
Em geral, o EA deve ser capaz de agir de acordo com as circunstâncias e com a lógica predefinida pelo usuário para o tratamento de erros nas ordens de negociação. Assim, nas configurações é possível fornecer ao EA uma indicação de como agir quando encontrados erros nas ordens de negociação:
- Se na ordem de negociação for detectado um erro, simplesmente saímos do método de negociação, permitindo que o usuário crie por conta própria um manipulador de parâmetros incorretos para uma ordem incorreta.
- Após determinar se possível corrigir um valor incorreto numa ordem de negociação, corrigimos imediatamente os valores e enviamos a
ordem de negociação correta.
- Ou, se precisarmos saber o que aconteceu em essência com o erro recebido, repetimos a ordem após uma pausa ou simplesmente fazemos novamente a ordem com os mesmos parâmetros.
Ao processar um erro nos parâmetros de uma ordem de negociação, são prováveis vários resultados:
- Não se pode continuar negociando com o EA até que o usuário corrija a causa dos erros.
- Não se pode enviar uma ordem de negociação - saída do método de negociação.
- São corrigidos valores incorretos e enviada uma ordem de negociação corrigida.
- É enviada imediatamente uma ordem de negociação com os parâmetros iniciais (supondo que as condições de negociação melhoraram).
- Após uma espera, são atualizados os dados de cotação e é enviada uma ordem de negociação com os parâmetros iniciais.
Hoje, nas ordens de negociação criaremos um manipulador de erros que corresponderá à funcionalidade listada, ou seja, o erro e sua causa serão verificados e será retornado o método de tratamento do erro:
- proibição de realizar operações de negociação,
- interrupção de operação de negociação,
- correção de parâmetros errôneos,
- solicitação de negociação com parâmetros iniciais,
- solicitação de negociação após espera (solução temporária),
- criação de uma solicitação de negociação pendente (nos próximos artigos)
Ideia
A realização de operações de negociação deve ser restrita quando é proibida a negociação no servidor ou quando é proibida usando EAs. Em tal situação é completamente irracional tentar enviar solicitações de negociação desnecessárias, pois o EA só pode trabalhar como assistente analítico. Para fazer isso, teremos uma sinalizador global que será definido ao tentar negociar pela primeira vez e ao determinar se é possível fazer isso.
Interrupção de operação de negociação — se houver algum erro, sairemos do método de negociação, deixando ao usuário a oportunidade de tomar uma decisão programática para continuar com as tentativas de negociação com os mesmos parâmetros inalterados.
A correção de parâmetros incorretos funcionará da seguinte forma: ao verificar se uma solicitação de negociação é correta, será compilada uma lista de erros encontrados. O método de verificação de parâmetros examinará todos os erros desta lista e retornará o código do comportamento do método de negociação. Se for encontrado um erro que não permita continuar negociando, o método retornará o código de saída do método de negociação, pois, seja como for, o envio de ordem de negociação não levará a um resultado positivo. Se for encontrado um erro corrigível, serão chamados os métodos para corrigir os valores correspondentes da ordem de negociação e será retornado um resultado indicando uma verificação bem-sucedida. O método também retornará códigos do comportamento do método de negociação, como "Aguardar e repetir", "Atualizar dados e repetir" e "Criar solicitação pendente".
O que significa isso?O comportamento "Aguardar e repetir" é necessário, por exemplo, quando o mercado esteja próximo de um dos stops da ordem ou do seu preço de
ativação, enquanto isso tentamos alterar o valor dos stops ou excluir a ordem/fechar a posição. Se o preço para acionar os níveis de stop
estiver dentro da zona de congelamento das operações de negociação, o servidor retornará que é proibido alterar os valores da ordem. Nessa
situação, existe apenas uma saída, isto é, esperar um pouco na esperança de que o preço saia da zona de congelamento das ordens de negociação e
enviar uma solicitação de negociação para alterar os parâmetros de ordem ou posição ou excluir a ordem após espera.
O comportamento
"Atualizar dados e repetir" é necessário, por exemplo, se os preços estiverem desatualizados durante o processamento de uma ordem de
negociação e recebemos uma requote.
Comportamento "Criar solicitação pendente". O que significa isso?
Se observarmos atentamente os dois métodos de
processamento anteriores, fica claro que, ao aguardar, simplesmente esperaremos no método de negociação até o tempo de espera expirar e
enviaremos a solicitação de negociação. Em princípio, esse comportamento é justificado se não for necessário realizar simultaneamente
uma análise do estado do ambiente de negociação. Portanto, para liberar o programa da necessidade de "permanecer" dentro do método de
negociação, simplesmente criamos uma solicitação de negociação pendente na qual serão registrados os parâmetros e o tempo de espera com o
número de tentativas.
Antes de começar, quero lembrá-lo de que no último artigo começamos a fazer alterações na definição de evento de negociação:
Várias vezes, os usuários relataram um erro ao receber o último evento de negociação. Acontece que, no EA de teste dos artigos que descrevem o recebimento de eventos de negociação, o recebimento da ocorrência de um evento de negociação foi criado usando como base a comparação do evento anterior com o atual. Isso era suficiente para testar como funcionava a biblioteca ao rastrear eventos de negociação, pois, na época em que escrevemos artigos sobre eventos de negociação, ainda não havia uma intenção de usar uma versão incompleta da biblioteca. Mas, resulta que agora a obtenção de ocorrências de eventos está sendo procurada, e precisamos saber exatamente qual é o último evento.
Aquela maneira de obter o evento de negociação nem sempre informava sobre o evento, bastava duas vezes seguidas fazer uma ordem pendente que já a segunda não era rastreada no programa (todos os eventos eram rastreados na biblioteca), uma vez que o penúltimo e o último evento eram iguais, mas, de fato, as ordens colocadas eram diferentes.
Por isso, vamos corrigir esse comportamento. Hoje, simplesmente criaremos um sinalizador que informará o programa sobre a ocorrência de um evento e já no programa poderemos ver de que tipo de evento se trata. No próximo artigo, concluiremos o recebimento de eventos de negociação no programa, para isso, criaremos uma lista completa contendo todos os eventos ocorrendo simultaneamente e a forneceremos ao programa. Assim, poderemos no programa não apenas descobrir a presença de um evento de negociação, mas também de ver todos os eventos ocorrendo simultaneamente, como foi realizado para eventos de conta e para eventos de coleção de símbolos.
Hoje, antes de continuar a trabalhar na classe de negociação, concluiremos o trabalho de alteração dessa funcionalidade.
Corrigindo a definição de eventos de negociação
Como quase todos nossos objetos são criados com base no objeto base de
objetos de biblioteca e nele já há uma lista contendo eventos e um método que retorna o sinalizador do evento deste objeto, agora
adicionamos todos os eventos de negociação à lista de eventos do objeto base e podemos obter o sinalizador de evento dessa classe através do
método IsEvent(). Os sinalizadores de eventos são definidos pela classe automaticamente. Mas precisaremos poder definir por
conta própria o sinalizador do evento de negociação, ocorrido a partir de outras classes, e seus manipuladores de eventos.
Para isso
adicionamos um método para definir o sinalizador de evento do objeto base
para a classe CEventBaseObj no arquivo BaseObj.mqh:
//--- Set/return the occurred event flag to the object data void SetEvent(const bool flag) { this.m_is_event=flag; } bool IsEvent(void) const { return this.m_is_event; }
Agora na classe de coleção de eventos de negociação CEventsCollection quando qualquer novo evento aparecer, precisamos criar uma descrição desse evento, colocá-la na lista de novos eventos da classe base de todos os objetos e definir o sinalizador para o novo evento.
Assim, todos os eventos recém-ocorridos - suas descrições - serão colocados na lista de eventos de negociação da classe base da coleção de símbolos. E já nesta lista, podemos ler facilmente essa lista no programa e processar cada evento contido nessa lista.
Faremos todas as melhorias necessárias no arquivo de classe do evento de negociação EventsCollection.mqh.
Na seção pública da classe, inserimos a definição de dois novos métodos —
método para obter o objeto de evento base por seu índice na listae
método
que retorna o número de novos eventos:
//--- Return (1) the last trading event on an account, (2) base event object by index and (3) number of new events ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } CEventBaseObj *GetTradeEventByIndex(const int index) { return this.GetEvent(index,false); } int GetTradeEventsTotal(void) const { return this.m_list_events.Total(); } //--- Reset the last trading event
O método que retorna o objeto de evento base por índice chama o método do objeto base GetEvent(), ao qual
transferimos o índice do evento necessário e o sinalizador redefinido (false)
que permite verificar se o índice fica fora da lista de eventos — para não ajustar o evento retornado se o índice ficar fora da lista de eventos. Em
outras palavras, se transferirmos um índice inexistente, o método retornará NULL. Se
transferíssemos no valor do sinalizador true, o método retornaria o último evento, que
não precisamos aqui.
Método que retorna o número de novos eventos apenas retorna
o tamanho da lista de eventos do objeto base.
Como, na classe de coleção de eventos de negociação no temporizador, as alterações das listas de ordens e posições históricas e de mercado são constantemente visualizadas, no método Refresh() é necessário limpar a lista de eventos básicos de negociação e definir sinalizador de lista classificada:
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals, const double changed_volume) { //--- Exit if the lists are empty if(list_history==NULL || list_market==NULL) return; //--- this.m_is_event=false; this.m_list_events.Clear(); this.m_list_events.Sort(); //--- If the event is in the market environment if(is_market_event) {
Em todos os métodos de criação de um novo evento CreateNewEvent(), após a linha de envio do evento, precisamos acrescentar a adição deste evento à lista de eventos básicos:
event.SendEvent(); CBaseObj::EventAdd(this.m_trade_event,order.Ticket(),order.Price(),order.Symbol());
Tudo isso já está incluído na lista de métodos, portanto, vamos economizar espaço no artigo, pois tudo pode ser baixado dos arquivos anexados ao artigo e lido.
Agora, na seção pública da classe do objeto principal da biblioteca CEngine inserimos o método
que retorna o objeto de evento base por seu índice na lista e o método que
retorna o número de novos eventos:
//--- Return (1) the list of order, deal and position events, (2) base trading event object by index and the (3) number of new trading events CArrayObj *GetListAllOrdersEvents(void) { return this.m_events.GetList(); } CEventBaseObj *GetTradeEventByIndex(const int index) { return this.m_events.GetTradeEventByIndex(index); } int GetTradeEventsTotal(void) const { return this.m_events.GetTradeEventsTotal(); } //--- Reset the last trading event
Esses métodos simplesmente chamam os métodos da classe da coleção de eventos de negociação com o mesmo nome, discutidos acima.
Essas são todas as alterações necessárias para que possamos rastrear todos os eventos ocorridos simultaneamente e enviados ao programa num
único pacote. Isso será visto mais tarde, ao testar a funcionalidade descrita no artigo.
Agora podemos começar a refinar ainda mais a classe de negociação.
Manipulador de erros nos parâmetros da solicitação de negociação
Primeiro, adicionamos os índices de mensagens necessárias ao arquivo Datas.mqh:
MSG_LIB_TEXT_REQUEST_REJECTED_DUE, // Request was rejected before sending to the server due to: MSG_LIB_TEXT_INVALID_REQUEST, // Invalid request: MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR, // Insufficient funds for performing a trade MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ, // Unsupported price parameter type in a request MSG_LIB_TEXT_TRADING_DISABLE, // Trading disabled for the EA until the reason is eliminated MSG_LIB_TEXT_TRADING_OPERATION_ABORTED, // Trading operation is interrupted MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST, // Correcting trading request parameters MSG_LIB_TEXT_CREATE_PENDING_REQUEST, // Creating a pending request MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT, // Unable to correct a lot };
e os textos correspondentes a esses índices:
{"Запрос отклонён до отправки на сервер по причине:","The request was rejected before being sent to the server due to:"}, {"Ошибочный запрос:","Invalid request:"}, {"Недостаточно средств для совершения торговой операции","Not enough money to perform trading operation"}, {"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"}, {"Торговля отключена для эксперта до устранения причины запрета","Trading for the expert is disabled until this ban is eliminated"}, {"Торговая операция прервана","Trading operation aborted"}, {"Корректировка параметров торгового запроса ...","Correction of trade request parameters ..."}, {"Создание отложенного запроса","Create a pending request"}, {"Нет возможности скорректировать лот","There is no possibility to correct the lot"}, };
No arquivo Defines.mqh inserimos as enumerações necessárias para determinar e retornar métodos para processar erros em solicitações de negociação e erros retornados pelo servidor de negociação.
Para definir o comportamento do erro diretamente para o EA
adicionamos uma enumeração que descreva o possível
comportamento do EA quando um erro for detectado numa solicitação de negociação ou quando um erro for retornado por um servidor de
negociação:
//+------------------------------------------------------------------+ //| EA behavior when handling errors | //+------------------------------------------------------------------+ enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, // Abort trading attempt ERROR_HANDLING_BEHAVIOR_CORRECT, // Correct invalid parameters ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST, // Create a pending request }; //+------------------------------------------------------------------+
Nas configurações do EA, será possível definir seu comportamento ao processar erros, especificando um dos parâmetros desta enumeração.
Ao verificar os valores dos parâmetros de uma solicitação de negociação, são possíveis vários métodos de tratamento de erros. Para que
possamos saber quais erros são detectados ao verificar vários parâmetros da ordem de negociação e quais condições de negociação
influenciam o aparecimento tais erros,
adicionamos uma enumeração com sinalizadores de possíveis métodos para processamento de situações
de erro:
//+------------------------------------------------------------------+ //| Flags indicating the trading request error handling methods | //+------------------------------------------------------------------+ enum ENUM_TRADE_REQUEST_ERR_FLAGS { TRADE_REQUEST_ERR_FLAG_NO_ERROR = 0, // No error TRADE_REQUEST_ERR_FLAG_FATAL_ERROR = 1, // Disable trading for an EA (critical error) - exit TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR = 2, // Library internal error - exit TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST = 4, // Error in the list - handle (ENUM_ERROR_CODE_PROCESSING_METHOD) }; //+------------------------------------------------------------------+
Ao verificar os parâmetros de uma ordem de negociação e a capacidade de executá-la, adicionaremos sinalizadores de comportamento ao processar erros:
- 0 — nenhum erro, o que significa que se pode enviar a ordem de negociação,
- 1 — erro fatal, com esse erro, é absolutamente inútil continuar as tentativas de negociação e o EA precisa ser transferido para o modo analítico de assistente de não negociação,
- 2 — algo deu errado e houve uma falha na biblioteca, simplesmente deve-se interromper a execução do método de negociação para evitar o mau funcionamento da classe de negociação,
- 4 — o erro pode ser corrigido e está registrado na lista de erros para chamar o método para corrigi-los.
O método de verificação de erros retornará maneiras de lidar com os erros encontrados, para que seja possível processá-los
corretamente posteriormente.
Para isso, adicionamos uma enumeração de possíveis métodos para processar os erros das ordens de
negociação e de retornos do servidor de negociação:
//+------------------------------------------------------------------+ //| The methods of handling errors and server return codes | //+------------------------------------------------------------------+ enum ENUM_ERROR_CODE_PROCESSING_METHOD { ERROR_CODE_PROCESSING_METHOD_OK, // No errors ERROR_CODE_PROCESSING_METHOD_DISABLE, // Disable trading for the EA ERROR_CODE_PROCESSING_METHOD_EXIT, // Exit the trading method ERROR_CODE_PROCESSING_METHOD_REFRESH, // Update data and repeat ERROR_CODE_PROCESSING_METHOD_WAIT, // Wait and repeat ERROR_CODE_PROCESSING_METHOD_PENDING, // Create a pending request }; //+------------------------------------------------------------------+
Nas descrições das constantes da enumeração, também são claros os métodos para lidar com situações de erro.
Também aprimoraremos o objeto básico de negociação.
Dependendo do método de construção de barras no gráfico, a negociação é realizada com base nos preços de Ask e Bid ou nos preços de Ask e Last. Atualmente, na classe básica de negociação é possível negociar apenas com base nos preços de Ask e Bid. Adicionamos uma rotina para verificar em que preços está baseado o gráfico e que preços usaremos ao negociar. Além disso, para verificar os códigos retornados pelo servidor de negociação, neste caso, após enviar a ordem de negociação para o servidor, em MQL5, existe a estrutura de resultado da solicitação de negociação MqlTradeResult, os campos retcode e comment conterão um código de erro e uma descrição do código de erro, respectivamente. Para MQL4, não é fornecida essa possibilidade, em cujo caso é necessário ler o código de erro com a função GetLastError(), que retorna o último código de erro. Como nossa biblioteca é multiplataforma, para MQL4, precisamos preencher os campos da estrutura do resultado da solicitação de negociação por conta própria após enviá-la ao servidor.
Ao verificar a distância de posicionamento de ordens de stop em relação ao preço, também observamos a distância de colocação do tamanho mínimo permitido dos níveis de stop (StopLevel), que definido para o símbolo. Se o valor StopLevel retornado pela função SymbolInfoInteger() com o ID da propriedade SYMBOL_TRADE_STOPS_LEVEL for igual a zero, isso não significa a ausência de um recuo mínimo expresso em pontos em relação ao preço para ordens de stop, mas, sim, que esse nível é flutuante. Portanto, para ajustar o tamanho do recuo das ordens de stop a partir do preço, é necessário selecionar o nível "em vigor" ou usar para o tamanho do recuo o tamanho do spread atual multiplicado por um determinado valor. Normalmente, para um ajuste suave do nível de stop, é usado o valor de spread duplo. multiplicadorPara definir o valor desse fator para cada objeto de cada símbolo, adicionamos ao objeto de negociação o multiplicador em si e seus os métodos de retorno e configuração.Faremos as alterações necessárias na classe do objeto básico de negociação CTradeObj no arquivo TradeObj.mqh.
Na seção privada da classe, declaramos duas variáveis-membro da classe, para
armazenar o tipo de preço para a construção de barras e
multiplicador de
spread para ajustar os níveis de ordem de stop:
SActions m_datas; MqlTick m_tick; // Tick structure for receiving prices MqlTradeRequest m_request; // Trade request structure MqlTradeResult m_result; // trade request execution result ENUM_SYMBOL_CHART_MODE m_chart_mode; // Price type for constructing bars ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; // Margin calculation mode ENUM_ORDER_TYPE_FILLING m_type_filling; // Filling policy ENUM_ORDER_TYPE_TIME m_type_expiration; // Order expiration type int m_symbol_expiration_flags; // Flags of order expiration modes for a trading object symbol ulong m_magic; // Magic number string m_symbol; // Symbol string m_comment; // Comment ulong m_deviation; // Slippage in points double m_volume; // Volume datetime m_expiration; // Order expiration time (for ORDER_TIME_SPECIFIED type order) bool m_async_mode; // Flag of asynchronous sending of a trade request ENUM_LOG_LEVEL m_log_level; // Logging level int m_stop_limit; // Distance of placing a StopLimit order in points bool m_use_sound; // The flag of using sounds of the object trading events uint m_multiplier; // The spread multiplier to adjust levels relative to StopLevel
Na seção pública da classe, escreveremos o método que define o valor do
multiplicador de spread e o método que retorna o valor do multiplicador:
public: //--- Constructor CTradeObj(); //--- Set/return the spread multiplier void SetSpreadMultiplier(const uint value) { this.m_multiplier=(value==0 ? 1 : value); } uint SpreadMultiplier(void) const { return this.m_multiplier; } //--- Set default values
Ao definir o valor do multiplicador de spread, verificamos o valor passado ao método para que a igualdade seja zero e, se for igual, atribuímos o valor de 1.
Também na seção pública da classe, inseriremos dois métodos: um
que define o código de erro da solicitação de negociaçãoe
outro que
define a descrição do código de erro da solicitação de negociação:
//--- Set the error code in the last request result void SetResultRetcode(const uint retcode) { this.m_result.retcode=retcode; } void SetResultComment(const string comment) { this.m_result.comment=comment; } //--- Data on the last request result:
No construtor da classe atribuímos ao multiplicador de spread um valor padrãoigual a 1:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeObj::CTradeObj(void) : m_magic(0), m_deviation(5), m_stop_limit(0), m_expiration(0), m_async_mode(false), m_type_filling(ORDER_FILLING_FOK), m_type_expiration(ORDER_TIME_GTC), m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy"), m_log_level(LOG_LEVEL_ERROR_MSG) { //--- Margin calculation mode this.m_margin_mode= ( #ifdef __MQL5__ (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else /* MQL4 */ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ); //--- Spread multiplier this.m_multiplier=1; //--- Set default sounds and flags of using sounds this.m_use_sound=false; this.InitSounds(); } //+------------------------------------------------------------------+
No método Init() que define os valores padrão dos parâmetros do objeto de negociação,
definimos o valor
através da variável m_chart_mode, que armazenam os preços de construção de barras:
//+------------------------------------------------------------------+ //| Set default values | //+------------------------------------------------------------------+ void CTradeObj::Init(const string symbol, const ulong magic, const double volume, const ulong deviation, const int stoplimit, const datetime expiration, const bool async_mode, const ENUM_ORDER_TYPE_FILLING type_filling, const ENUM_ORDER_TYPE_TIME type_expiration, ENUM_LOG_LEVEL log_level) { this.SetSymbol(symbol); this.SetMagic(magic); this.SetDeviation(deviation); this.SetVolume(volume); this.SetExpiration(expiration); this.SetTypeFilling(type_filling); this.SetTypeExpiration(type_expiration); this.SetAsyncMode(async_mode); this.SetLogLevel(log_level); this.m_symbol_expiration_flags=(int)::SymbolInfoInteger(this.m_symbol,SYMBOL_EXPIRATION_MODE); this.m_volume=::SymbolInfoDouble(this.m_symbol,SYMBOL_VOLUME_MIN); this.m_chart_mode=#ifdef __MQL5__ (ENUM_SYMBOL_CHART_MODE)::SymbolInfoInteger(this.m_symbol,SYMBOL_CHART_MODE) #else SYMBOL_CHART_MODE_BID #endif ; } //+------------------------------------------------------------------+
Neste caso, para MQL5 obtemos
os dados com a ajuda da função SymbolInfoInteger() com identificador SYMBOL_CHART_MODE,
enquanto
para MQL4, registramos
que as barras são construídas com base no preço Bid.
Agora precisamos adicionar a cada método de negociação o preenchimento da estrutura de retorno do servidor de negociação.
Vejamos um exemplo
usando um método de abertura de posição:
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const double sl=0, const double tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const ulong deviation=ULONG_MAX) { ::ResetLastError(); //--- If failed to get the current prices, write the error code and description, send the message to the journal and return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.type = OrderTypeByPositionType(type); this.m_request.price = (type==POSITION_TYPE_BUY ? this.m_tick.ask : (this.m_chart_mode==SYMBOL_CHART_MODE_BID ? this.m_tick.bid : this.m_tick.last)); this.m_request.volume = volume; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.deviation= (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); //--- Return the result of sending a request to the server #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::ResetLastError(); int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE); if(ticket!=WRONG_VALUE) { ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.deal=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price); this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif } //+------------------------------------------------------------------+
Neste caso, para MQL5, nós, como antes, retornamos
o resultado da função OrderSend(), enquanto, para
MQL4, verificamos o número do ticket que retorna a função
MQL4 de envio de ordem. Se a solicitação de negociação for concluída com êxito, a função retornará o ticket da ordem aberta. Se
houve erro — WRONG_VALUE. Portanto, verificamos que a função não nos retorna -1, e, se
for assim, atualizamos a cotação do símbolo, preenchemos os campos da estrutura do resultado da solicitação de negociação com os dados
apropriados e retornamos true (função concluída com sucesso).
Se
a função de envio de ordem retorna -1, na estrutura do resultado da
solicitação de negociação, escrevemos o código do último erro, os preços
atuais, a descriptografia do código do último erro.
Todos os outros campos da estrutura são deixados zerados. Como resultado,
retornamos false —
erro ao enviar ordem de negociação.
Como resultado desta modificação, podemos ver o resultado da solicitação usando esses métodos de classe para qualquer resultado do envio de uma ordem de negociação:
//--- Data on the last request result: //--- Return (1) operation result code, (2) performed deal ticket, (3) placed order ticket, //--- (4) deal volume confirmed by a broker, (5) deal price confirmed by a broker, //--- (6) current market Bid (requote) price, (7) current market Ask (requote) price //--- (8) broker comment to operation (by default, it is filled by the trade server return code description), //--- (9) request ID set by the terminal when sending, (10) external trading system return code uint GetResultRetcode(void) const { return this.m_result.retcode; } ulong GetResultDeal(void) const { return this.m_result.deal; } ulong GetResultOrder(void) const { return this.m_result.order; } double GetResultVolume(void) const { return this.m_result.volume; } double GetResultPrice(void) const { return this.m_result.price; } double GetResultBid(void) const { return this.m_result.bid; } double GetResultAsk(void) const { return this.m_result.ask; } string GetResultComment(void) const { return this.m_result.comment; } uint GetResultRequestID(void) const { return this.m_result.request_id; } uint GetResultRetcodeEXT(void) const { return this.m_result.retcode_external;}
Os outros métodos de negociação são modificados de maneira semelhante, aqui não faz sentido considerá-los, pois tudo está nos arquivos anexados no final do artigo.
Na classe do objeto-conta CAccount no arquivo Account.mqh modificamos um pouco o método que retorna a margem necessária para abrir uma posição ou fazer um pedido pendente:
//+------------------------------------------------------------------+ //| Return the margin required for opening a position | //| or placing a pending order | //+------------------------------------------------------------------+ double CAccount::MarginForAction(const ENUM_ORDER_TYPE action,const string symbol,const double volume,const double price) const { double margin=EMPTY_VALUE; #ifdef __MQL5__ return(!::OrderCalcMargin(ENUM_ORDER_TYPE(action%2),symbol,volume,price,margin) ? EMPTY_VALUE : margin); #else return this.MarginFree()-::AccountFreeMarginCheck(symbol,ENUM_ORDER_TYPE(action%2),volume); #endif } //+------------------------------------------------------------------+
Tudo o que precisamos adicionar aqui é uma conversão do tipo de ordem transferido ao método em dois valores - ORDER_TYPE_BUY ou
ORDER_TYPE_SELL -, uma vez que as funções MQL5 e MQL4 com as quais trabalha o método exigem apenas esse tipo de ordem.
Deixe-me lembrá-lo
de que o restante da divisão por 2 valores da constante do tipo de ordem sempre
retorna um dos dois valores:
- ou 0 (ORDER_TYPE_BUY),
- ou 1 (ORDER_TYPE_SELL).
O que precisamos converter no tipo correto de ordem.
Na classe CTrading no arquivo Trading.mqh criamos anteriormente uma estrutura personalizada para preencher os parâmetros de preço da ordem de negociação:
struct SDataPrices { double open; // Open price double limit; // Limit order price double sl; // StopLoss price double tp; // TakeProfit price }; SDataPrices m_req_price; // Trade request prices
Mas em MQL existe uma estrutura especial MqlTradeRequest,
e para não confundir com uma estrutura desnecessária,
substituímos na seção
privada da classe a estrutura personalizada pela padrão, também
declaramos
uma variável-membro da classe para armazenar sinalizadores de causas de erros na solicitação de negociação e
uma variável para armazenar o comportamento do EA em caso de erros no envio de ordens de negociação:
//+------------------------------------------------------------------+ //| Trading class | //+------------------------------------------------------------------+ class CTrading { private: CAccount *m_account; // Pointer to the current account object CSymbolsCollection *m_symbols; // Pointer to the symbol collection list CMarketCollection *m_market; // Pointer to the list of the collection of market orders and positions CHistoryCollection *m_history; // Pointer to the list of the collection of historical orders and deals CArrayInt m_list_errors; // Error list bool m_is_trade_disable; // Flag disabling trading bool m_use_sound; // The flag of using sounds of the object trading events ENUM_LOG_LEVEL m_log_level; // Logging level MqlTradeRequest m_request; // Trade request prices ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags; // Flags of error source in a trading method ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Behavior when handling error //--- Add the error code to the list bool AddErrorCodeToList(const int error_code);
Também na seção privada da classe, escreveremos o método que retorna a
presença de sinalizador na variável que armazena os sinalizadores de causas de erros,
o método
que retorna a presença de código de erro na lista de erros e
os métodos de
definição e retorno de ações durante o tratamento de erros:
//--- Return the flag presence in the trading event error reason bool IsPresentErrorFlag(const int code) const { return (this.m_error_reason_flags & code)==code; } //--- Return the error code in the list bool IsPresentErorCode(const int code) { this.m_list_errors.Sort(); return this.m_list_errors.Search(code)>WRONG_VALUE; } //--- Set/return the error handling action void SetErrorHandlingBehavior(const ENUM_ERROR_HANDLING_BEHAVIOR behavior) { this.m_err_handling_behavior=behavior; } ENUM_ERROR_HANDLING_BEHAVIOR ErrorHandlingBehavior(void) const { return this.m_err_handling_behavior; } //--- Check trading limitations
Do método que verifica se há suficientes fundos removemos o código de exibição de mensagem sobre fundos insuficientes no log:
if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif ) { if(this.m_log_level>LOG_LEVEL_NO_MSG) { //--- создаём текст сообщения string message= ( symbol_obj.Name()+" "+::DoubleToString(volume,symbol_obj.DigitsLot())+" "+ ( order_type>ORDER_TYPE_SELL ? OrderTypeDescription(order_type,false,false) : PositionTypeDescription(PositionTypeByOrderType(order_type)) )+" ("+::DoubleToString(money_free,(int)this.m_account.CurrencyDigits())+")" ); //--- выводим сообщение в журнал if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR),": ",message); this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR); } return false; }
Agora, a partir de outro método será exibida uma mensagem sobre falta de fundos.
Neste método, apenas adicionamos
um sinalizador indicando que o erro deve ser pesquisado na lista de erros, e
adicionamos
o código de erro à lista de erros:
//+------------------------------------------------------------------+ //| Check if the funds are sufficient | //+------------------------------------------------------------------+ bool CTrading::CheckMoneyFree(const double volume, const double price, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method, const bool mess=true) { ::ResetLastError(); //--- Get the type of a market order by a trading operation type ENUM_ORDER_TYPE action=this.DirectionByActionType((ENUM_ACTION_TYPE)order_type); //--- Get the value of free funds to be left after conducting a trading operation double money_free= ( //--- For MQL5, calculate the difference between free funds and the funds required to conduct a trading operation #ifdef __MQL5__ this.m_account.MarginFree()-this.m_account.MarginForAction(action,symbol_obj.Name(),volume,price) //--- For MQL4, use the operation result of the standard function returning the amount of funds left #else/*__MQL4__*/::AccountFreeMarginCheck(symbol_obj.Name(),action,volume) #endif ); //--- If no free funds are left, inform of that and return 'false' if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif ) { this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR); return false; } //--- Verification successful return true; } //+------------------------------------------------------------------+
Declaramos os métodos para ajustar os preços de ordens de stop e o preço para definir
uma ordem pendente, o método para ajustar o volume numa ordem de negociação,
o método
que indica como lidar com o erro e ométodo de correção de erros na ordem de
negociação:
//--- Return the correct (1) StopLoss, (2) TakeProfit and (3) order placement price double CorrectStopLoss(const ENUM_ORDER_TYPE order_type, const double price_set, const double stop_loss, const CSymbol *symbol_obj, const uint spread_multiplier=1); double CorrectTakeProfit(const ENUM_ORDER_TYPE order_type, const double price_set, const double take_profit, const CSymbol *symbol_obj, const uint spread_multiplier=1); double CorrectPricePending(const ENUM_ORDER_TYPE order_type, const double price_set, const double price, const CSymbol *symbol_obj, const uint spread_multiplier=1); //--- Return the volume, at which it is possible to open a position double CorrectVolume(const double price, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method); //--- Return the error handling method ENUM_ERROR_CODE_PROCESSING_METHOD ResultProccessingMethod(void); //--- Correct errors ENUM_ERROR_CODE_PROCESSING_METHOD RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj); public:
Complementamos a especificação di método para verificar
restrições e erros, e alteramos o tipo de retorno de bool para ENUM_ERROR_CODE_PROCESSING_METHOD:
//--- Check limitations and errors ENUM_ERROR_CODE_PROCESSING_METHOD CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, CSymbol *symbol_obj, const CTradeObj *trade_obj, const string source_method, const double limit=0, double sl=0, double tp=0);
O método agora se torna mais completo, ele verifica imediatamente possíveis métodos para corrigir erros na ordem de negociação, e agora o
método retorna um método para processar erros encontrados. Anteriormente, ele simplesmente retornava o sinalizador de verificação
bem-sucedida.
Declaramos um método para definir o multiplicador de spread:
//--- Set the following for symbol trading objects: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) spread multiplier void SetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetMagic(const ulong magic,const string symbol=NULL); void SetComment(const string comment,const string symbol=NULL); void SetDeviation(const ulong deviation,const string symbol=NULL); void SetVolume(const double volume=0,const string symbol=NULL); void SetExpiration(const datetime expiration=0,const string symbol=NULL); void SetAsyncMode(const bool mode=false,const string symbol=NULL); void SetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol=NULL); void SetSpreadMultiplier(const uint value=1,const string symbol=NULL);
Adicionamos os métodos de definição e de retorno
de sinalizador de permissão de negociação para o EA:
//--- Set/return the flag enabling sounds void SetUseSounds(const bool flag); bool IsUseSounds(void) const { return this.m_use_sound; } //--- Set/return the flag enabling trading void SetTradingDisableFlag(const bool flag) { this.m_is_trade_disable=flag; } bool IsTradingDisable(void) const { return this.m_is_trade_disable;} //--- Open (1) Buy, (2) Sell position
Existem erros que após serem identificados não é possível realizar operações de negociação adicionais, restringindo completamente a
negociação para a conta. Este sinalizador é definido quando esses erros são detectados e não permite o envio de novas ordens de negociação
desnecessárias.
No construtor da classe redefinimos o sinalizador de proibição de negociação,
e
definimos o comportamento padrão do EA em caso de erros de solicitação de
negociação como "ajustar parâmetros":
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTrading::CTrading() { this.m_list_errors.Clear(); this.m_list_errors.Sort(); this.m_log_level=LOG_LEVEL_ALL_MSG; this.m_is_trade_disable=false; this.m_err_handling_behavior=ERROR_HANDLING_BEHAVIOR_CORRECT; ::ZeroMemory(this.m_request); } //+------------------------------------------------------------------+
O comportamento do EA em caso de erros nos métodos de negociação pode ser definido nas configurações do EA. Mas até que todos os
manipuladores estejam prontos, usaremos o método de correção automática de erros.
A implementação do método para processar códigos de retorno do servidor de negociação nesta execução retorna apenas um sinalizador de sucesso:
//+------------------------------------------------------------------+ //| Return the error handling method | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(void) { return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
Porque é assim? Só que neste artigo não consideramos esse método, uma vez que faremos o processamento dos códigos de retorno do servidor de
negociação no próximo artigo. Mas o método já foi descrito e implementado numa execução mínima.
Implementação do método de correção de erros numa ordem de negociação:
//+------------------------------------------------------------------+ //| Correct errors | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(MqlTradeRequest &request, const ENUM_ORDER_TYPE order_type, const uint spread_multiplier, CSymbol *symbol_obj) { //--- The empty error list means no errors are detected, return success int total=this.m_list_errors.Total(); if(total==0) return ERROR_CODE_PROCESSING_METHOD_OK; //--- In the current implementation, all these codes are temporarily handled by interrupting a trading request if( this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED) || // Trading is disabled for the current account this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED) || // Trading on the trading server side is disabled for EAs on the current account this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED) || // Trading operations are disabled in the terminal this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED) || // Trading operations are disabled for the EA this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED) || // Trading on a symbol is disabled this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY) || // Close only this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED) || // Market orders disabled this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED) || // Limit orders are disabled this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED) || // Stop orders are disabled this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED) || // StopLimit orders are disabled this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY) || // Only short positions are allowed this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY) || // Only long positions are allowed this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED) || // CloseBy orders are disabled this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED) || // Exceeded maximum allowed aggregate volume of orders and positions in one direction this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED) || // Close by is disabled this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL) || // Symbols of opposite positions are not equal this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ) || // Unsupported price parameter type in a request this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE) || // Trading disabled for the EA until the reason is eliminated this.IsPresentErorCode(10006) || // Request rejected this.IsPresentErorCode(10011) || // Request handling error this.IsPresentErorCode(10012) || // Request rejected due to expiration this.IsPresentErorCode(10013) || // Invalid request this.IsPresentErorCode(10017) || // Trading disabled this.IsPresentErorCode(10018) || // Market closed this.IsPresentErorCode(10023) || // Order status changed this.IsPresentErorCode(10025) || // No changes in the request this.IsPresentErorCode(10026) || // Auto trading disabled by server this.IsPresentErorCode(10027) || // Auto trading disabled by client terminal this.IsPresentErorCode(10032) || // Transaction is allowed for live accounts only this.IsPresentErorCode(10033) || // The maximum number of pending orders is reached this.IsPresentErorCode(10034) // You have reached the maximum order and position volume for this symbol ) return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- View the full list of errors and correct trading request parameters for(int i=0;i<total;i++) { int err=this.m_list_errors.At(i); if(err==NULL) continue; switch(err) { //--- Correct an invalid volume and stop levels in a trading request case MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME : case MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME : case MSG_LIB_TEXT_INVALID_VOLUME_STEP : request.volume=symbol_obj.NormalizedLot(request.volume); break; case MSG_SYM_SL_ORDER_DISABLED : request.sl=0; break; case MSG_SYM_TP_ORDER_DISABLED : request.tp=0; break; case MSG_LIB_TEXT_PR_LESS_STOP_LEVEL : request.price=this.CorrectPricePending(order_type,request.price,0,symbol_obj,spread_multiplier); break; case MSG_LIB_TEXT_SL_LESS_STOP_LEVEL : request.sl=this.CorrectStopLoss(order_type,request.price,request.sl,symbol_obj,spread_multiplier); break; case MSG_LIB_TEXT_TP_LESS_STOP_LEVEL : request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp,symbol_obj,spread_multiplier); break; //--- If unable to select the position lot, return "abort trading attempt" since the funds are insufficient even for the minimum lot case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR : request.volume=this.CorrectVolume(request.volume,request.price,order_type,symbol_obj,DFUN); if(request.volume==0) return ERROR_CODE_PROCESSING_METHOD_EXIT; break; //--- Proximity to the order activation level is handled by five-second waiting - during this time, the price may go beyond the freeze level case MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL : case MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL : case MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT - wait 5 seconds default: break; } } return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários no código. Em resumo: se encontrarmos códigos de erro não processados, retornamos o método de processamento "interromper tentativa de negociar"; quando encontramos erros que podem ser corrigidos, corrigimos os valores dos parâmetros e retornamos OK.
Modificação do método que verifica restrições para negociação e erros da solicitação de negociação:
//+------------------------------------------------------------------+ //| Check limitations and errors | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, CSymbol *symbol_obj, const CTradeObj *trade_obj, const string source_method, const double limit=0, double sl=0, double tp=0) { //--- Check the previously set flag disabling trading for an EA if(this.IsTradingDisable()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_FATAL_ERROR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); return ERROR_CODE_PROCESSING_METHOD_DISABLE; } //--- result of all checks and error flags this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; bool res=true; //--- Clear the error list this.m_list_errors.Clear(); this.m_list_errors.Sort(); //--- Check trading limitations res &=this.CheckTradeConstraints(volume,action,symbol_obj,source_method,sl,tp); //--- Check the funds sufficiency for opening positions/placing orders if(action<ACTION_TYPE_CLOSE_BY) res &=this.CheckMoneyFree(volume,price,order_type,symbol_obj,source_method); //--- Check parameter values by StopLevel and FreezeLevel res &=this.CheckLevels(action,order_type,price,limit,sl,tp,symbol_obj,source_method); //--- If there are limitations, display the header and the error list if(!res) { //--- Request was rejected before sending to the server due to: int total=this.m_list_errors.Total(); if(this.m_log_level>LOG_LEVEL_NO_MSG) { //--- For MQL5, first display the list header followed by the error list #ifdef __MQL5__ ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST)); for(int i=0;i<total;i++) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); //--- For MQL4, the journal messages are displayed in the reverse order: the error list in the reverse loop is followed by the list header #else for(int i=total-1;i>WRONG_VALUE;i--) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST)); #endif } //--- If the action is performed at the "abort trading operation" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- If the action is performed at the "create a pending request" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) return ERROR_CODE_PROCESSING_METHOD_PENDING; //--- If the action is performed at the "correct parameters" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_CORRECT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST)); //--- Return the result of an attempt to correct the request parameters return this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj); } } //--- No limitations return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
O código completo está marcado em amarelo. Agora, no método, primeiro é verificado o sinalizador de proibição de negociação definido e, se definido, é retornado o tipo de processamento de erro "proibir que o EA opere". Além disso, dependendo do comportamento predeterminado do EA durante erros e de acordo com o código de erro, é retornado o método de processamento de erro necessário. Se não houver erros, será retornado um código que não exige processar erros.
No método de verificação de restrições de negociação foram feitas melhorias semelhantes, mas todas se referem apenas à adição de
sinalizadores necessários que indicam a presença de diferentes tipos de erros e como lidar com eles.
Como todas as ações executadas no
método e sua lógica são bem descritas nos comentários do código, veremos apenas a versão final do método já desenvolvido:
//+------------------------------------------------------------------+ //| Check trading limitations | //+------------------------------------------------------------------+ bool CTrading::CheckTradeConstraints(const double volume, const ENUM_ACTION_TYPE action_type, const CSymbol *symbol_obj, const string source_method, double sl=0, double tp=0) { //--- the result of conducting all checks bool res=true; //--- Check connection with the trade server (not in the test mode) if(!::TerminalInfoInteger(TERMINAL_CONNECTED)) { if(!::MQLInfoInteger(MQL_TESTER)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(10031); return false; } } //--- Check if trading is enabled for an account (if there is a connection with the trade server) else if(!this.m_account.TradeAllowed()) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED); return false; } //--- Check if trading is allowed for any EAs/scripts for the current account if(!this.m_account.TradeExpert()) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED); return false; } //--- Check if auto trading is allowed in the terminal. //--- AutoTrading button (Options --> Expert Advisors --> "Allowed automated trading") if(!::TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED); return false; } //--- Check if auto trading is allowed for the current EA. //--- (F7 --> Common --> Allow Automated Trading) if(!::MQLInfoInteger(MQL_TRADE_ALLOWED)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED); return false; } //--- Check if trading is enabled on a symbol. //--- If trading is disabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_DISABLED) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_DISABLED); return false; } //--- If not closing/removal/modification if(action_type<ACTION_TYPE_CLOSE_BY) { //--- In case of close-only, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_CLOSEONLY); return false; } //--- Check the minimum volume if(volume<symbol_obj.LotsMin()) { //--- The volume in a request is less than the minimum allowed one. //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the maximum volume else if(volume>symbol_obj.LotsMax()) { //--- The volume in the request exceeds the maximum acceptable one. //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the minimum volume gradation double step=symbol_obj.LotsStep(); if(fabs((int)round(volume/step)*step-volume)>0.0000001) { //--- The volume in the request is not a multiple of the minimum gradation of the lot change step //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_INVALID_VOLUME_STEP); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } } //--- When opening a position if(action_type<ACTION_TYPE_BUY_LIMIT) { //--- Check if sending market orders is allowed on a symbol. //--- If using market orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsMarketOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_MARKET_ORDER_DISABLED); return false; } } //--- When placing a pending order else if(action_type>ACTION_TYPE_SELL && action_type<ACTION_TYPE_CLOSE_BY) { //--- If there is a limitation on the number of pending orders on an account and placing a new order exceeds it if(this.m_account.LimitOrders()>0 && this.OrdersTotalAll()+1 > this.m_account.LimitOrders()) { //--- The limit on the number of pending orders is reached - write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(10033); return false; } //--- Check if placing limit orders is allowed on a symbol. if(action_type==ACTION_TYPE_BUY_LIMIT || action_type==ACTION_TYPE_SELL_LIMIT) { //--- If setting limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsLimitOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_LIMIT_ORDER_DISABLED); return false; } } //--- Check if placing stop orders is allowed on a symbol. else if(action_type==ACTION_TYPE_BUY_STOP || action_type==ACTION_TYPE_SELL_STOP) { //--- If setting stop orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsStopOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_STOP_ORDER_DISABLED); return false; } } //--- For MQL5, check if placing stop limit orders is allowed on a symbol. #ifdef __MQL5__ else if(action_type==ACTION_TYPE_BUY_STOP_LIMIT || action_type==ACTION_TYPE_SELL_STOP_LIMIT) { //--- If setting stop limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsStopLimitOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_STOP_LIMIT_ORDER_DISABLED); return false; } } #endif } //--- In case of opening/placing/modification if(action_type!=ACTION_TYPE_CLOSE_BY) { //--- If not modification if(action_type!=ACTION_TYPE_MODIFY) { //--- When buying, check if long trading is enabled on a symbol if(this.DirectionByActionType(action_type)==ORDER_TYPE_BUY) { //--- If only short positions are enabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_SHORTONLY); return false; } //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction if(symbol_obj.VolumeLimit()>0) { //--- (If the total volume of placed long orders and open long positions)+open volume exceed the maximum one if(this.OrdersTotalVolumeLong()+this.PositionsTotalVolumeLong()+volume > symbol_obj.VolumeLimit()) { //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction //--- write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED); return false; } } } //--- When selling, check if short trading is enabled on a symbol else if(this.DirectionByActionType(action_type)==ORDER_TYPE_SELL) { //--- If only long positions are enabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_LONGONLY); return false; } //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction if(symbol_obj.VolumeLimit()>0) { //--- (If the total volume of placed short orders and open short positions)+open volume exceed the maximum one if(this.OrdersTotalVolumeShort()+this.PositionsTotalVolumeShort()+volume > symbol_obj.VolumeLimit()) { //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction //--- write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED); return false; } } } } //--- If the request features StopLoss and its placing is not allowed if(sl>0 && !symbol_obj.IsStopLossOrdersAllowed()) { //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_SL_ORDER_DISABLED); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- If the request features TakeProfit and its placing is not allowed if(tp>0 && !symbol_obj.IsTakeProfitOrdersAllowed()) { //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TP_ORDER_DISABLED); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } } //--- When closing by an opposite position else if(action_type==ACTION_TYPE_CLOSE_BY) { //--- When closing by an opposite position is disabled if(!symbol_obj.IsCloseByOrdersAllowed()) { //--- write the error code to the list and return 'false' this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED); return false; } } return res; } //+------------------------------------------------------------------+
No método que verifica os valores dos parâmetros pelos níveis StopLevel e FreezeLevel, a cada erro encontrado adicionamos um sinalizador indicando que o erro deve ser visto na lista de erros:
//+------------------------------------------------------------------+ //| Check parameter values by StopLevel and FreezeLevel | //+------------------------------------------------------------------+ bool CTrading::CheckLevels(const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, double price, double limit, double sl, double tp, const CSymbol *symbol_obj, const string source_method) { //--- the result of conducting all checks bool res=true; //--- StopLevel //--- If this is not a position closure/order removal if(action!=ACTION_TYPE_CLOSE && action!=ACTION_TYPE_CLOSE_BY) { //--- When placing a pending order if(action>ACTION_TYPE_SELL) { //--- If the placement distance in points is less than StopLevel if(!this.CheckPriceByStopLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_STOP_LEVEL); res &=false; } } //--- If StopLoss is present if(sl>0) { //--- If StopLoss distance in points from the open price is less than StopLevel double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); if(!this.CheckStopLossByStopLevel(order_type,price_open,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL); res &=false; } } //--- If TakeProfit is present if(tp>0) { double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); //--- If TakeProfit distance in points from the open price is less than StopLevel if(!this.CheckTakeProfitByStopLevel(order_type,price_open,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL); res &=false; } } } //--- FreezeLevel //--- If this is a position closure/order removal/modification if(action>ACTION_TYPE_SELL_STOP_LIMIT) { //--- If this is a position if(order_type<ORDER_TYPE_BUY_LIMIT) { //--- StopLoss modification if(sl>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckStopLossByFreezeLevel(order_type,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL); res &=false; } } //--- TakeProfit modification if(tp>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckTakeProfitByFreezeLevel(order_type,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL); res &=false; } } } //--- If this is a pending order else { //--- Placement price modification if(price>0) { //--- If the distance from the price to the order activation price is less than FreezeLevel if(!this.CheckPriceByFreezeLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL); res &=false; } } } } return res; } //+------------------------------------------------------------------+
No método de definição de preço de solicitação de negociação adicionamos a
atualização de preço e a saída ao falhar a atualização com indicação do código de erro:
//+------------------------------------------------------------------+ //| Set trading request prices | //+------------------------------------------------------------------+ template <typename PR,typename SL,typename TP,typename PL> bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj) { //--- Reset prices ::ZeroMemory(this.m_request); //--- Update all data by symbol if(!symbol_obj.RefreshRates()) { this.AddErrorCodeToList(10021); return false; } //--- Open/close price
Além disso, foi alterado o cálculo dos preços no método de definição de preço de solicitação de negociação:
//--- Calculate the order price switch((int)action) { //--- Pending order case ORDER_TYPE_BUY_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.Ask()-price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.Ask()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits()); break; //--- Default - current position open prices default : this.m_request.price= ( this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits()) ); break; }
Agora, o método de classe do objeto-símbolo Bid() é substituído pelo método BidLast(), que retorna o preço Bid ou Last, dependendo do modo de construção de gráficos.
Método que define o multiplicador de spread para objetos de negociação todos os símbolos:
//+------------------------------------------------------------------+ //| Set the spread multiplier | //| for trading objects of all symbols | //+------------------------------------------------------------------+ void CTrading::SetSpreadMultiplier(const uint value=1,const string symbol=NULL) { CSymbol *symbol_obj=NULL; if(symbol==NULL) { CArrayObj *list=this.m_symbols.GetList(); if(list==NULL || list.Total()==0) return; int total=list.Total(); for(int i=0;i<total;i++) { symbol_obj=list.At(i); if(symbol_obj==NULL) continue; CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) continue; trade_obj.SetSpreadMultiplier(value); } } else { CTradeObj *trade_obj=this.GetTradeObjBySymbol(symbol,DFUN); if(trade_obj==NULL) return; trade_obj.SetSpreadMultiplier(value); } } //+------------------------------------------------------------------+
Ao método é transferido o valor do multiplicador (por padrão1) e o nome do símbolo (por padrão NULL)
Se como símbolo for transferido NULL, o valor do multiplicador será definido para
os objetos de negociação de todos os símbolos da coleção de símbolos existente.
Caso contrário, o valor será definido para o objeto de
negociação do símbolo cujo nome é transferido para o método.
Devido ao novo tratamento de erros, foram modificados todos os métodos de negociação.
Vejamos o código do método de
abertura de posição Buy:
//+------------------------------------------------------------------+ //| Open Buy position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CTrading::OpenBuy(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX) { //--- Set the trading request result as 'true' and the error flag as "no errors" bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=ACTION_TYPE_BUY; ENUM_ORDER_TYPE order_type=ORDER_TYPE_BUY; //--- Get a symbol object by a symbol name. If failed to get CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Set the prices //--- If failed to set - write the "internal error" flag, display the message in the journal and return 'false' if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); return false; } //--- Write the volume to the request structure this.m_request.volume=volume; //--- Get the method of handling errors from the CheckErrors() method while checking for errors ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,symbol_obj.Ask(),action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp); //--- In case of trading limitations, funds insufficiency, //--- if there are limitations by StopLevel or FreezeLevel ... if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { //--- If trading is disabled completely, display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "abort trading operation" - display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "waiting", display the message in the journal if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned) ::Sleep(method); //--- after waiting, update all data symbol_obj.Refresh(); } //--- If the check result is "create a pending request", do nothing temporarily if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); } } //--- Send the request res=trade_obj.OpenPosition(POSITION_TYPE_BUY,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation); //--- If the request is successful, play the success sound set for a symbol trading object for this type of trading operation if(res) { if(this.IsUseSounds()) trade_obj.PlaySoundSuccess(action,order_type); } //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode())); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); } //--- Return the result of sending a trading request in a symbol trading object return res; } //+------------------------------------------------------------------+
Todas as explicações são descritas em detalhes nos comentários do código. Os demais métodos de negociação foram modificados de maneira
semelhante. Espero que esteja tudo claro. Em qualquer caso, você sempre pode tirar dúvidas nos comentários do artigo.
Métodos que retornam os preços corretos calculados para colocar ordens stop e ordens pendentes:
//+------------------------------------------------------------------+ //| Return correct StopLoss relative to StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectStopLoss(const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const CSymbol *symbol_obj,const uint spread_multiplier=1) { if(stop_loss==0) return 0; uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set); return (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits()) : ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits()) ); } //+------------------------------------------------------------------+ //| Return correct TakeProfit relative to StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectTakeProfit(const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const CSymbol *symbol_obj,const uint spread_multiplier=1) { if(take_profit==0) return 0; uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set); return (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) : ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) ); } //+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectPricePending(const ENUM_ORDER_TYPE order_type,const double price_set,const double price,const CSymbol *symbol_obj,const uint spread_multiplier=1) { uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double pp=0; switch((int)order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? symbol_obj.Ask() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? symbol_obj.Ask() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); default : if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),::EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+
Aqui tudo deve ficar claro, mesmo sem comentar o código, pois basta comparar os preços passados aos métodos com o preço recebido como o recuo do StopLevel em relação ao preço de abertura. O preço correto (mais alto/mais baixo, dependendo do tipo de ordem) é retornado ao programa de chamada.
Método que retorna o volume com qual é possível abrir uma posição:
//+------------------------------------------------------------------+ //| Return the volume, at which it is possible to open a position | //+------------------------------------------------------------------+ double CTrading::CorrectVolume(const double price,const ENUM_ORDER_TYPE order_type,CSymbol *symbol_obj,const string source_method) { //--- If funds are insufficient for the minimum lot, inform of that and return zero if(!this.CheckMoneyFree(symbol_obj.LotsMin(),price,order_type,symbol_obj,source_method)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT)); return 0; } //--- Update account and symbol data this.m_account.Refresh(); symbol_obj.RefreshRates(); //--- Calculate the lot, which is closest to the acceptable one double vol=symbol_obj.NormalizedLot(this.m_account.Equity()*this.m_account.Leverage()/symbol_obj.TradeContractSize()/(symbol_obj.CurrencyBase()=="USD" ? 1.0 : symbol_obj.BidLast())); //--- Calculate a sufficient lot double margin=this.m_account.MarginForAction(order_type,symbol_obj.Name(),1.0,price); if(margin!=EMPTY_VALUE) vol=symbol_obj.NormalizedLot(this.m_account.MarginFree()/margin); //--- If the calculated lot is invalid or the margin calculation returns an error if(!this.CheckMoneyFree(vol,price,order_type,symbol_obj,source_method)) { //--- In the do-while loop, while the calculated valid volume exceeds the minimum lot do { //--- Subtract the minimum lot from the valid lot value vol-=symbol_obj.LotsStep(); //--- If the calculated lot allows opening a position/setting an order, return the lot value if(this.CheckMoneyFree(symbol_obj.NormalizedLot(vol),price,order_type,symbol_obj,source_method)) return vol; } while(vol>symbol_obj.LotsMin() && !::IsStopped()); } //--- If the lot is calculated correctly, return the calculated lot else return vol; //--- If the current stage is reached, the funds are insufficient. Inform of that and return zero if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT)); return 0; } //+------------------------------------------------------------------+
Aqui também o código está comentado.
Eu saliento que, primeiro,
verificamos a capacidade de abrir um lote mínimo, e se isso for impossível, outros cálculos não terão sentido, portanto, retornamos
zero.
Em seguida, calculamos o lote permitido aproximado
(para que, com seu possível ajuste, não inicie a "seleção" do lote necessário a partir do seu valor máximo).
Logo, calculamos
o lote máximo que permite abrir uma posição com todos os fundos disponíveis (por que razão dessa maneira? Bem, simplesmente porque
se não temos dinheiro suficiente para abrir uma posição, isso implica que o volume necessário é grande, o que significa que precisamos
calcular o volume que será o máximo possível).
Neste cálculo, é usada a função OrderCalcMargin(), que em caso de erro pode retornar false, já o método MarginForAction() da classe CAccount, que usa esta função, em tal situação retorna EMPTY_VALUE, o que corresponde ao valor da constante DBL_MAX (valor máximo que pode ser representado pelo tipo double). Se obtivermos esse valor, significa que houve um erro e nosso lote não foi calculado.
Neste caso (e não apenas se acontecer um erro, mas também ao verificar se o cálculo
está certo), recorreremos à "seleção" do lote máximo necessário, subtraindo simplesmente o passo do lote do volume máximo possível
calculado na ordem de negociação. É aqui que é útil o volume permitido
aproximado calculado anteriormente, pois se não for possível calcular o volume exato, o ciclo de redução do lote começará não a
partir do valor máximo do lote definido para o símbolo, mas, sim, a partir do mais próximo, o que reduzirá bastante o número de iterações do
ciclo.
A propósito, durante a verificação, não recebi erros de função OrderCalcMargin()
ao calcular o lote, mas ocorreram cálculos errôneos de cerca de um passo de alteração do lote.
Essas são todas as mudanças e melhorias na classe de negociação.
Teste
Para testar, pegamos o EA do artigo anterior e o salvamos na nova pasta \MQL5\Experts\TestDoEasy\ Part24\ usando o novo nome TestDoEasyPart24.mq5.
À lista de variáveis globais adicionamos uma variável-sinalizador para trabalhar no testador de estratégia:
//--- global variables CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; //+------------------------------------------------------------------+
No manipulador OnInit() definimos o valor da variável-sinalizador para trabalhar no testador de estratégia:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); }
Para enviar eventos para o manipulador de eventos da biblioteca OnDoEasyEvent() ao trabalhar no testador, temos uma
função especial EventsHandling().
Ela sofreu uma modificação menor:
//+------------------------------------------------------------------+ //| Working with events in the tester | //+------------------------------------------------------------------+ void EventsHandling(void) { //--- If a trading event is present if(engine.IsTradeEvent()) { //--- Number of trading events occurred simultaneously int total=engine.GetTradeEventsTotal(); for(int i=0;i<total;i++) { //--- Get the next event from the list of simultaneously occurred events by index CEventBaseObj *event=engine.GetTradeEventByIndex(i); if(event==NULL) continue; long lparam=i; double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } //--- If there is an account event if(engine.IsAccountsEvent()) { //--- Get the list of all account events occurred simultaneously CArrayObj* list=engine.GetListAccountEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a symbol collection event if(engine.IsSymbolsEvent()) { //--- Get the list of all symbol events occurred simultaneously CArrayObj* list=engine.GetListSymbolsEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
Tudo aqui é claro se vermos os comentários do código.
Desde que criamos uma lista de novos eventos de negociação,
no manipulador de eventos da biblioteca OnDoEasyEvent() obtemos
cada evento da lista de todos os novos eventos de negociação pelo índice do evento na lista, e simplesmente exibimos no log uma
descrição de cada um dos eventos recebidos da lista:
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=engine.EventMSC(lparam); ushort reason=engine.EventReason(lparam); ushort source=engine.EventSource(lparam); long time=TimeCurrent()*1000+msc; //--- Handling symbol events if(source==COLLECTION_SYMBOLS_ID) { CSymbol *symbol=engine.GetSymbolObjByName(sparam); if(symbol==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits()); //--- Event text description string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Check event reasons and display its description in the journal if(reason==BASE_EVENT_REASON_INC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling account events else if(source==COLLECTION_ACCOUNT_ID) { CAccount *account=engine.GetAccountCurrent(); if(account==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits()); //--- Event text description string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Checking event reasons and handling the increase of funds by a specified value, //--- In case of a property value increase if(reason==BASE_EVENT_REASON_INC) { //--- Display an event in the journal Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); //--- if this is an equity increase if(idx==ACCOUNT_PROP_EQUITY) { //--- Get the list of all open positions CArrayObj* list_positions=engine.GetListMarketPosition(); //--- Select positions with the profit exceeding zero list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE); if(list_positions!=NULL) { //--- Sort the list by profit considering commission and swap list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the position index with the highest profit int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list_positions.At(index); if(position!=NULL) { //--- Get a ticket of a position with the highest profit and close the position by a ticket engine.ClosePosition(position.Ticket()); } } } } } //--- Other events are simply displayed in the journal if(reason==BASE_EVENT_REASON_DEC) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling market watch window events else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { //--- Market Watch window event string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx); string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } //--- Handling trading events else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { //--- Get the list of trading events CArrayObj *list=engine.GetListAllOrdersEvents(); if(list==NULL) return; //--- get the event index shift relative to the end of the list //--- in the tester, the shift is passed by the lparam parameter to the event handler //--- outside the tester, events are sent one by one and handled in OnChartEvent() int shift=(testing ? (int)lparam : 0); CEvent *event=list.At(list.Total()-1-shift); if(event==NULL) return; //--- Accrue the credit if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT) { Print(DFUN,event.TypeEventDescription()); } //--- Additional charges if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE) { Print(DFUN,event.TypeEventDescription()); } //--- Correction if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION) { Print(DFUN,event.TypeEventDescription()); } //--- Enumerate bonuses if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS) { Print(DFUN,event.TypeEventDescription()); } //--- Additional commissions if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION) { Print(DFUN,event.TypeEventDescription()); } //--- Daily commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Daily agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Interest rate if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled buy deal if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled sell deal if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Dividend operations if(event.TypeEvent()==TRADE_EVENT_DIVIDENT) { Print(DFUN,event.TypeEventDescription()); } //--- Accrual of franked dividend if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED) { Print(DFUN,event.TypeEventDescription()); } //--- Tax charges if(event.TypeEvent()==TRADE_EVENT_TAX) { Print(DFUN,event.TypeEventDescription()); } //--- Replenishing account balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL) { Print(DFUN,event.TypeEventDescription()); } //--- Withdrawing funds from balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order placed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order removed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order partially activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened partially if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by partial market order execution (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial execution of a market order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial activation of a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position partially closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- StopLimit order activation if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and StopLoss price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order, StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position's StopLoss and TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } } } //+------------------------------------------------------------------+
Neste caso, por simplicidade, obtemos o evento na lista pelo seu índice (no caso do testador, o índice é transferido ao parâmetro lparam com a ajuda da função EventsHandling(), enquanto, na versão demo e na vida real o índice é igual a zero, pois cada evento é enviado para OnChartEvent() como evento independente e não a partir da lista), imprimimos no log a descrição do evento recebido.
Cada um pode encontrar uma maneira própria de realizar seu processamento. É possível diretamente neste código quer inserir o processamento, quer declarar a lista de sinalizadores de eventos, e, neste caso, somente definir os sinalizadores de eventos ocorridos, enquanto delegamos seu processamento a outras funções.
Estas são todas as alterações e aprimoramentos necessários para controlar todos os eventos de negociação ocorridos de maneira simultânea. O
que é necessário para corrigir automaticamente os erros nos parâmetros da solicitação de negociação está pronto na biblioteca em si mesma,
por isso, por enquanto, não precisamos fazer nada no EA para conseguir isso. Em seguida, após a criação de todas as maneiras de processar
erros, inserimos um parâmetro de entrada adicional que mostra o comportamento do EA em caso de situação de erro.
Compilamos o EA e o iniciamos no testador. Definimos algumas ordens pendentes e, depois, as removemos todas num ciclo:
O EA exibe no log quatro eventos ocorridos ao remover quatro ordens pendentes num ciclo após clicar o botão "Delete pending".
Agora nas configurações do EA no testador de estratégia inserimos um lote maior, por exemplo, 100.0 e tentamos definir uma ordem pendente ou abrir uma posição:
Após a tentativa de definir uma ordem pendente ou abrir uma posição com volume de 100.0 lotes, recebemos uma mensagem no log indicando fundos
insuficientes e a posterior correção do volume. Depois disso, é definida com sucesso uma ordem e aberta uma posição.
O que vem agora?
No próximo artigo, trataremos dos erros retornados pelo servidor de negociação.
Abaixo estão anexados todos os arquivos da versão atual da biblioteca e os arquivos do EA de teste. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.
Artigos desta série:
Parte 1. Conceito, gerenciamento de dados e primeiros resultadosParte 2. Coleção do histórico de ordens e negócios
Parte 3. Coleção de ordens e posições de mercado, busca e ordenação
Parte 4. Eventos de Negociação. Conceito
Parte 5. Classes e coleções de eventos de negociação. Envio de eventos para o programa
Parte 6. Eventos da conta netting
Parte 7. Eventos de ativação da ordem stoplimit, preparação da funcionalidade para os eventos de modificação de ordens e posições
Parte 8. Eventos de modificação de ordens e posições
Parte 9. Compatibilidade com a MQL4 - preparação dos dados
Parte 10. Compatibilidade com a MQL4 - eventos de abertura de posição e ativação de ordens pendentes
Parte 11. Compatibilidade com a MQL4 - eventos de encerramento de posição
Parte 12. Implementação da classe de objeto "conta" e da coleção de objetos da conta
Parte 13. Eventos do objeto conta
Parte 14. O objeto símbolo
Parte 15. Coleção de objetos-símbolos
Parte 16. Eventos de coleção de símbolos
Parte 17. Interatividade de objetos de biblioteca
Parte 18. Interatividade do objeto-conta e quaisquer de outros objetos da biblioteca
Parte 19. Classe de mensagens de biblioteca
Parte 20. Criação e armazenamento de recursos de programas
Parte 21 Classes de negociação - objeto básico de negociação multiplataforma
Parte 22. Classes de negociação - classe básica de negociação, controle de restrições
Parte 23. Classes de negociação - classe básica de negociação, controle de parâmetros válidos
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/7326
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso