Biblioteca de clases genéricas - errores, descripción, preguntas, características de uso y sugerencias - página 12

 
Sergey Dzyublik:

¿Entiendes qué hace el código si no hay una implementación explícita de la funciónGetHashCode para el tipo T?
Respuesta: es una putada porque se silencia el problema de la falta de aplicación. Todos los objetos de la misma clase devolverán el mismo valor hash.

¿Qué tiene que ver la implementación (el cuerpo)? He añadido esto.

int GetHashCode(T & value)

Introduje el cuerpo de la bola.

 
fxsaber:

¿Qué tiene que ver la implementación (el cuerpo)? He añadido esto.

Puse la carrocería desde cero.


Te hablan de las moscas (que no debes hacer eso, el código puede enfermarte en el futuro) y sigues con las chuletas.
Bien, tened buen apetito.

 

Decidí mirar las características de velocidad de la solución propuesta. Asesoramiento experto para el probador

#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);
}

El Asesor Experto abre 100.000 operaciones y luego busca los beneficios totales de las operaciones al azar utilizando varios métodos (ver comentarios). El resultado es

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

Aquí comparamos los dos valores destacados. Resulta que el acceso al HashMap es 4 veces más rápido que el de los desarrolladores. Pero en los desarrolladores ya incluye la historia...

¿Es 4 veces más rápido o no es suficiente para esta situación? Pues aquí son 24 milisegundos. Si accede mucho al historial, probablemente podría ahorrar mucho. Pero no estoy seguro.


Para un caso de prueba más realista (2.000 operaciones y 1.000.000 de accesos al historial), el resultado es el siguiente

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

Se ahorran casi 100 mseg por pasada. Si, por ejemplo, hacemos Optimizar para 10.000 pases completos, la variante Hash acabará siendo 15 minutos más rápida.

Es demasiado pronto para dar a los desarrolladores un sobresaliente en Historia. Es obvio que pueden acelerarlo, ya que incluso la solución MQL es cada vez más rápida.

 
fxsaber:

Decidí mirar las características de velocidad de la solución propuesta. Asesoramiento experto para el probador

El Asesor Experto abre 100.000 operaciones y luego busca los beneficios totales de las operaciones al azar utilizando varios métodos (ver comentarios). El resultado es

Aquí comparamos los dos valores destacados. Resulta que el acceso al HashMap es 4 veces más rápido que el de los desarrolladores. Pero en los desarrolladores ya incluye la historia...

¿Es 4 veces más rápido o no es suficiente para esta situación? Pues aquí son 24 milisegundos. Si accede al historial muchas veces, probablemente podrá ahorrar mucho dinero. Pero no estoy seguro.

En las llamadas a través de la plataforma, se pasa a través de objetos de sincronización dos veces en GetDealProfitFull y una vez en GetDealProfitClear y un montón de comprobaciones obligatorias en cada iteración.

Así que la velocidad es notoriamente más lenta que en limpio y optimizada con un montón de incrustaciones que trabajan en un hashmap local pre-preparado.

 
Renat Fatkhullin:

Cuando se llama a través de la plataforma, se pasa a través de objetos de sincronización dos veces en GetDealProfitFull y una vez en GetDealProfitClear y un montón de comprobaciones obligatorias en cada iteración.

Por lo tanto, la velocidad es intrínsecamente más lenta que en un trabajo limpio y optimizado basado en un hashmap local preliminarmente preparado con un montón de incrustaciones.

Corregido mi post anterior. Por eso el doble control se llama Full.

No entiendo muy bien de qué costosos objetos de sincronización y muchas comprobaciones estamos hablando en el Probador de Estrategias para HistoryDealGetDouble?
 
fxsaber:

Corregido mi post anterior. La doble comprobación es la razón por la que se llama Full.

No estoy muy seguro de qué costosos objetos de sincronización y masas de comprobaciones habla el Probador para HistoryDealGetDouble?

Cuál es la diferencia:

  1. Para una prueba de hashmap, se han prepoblado los datos en el almacenamiento local con un acceso rápido no sincronizado, mientras que en las llamadas de plataforma se necesita caer dentro del almacenamiento
  2. En la plataforma el almacenamiento es de otro tipo, no hashmap
  3. cuando se recupera un único valor en una llamada a la plataforma, hay que tratar la solicitud "como si fuera la primera vez", comprobando dos veces si todos los datos son correctos y están presentes. no se sabe si algo ha cambiado entre las llamadas


He mirado nuestro código - hay una manera de optimizar las llamadas a la base comercial. Intentaremos implementarlo para el lanzamiento de la próxima semana.

 

Renat Fatkhullin:

En la plataforma, al extraer un solo valor, hay que tratar la consulta "como si fuera la primera vez", comprobando dos veces la corrección y la presencia de todos los datos

¿Pero no se llama a TryGetValue sin comprobar si es correcto? Según los registros, se puede ver que HistorySelect en el probador es libre.

Lo que no entiendo es por qué para obtener todos los datos de una operación hay que llamar a un montón de caras funciones HistoryDealGet*. Sólo hay una llamada para llenar la estructura MqlDeal.

Obviamente, el usuario, cuando quiera trabajar con el historial a través del HashMap, rellenará el CHashMap<ulong, MqlDeal>.

¿Tal vez deberíamos hacer MqlDealInteger, MqlDealDouble, MqlDealString o algo similar, para no multiplicar las costosas llamadas a la unidad, ya que toda la tabla del historial está en el probador de todos modos? Y es suficiente con comprobar la corrección del DealTicket una vez, no cada vez.

 
fxsaber:

¿No se hace la llamada TryGetValue para comprobar la corrección? Según los registros, se puede ver que HistorySelect está libre en el probador.

¿Cómo es que es gratis? No es gratis en absoluto.


Lo que no entiendo es por qué para obtener todos los datos de una operación hay que llamar a un montón de caras funciones HistoryDealGet*. Sólo hay una llamada para llenar la estructura MqlDeal.

Obviamente, el usuario, cuando quiere trabajar con el historial a través de HashMap, rellena el CHashMap<ulong, MqlDeal>.

No tenemos una estructura MqlDeal porque los formatos de los registros comerciales son flotantes y se expanden periódicamente. Sin ella, es imposible ampliar la funcionalidad de la plataforma.

Por lo tanto, la única opción es acceder a ellos a través de la función Get. Y el acceso a otros campos del registro al que se ha accedido anteriormente es mucho más rápido que el primer acceso, porque el registro se almacena en la caché.

Y es suficiente para comprobar la corrección entonces DealTicket una vez y no cada vez.

En la prueba anterior, cada vez el número de ofertas es nuevo, lo que interrumpe constantemente el caché de la oferta seleccionada anteriormente. Además, no hay garantía de que algo no haya cambiado entre las llamadas. Todavía se puede intercambiar entre las solicitudes de la historia.

 
Renat Fatkhullin:
¿Cómo es que es gratis? No es gratis en absoluto.

Foro sobre trading, sistemas de trading automatizados y pruebas de estrategias de trading

Biblioteca de clases genéricas - errores, descripción, problemas, casos de uso y sugerencias

fxsaber, 2017.12.08 22:46

El asesor abre 100.000 operaciones, luego busca el beneficio total de las operaciones al azar utilizando diferentes métodos (ver comentarios). Resultado

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

Por cada 100.000 operaciones (y el mismo número de órdenes) 1 microsegundo es gratis. Todo depende del probador.

En la prueba anterior, los números de las ofertas son nuevos cada vez, lo que interrumpe constantemente el caché de las ofertas previamente seleccionadas. Además, no hay garantía de que algo no haya cambiado entre las llamadas. Puede intercambiar entre las solicitudes de la historia.

Así, el historial (especialmente en el probador) sólo se complementa, los registros antiguos no se modifican. Se trata de la variante Clear.


En el mercado real, parece que incluso cuando una orden se ejecuta parcialmente y genera varias operaciones, no llega al historial hasta que se ejecuta completamente o se cancela. Es decir, se mantiene la regla de la historia congelada.

 
fxsaber:

Para 100.000 operaciones (y el mismo número de órdenes) 1 microsegundo es gratis. Todo gira en torno al probador todo el tiempo.

HistorySelect en el probador es absolutamente virtual/falsa, más aún con los parámetros 0, INT_MAX. Esto se optimizó hace mucho tiempo.

No se puede comparar HistorySelect(pone el rango de acceso en el probador) e HistoryDealSelect(ticket), que realmente busca un ticket específico y lo almacena en caché.

Razón de la queja: