MT5 y la velocidad en acción - página 20

 
fxsaber:

Puede haber una opción más rápida. Pero un paso a la izquierda en la condición de lo que hay que contar, y la lógica puede tener que cambiar considerablemente. No es fácil, en general.

CHashMap<ulong, ulong> DealsIn;  // По PositionID возвращает DealIn.


No es caché, es indexación. Aquí está el almacenamiento en caché (parte del código):

class DealInfo {
public:
  datetime Closed;
  ulong Ticket;
  ulong Position;
  string Symbol;
  long Type;
  long Reason;
  double Volume;
  double Price;
  double Profit;
  double Swap;
  long Magic;
};

class DealsComparer : public IComparer<DealInfo*> {
  int Compare(DealInfo* x, DealInfo* y) {
    int res = (int)(x.Closed - y.Closed);
    if (res == 0) {
      res = (int)(x.Ticket - y.Ticket);
    }
    
    return res;
  }
};

CArrayList<DealInfo*> dealsHistory;

inline bool UpdateDeals(CArrayList<DealInfo*> &deals, datetime &lastUpdated) {
  DealInfo* dealsToAdd[];
  DealsComparer comparer;
  int toAdd = 0;
  DealInfo* deal;
  
  if (!HistorySelect(lastUpdated, TimeLocal() + 12*3600)) {
    return false;
  }

  for (int i = 0, total = HistoryDealsTotal(); i < total; i++) {
    DealInfo tmp;
    ulong ticket = HistoryDealGetTicket(i);
    if (ticket == 0) continue;
    
    datetime dt = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
    if (lastUpdated < dt) {
      lastUpdated = dt;
    }

    if (HistoryDealGetInteger(ticket, DEAL_ENTRY) != DEAL_ENTRY_OUT) continue;

    ulong reason = HistoryDealGetInteger(ticket, DEAL_REASON);

    tmp.Ticket = ticket;
    tmp.Closed = dt;
    int idx = deals.BinarySearch(&tmp, &comparer);
    if (idx >= 0 && deals.TryGetValue(idx, deal) && deal != nullptr && deal.Ticket == ticket)
      continue;

    deal = new DealInfo;
    deal.Ticket = ticket;
    deal.Closed = dt;
    deal.Position = HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
    deal.Symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
    deal.Type = HistoryDealGetInteger(ticket, DEAL_TYPE);
    deal.Reason = HistoryDealGetInteger(ticket, DEAL_REASON);
    deal.Volume = HistoryDealGetDouble(ticket, DEAL_VOLUME);
    deal.Price = HistoryDealGetDouble(ticket, DEAL_PRICE);
    deal.Swap = HistoryDealGetDouble(ticket, DEAL_SWAP);
    deal.Profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
    deal.Magic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
    
    ArrayResize(dealsToAdd, toAdd + 1, total);
    dealsToAdd[toAdd++] = deal;
  }
  
  if (toAdd > 0) {
    deals.AddRange(dealsToAdd);
    deals.Sort(&comparer);
  }
  
  return (toAdd > 0);
}

El código fue escrito apresuradamente y hay mucho que refinar, dado el frecuente ArrayResize, pero actualiza exactamente la caché, ordenada por Closed. Si quieres buscar más tarde, utiliza tu propio índice. Pero sólo hay que actualizar una pequeña parte cada vez.
No recuerdo por qué"12*3600" está ahí, no creo que todas las ofertas hayan sido emitidas para mí.

 
Andrey Pogoreltsev:

No es un caché, es un índice.

Por favor, lea atentamente.

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

MT5 y Speed en acción

fxsaber, 2020.08.28 21:10

El MQL5 puro es 100 veces más lento que el caché parcial (sólo HistorySelectByPosition).

Si se haceun almacenamiento en caché completo, hay que pensarlo bien. Hay muchas trampas.

Aquí está el almacenamiento en caché (parte del código):

El código fue escrito apresuradamente y tiene algunas cosas que mejorar teniendo en cuenta los frecuentes ArrayResize, pero actualiza la caché ordenada por Closed. Si quieres buscar más tarde, utiliza tu propio índice. Pero sólo hay que actualizar una pequeña parte cada vez.

Esto es exactamente un ejemplo de ahorro de historia frontal sin los trucos reales. Incluso en MT4Orders el caché parcial se hace con un margen de cinco segundos...

No recuerdo por qué"12*3600" está ahí, creo que no se me emitieron todos los oficios.

Siempre hay que poner INT_MAX.

 
fxsaber:

Desde el punto de vista de la arquitectura, el almacenamiento en caché completo requiere mucha reflexión. Hay muchas trampas.

Realmente no hay nada complicado. Puede hacer un muestreo con horas de antelación, por ejemplo, para todos los pedidos que puedan aparecer en el historial de forma retroactiva (algo muy extraño, por cierto).

Este es sólo un ejemplo de ahorro de historia de frente, sin trucos en tiempo real. Incluso en MT4Orders el caché parcial se hace con un margen de cinco segundos...

Siempre hay que poner INT_MAX.

No entiendo el sentido del cambio. Como si existiera una marca de tiempo actual, quiero ponerme en relación con ella y es extraño que haya que especificar un tiempo que no existe. Quiero una explicación lógica.

Mi caché, por cierto, funciona en cuentas reales con más de 10k operaciones.

Y los principales frenos en el código hasta ahora son las funciones de red.

 
De paso, he pensado en resumir lo que ya se ha insinuado aquí. Si se necesita un rendimiento superrápido a cualquier precio, se consigue de forma tradicional, a costa del consumo de otros recursos, por ejemplo, la memoria. En otras palabras, no es necesario ejecutar un bucle a través de toda la historia cada vez que se requiere para calcular algún valor agregado (puede ser no sólo la duración total), y calcular todo por adelantado en el "array" (u otra estructura de datos optimizada) y luego sólo aumentar. Y una solicitud de un valor por identificador de posición (o cualquier parámetro, por el cual se hace el desglose) degenerará en una referencia de celda de memoria. En la forma más general - es algo así como OLAP (aunque mi implementación no ha terminado de actualización en línea), pero para un problema particular se puede hacer y más simple.
 

En la última beta 2588, la función HistorySelect está muy bien cacheada y casi siempre (excepto la primera vez) es gratuita.

Es probable que realicemos otras mejoras para el lanzamiento.



Como he explicado antes, en MT5 no hay ningún coste adicional en la creación automática de instantáneas de mercado para cada EA antes de cada evento como se hacía en MT4. Esto reduce los retrasos y permite que los robots funcionen más rápido. Cada promotor pide exactamente lo que necesita.

Por lo tanto, usted debe entender claramente que el enfoque de "llamar a HistorySelect en toda la historia, y luego inmediatamente hacer otra selección de HistorySelectByPosition" matará a los cachés creados previamente de la historia. Esto es un tiro en el pie.


Después del lanzamiento comenzaremos un gran trabajo para añadir nuevas funciones MQL5 más eficientes y abrir estructuras de datos de órdenes/comercio nativas, para poder simplificar y acelerar el algotrading.

 
Renat Fatkhullin:

En la última beta del 2588, la función HistorySelect está muy bien cacheada y casi siempre (excepto la primera vez) está libre.

#include <fxsaber\Benchmark.mqh> // https://c.mql5.com/3/321/Benchmark.mqh

input int inAlertTime = 1; // Нижний порог в миллисекундах

#define _B2(A) _B(A, inAlertTime)
#define  ALERT(A) Alert(#A + " = " + (string)(A))

void OnInit()
{
  if (HistorySelect(0, INT_MAX))
  {
    ALERT(HistoryDealsTotal());
    ALERT(HistoryOrdersTotal());
  }
}

void OnTick()
{
  static int i = 0;
  
  ALERT(i++);
  
  _B2(HistorySelect(TimeCurrent(), INT_MAX));
  _B2(HistorySelect(0, INT_MAX));
}


Resultado.

2020.09.01 22:56:46.089 Test6 (EURAUD,M1)       Alert: HistoryDealsTotal() = 9435
2020.09.01 22:56:46.089 Test6 (EURAUD,M1)       Alert: HistoryOrdersTotal() = 12529
2020.09.01 22:56:46.575 Test6 (EURAUD,M1)       Alert: i++ = 0
2020.09.01 22:56:46.579 Test6 (EURAUD,M1)       Alert: Time[Test6.mq5 25: HistorySelect(0,INT_MAX)] = 3 ms.
2020.09.01 22:56:47.424 Test6 (EURAUD,M1)       Alert: i++ = 1
2020.09.01 22:56:47.428 Test6 (EURAUD,M1)       Alert: Time[Test6.mq5 25: HistorySelect(0,INT_MAX)] = 3 ms.
2020.09.01 22:56:47.765 Test6 (EURAUD,M1)       Alert: i++ = 2
2020.09.01 22:56:47.768 Test6 (EURAUD,M1)       Alert: Time[Test6.mq5 25: HistorySelect(0,INT_MAX)] = 3 ms.
2020.09.01 22:56:47.902 Test6 (EURAUD,M1)       Alert: i++ = 3
2020.09.01 22:56:47.906 Test6 (EURAUD,M1)       Alert: Time[Test6.mq5 25: HistorySelect(0,INT_MAX)] = 3 ms.
2020.09.01 22:56:48.453 Test6 (EURAUD,M1)       Alert: i++ = 4
2020.09.01 22:56:48.456 Test6 (EURAUD,M1)       Alert: Time[Test6.mq5 25: HistorySelect(0,INT_MAX)] = 3 ms.
2020.09.01 22:56:48.516 Test6 (EURAUD,M1)       Alert: i++ = 5
2020.09.01 22:56:48.521 Test6 (EURAUD,M1)       Alert: Time[Test6.mq5 25: HistorySelect(0,INT_MAX)] = 4 ms.


En cada garrapata hay un problema.


ZY instalado Win10, LatencyMon muestra que todo está bien.

 
De momento veo que en el 99% de los casos sólo se debe utilizar HistorySelect(0, INT_MAX). Intenta no utilizar otras opciones.
 
Renat Fatkhullin:

Después del lanzamiento, empezaremos a trabajar mucho para añadir nuevas funciones MQL5 más eficientes y abrir estructuras de datos de órdenes/operaciones nativas para que el trading algorítmico se pueda simplificar y acelerar.

MqlDeal, MqlOrder y MqlPosition serían geniales. Incluso podría ser más sencillo.

 
fxsaber:
De momento, veo que en el 99% de los casos sólo deberíamos utilizar HistorySelect(0, INT_MAX). Intenta no utilizar las otras opciones.

Si tengo cientos de miles de pedidos en mi historial, ¿también será más rápido que tomar el historial de última hora?

Entonces, ¿tengo que repasar toda esta historia? No tiene sentido.

 
Dmi3:

Si tengo cientos de miles de pedidos en mi historial, ¿también será más rápido que tomar el historial de última hora?

Sólo queda la variante 0-INT_MAX en los robots de combate. Dejó de notar los retrasos.