Особенности языка mql5, тонкости и приёмы работы - страница 268

 

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Ошибки, баги, вопросы

fxsaber, 2024.09.23 19:07

Допустим, по клавише Q вы открываете позицию.


  1. Не в OnChartEvent запустился расчет.
  2. Во время расчета вы нажали Q-клавишу три раза, не понимая, почему не открывается позиция.
  3. После завершения расчетов (а это может быть через много времени, когда уже не актуально) советник откроет три позиции.

Чтобы такого не происходило, были даны решения.

Задача об игнорировании случайных ChartEvent-событий. Она решена.


Симуляция задачи.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Ошибки, баги, вопросы

fxsaber, 2024.09.23 09:13

double Calc()
{
  double Res = 0;

  for (int i = 0; i < 1e8; i++)
    Res += MathSin(MathRand());
    
  return(Res);
}

void OnChartEvent( const int id, const long&, const double&, const string& ) 
{
  if (id == CHARTEVENT_KEYDOWN)
  {
    Print("Calculating...");    
    Print(Calc());
  }
}

Этот советник по нажатию клавиши делает расчеты. Как модифицировать советник, чтобы повторные нажатия клавиши во время расчетов не вызвали повторные расчеты?


2024.09.23 10:09:54.802 Calculating...
2024.09.23 10:09:57.138 2380.6605050566823
2024.09.23 10:09:57.138 Calculating...
2024.09.23 10:09:59.475 3362.419766547137

Это лог, когда я дважды (без перерыва) нажал на клавишу. Видно, что после расчета запустился второй расчет.



Решение1.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Ошибки, баги, вопросы

fxsaber, 2024.09.23 09:35

К сожалению, только такое решение пришло в голову.

void OnChartEvent( const int id, const long&, const double&, const string& ) 
{
  if (id == CHARTEVENT_KEYDOWN)
  {
    TEMP Temp;
    
    if (Temp.Is())
    {
      Print("Calculating...");    
      Print(Calc());
    }   
  }
}

class TEMP
{
private:
  static ulong PrevTime;
  
  const ulong Interval;
  bool IsValue;
  
public:
  TEMP( const ulong uInterval = 1000 ) : Interval(uInterval), IsValue(false) {}
  
  bool Is() { return(this.IsValue = (::GetTickCount64() - TEMP::PrevTime) > this.Interval); }
  
  ~TEMP( void ) { if (this.IsValue) TEMP::PrevTime = ::GetTickCount64(); }
};

static ulong TEMP::PrevTime = 0;

Решение2.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Ошибки, баги, вопросы

Aleksandr Slavskii, 2024.09.23 11:00

Да, по ваше подсказке вроде работает)))

//+------------------------------------------------------------------+
double Calc()
  {
   double Res = 0;

   for(int i = 0; i < 1e8; i++)
      Res += MathSin(MathRand());

   EventChartCustom(0, 0, 0, 0, "");

   return(Res);
  }
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long&, const double&, const string&)
  {
   static bool aaa = true;
   if(id == CHARTEVENT_KEYDOWN && aaa)
     {
      Print("Calculating...");
      Print(Calc());
      aaa = false;
     }
   if(id == CHARTEVENT_CUSTOM)
      aaa = true;
  }
//+------------------------------------------------------------------+
 
fxsaber #:


Симуляция задачи.



Решение1.


Решение2.

Второй раз кнопка нажмется в то время, когда будут выполняться инструкции после вызова 

EventChartCustom(0, 0, 0, 0, "");

, но до выхода из текущего вызова OnChartEvent (тот же Print еще даже не начнет выполняться), и, по закону подлости, именно тогда создадутся все условия к тому, что бы произошел наиболее неблагоприятный сценарий)))). Бинго!!! Это будет эпичный и поучительный фейл)))

Вопрос: кто будет в этом виноват? 

Ответ: тот кто юзает асинхронщину в боевом коде, слабо представляя всю глубину возможных глубин и ту боль, которая там живет.

Это, что бы все могли понять, как это будет (нажать копку после появления Calculated):

double Calc()
  {
   double Res = 0;

   for(int i = 0; i < 1e8; i++)
      Res += MathSin(MathRand());
   EventChartCustom(0, 0, 0, 0, "");
   return(Res);
  }
//+------------------------------------------------------------------+

void OnChartEvent(const int id, const long&, const double&, const string&)
  {
   static bool aaa = true;
   if(id == CHARTEVENT_KEYDOWN && aaa)
     {
      Print("Calculating...");
      double res = Calc();
      Print("Calculated");
      Sleep(5000);
      Print(res);
      aaa = false;
     }
   if(id == CHARTEVENT_CUSTOM)
      aaa = true;
  }

Как-то так надо:

class EventLocker{
public:
   EventLocker(){
      m_lock = true;
   }
   ~EventLocker(){
      EventChartCustom(0, 0, 0, 0, "");
   }
   static void Unlock() {m_lock = false;}
   static bool Locked() {return m_lock;}
private:
   static bool m_lock;
};

bool EventLocker::m_lock = false;

double Calc()
  {
   double Res = 0;

   for(int i = 0; i < 1e8; i++)
      Res += MathSin(MathRand());
   return(Res);
  }
//+------------------------------------------------------------------+

void OnChartEvent(const int id, const long&, const double&, const string&)
  {
   if(id == CHARTEVENT_KEYDOWN && !EventLocker::Locked())
     {
      EventLocker lk;
      Print("Calculating...");
      Print(Calc());
     }
   if(id == CHARTEVENT_CUSTOM)
      EventLocker::Unlock();
  }
 
Vladimir Simakov #:

Это, что бы все могли понять, как это будет (нажать копку после появления Calculated):

Нахрена?! Очевидно же, что EventChartCustom делается после выхода расчетов. У Вас Sleep - это завершающие "расчеты". Поэтому именно после него должен был быть EventChartCustom.

Собственно, именно это Вы и сделали через ООП. Изначально демонстрация же была подхода для допиливания, собранная на коленке.

 

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Особенности языка mql5, тонкости и приёмы работы

Vladimir Simakov, 2024.09.24 11:06

Как-то так надо:

class EventLocker{
public:
   EventLocker(){
      m_lock = true;
   }
   ~EventLocker(){
      m_lock = EventChartCustom(0, 0, 0, 0, "");
   }
   static void Unlock() {m_lock = false;}
   static bool Locked() {return m_lock;}
private:
   static bool m_lock;
};

bool EventLocker::m_lock = false;

Такое добавил бы для надежности.

 
fxsaber #:

Нахрена?! Очевидно же, что EventChartCustom делается после выхода расчетов. У Вас Sleep - это завершающие "расчеты". Поэтому именно после него должен был быть EventChartCustom.

Собственно, именно это Вы и сделали через ООП. Изначально демонстрация же была подхода для допиливания, собранная на коленке.

Потому, что, результат будет таким:

class TComputer{
public:
   TComputer(int _init):m_init(_init){}
   ~TComputer(){
      EventChartCustom(0, 0, 0, 0, "Hello world!!!");
   }
   
   double Compute(){
      double Res = 0;
   
      for(int i = 0; i < m_init; i++)
         Res += MathSin(MathRand());
      return Res ;
   }
private:
   int m_init;
};

double Calc()
  {
   double Res = TComputer(1e8).Compute();
   Print("Calculated");
   Sleep(5000);
   EventChartCustom(0, 0, 0, 0, "");
   return(Res);
  }
//+------------------------------------------------------------------+

void OnChartEvent(const int id, const long&, const double&, const string&)
  {
   static bool aaa = true;
   if(id == CHARTEVENT_KEYDOWN && aaa)
     {
      Print("Calculating...");
      double res = Calc();
      Print(res);
      aaa = false;
     }
   if(id == CHARTEVENT_CUSTOM)
      aaa = true;
  }

TComputer - это шикарная, сторонняя библиотека, которой весь мир будет пользоваться))) Не, даже не так, это будет частью местной стандартной библиотеки)))

Естественно, твой код, который давным-давно работал без сбоев, будет на нее переведен и даже без тестов, а зачем, тут ведь все так тривиально и именно такой вызов в справке показан)))

Я сейчас про то, что недоработанные решения, вкинутые один раз, ведут именно к таким последствиям. Поверь, я через это проходил. Асинхронное программирование - это именно про такие вот подлянки.

 
fxsaber #:

Такое добавил бы для надежности.

Молодец! Правильно. Теперь и конструктор не нужен)

Только сразу вылезла проблема. EventChartCustom завершился с false. Дальше что делаем, как защищаться от нашего зла-злого будем?)))

 
Vladimir Simakov #:

EventChartCustom завершился с false. Дальше что делаем, как защищаться от нашего зла-злого будем?)))

Ровно то, что по коду.

 

Кстати, о птичках, решение-то есть, если сразу о будущем думать, только вот на уровне терминала это надо делать.

Как пример, так, первое, что в голову пришло.

Надо во все событийные вызовы (OnX) два поля добавить.

ulong id; // монотонно увеличивается на каждом событии.

ulong lastFinishedId; // id последнего OnX, который был завершен на момент начала формирования этого события.

 
fxsaber #:

Ровно то, что по коду.

То есть, если повторно была нажата кнопка, то получим неожидаемый вызов?

 
Vladimir Simakov #:

То есть, если повторно была нажата кнопка, то получим неожидаемый вызов?

Да. Когда ChartEventCustom возвращает false, то это нештатная ситуация. Помочь с ней может добавление Решения1.