Erros, bugs, perguntas - página 105

 
Interesting:

O provador tem a sua própria lista separada de ferramentas e precisa de ser gerada (de preferência quando da inicialização do perito).

Muito obrigado! Já está. Faltou muito...
 
Renat:

O código era aproximado (copiado de duas peças), mas os seus comentários estão correctos.

Aqui está a versão corrigida:

Adicionei-lhe um par de chamadas Print() para deixar claro como funciona realmente:

double CalculateMaxVolume(string symbol)
  {
   double price=0.0;
   double margin=0.0;
//--- select lot size
   if(!SymbolInfoDouble(symbol,SYMBOL_ASK,price))                return(0.0);
   if(!OrderCalcMargin(ORDER_TYPE_BUY,symbol,1.0,price,margin)) return(0.0);
   if(margin<=0.0)                                            return(0.0);

   double lot_pure=AccountInfoDouble(ACCOUNT_FREEMARGIN)/margin;
   double lot=NormalizeDouble(lot_pure,2);
   Print("lot_pure = ", lot_pure, ", lot = ", lot);
//--- normalize and check limits
   double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
   if(stepvol>0.0)
     {
      double newlot=stepvol*NormalizeDouble(lot/stepvol,0);
      if(newlot>lot) { Print("Чёрт побери: lot = ", lot, ", newlot = ", newlot);
                       lot=NormalizeDouble(newlot-stepvol,2);
                     }
      else           lot=newlot;
     }

   double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
   if(lot<minvol) lot=0.0;   // 

   double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
   if(lot>maxvol) lot=maxvol;
//--- return trading volume
   return(lot);
  }

void OnStart()
{
  Print("CalculateMaxVolume(Symbol()) = ", CalculateMaxVolume(Symbol()));
}

/* Вывод в лог (хронология - сверху вниз)
CO      0       1 (EURUSD,M15)  01:40:33        lot_pure = 7.799703611262773, lot = 7.8
JG      0       1 (EURUSD,M15)  01:40:33        Чёрт побери: lot = 7.8, newlot = 7.800000000000001
MQ      0       1 (EURUSD,M15)  01:40:33        CalculateMaxVolume(Symbol()) = 7.7
*/

Agora funciona correctamente. Mas com uma advertência - nas condições que se verificam actualmente. Se forem prolongados no futuro, este código começará a cometer erros em alguns casos sob certas condições. Explicarei a seguir.

Na verdade, tive de fazer alguma investigação e obtive resultados muito interessantes como resultado. Mas falemos um a um sobre eles.

A primeira coisa que me salta aos olhos é esta: porque é que tive de dançar tanto com o NormalizeDouble()? Bem, o que é que NormalizeDoubel() faz? Liga um valor livre à grelha. Por exemplo, neste fragmento de código:

double lot=NormalizeDouble(lot_pure,2);

NormalizeDouble() toma como parâmetro lot_pure (valor livre, ou seja, aquele calculado por margem livre e margem necessária para 1 lote sem qualquer arredondamento e outra ligação) e dá o valor, ligado ao valor mais próximo da grelha com início em 0 e passo 0,01.
Aqui é importante notar: para o nó mais próximo da grelha, incluindo o maior!

Para que serve esta vinculação neste lugar do código? E porquê para a grelha 0,01 e não para, digamos, 0,001?

A propósito, podemos ver que o resultado marcado com CO no registo (a primeira linha do fragmento mostrado) resultou no aumento do valor.

Além disso, sabemos que todas as funções comerciais que aceitam o número de lotes como um dos parâmetros, exigem que este valor esteja ligado à grelha: minvol + N * stepvol, onde N é um número inteiro de 0 ao valor de uma parte inteira da expressão (maxvol - minvol) / stepvol. Consequentemente, o valor do lote livre obtido neste fragmento:

double lot_pure=AccountInfoDouble(ACCOUNT_FREEMARGIN)/margin;

