Ordens Buys/Sell Stop duplicadas e executadas inapropriadamente

 

Olá Pessoal!

Criei um EA baseado em Hedge a partir da estratégia de 'zone recovery' (veja esta thread: https://www.forexfactory.com/showthread.php?t=400548&page=4). Minha implementação, em linhas gerais tem a ver com esse produto: https://www.mql5.com/pt/market/product/20218.

Estou satisfeito com a execução da ideia principal, pois pude validar que funciona bem para a B3, tanto no índice quanto no dólar. Porém, existe um problema que eu não consegui resolver ainda, e é crítico para o bom funcionamento da estratégia. 

Basicamente, cada vez que o corredor de preço é rompido, eu coloco uma nova ordem Stop no sentido oposto para o caso de o preço reverter. O problema que observei é justamente quando a ordem STOP está em processo de conversão em uma posição. O meu código rastreia a cada tick se uma ordem Stop existe na direção oposta, logo quando a ordem Stop é executada, por uma fração de segundos, a ordem não existe mais como uma ordem por ter sido acionada, tampouco a nova posição foi completamente criada. Com isso, o EA acha que precisa criar a mesma ordem Stop que acabou de ser executada, e como o preço está na zona de execução da ordem, essa nova ordem é executada novamente, causando uma duplicação, o que gera um desbalanço entre ordens compradas e vendidas (isso estraga a estratégia como um todo). Minha solução inicial foi colocar um 'Sleep' de 5 segundos após enviar a ordem Stop, o que ajudou MUITO, porém não resolveu o problema e adicionou o risco de esperar demais apra abrir a ordem Stop do próximo nível. Também tentei implementar uma função que filtra ordens que estão em execução (em processo de criação da posição), porém também não funcionou...

Em suma, preciso de uma maneira de identificar que a ordem Stop já foi executada e totalmente convertida em uma posição de maneira a não enviar outra no mesmo nível. 

A minha função que abre as ordens pendentes está abaixo ('trader' é um objeto da classe CTrade):

void openPendingStopOrders(double countOfPositions, double entryPrice, double takeProfit, double stopLoss, long orderType, double newVolume){
   _enableProfitReversion = false;
   string comment = StringSubstr(_Symbol,0,3);
   
   if(MaxHedgeLevels > 0 && MaxHedgeLevels <= countOfPositions - 1){
      PrintDebug("Max Hedge Levels has been reached! Max Levels = " + DTS0(MaxHedgeLevels) + ". Current Levels: " + DTS0(countOfPositions));
      return;
   }
   
   //avoid opening another STOP order at the current price , in case the terminal is slow (avoid duplication at a given level)
   if(filterInProgressOrder()){
      Print("ATTENTION!!! Order has been filtered due to another one being processed in the terminal!");
      return;
   }
   
   Print("Sending " + decodeOrderType((int)orderType) + " for HEDGE...");
   
   trader.SetTypeFilling(ORDER_FILLING_RETURN);   
   if(orderType == ORDER_TYPE_BUY_STOP){
      trader.SetExpertMagicNumber(HedgeBuyMagicNumber);
      comment += " Hedge Buy";
      if(trader.BuyStop(newVolume, entryPrice,_Symbol,stopLoss,takeProfit,0,0,comment)){
         Print("Success sending BUY STOP! newVolume = " + DTS5(newVolume) + " / entryPrice = " + DTS5(entryPrice) + " / stopLoss = " + DTS5(stopLoss) + " / takeProfit = " + DTS5(takeProfit));
      }else {
         Print("ERROR sending BUY STOP! ERROR# " + DTS0(GetLastError()) + ". Description: " + decodeErrorDescription(GetLastError()) + ". newVolume = " + DTS5(newVolume) + " / entryPrice = " + DTS5(entryPrice) + " / takeProfit = " + DTS5(takeProfit) + " / stopLoss = " + DTS5(stopLoss));
      }
      
   }else if(orderType == ORDER_TYPE_SELL_STOP){
      trader.SetExpertMagicNumber(HedgeSellMagicNumber);
      comment += " Hedge Sell";
      if(trader.SellStop(newVolume, entryPrice,_Symbol,stopLoss,takeProfit,0,0,comment)){
         Print("Success sending SELL STOP! newVolume = " + DTS5(newVolume) + " / entryPrice = " + DTS5(entryPrice) + " / stopLoss = " + DTS5(stopLoss) + " / takeProfit = " + DTS5(takeProfit));
      }else {
         Print("ERROR sending SELL STOP! ERROR# " + DTS0(GetLastError()) + ". Description: " + decodeErrorDescription(GetLastError()) + ". newVolume = " + DTS5(newVolume) + " / entryPrice = " + DTS5(entryPrice) + " / takeProfit = " + DTS5(takeProfit) + " / stopLoss = " + DTS5(stopLoss));
      }
   }
   //TODO: implement a better way of avoiding double-orders...
   Sleep(SafetyWait);
}


Esta foi minha tentativa de filtrar a criação de uma nova ordem caso exista alguma que esteja ainda em processo de conversão em posição - não houve sucesso:

bool filterInProgressOrder(){
   ulong  ticket = 0;
   bool orderWIP = false;
   long orderState = 0;
   int ordTotal = OrdersTotal();
   
   if(ordTotal > 0){
      for(int i = ordTotal-1; i>=0; i--){
         ticket = OrderGetTicket(i);
         if(OrderSelect(ticket)){
            if(OrderGetString(ORDER_SYMBOL) == Symbol()){
               if(OrderGetInteger(ORDER_STATE, orderState)){
                  if(orderState == ORDER_STATE_STARTED || orderState ==  ORDER_STATE_REQUEST_ADD 
                     || orderState == ORDER_STATE_PARTIAL || orderState == ORDER_STATE_FILLED || orderState == ORDER_STATE_REQUEST_MODIFY){
                     orderWIP = true;
                  }
               }
            }
         }else{
            PrintDebug("FAILURE Selecting Order for filterInProgressOrder" + DTS0(ticket) + " : " + ITS(GetLastError()));
         }
      }
   }
   if(orderWIP) Print("IMPORTANT! Found an order which is NOT fully converted to a POSITION yet! Ticket = " + DTS0(ticket) + ". State = " + decodeOrderState((int)orderState));
   return(orderWIP);
}


Qual seria a melhor maneira de resolver este problema? Desde já agradeço qualquer ajuda!


Abraço e obrigado!


Marcos

 
mjhermes:


Bom dia Hermes,

só vou tratar os problemas identificados no código. 

1) O Magic Number não foi testado no filterInProgressOrder().

2)  Se você só quer verificar se existe na fila de ordens ativas uma ordem para ativo, então o teste do orderState é desnecessário.

3) Para testar orderState==ORDER_STATUS_FILLED, você tem que selecionar a ordem na lista de  histórico de ordens e não na fila de ordens ativas.

 
Rogerio Giannetti Torres:

Bom dia Hermes,

só vou tratar os problemas identificados no código. 

1) O Magic Number não foi testado no filterInProgressOrder().

2)  Se você só quer verificar se existe na fila de ordens ativas uma ordem para ativo, então o teste do orderState é desnecessário.

3) Para testar orderState==ORDER_STATUS_FILLED, você tem que selecionar a ordem na lista de  histórico de ordens e não na fila de ordens ativas.


Muito obrigado Rogério! 

1) Neste caso, não verificar o magic# foi de propósito, pois eu queria evitar a entrada de qualquer ordem Stop nova no símbolo não importa se era uma ordem inicial (minha entrada também se dá por uma ordem Stop) ou uma ordem de Hedge. Cada uma tem um magic# diferente pra eu poder diferenciar elas depois.

2) O uso do OrderState foi minha tentativa de identificar uma ordem que tenha sido disparada, porém não foi 100% convertida em uma posição ainda... isso pra eu evitar de enviar uma nova ordem Stop no mesmo nível de preço. Uma vez que ela tenha sido convertida em uma posição, minha lógica funciona sem problemas, pois ela se baseia nos dados da última posição para inserir a próxima ordem Stop. 

3) Tens razão, aquilo ali é totalmente inútil. Já removi :).


Alguma outra ideia sobre como seria possível identificar que a ordem não foi totalmente convertida em uma posição e aguardar até a posição estar disponível? 

Abraço e obrigado,

Marcos

 
mjhermes:


Bom dia Marcos,

esse problema de  transição de uma ordem para uma posição me perturbou, não sabia que podia acontecer e como é inerente a lógica do seu programa fica mais complicado ainda observar.  

Em meus programas ( todos modo NETTING )  eu uso  OnTradeTransaction para controlar quando uma posição é aberta e fechada

//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   long     deal_entry=0;
   string   deal_symbol       ="";
   long     deal_magic        =0;
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
     {
      if(HistoryDealSelect(trans.deal))
        {
         deal_symbol=HistoryDealGetString(trans.deal,DEAL_SYMBOL);
         deal_magic=HistoryDealGetInteger(trans.deal,DEAL_MAGIC);
         deal_entry=HistoryDealGetInteger(trans.deal,DEAL_ENTRY);
         if(deal_symbol==Symbol() && deal_magic==inpMagicNumber)
           {
            if((ENUM_DEAL_ENTRY)deal_entry!=DEAL_ENTRY_OUT)
               flagOnTrade=true;  //---  Acabou de abrir uma posição
            if((ENUM_DEAL_ENTRY)deal_entry==DEAL_ENTRY_OUT)
              {
               if(!PositionSelect(_Symbol)) // Execução Parcial?
                 {
                  flagOnTrade=false;  //--- Acabou de fechar uma posição
                 }
              }
           }
        }
     }
  }


Razão: