Biblioteca de classes genéricas - bugs, descrição, perguntas, recursos de uso e sugestões - página 12

 
Sergey Dzyublik:

Você entende o que o código faz se não houver uma implementação explícita da funçãoGetHashCode para o tipo T?
Resposta: é uma chatice porque o problema da falta de implementação é silenciado. Todos os objetos da mesma classe retornarão o mesmo valor de hash.

O que tem a implementação (corpo) a ver com isso?! Eu acrescentei isto.

int GetHashCode(T & value)

Eu inseri o corpo da bola.

 
fxsaber:

O que tem a implementação (corpo) a ver com isso?! Eu acrescentei isto.

Eu coloquei o corpo do zero.


Fala-se de moscas (que não se deve fazer isso, o código pode adoecer no futuro) e continua-se a falar de costeletas.
OK, tenha um bom apetite.

 

Decidiu olhar para as características de velocidade da solução proposta. Consultor especializado para o testador

#include <MT4Orders.mqh>
#include <Generic\HashMap.mqh>

CHashMap<ulong, double> DealsProfit;

// Создаем историю из Amount сделок в тестере
void CreateHistory( const int Amount, const double Lots = 0.1 )
{
  MqlTick Tick;
  
  if (SymbolInfoTick(_Symbol, Tick) && Tick.ask && Tick.bid)
    for (int i = (Amount >> 1) - 1; i >= 0; i--)
      OrderClose(OrderSend(_Symbol, OP_BUY, Lots, Tick.ask, 0, 0, 0), Lots, Tick.bid, 0);
}

// Заполняем массив случайно выбранными сделками
void GetDeals( const int Amount, const int MaxDealTicket, ulong &Deals[] )
{
  for (int i = ArrayResize(Deals, Amount) - 1; i >= 0; i--)  
    Deals[i] = MathRand() * MaxDealTicket / SHORT_MAX;
}

// Заполнили HashMap
void SetHashMap()
{
  if (HistorySelect(0, INT_MAX))
    for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
    {
      const ulong DealTicket = HistoryDealGetTicket(i);
      
      DealsProfit.Add(DealTicket, HistoryDealGetDouble(DealTicket, DEAL_PROFIT));
    }
}

double GetDealProfitHashClear( const ulong Deal )
{
  static double Profit = 0;
  
  return(DealsProfit.TryGetValue(Deal, Profit) ? Profit : 0);
}

double GetDealProfitFull( const ulong Deal )
{
  return(HistoryDealSelect(Deal) ? HistoryDealGetDouble(Deal, DEAL_PROFIT) : 0);
}

double GetDealProfitClear( const ulong Deal )
{
  return(HistoryDealGetDouble(Deal, DEAL_PROFIT));
}

typedef double (*GetDealProfit)( const ulong );

// Находим суммарный профит сделок из массива
double SumProfit( const ulong &Deals[], GetDealProfit DealProfit )
{
  double Profit = 0;
  
  for (int i = ArraySize(Deals) - 1; i >= 0; i--)
    Profit += DealProfit(Deals[i]);
    
  return(Profit);
}

#define  BENCH(A)                                                              \
{                                                                             \
  const ulong StartTime = GetMicrosecondCount();                              \
  A;                                                                          \
  Print("Time[" + #A + "] = " + (string)(GetMicrosecondCount() - StartTime)); \
} 

int OnInit()
{
  const int Amount = 100000;  
  CreateHistory(Amount); // Создаем историю из Amount сделок в тестере
  
  ulong Deals[];
  GetDeals(Amount, Amount, Deals); // Заполняем массив случайно выбранными сделками

  // Находим суммарный профит сделок из массива
  
  BENCH(Print(SumProfit(Deals, GetDealProfitFull))); // Полноценная классическая реализация
  
  BENCH(SetHashMap()); // Заполнили HashMap
  BENCH(Print(SumProfit(Deals, GetDealProfitHashClear))); // Реализация через HashMap
  
  BENCH(HistorySelect(0, INT_MAX));
  BENCH(Print(SumProfit(Deals, GetDealProfitClear))); // Реализация с предварительно загруженной историей
  
  return(INIT_FAILED);
}

O Expert Advisor abre 100.000 negociações e depois procura por lucros totais de negociações aleatórias usando vários métodos (ver comentários). O resultado é

2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitFull))] = 38082
2017.12.05 00:00:00   Time[SetHashMap()] = 57849
2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitHashClear))] = 7437
2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1
2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitClear))] = 31669

Aqui comparamos os dois valores destacados. Acontece que o acesso ao HashMap é 4 vezes mais rápido do que o dos desenvolvedores. Mas nos criadores já inclui história...

É 4 vezes mais rápido ou não é rápido o suficiente para esta situação? Bem, aqui está em 24 milissegundos. Se você acessar o histórico muitas vezes, você provavelmente poderia economizar muito dinheiro. Mas não tenho a certeza.


Para um caso de teste mais realista (2000 negócios e 1 000 000 de acessos individuais ao histórico), o resultado é o seguinte

2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitFull))] = 122969
2017.12.05 00:00:00   Time[SetHashMap()] = 816
2017.12.05 00:00:00   4829800340.792288
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitHashClear))] = 23852
2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1
2017.12.05 00:00:00   4829800340.792288
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitClear))] = 114427

Quase 100 msec economizados por passe! Se, digamos, fizermos o Optimize para 10.000 passes completos, então a variante Hash acabará 15 minutos mais rápida.

É muito cedo para dar aos desenvolvedores um "A" de História. É óbvio que eles podem acelerá-lo, uma vez que até a solução MQL está ficando mais rápida.

 
fxsaber:

Decidiu olhar para as características de velocidade da solução proposta. Consultor especializado para o testador

O Expert Advisor abre 100.000 negociações e depois procura por lucros totais de negociações aleatórias usando vários métodos (ver comentários). O resultado é

Aqui comparamos os dois valores destacados. Acontece que o acesso ao HashMap é 4 vezes mais rápido do que o dos desenvolvedores. Mas nos criadores já inclui história...

É 4 vezes mais rápido ou não é rápido o suficiente para esta situação? Bem, aqui está em 24 milissegundos. Se você acessar o histórico muitas vezes, você provavelmente poderia economizar muito dinheiro. Mas não tenho a certeza.

Nas chamadas através da plataforma, você passa por objetos de sincronização duas vezes em GetDealProfitFull e uma vez em GetDealProfitClear e um monte de verificações obrigatórias a cada iteração.

Portanto, a velocidade é notoriamente mais lenta do que em limpos e otimizados com um monte de incrustações trabalhando em um hashmap local pré-preparado.

 
Renat Fatkhullin:

Ao chamar através da plataforma, você passa por objetos de sincronização duas vezes em GetDealProfitFull e uma vez em GetDealProfitClear e um monte de verificações obrigatórias a cada iteração.

Portanto, a velocidade é inerentemente mais lenta do que em um trabalho limpo e otimizado baseado em um hashmap local preparado preliminarmente com um monte de incrustações.

Corrigi o meu posto anterior. É por isso que o cheque duplo se chama Full.

Eu não entendo bem que objetos de sincronização caros e muitas verificações estamos falando no Strategy Tester for HistoryDealGetDouble?
 
fxsaber:

Corrigi o meu posto anterior. A dupla verificação é a razão pela qual se chama Full.

Não sei bem de que objectos de sincronização caros e massas de cheques o Testador está a falar para a HistóriaDealGetDouble?

Qual é a diferença?

  1. Para um teste de hashmap, você pré-populou os dados em armazenamento local com acesso rápido e não sincronizado, enquanto nas chamadas de plataforma você precisa cair dentro do armazenamento
  2. Na plataforma o armazenamento é de um tipo diferente, não hashmap
  3. ao recuperar um único valor na plataforma, você precisa tratar o pedido "como uma primeira vez", verificando novamente se todos os dados estão corretos e presentes. apenas no caso de algo ter mudado entre as chamadas


Olhei para o nosso código - há uma forma de optimizar as chamadas para a base comercial. Vamos tentar implementar até ao lançamento da próxima semana.

 

Renat Fatkhullin:

Na plataforma, ao extrair um único valor, você precisa tratar a consulta "como uma primeira vez", verificando novamente a correção e a presença de todos os dados

Mas o TryGetValue não é chamado sem verificar se está correcto? De acordo com os registros, você pode ver que o HistorySelect no testador é gratuito.

O que eu não entendo, por que para obter todos os dados sobre um negócio, você precisa chamar um monte de funções caras do HistoryDealGet*-functions? Há apenas uma chamada para preencher a estrutura do MqlDeal.

Obviamente, o usuário, quando quiser trabalhar com história através do HashMap, irá preencher o CHashMap<ulong, MqlDeal>.

Talvez devêssemos fazer MqlDealInteger, MqlDealDouble, MqlDealString ou algo semelhante, para não multiplicar chamadas de unidades caras, já que toda a tabela de histórico está no testador de qualquer maneira? E é suficiente verificar a correcção do DealTicket então uma vez, não de cada vez.

 
fxsaber:

A chamada TryGetValue não é feita para verificar se está correcta? De acordo com os registros, você pode ver que o HistorySelect está livre no testador.

Como é livre? Não é nada grátis.


O que eu não entendo, por que para obter todos os dados sobre um negócio, você precisa chamar um monte de funções caras do HistoryDealGet*-functions? Há apenas uma chamada para preencher a estrutura do MqlDeal.

Obviamente, o usuário, quando quiser trabalhar com história via HashMap, preencha o CHashMap<ulong, MqlDeal>.

Nós não temos uma estrutura MqlDeal porque os formatos dos registros comerciais são flutuantes e em expansão periódica. Sem ela, é impossível estender a funcionalidade da plataforma.

Portanto, a única opção é acessá-los através da função Obter. E o acesso a outros campos do registro anteriormente acessado é muitas vezes mais rápido que o primeiro acesso, pois o registro está em cache.

E é o suficiente para verificar se está correcto, então o DealTicket uma vez e não sempre.

No teste acima, cada vez que os números de negócios são novos, o que perturba constantemente o cache do negócio previamente selecionado. Além disso, não há garantia de que algo não tenha mudado entre as chamadas. Você ainda pode negociar entre pedidos de histórico.

 
Renat Fatkhullin:
Como é grátis? Não é de graça nenhuma.

Fórum sobre negociação, sistemas de negociação automatizados e testes de estratégia de negociação

Biblioteca de classes genéricas - bugs, descrição, problemas, casos de uso e sugestões

fxsaber, 2017.12.08 22:46

O Advisor abre 100.000 negociações, depois procura lucro total de negociações aleatórias usando métodos diferentes (ver comentários). Resultado

2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1

Por 100.000 negócios (e o mesmo número de ordens) 1 microssegundo é grátis. É tudo sobre o testador.

No teste acima, os números de negócios são novos a cada vez, o que perturba constantemente o cache de negócios previamente selecionados. Além disso, não há garantia de que algo não tenha mudado entre as chamadas. Você pode negociar entre pedidos de histórico.

Assim, a história (especialmente no testador) é apenas complementada, os registos antigos não são alterados. Estamos a falar da variante Clear.


No mercado real, parece que mesmo quando uma ordem é parcialmente executada e gera vários negócios, ela não chega ao histórico até que seja completamente preenchida ou cancelada. Isto é, a regra da história congelada é mantida.

 
fxsaber:

Para 100.000 negócios (e o mesmo número de ordens) 1 microssegundo é grátis. É tudo sobre o testador o tempo todo.

HistorySelect no testador é absolutamente virtual/featured, ainda mais com os parâmetros 0, INT_MAX. Isto foi optimizado há muito tempo.

Você não pode comparar HistorySelect(coloca o intervalo de acesso no testador) e HistoryDealSelect(ticket), que na verdade procura por um ticket específico e o armazena em cache.