deve ser ligado ao nó mais próximo da grelha especificada: minvol + N * stepvol. Isto significa que deve primeiro subtrair minvol do valor do lote antes de dividir por stepvol (para obter esse N inteiro), e depois de multiplicar por N, adicionar minvol. Mas divide-se imediatamente por stepvol, implicitamente assumindo que stepvol é o divisor do minvol, ou seja, cabe um número inteiro de vezes, porque só se esta condição for preenchida se pode "simplificar" desta forma e não obter efeitos secundários:

double newlot=stepvol*NormalizeDouble(lot/stepvol,0);

Mais uma vez usa NormalizeDouble(), desta vez para se ligar a uma grelha com início em 0 e passo 1, ou seja, a inteiros. Os parâmetros de ligação da malha são correctos, mas a ferramenta de ligação é um pouco infeliz: liga-se ao nó de malha mais próximo, incluindo um maior se por acaso estiver mais próximo. E, no nosso caso, levará a um posterior trabalho obrigatório de correcção do código. Porque não utilizar aqui uma maravilhosa ferramenta de ligação a "uma grelha de números inteiros" usando em vez de chamar a NormalizeDouble() um molde para o tipo inteiro que não incrementa o valor que está a ser lançado, mas apenas o diminui para o número inteiro mais próximo, se necessário, ou seja - o que precisamos?

Mas mais um artefacto interessante ocorre aqui que é provado na segunda linha do citado fragmento marcado com JG. Acontece que a expressão "0.1 * NormalizeDuplo(7.8 / 0.1)" dá o resultado 7.800000000000001, o que faz com que o código de correcção funcione! Porque precisaria de um código com um desempenho tão deficiente que precisa de lhe acrescentar um código corrector?

Claramente, o código de ligação à grelha de valores de lote aceitáveis deve ser substituído por um melhor.

Claro que também podemos deixar este código - afinal de contas, a parte de correcção do código, se é que alguma coisa, vai funcionar. Aqui está a terceira linha do registo prova-o: o resultado regressa logo no final. Mas este código, por outro lado, é um indicador do profissionalismo e da qualidade dos seus criadores. Incluindo a qualidade do código da plataforma MT5. E a prova disto será dada por mim, porque tropecei em dois insectos como resultado da minha investigação.

A propósito, vejamos de novo o código de encadernação inicial do valor calculado do lote_puro e o código de correcção:

double lot=NormalizeDouble(lot_pure,2);
...
lot=NormalizeDouble(newlot-stepvol,2);
Em ambos os casos há uma ligação à grelha com o passo 0,01. Porquê esta grelha? Porque precisa de se ligar à grelha minvol + N * stepvol que tem um stepvol. O que acontecerá quando no futuro tivermos um valor mínimo de lote e um stepvol de 0,001?

É muito simples - o código dará resultados errados nos casos em que no processo de ligação à grelha com o passo 0,01 o valor livre muda no valor mais do que 0,001. Esta é a advertência que mencionei no início.

Se "arredondamento para cima" por mais de 0,001, os valores do lote serão devolvidos com margem livre insuficiente para abrir a posição, enquanto no caso de "arredondamento para baixo" pelo mesmo montante - valores subestimados ou 0, se o valor livre estiver dentro do intervalo de 0,001 - 0,004999...

Ou seja, o código contém um potencial bug para o futuro. É uma questão de profissionalismo do desenvolvedor e qualidade do seu código.

Agora, tendo em conta o que encontrei, irei sugerir a minha própria variante da função:

double CalculateMaxVolume_New(string symbol)
{
  double stepvol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
  double minvol  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
  double maxvol  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);

  // Не доверяем значениям, которые вернули функции
  if(stepvol > 0 && minvol > 0 && maxvol > minvol)
  {
    double tmp = 0;

    // Вычисляем цену Ask, Margin на 1 лот, лотов на FreeMargin
    if(SymbolInfoDouble(symbol, SYMBOL_ASK, tmp)            && tmp > 0       &&
       OrderCalcMargin(ORDER_TYPE_BUY, symbol, 1, tmp, tmp) && tmp > 0       &&
       (tmp = AccountInfoDouble(ACCOUNT_FREEMARGIN) / tmp)         >= minvol &&
       tmp < ULONG_MAX * stepvol)
    {
      Print("pure_lot = ", tmp); // Эту строку нужно удалить из рабочего кода

      if(tmp > maxvol) // Здесь в tmp содержится недискретизированное число лотов
        return maxvol;

      // Привязываемся к сетке дискретизации
      return minvol + stepvol * (ulong)((tmp - minvol) / stepvol);
    }
  }

  return 0;
}

void OnStart()
{
  Print("CalculateMaxVolume_New(Symbol()) = ", CalculateMaxVolume_New(Symbol()));
}

/* Вывод в лог (хронология - сверху вниз)
LQ      0       1 (EURUSD,M15)  01:39:07        pure_lot = 7.799095304944626
KD      0       1 (EURUSD,M15)  01:39:07        CalculateMaxVolume_New(Symbol()) = 7.7
*/

Há vários casos, em relação ao valor dos lotes (armazenados na minha tmp), calculados mas ainda não vinculados a uma grelha de valores válidos. Chamemos discretizado o valor da grelha.

1. O caso quando tmp < minvol. Neste caso, a desigualdade também permanecerá depois de tmp ser discretizada, porque o processo de discretização envolve apenas a redução do valor calculado (caso contrário, não há margem livre suficiente, pois o valor calculado é o valor máximo possível para o montante dado de margem livre).

Por conseguinte, este caso pode ser eliminado numa fase inicial, antes da amostragem.

2. O caso quando tmp > maxvol. Neste caso, a limitação não é a margem livre, mas o número máximo permitido de lotes aceites pelas funções de negociação. Neste caso, basta devolver o valor de maxvol.

Para simplesmente devolver o valor de maxvol, não é necessária qualquer amostragem de tmp, pelo que este caso também é cortado antes do código de amostragem.

3. O caso em que minvol <= tmp <= maxvol. Neste caso é necessário discretizar, mas o valor discretizado permanece dentro de uma desigualdade para este caso, ou seja, não há necessidade de corrigir nada após a discretização.

O código de amostragem é simples e eficiente:

return minvol + stepvol * (ulong)((tmp - minvol) / stepvol);

Aqui, a expressão "(tmp - minvol) / stepvol" calcula o mesmo número N (o parâmetro da grelha de ancoragem), mas com uma parte fracionária. Uma vez que a desigualdade minvol <= tmp (caso 3) é aqui cumprida, é assim garantido que o valor calculado é não negativo. Depois lançamos explicitamente o valor calculado para um valor do tipo ulong. É ulong porque é garantido que o valor calculado é não-negativo.

Durante a conversão para o tipo inteiro, a parte fracionária do tipo real é descartada. Não é arredondado para o mais próximo, mas a parte fracionada é descartada, o que garante que o valor máximo dos lotes, que a margem livre permite, não será aumentado. É exactamente o que precisamos.

Como o valor inteiro de N é obtido, então o valor máximo discretizado do número de lotes, que permite a margem livre, é obtido da forma padrão (ou seja, o próximo inteiro na grelha de valores discretizados de lotes não permitirá a margem livre, enquanto que o N obtido ainda permitirá a margem livre): "minvol + stepvol * N".

Gostaria de destacar um ponto bastante importante. O problema é que os números do tipo duplo podem ter valores máximos até cerca de 1,8e308, enquanto que os números do tipo ulong - apenas cerca de 1,8e19 (é apenas uma entrada por conveniência, a constante 1,8e19 em si não é do tipo ulong).

E se o valor da expressão "(tmp - minvol) / stepvol" exceder 1,8e19? Neste caso, durante a conversão para o tipo ulong, "corte" ocorrerá - o valor será o restante da divisão inteira do valor da expressão por ULONG_MAX.

Se os números não fossem tão grandes, em termos de MQL5, pareceria "X % ULONG_MAX", onde X é um número inteiro igual a "(tmp - minvol) / stepvol".

Este não é um caso típico, mas porquê deixar os bugs no código? Além disso, não se pode confiar nas funções da biblioteca MQL5, podem devolver qualquer disparate (darei prova disso).

Para os casos em que o valor da expressão "tmp / stepvol" não cabe em 1.8e19, introduzimos propositadamente uma verificação (a última linha de se condição):

tmp < ULONG_MAX * stepvol

Claro, pode escrever "tmp / stepvol < (duplo)ULONG_MAX" mas primeiro, tento evitar operações de divisão quando não há necessidade explícita e segundo, teria de realizar uma conversão de tipo explícita, uma vez que a constante ULONG_MAX é do tipo ulong, Ao comparar operandos, eles não são implicitamente lançados a um tipo superior (pelo menos é assim em C/C++), e no terceiro - eu não teria encontrado um bug agradável na própria linguagem, nem mesmo em funções de biblioteca - o bug não é aquele no ADN, mas literalmente em moléculas e átomos de MQL5.

O operando esquerdo da operação de comparação é tmp e tem o tipo duplo, enquanto o operando direito é a expressão "ULONG_MAX * stepvol" e tem também o tipo duplo.

Esta expressão tem dois operandos, um de tipo ulong e o outro de tipo duplo. De acordo com as regras de conversão implícita do tipo, primeiro o operando do tipo inferior é fundido ao operando do tipo superior, a operação é executada e o resultado é fundido ao operando do tipo superior. O tipo duplo é "mais antigo" do que ulong, por isso o valor ULONG_MAX de ulong é implicitamente fundido para duplicar, a operação é executada e o resultado é de tipo duplo.

No entanto, existe aqui um bug que, a propósito, nem sempre aparece mas apenas em alguns casos incluindo este, e o bug consiste em que o resultado da expressão "ULONG_MAX * stepvol" é apenas o valor do stepvol.

Portanto, a função que estou a exibir não funciona e não funcionará até que os programadores da MetaQuotes corrijam este bug.

Para começar a utilizar esta função agora, deve tirar partido da peculiaridade do bug: desaparece se efectuar uma conversão explícita do tipo:

tmp < (double)ULONG_MAX * stepvol

Voltando à verificação descrita: garante que o valor da expressão "tmp / stepvol" não excederá ULONG_MAX. Mas o código de amostragem utiliza a expressão "(tmp - minvol) / stepvol".

O valor desta expressão também não excederá ULONG_MAX porque as verificações anteriores asseguram que minvol > 0 e tmp >= minvol, ou seja, tmp - minvol < tmp.

Portanto, a garantia de não exceder ULOMG_MAX aplica-se também à expressão "(tmp - minvol) / stepvol".

Geralmente, uma das maiores diferenças entre um profissional e um leigo é que um profissional pode pelo menos garantir algo, enquanto que um leigo...

Desmontei os dois bugs encontrados no outropost, ao mesmo tempo que clarifiquei o que a MetaQuotes fez e não conseguiu fazer.

 

Для чего в этом месте кода выполняется эта привязка? И почему именно к сетке 0.01, а не к, скажем, 0.001?

No sistema, o lote mínimo = 0,01


Notas:

  1. A sua condição inicial minvol + N * stepvol não está garantida de ser correcta, pode definir minlot para um valor diferente e a sua lógica será quebrada.
  2. Não deveria ter mudado para ulong - criou dificuldades para si próprio, e depois escreveu uma página inteira de pensamentos sobre o assunto
  3. A substituição de tmp no seu código é demasiado inteligente, a minha versão é muito mais clara em termos de operações
 

Falo apenas por mim (mas se vires o teu reflexo, não és o único).

Ao longo dos últimos meses da perseguição de insectos, desenvolvi o hábito de primeiro considerar um programa não funcional como um insecto no MetaTrader.

Porquê, é apenas um padrão bem testado, se algo não funcionar, então é um insecto e deixa tocar os sinos de alarme.

Exemplo: encontrei um bug, enviei um pedido ao servicedesk, eles escreveram um código de verificação, mas nada.

Voltei a candidatar-me e, em antecipação de uma resposta, encontrei a minha falta de jeito.

O resultado é que tenho vergonha de ter distraído as pessoas no local.

Mas analisando o fluxo de mensagens compreendo que a massa de pessoas, mesmo se as pessoas inteligentes ainda estão sujeitas à psicologia da multidão.

Se houver erros, escreverei um erro e deixarei que Renat resolva o meu código e aponte o dedo ao meu erro.

Compreendo que a tolerância não permite dizer: sim, é um idiota, o seu código é torto.

Mas não se pode ir tão longe, e prolongando ainda mais, todo o pessoal MQ estará em breve empenhado em que se sentem nos códigos de outras pessoas em lamentável contemplação "mas porque é que precisamos de tudo", enquanto o campeonato está a chegar, e lá e ir para os relatos reais não estão longe.

Vou terminar, o meu lema para hoje é "Se vai publicar um bug, verifique se o problema está nas suas mãos".

Общайтесь с разработчиками через Сервисдеск!
Общайтесь с разработчиками через Сервисдеск!
  • www.mql5.com
Ваше сообщение сразу станет доступно нашим отделам тестирования, технической поддержки и разработчикам торговой платформы.
 

Nova construção - novos problemas. Perito, que trabalhou bem em 306 após compilação em 314 (compilação sem erros), cede em testador:

2010.08.21 17:03:36 Core 1 desligado
2010.08.21 17:03:36 O testador do Core 1 parou porque o OnInit falhou
2010.08.21 17:03:36 Core 1 2010.01.04 00:00:00 Violação de acesso lido a 0x0000000000000014
2010.08.21 17:03:36 Núcleo 1 2010.01.04 00:00:00 Saldo=10000.00 Equite=10000.00 Lucro=0.00
2010.08.21 17:03:36 Núcleo 1 2010.01.04 01.04 01.04 00:00:00 PriceChannel_multi_Ch_Timer Expert Advisor começou a trabalhar em 2010.01.04 00:00 no gráfico EURUSD para o período H1

Também descarrega na vida real. Parece que a fonte de erro é uma linha

m_symbol[j].Name(TradeSymbols[i]);

Substituindo-o por um par de linhas

string curSymbol=TradeSymbols[i];
m_symbol[j].Name(curSymbol);

devolveu o status quo ao Consultor Especialista.

Qual é o problema ???

A propósito, o código compilado na última compilação, também funciona bem nesta.

 
Valmars:

Qual é o problema ???

A propósito, o código compilado na última construção funciona bem também nesta.

O nosso erro - vamos definitivamente corrigi-lo.
 
Renat:

Lote mínimo = 0.01


Notas:

  1. A sua condição inicial minvol + N * stepvol não está garantida de ser correcta, pode definir minlot para um valor diferente e a sua lógica será quebrada.
  2. Não deveria ter mudado para ulong - criou dificuldades para si próprio, e depois escreveu uma página inteira de pensamentos sobre o assunto
  3. A substituição de tmp no seu código é demasiado inteligente, enquanto a minha versão é muito mais clara em termos de operações.

Neste momento o lote mínimo do sistema = 0,01. Mas e dentro de um ano? Em dois anos?

1) Qual é a condição correcta? Qual é então a fórmula correcta? Digamos, para minvol = 0,15 e stepvol = 0,1 - quais seriam os primeiros valores de lote válidos? a) 0,15, 0,25, 0,35... ? б) 0.15, 0.2, 0.3... ? в) ... ? Tenho vindo a assumir que é a opção a.

2. Mudei para ulong por uma razão - tenho o direito de escolher o tipo com a gama mais ampla possível, de modo a ser suficiente para a gama mais ampla possível de casos, porque tais funções são tijolos muito básicos. E o facto de me ter deparado com um insecto não significa que tenha sido eu a criar os problemas. :) O Reasoning escreveu mais para que os outros o tornassem claro ao mais amplo leque possível - não temos aqui uma correspondência pessoal.

3. A substituição não é complicada - apenas poupar, de modo a não criar variáveis de utilização única. E foi verificado e verificado que a variável é passada por referência quando uma função é chamada no máximo uma vez, de modo a que possíveis erros sejam evitados por causa dela. Se incomoda algumas pessoas, elas podem criar uma variável para cada valor transferido (mesmo uma variável intermédia, tal como o preço Ask), como você fez. Este ponto não é importante.

Muito mais importante é o mecanismo de ligação à grelha de valores admissíveis, que, além disso, não requer correcção, e que garante contra a ocorrência de falhas em diferentes casos não muito típicos, mantendo ao mesmo tempo a máxima simplicidade possível.

A premissa é que o bloco básico de construção deve ser tão robusto e versátil quanto possível - então é provável que toda a casa sobreviva a um terramoto.

 
Urain:

Falo apenas por mim (mas se vires o teu reflexo, não és o único).

Nos últimos meses da corrida aos insectos, desenvolvi o hábito de considerar um insecto em MetaTrader como um insecto em primeiro lugar.

Porquê, apenas um padrão bem estabelecido, se algo não funciona, então é um insecto e deixa tocar os sinos de alarme.

O facto de os bugs MQL5 e MQL5 se parecerem muito com os deles desempenha aqui um papel importante. E há muitos bugs MQL5.

Se a MQL5 tivesse significativamente menos bugs e estes não fossem tão simples, seria muito mais difícil confundi-los.

Urain:

Já começaram a pensar na possibilidade de iniciar o Campeonato, e chegou o momento de começarem a trabalhar nas contas comerciais reais.

Vou embrulhá-lo, o lema de hoje é "Se vai publicar um bug, verifique se o problema está nas suas mãos".

O facto de o Campeonato para Consultores Especialistas, escrito APENAS em MQL5, ser uma aposta, foi claro na altura em que a decisão foi anunciada. Mas a direcção da EA tem a sua própria visão. Eles próprios assim o decidiram. Ninguém interferiu na sua decisão. Então, e se o campeonato estiver ao virar da esquina, eles fizeram uma vida para si próprios.

Aqui é fácil: é preciso fazer algum trabalho de localização do bug: começar a remover do código tudo o que não afecta o bug. Por fim, obterá uma espécie de exemplo de teste, que é suficientemente pequeno mas demonstra sobretudo o insecto. Este já não será "o código de outra pessoa" mas sim "código que demonstra o erro MQL5".

 
Escreveu um guião para testar a função OrderCalcMargin()
void OnStart()
  {
//---
   int total=SymbolsTotal(false);
   double marginbay;
   double marginsell;
   MqlTick last_tick;
   for(int i=0;i<total;i++)
     {

      string symbol=SymbolName(i,false);
      Print("************************************************");
      Print("Инструмент - ",symbol);
      Print("Валюта депозита = ",AccountInfoString(ACCOUNT_CURRENCY));
      Print("Базовая валюта = ",SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE));
      Print("Валюта маржи = ",SymbolInfoString(symbol,SYMBOL_CURRENCY_MARGIN));
      if(SymbolInfoTick(symbol,last_tick))
        {
         OrderCalcMargin(ORDER_TYPE_BUY,symbol,1.0,last_tick.ask,marginbay);
         OrderCalcMargin(ORDER_TYPE_SELL,symbol,1.0,last_tick.bid,marginsell);
         Print("Маржа для покупки = ",marginbay);
         Print("Маржа для продажи = ",marginsell);
        }
      else Print("SymbolInfoTick() failed, error = ",GetLastError());
     }
  }
//+------------------------------------------------------------------+
A função retorna zero para alguns instrumentos, isto é um bug ou é concebido dessa forma?
 
sergey1294:
Escrevi um guião para verificar a função OrderCalcMargin(). A função retorna zero para alguns símbolos.

Isto é provavelmente para aqueles símbolos que não estão na MarketWatch, pois diz-se que SymbolName é para SymbolName:

SymbolName

Devolve o nome do símbolo especificado.

stringSymbolName(
intpos,// número na lista
bool seleccionado// true - apenas símbolos em MarketWatch
);

Parâmetros

pos

[em] Número do símbolo em ordem.

seleccionado

[em] modo de consulta. Se for verdade, então o símbolo é retirado da lista de seleccionados na MarketWatch. Se o valor for falso, então o símbolo é retirado da lista comum.

Valor devolvido

Valor do tipo de corda com nome de símbolo.

Imprimir o nome do símbolo pelo qual se obtém um resultado inesperado e compará-lo com a lista da MarketWatch.
Razão: