표준 기능/접근법의 대체 구현

 

NormalizeDouble

 #define EPSILON ( 1.0 e- 7 + 1.0 e- 13 )
#define HALF_PLUS  ( 0.5 + EPSILON)

double MyNormalizeDouble( const double Value, const int digits )
{
   // Добавление static ускоряет код в три раза (Optimize=0)!
   static const double Points[] = { 1.0 e- 0 , 1.0 e- 1 , 1.0 e- 2 , 1.0 e- 3 , 1.0 e- 4 , 1.0 e- 5 , 1.0 e- 6 , 1.0 e- 7 , 1.0 e- 8 };

   return (( int )((Value > 0 ) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
}

ulong Bench( const int Amount = 1.0 e8 )
{
   double Price = 1.23456 ;
   const double point = 0.00001 ;
  
   const ulong StartTime = GetMicrosecondCount ();
  
   int Tmp = 0 ;
  
   for ( int i = 0 ; i < Amount; i++)
  {
    Price = NormalizeDouble (Price + point, 5 ); // замените на MyNormalizeDouble и почувствуйте разницу
    
     // Если убрать, то общее время выполнения будет нулевым при любом Amount (Optimize=1) - круто! В варианте NormalizeDouble оптимизации такой не будет.  
     if (i + i > Amount + Amount)
       return ( 0 );
  }
  
   return ( GetMicrosecondCount () - StartTime);
}

void OnStart ( void )
{
   Print (Bench());
    
   return ;
};

결과는 1123275 및 1666643으로 MyNormalizeDouble을 선호합니다(최적화=1). 최적화 없이 - 4배 더 빠릅니다(메모리의 경우).


 

교체하는 경우

 static const double Points[] = { 1.0 e- 0 , 1.0 e- 1 , 1.0 e- 2 , 1.0 e- 3 , 1.0 e- 4 , 1.0 e- 5 , 1.0 e- 6 , 1.0 e- 7 , 1.0 e- 8 };

스위치 변형에서 스위치 구현의 품질을 숫자로 확인할 수 있습니다.

 

NormalizeDouble을 사용하여 정리된 버전의 스크립트를 고려하십시오.

 #define EPSILON ( 1.0 e- 7 + 1.0 e- 13 )
#define HALF_PLUS  ( 0.5 + EPSILON)
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double MyNormalizeDouble( const double Value, const int digits)
  {
   static const double Points[]={ 1.0 e- 0 , 1.0 e- 1 , 1.0 e- 2 , 1.0 e- 3 , 1.0 e- 4 , 1.0 e- 5 , 1.0 e- 6 , 1.0 e- 7 , 1.0 e- 8 };

   return (( int )((Value > 0 ) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong BenchStandard( const int Amount= 1.0 e8)
  {
   double        Price= 1.23456 ;
   const double point= 0.00001 ;
   const ulong   StartTime= GetMicrosecondCount ();
//---
   for ( int i= 0 ; i<Amount;i++)
     {
      Price= NormalizeDouble (Price+point, 5 );
     }
   
   Print ( "Result: " ,Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
//---
   return ( GetMicrosecondCount () - StartTime);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ulong BenchCustom( const int Amount= 1.0 e8)
  {
   double        Price= 1.23456 ;
   const double point= 0.00001 ;
   const ulong   StartTime= GetMicrosecondCount ();
//---
   for ( int i= 0 ; i<Amount;i++)
     {
      Price=MyNormalizeDouble(Price+point, 5 );
     }
   
   Print ( "Result: " ,Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
//---
   return ( GetMicrosecondCount () - StartTime);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart ( void )
  {
   Print ( "Standard: " ,BenchStandard(), " msc" );
   Print ( "Custom:   " ,BenchCustom(),   " msc" );
  }

결과:

Custom:   1110255 msc
Result:   1001.23456

Standard: 1684165 msc
Result:   1001.23456

즉시 의견 및 설명:

  1. 컴파일러가 함수에서 이 배열을 가져오고 함수가 호출될 때마다 스택에 생성 하지 않도록 static이 필요합니다. C++ 컴파일러도 동일한 작업을 수행합니다.
     static const double Points
  2. 컴파일러가 쓸모없어서 루프를 버리지 않도록 계산 결과를 사용해야 합니다. 예를 들어 Print 변수를 Price로 만듭니다.

  3. 함수에 오류가 있습니다. 자릿수 경계가 확인되지 않아 배열이 쉽게 범위를 벗어날 수 있습니다.

    예를 들어 MyNormalizeDouble(Price+point, 10 )과 같이 호출하고 오류를 포착합니다.
    array out of range in 'BenchNormalizeDouble.mq5' (19,45)
    
    수표를 생략하여 속도를 높이는 방법은 허용되지만 우리의 경우에는 그렇지 않습니다. 우리는 잘못된 데이터 입력을 처리할 의무가 있습니다.

  4. 8보다 큰 인덱스에 대한 간단한 조건을 추가하고 코드를 단순화하기 위해 숫자 변수의 유형을 uint로 교체하여 추가 조건 <0 대신 >8에 대한 비교를 수행하겠습니다.
     //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    double MyNormalizeDouble( const double Value, uint digits)
      {
       static const double Points[]={ 1.0 e- 0 , 1.0 e- 1 , 1.0 e- 2 , 1.0 e- 3 , 1.0 e- 4 , 1.0 e- 5 , 1.0 e- 6 , 1.0 e- 7 , 1.0 e- 8 };
    //---
       if (digits> 8 )
          digits= 8 ;
    //---
       return (( int )((Value > 0 ) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
      }
    

  5. 우리는 코드를 실행하고 ... 우리는 놀랐습니다!
    Custom:   1099705 msc
    Result:   1001.23456
    
    Standard: 1695662 msc
    Result:   1001.23456
    
    귀하의 코드는 표준 NormalizeDouble 기능 보다 훨씬 앞서 있습니다!

    또한 조건을 추가하면 시간이 단축됩니다(사실 여기에서 감소는 오차 범위 내입니다). 왜 이렇게 속도 차이가 나는 걸까요?

  6. 성능 테스터의 표준 오류에 관한 모든 것입니다.

    테스트를 작성할 때 컴파일러에서 적용할 수 있는 최적화의 전체 목록을 염두에 두어야 합니다. 어떤 입력을 사용하고 있으며 단순화된 샘플 테스트를 작성할 때 입력이 어떻게 소멸되는지에 대해 매우 명확해야 합니다.

    컴파일러가 만드는 전체 최적화 세트를 단계별로 평가하고 적용해 보겠습니다.

  7. 지속적인 전파부터 시작하겠습니다. 이것은 이 테스트에서 저지른 중요한 실수 중 하나입니다.

    입력 데이터의 절반이 상수입니다. 분포를 고려하여 예제를 다시 작성해 보겠습니다.

     ulong BenchStandard( void )
      {
       double       Price= 1.23456 ;
       const ulong StartTime= GetMicrosecondCount ();
    //---
       for ( int i= 0 ; i< 1.0 e8;i++)
         {
          Price= NormalizeDouble (Price + 0.00001 , 5 );
         }
    
       Print ( "Result: " ,Price);
    //---
       return ( GetMicrosecondCount () - StartTime);
      }
    
    ulong BenchCustom( void )
      {
       double       Price= 1.23456 ;
       const ulong StartTime= GetMicrosecondCount ();
    //---
       for ( int i= 0 ; i< 1.0 e8;i++)
         {
          Price=MyNormalizeDouble(Price + 0.00001 , 5 );
         }
    
       Print ( "Result: " ,Price, " " , 1.0 e8);
    //---
       return ( GetMicrosecondCount () - StartTime);
      }
    
    출시 이후에는 변경된 것이 없습니다.

  8. 계속하십시오 - 코드를 인라인하십시오(NormalizeDouble은 인라인할 수 없음)

    이것은 불가피한 인라인 이후에 함수가 실제로 바뀌는 것입니다. 호출 비용 절감, 배열 추출 비용 절감, 검사는 상수 구문 분석을 통해 제거됩니다.
     ulong BenchCustom( void )
      {
       double               Price= 1.23456 ;
       const ulong          StartTime= GetMicrosecondCount ();
    //---
       for ( int i= 0 ; i< 1.0 e8;i++)
         {
           //--- этот код полностью вырезается, так как у нас заведомо константа 5
           //if(digits>8)
           //   digits=8;
           //--- распространяем переменные и активно заменяем константы
           if ((Price+ 0.00001 )> 0 )
             Price= int ((Price+ 0.00001 )/ 1.0 e- 5 +( 0.5 + 1.0 e- 7 + 1.0 e- 13 ))* 1.0 e- 5 ;
           else
             Price= int ((Price+ 0.00001 )/ 1.0 e- 5 -( 0.5 + 1.0 e- 7 + 1.0 e- 13 ))* 1.0 e- 5 ;
         }
    
       Print ( "Result: " ,Price);
    //---
       return ( GetMicrosecondCount () - StartTime);
      }
    
    나는 시간을 낭비하지 않기 위해 순수한 상수를 요약하지 않았습니다. 그것들은 모두 컴파일 타임에 접히는 것이 보장됩니다.

    코드를 실행하고 원래 버전과 동일한 시간을 가져옵니다.
    Custom:   1149536 msc
    Standard: 1767592 msc
    
    숫자의 지터에주의를 기울이지 마십시오. 마이크로 초 수준에서 타이머 오류와 컴퓨터의 부동 부하는 정상 범위 내에 있습니다. 비율이 완전히 유지됩니다.

  9. 고정 입력으로 인해 실제로 테스트를 시작한 코드를 보십시오.

    컴파일러에는 매우 강력한 최적화 기능이 있으므로 작업이 효과적으로 단순화되었습니다.


  10. 그렇다면 성능 테스트는 어떻게 합니까?

    컴파일러가 작동하는 방식을 이해하려면 사전 최적화 및 단순화를 적용하지 않도록 해야 합니다.

    예를 들어 숫자 매개변수를 변수로 만들어 보겠습니다.

     #define EPSILON ( 1.0 e- 7 + 1.0 e- 13 )
    #define HALF_PLUS  ( 0.5 + EPSILON)
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    double MyNormalizeDouble( const double Value, uint digits)
      {
       static const double Points[]={ 1.0 e- 0 , 1.0 e- 1 , 1.0 e- 2 , 1.0 e- 3 , 1.0 e- 4 , 1.0 e- 5 , 1.0 e- 6 , 1.0 e- 7 , 1.0 e- 8 };
    //---
       if (digits> 8 )
          digits= 8 ;
    //---   
       return (( int )((Value > 0 ) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]);
      }
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    ulong BenchStandard( const int Amount= 1.0 e8)
      {
       double        Price= 1.23456 ;
       const double point= 0.00001 ;
       const ulong   StartTime= GetMicrosecondCount ();
    //---
       for ( int i= 0 ; i<Amount;i++)
         {
          Price= NormalizeDouble (Price+point, 2 +(i& 15 ) );
         }
    
       Print ( "Result: " ,Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
    //---
       return ( GetMicrosecondCount () - StartTime);
      }
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    ulong BenchCustom( const int Amount= 1.0 e8)
      {
       double        Price= 1.23456 ;
       const double point= 0.00001 ;
       const ulong   StartTime= GetMicrosecondCount ();
    //---
       for ( int i= 0 ; i<Amount;i++)
         {
          Price=MyNormalizeDouble(Price+point, 2 +(i& 15 ) );
         }
    
       Print ( "Result: " ,Price);   // специально выводим результат, чтобы цикл не оптимизировался в ноль
    //---
       return ( GetMicrosecondCount () - StartTime);
      }
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnStart ( void )
      {
       Print ( "Standard: " ,BenchStandard(), " msc" );
       Print ( "Custom:   " ,BenchCustom(), " msc" );
      }
    
    우리는 그것을 실행하고... 우리는 이전과 같은 속도 결과를 얻습니다.

    귀하의 코드는 이전과 같이 약 35% 승리합니다.

  11. 왜 그래?

    우리는 여전히 인라인으로 인해 최적화에서 우리 자신을 구하지 못했습니다. 스택을 통해 동일한 구현의 NormalizeDouble 함수로 데이터를 전달하여 100,000,000번의 호출을 저장하면 이러한 가속을 쉽게 얻을 수 있습니다.

    MQL5 프로그램에서 함수 재배치 테이블을 로드할 때 NormalizeDouble을 direct_call 메커니즘에 넣지 않았다는 의심이 하나 더 있습니다.

    아침에 확인 후 이 경우 direct_call로 전송하고 속도를 다시 확인합니다.

다음은 NormalizeDouble에 대한 연구입니다.

MQL5 컴파일러는 C++ 코드의 속도와 비교할 때 적절함을 보여주는 시스템 기능을 물리쳤습니다.

 
fxsaber :

교체하는 경우

스위치 변형에서 스위치 구현의 품질을 숫자로 확인할 수 있습니다.

상수 인덱스(필드에서 상수로 변질됨) 및 스위치로 정적 배열 에 대한 직접 인덱싱된 액세스를 혼동하고 있습니다.

스위치는 이 경우에 그다지 경쟁력이 없습니다. switch에는 일반적으로 사용되는 몇 가지 양식 최적화가 있습니다.

  • "분명히 정렬되고 짧은 값은 정적 배열 및 인덱스 전환에 삽입됩니다." - 가장 간단하고 빠르며 정적 배열과 경쟁할 수 있지만 항상 그런 것은 아닙니다.
  • "영역 경계를 확인하는 정렬되고 가까운 값의 여러 배열" - 여기에 이미 브레이크가 있습니다.
  • "확인된 값이 너무 적은 경우 어리석게 확인하는 경우" - 속도는 없지만 프로그래머가 탓하며 스위치를 제자리에서 사용하지 않습니다.
  • "이진 검색이 포함된 완전히 희소한 순서의 테이블" - 끔찍한 경우에 매우 느림

사실, 가장 좋은 스위치 전략은 개발자가 의도적으로 낮은 숫자 집합의 값 집합을 범위에서 축소하려고 할 때입니다.

 
Renat Fatkhullin :

NormalizeDouble을 사용하여 정리된 버전의 스크립트를 고려하십시오.

결과:


즉시 의견 및 설명:

  1. 컴파일러가 함수에서 이 배열을 가져오고 함수가 호출될 때마다 스택에 생성 하지 않도록 static이 필요합니다. C++ 컴파일러도 동일한 작업을 수행합니다.
"최적화 = 0"을 사용하면 됩니다. "Optimize=1"을 사용하면 이를 버릴 수도 있습니다. 컴파일러 최적화 프로그램은 똑똑한 것으로 밝혀졌습니다.
  1. 컴파일러가 쓸모없어서 루프를 버리지 않도록 계산 결과를 사용해야 합니다. 예를 들어, Print 변수를 Price로 만듭니다.
멋진 기능!
  1. 함수에 오류가 있습니다. 자릿수 경계가 확인되지 않아 배열이 쉽게 범위를 벗어날 수 있습니다.

    예를 들어 MyNormalizeDouble(Price+point, 10 )과 같이 호출하고 오류를 포착합니다.
    수표를 생략하여 속도를 높이는 방법은 허용되지만 우리의 경우에는 그렇지 않습니다. 우리는 잘못된 데이터 입력을 처리할 의무가 있습니다.

  2. 8보다 큰 인덱스에 대한 간단한 조건을 추가하고 코드를 단순화하기 위해 숫자 변수의 유형을 uint로 교체하여 추가 조건 <0 대신 >8에 대한 비교를 수행하겠습니다.
네, 최고인 것 같습니다.
 double MyNormalizeDouble( const double Value, const uint digits )
{
   static const double Points[] = { 1.0 e- 0 , 1.0 e- 1 , 1.0 e- 2 , 1.0 e- 3 , 1.0 e- 4 , 1.0 e- 5 , 1.0 e- 6 , 1.0 e- 7 , 1.0 e- 8 };
   const double point = digits > 8 ? 1.0 e- 8 : Points[digits];

   return (( int )((Value > 0 ) ? Value / point + HALF_PLUS : Value / point - HALF_PLUS) * point);
}
  1. 성능 테스터의 표준 오류에 관한 모든 것입니다.

    테스트를 작성할 때 컴파일러에서 적용할 수 있는 최적화의 전체 목록을 염두에 두어야 합니다. 어떤 입력을 사용하고 있으며 단순화된 샘플 테스트를 작성할 때 입력이 어떻게 소멸되는지에 대해 매우 명확해야 합니다.
  2. 그렇다면 성능 테스트는 어떻게 해야 할까요?

    컴파일러가 작동하는 방식을 이해하려면 사전 최적화 및 단순화를 적용하지 않도록 해야 합니다.

    예를 들어 숫자 매개변수를 변수로 만들어 보겠습니다.
컴파일러 성능 측정을 올바르게 준비하는 방법에 대한 자세한 설명에 감사드립니다! 실제로 상수 최적화 가능성을 고려하지 않았습니다.

다음은 NormalizeDouble에 대한 연구입니다.

MQL5 컴파일러는 C++ 코드의 속도와 비교할 때 적절함을 보여주는 시스템 기능을 물리쳤습니다.

예, 그러한 결과는 자부심의 문제입니다.
 
Renat Fatkhullin :

상수 인덱스(필드에서 상수로 변질됨) 및 스위치로 정적 배열 에 대한 직접 인덱싱된 액세스를 혼동하고 있습니다.

스위치는 이 경우에 그다지 경쟁력이 없습니다. switch에는 일반적으로 사용되는 몇 가지 양식 최적화가 있습니다.

  • "분명히 정렬되고 짧은 값은 정적 배열 및 인덱스 전환에 삽입됩니다." - 가장 간단하고 빠르며 정적 배열과 경쟁할 수 있지만 항상 그런 것은 아닙니다.

이것이 바로 그러한 질서의 경우입니다.

사실, 가장 좋은 스위치 전략은 개발자가 의도적으로 낮은 숫자 집합의 값 집합을 범위에서 축소하려고 할 때입니다.

32비트 시스템에서 시도했습니다. 거기에서 위의 예에서 스위치로 교체하면 심각한 브레이크가 발생했습니다. 새 차에서는 테스트하지 않았습니다.
 
fxsaber :

이것이 바로 그러한 질서의 경우입니다.

따로 확인이 필요하지만 나중에.


32비트 시스템에서 시도했습니다. 거기에서 위의 예에서 스위치로 교체하면 심각한 브레이크가 발생했습니다. 새 차에서는 테스트하지 않았습니다.

각 MQL5에는 실제로 2개의 컴파일된 프로그램이 있습니다. 하나는 32비트용으로 단순화된 프로그램이고 다른 하나는 64비트용으로 최대로 최적화되어 있습니다. 32비트 MT5에서는 새로운 옵티마이저가 전혀 적용되지 않고, 32비트 운영체제용 코드는 MT4의 MQL4처럼 간단하다.

64비트 버전의 MT5에서 실행할 때만 10배 더 빠르게 코드를 생성할 수 있는 컴파일러의 모든 효율성: https://www.mql5.com/en/forum/58241

우리는 플랫폼의 64비트 버전에 전적으로 집중하고 있습니다.

 

NormalizeDouble 주제에 그런 넌센스가 있습니다.

거래, 자동 거래 시스템 및 거래 전략 테스트에 관한 포럼

열거 형을 순차적으로 반복하는 방법은 무엇입니까?

fxsaber , 2016.08.26 16:08

기능 설명 에 메모가 있습니다

이것은 최소 가격 단계가 10^N인 기호에 대해서만 해당됩니다. 여기서 N은 양수가 아닌 정수입니다. 최소 가격 단계의 값이 다른 경우 OrderSend 전에 가격 수준을 정규화하는 것은 무의미한 작업이며 대부분의 경우 OrderSend가 false를 반환합니다.


도움말에서 오래된 보기를 수정하는 것이 좋습니다.

NormalizeDouble은 완전히 불신입니다. 구현 속도가 느릴 뿐만 아니라 다양한 주식 기호(예: RTS, MIX 등)에서도 의미가 없습니다 .

NormalizeDouble은 원래 Order* 작업을 위해 사용자가 만들었습니다. 기본적으로 가격과 제비를 위해. 그러나 비표준 TickSize 및 VolumeStep이 나타났습니다. 그리고 이 기능은 더 이상 사용되지 않습니다. 이 때문에 브레이크 코드를 작성합니다. 표준 라이브러리의 예
 double CTrade::CheckVolume( const string symbol, double volume, double price, ENUM_ORDER_TYPE order_type)
  {
//--- check
   if (order_type!= ORDER_TYPE_BUY && order_type!= ORDER_TYPE_SELL )
       return ( 0.0 );
   double free_margin= AccountInfoDouble ( ACCOUNT_FREEMARGIN );
   if (free_margin<= 0.0 )
       return ( 0.0 );
//--- clean
   ClearStructures();
//--- setting request
   m_request.action= TRADE_ACTION_DEAL ;
   m_request.symbol=symbol;
   m_request.volume=volume;
   m_request.type  =order_type;
   m_request.price =price;
//--- action and return the result
   if (!:: OrderCheck (m_request,m_check_result) && m_check_result.margin_free< 0.0 )
     {
       double coeff=free_margin/(free_margin-m_check_result.margin_free);
       double lots= NormalizeDouble (volume*coeff, 2 );
       if (lots<volume)
        {
         //--- normalize and check limits
         double stepvol= SymbolInfoDouble (symbol, SYMBOL_VOLUME_STEP );
         if (stepvol> 0.0 )
            volume=stepvol*( MathFloor (lots/stepvol)- 1 );
         //---
         double minvol= SymbolInfoDouble (symbol, SYMBOL_VOLUME_MIN );
         if (volume<minvol)
            volume= 0.0 ;
        }
     }
   return (volume);
  }

글쎄, 당신은 할 수 없습니다! NormalizeDouble을 잊어 버리고 몇 배 더 빠를 수 있습니다.

 double NormalizePrice( const double dPrice, double dPoint = 0 )
{
   if (dPoint == 0 ) 
    dPoint = :: SymbolInfoDouble (:: Symbol (), SYMBOL_TRADE_TICK_SIZE );

   return (( int )((dPrice > 0 ) ? dPrice / dPoint + HALF_PLUS : dPrice / dPoint - HALF_PLUS) * dPoint);
}

그리고 같은 볼륨에 대해 다음을 수행합니다.

volume = NormalizePrice(volume, stepvol);

가격을 위해

NormalizePrice(Price, TickSize)

표준 NormalizeDouble의 오버로드로 이와 같은 것을 추가하는 것이 옳을 것 같습니다. 여기서 두 번째 매개변수 "digits"는 int가 아니라 double입니다.

 

2016년까지 대부분의 C++ 컴파일러는 동일한 수준의 최적화에 도달했습니다.

MSVC는 각 업데이트의 개선 사항에 대해 궁금해하게 만들고 컴파일러로서의 Intel C++가 병합되었지만 대규모 프로젝트 에서 "내부 오류"가 해결되지 않았습니다.

컴파일러에서 1400 빌드의 또 다른 개선 사항은 복잡한 프로젝트를 컴파일하는 속도가 빨라졌다는 것입니다.

 

이 주제에. 표준 함수는 때때로 필요한 것을 제공하지 않기 때문에 표준 함수에 대한 대안을 만들어야 합니다. 다음은 SymbolInfoTick의 대안 예입니다.

 // Получение тика, который на самом деле вызвал крайнее событие NewTick
bool MySymbolInfoTick( const string Symb, MqlTick &Tick, const uint Type = COPY_TICKS_ALL )
{
   MqlTick Ticks[];
   const int Amount = :: CopyTicks (Symb, Ticks, Type, 0 , 1 );
   const bool Res = (Amount > 0 );
  
   if (Res)
    Tick = Ticks[Amount - 1 ];
  
   return (Res);
}

// Возвращает в точности то, что SymbolInfoTick
bool CloneSymbolInfoTick( const string Symb, MqlTick &Tick )
{
   MqlTick TickAll, TickTrade, TickInfo;
   const bool Res = (MySymbolInfoTick(Symb, TickAll) &&
                    MySymbolInfoTick(Symb, TickTrade, COPY_TICKS_TRADE ) &&
                    MySymbolInfoTick(Symb, TickInfo, COPY_TICKS_INFO ));
  
   if (Res)
  {
    Tick = TickInfo;

    Tick.time = TickAll.time;
    Tick.time_msc = TickAll.time_msc;
    Tick.flags = TickAll.flags;
    
    Tick.last = TickTrade.last;
    Tick.volume = TickTrade.volume;    
  }
  
   return (Res);
}

테스터에서 각 이벤트 에 대해 NewTick SymbolInfoTick을 호출하고 거래량 필드를 추가하여 교환 회전율을 확인하는 것 같습니다. 하지만 아니, 당신은 할 수 없습니다! MySymbolInfoDouble을 의미하는 데 훨씬 더 논리적이어야 합니다.

 
fxsaber :

NormalizeDouble 주제에 그런 넌센스가 있습니다.

NormalizeDouble은 원래 Order* 작업을 위해 사용자가 만들었습니다. 기본적으로 가격과 제비를 위해. 그러나 비표준 TickSize 및 VolumeStep이 나타났습니다. 그리고 이 기능은 더 이상 사용되지 않습니다. 이 때문에 브레이크 코드를 작성합니다. 표준 라이브러리의 예

글쎄, 당신은 그것을 할 수 없습니다! NormalizeDouble을 잊어 버리고 몇 배 더 빠를 수 있습니다.

그리고 같은 볼륨에 대해 다음을 수행합니다.

가격을 위해

표준 NormalizeDouble의 오버로드로 이와 같은 것을 추가하는 것이 옳을 것 같습니다. 여기서 두 번째 매개변수 "digits"는 int가 아니라 double입니다.

주변의 모든 것을 최적화할 수 있습니다.

끝이 없는 과정입니다. 그러나 99%의 경우 경제적으로 실행 가능하지 않습니다.

사유: