Как заставить терминал MetaTrader4 экспортировать в CSV в реальном времени?

Я только что закончил сборку своего алгоритма, но теперь мне нужно каждую минуту экспортировать данные из терминала MetaTrader в файл CSV, в котором мой алгоритм может читать и выполнять прогнозы.

Есть несколько способов онлайн-экспорта данных MetaTrader в CSV-файл в режиме реального времени, но я не могу найти ничего, что позволило бы мне экспортировать даже только цену открытия новой свечи, как только она сформируется.

Я хотел бы экспортировать последние 10 свечей OHLC на минутном таймфрейме и цену открытия текущей 11-й свечи. Цена открытия текущей свечи, которая все еще формируется и еще не закрылась. Мне просто нужна цена открытия для этого, как только начнется свеча.

Есть идеи? Я застрял здесь

ОБНОВИТЬ

Я добавил код.
Этот текущий код представляет собой скрипт MetaTrader, который выбирает последние 10 свечей OHLCV и 11-ю свечу, как я уже упоминал.
Однако у меня есть три проблемы с этим сценарием:

  1. Это не позволяет мне перезаписать существующий CSV.
  2. Он не работает в режиме реального времени и постоянно обновляется.
  3. 11-я свеча не самая последняя (свеча еще в строю).

Любая помощь?

//+------------------------------------------------------------------+
#include <stdlib.mqh>
#include <stderror.mqh>
//+------------------------------------------------------------------+
//| Input Parameters Definition                                      |
//+------------------------------------------------------------------+
extern int    BarCount = 11;
extern string Pairs = "EURUSD";
extern string delimiter = ",";
//+------------------------------------------------------------------+
//| Local Parameters Definition                                      |
//+------------------------------------------------------------------+
datetime lastExport[];
string pairs[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
{
  //------------------------------------------------------------------
  Split(Pairs, pairs, ",");
  //------------------------------------------------------------------
  if (ArraySize(pairs) == 0 || StringTrimLeft(StringTrimRight(pairs[0])) == "")
  {
    Alert("Pairs are not entered correctly please check it...");
    return (0);
  }
  //------------------------------------------------------------------
  ArrayResize(lastExport, ArraySize(pairs));
  ArrayInitialize(lastExport, 0);
  //------------------------------------------------------------------
  Comment("quote exporter is active :)");
  //------------------------------------------------------------------
  return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
{
  //------------------------------------------------------------------
  Comment("");
  //------------------------------------------------------------------
  return(0);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
{
  //------------------------------------------------------------------
  if (ArraySize(pairs) == 0 || StringTrimLeft(StringTrimRight(pairs[0])) == "") return (0);
  //------------------------------------------------------------------
  BarCount = MathMin(Bars, BarCount);
  //------------------------------------------------------------------
  for (int j = 0; j < ArraySize(pairs); j++)
  {
    if (lastExport[j] == Time[0]) continue;
    lastExport[j] = Time[0];
    if (StringTrimLeft(StringTrimRight(pairs[j])) == "") continue;
    if (MarketInfo(pairs[j], MODE_BID) == 0) { Alert("symbol " + pairs[j] + " is not loaded"); continue; }
    //------------------------------------------------------------------
    string file = pairs[j] + "_" + GetTimeFrameName(0) + ".csv";
    int log = FileOpen(file, FILE_CSV|FILE_WRITE, "~");
    if (log < 0) { Alert("can not create/overwrite csv file " + file + "!"); continue; }
    string buffer;
    buffer = "Date"+delimiter+"Time"+delimiter+"Open"+delimiter+"High"+delimiter+"Low"+delimiter+"Close"+delimiter+"Volume";
    FileWrite(log, buffer);
    int digits = MarketInfo(pairs[j], MODE_DIGITS);
    for (int i = BarCount; i >= 1; i--)
    {
      buffer = TimeToStr(Time[i], TIME_DATE)+delimiter+TimeToStr(Time[i], TIME_MINUTES)+delimiter+DoubleToStr(iOpen(pairs[j], 0, i), digits)+delimiter+DoubleToStr(iHigh(pairs[j], 0, i), digits)+delimiter+DoubleToStr(iLow(pairs[j], 0, i), digits)+delimiter+DoubleToStr(iClose(pairs[j], 0, i), digits)+delimiter+DoubleToStr(iVolume(pairs[j], 0, i), 0);
      FileWrite(log, buffer);
    }
}
  //------------------------------------------------------------------
  return(0);
}
//+------------------------------------------------------------------+
string GetTimeFrameName(int TimeFrame)
{
  switch (TimeFrame)
  {
    case PERIOD_M1: return("M1");
    case PERIOD_M5: return("M5");
    case PERIOD_M15: return("M15");
    case PERIOD_M30: return("M30");
    case PERIOD_H1: return("H1");
    case PERIOD_H4: return("H4");
    case PERIOD_D1: return("D1");
    case PERIOD_W1: return("W1");
    case PERIOD_MN1: return("MN1");
    case 0: return(GetTimeFrameName(Period()));
  }
}
//+------------------------------------------------------------------+
void Split(string buffer, string &splitted[], string separator)
{
  string value = "";
  int index = 0;
  ArrayResize(splitted, 0);
  if (StringSubstr(buffer, StringLen(buffer) - 1) != separator) buffer = buffer + separator;
  for (int i = 0; i < StringLen(buffer); i++)
    if (StringSubstr(buffer, i, 1) == separator)

    {
      ArrayResize(splitted, index + 1);
      splitted[index] = value;
      index ++;
      value = "";
    }
    else
      value = value + StringSubstr(buffer, i, 1);
}
//+------------------------------------------------------------------+

1 ответ

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

Никогда, действительно НИКОГДА, не делайте ничего подобного CustomIndicator:

Зачем? После некоторых внутренних изменений MetaQuote, Inc. подтвердил, что все, да, ВСЕ CustomIndicator блоки выполнения кода, которые работают внутри платформы MT4, делятся одной одиночной резьбой.

Никогда не пытайтесь помещать какие-либо медленные (с высокой задержкой / низкой пропускной способностью) действия, такие как fileIO, в CustomIndicator, НИКОГДА. 10~15 мс для fileIO убивают реальность в реальном времени, так как длительности TLP значительно ниже 20 мс для майоров в прайм-тайм (см. Рисунок).

(Для более подробной информации, вы можете проверить другие сообщения об алгоритмической торговле).

Чем меньше БЛОКИРОВКИ. Alert() быть таким примером.


Лучшая вещь для RealTime?

Лучше использовать Script тип MQL4-кода, а не CustomIndicator,

Используйте межпроцессные сообщения с низкой задержкой, такие как nanomsg или zeromq.

Я начал использовать оболочку ZeroMQ для MQL4 много лет назад для перемещения aDataSEGMENT[] во внешний механизм предикторов AI/ML и возвращать прогнозы обратно, имея время оборота нетто менее 80 мс, вкл. вычисление AI/ML-предсказания.


Этот код, если менее неправильно:

//+------------------------------------------------------------------+
#include <stdlib.mqh>
#include <stderror.mqh>
//+------------------------------------------------------------------+
extern int      BarCount  =  11;
extern string   Pairs     =  "EURUSD";
extern string   delimiter =  ",";
       datetime lastExport[];
       string   fileNAME[];                                         // USE INSTEAD OF REPETITIVE RE-ASSIGNMENTS
       string   pairs[];
//+------------------------------------------------------------------+
int init() {
    //--------------------------------------------------------------
    Split( Pairs, pairs, "," );                                     // REF. BELOW FOR A PROPER APPROACH
    //--------------------------------------------------------------
    if (   0 == ArraySize( pairs )
       || "" == StringTrimLeft( StringTrimRight( pairs[0] ) )
          ){    Alert( "WARN: Pairs are not entered correctly please check it..." ); // !BLOCKS!
                return( 0 );
    }
    //--------------------------------------------------------------
    ArrayResize(     lastExport, ArraySize( pairs ) );
    ArrayInitialize( lastExport, 0 );
    ArrayResize(     fileNAME,   ArraySize( pairs ) );              // POPULATE
    for ( int j = 0;         j < ArraySize( pairs ); j++ ) fileNAME = StringFormat( "%s_M1.CSV", pairs[j] );
    //--------------------------------------------------------------
    Comment( "INF: Script started. A quote exporter is active :)" );
    //--------------------------------------------------------------
    return( 0 );
}
//+------------------------------------------------------------------+
int deinit() {                                                      // USE OnDeinit(){...} SYNTAX WITH REASON PARAMETER
    //--------------------------------------------------------------
    Comment( "INF: Script will terminate." );
    //--------------------------------------------------------------
    return( 0 );
}
//+------------------------------------------------------------------+
int start() {
    //--------------------------------------------------------------
    if (  0  == ArraySize( pairs )
       || "" == StringTrimLeft( StringTrimRight( pairs[0] ) )
          ) return( 0 );
    //--------------------------------------------------------------
    for ( int j  = 0;
              j <  MathMin( ArraySize( pairs ), CHART_MAX - 1 );
              j++ ) //--------------------------------------------------iterateOverAllListedINSTRUMENTs:
        ChartOpen( pairs[j], PERIOD_M1 );                            // enforce MT4 DataPumps to pre-load & collect QUOTEs
    //------------------------------------------------------------------
    while ( True ) { //-------------------------------------------------iterateINFINITELY: 
            ulong loopSTART = GetMicrosecondCount();
            for ( int j = 0; j < ArraySize( pairs ); j++ ) { //---------iterateOverAllListedINSTRUMENTs:
                  if (            "" == StringTrimLeft( StringTrimRight( pairs[j] ) ) )                                        continue;   // .LOOP-NEXT INSTRUMENT
                  else                  RefreshRates();                                                                                    // .REFRESH   MT4 DataPumps
                  if (             0 == MarketInfo( pairs[j], MODE_BID ) ) { Alert( "symbol " + pairs[j] + " is not loaded" ); continue; } // .LOOP-NEXT INSTRUMENT         // !BLOCKS!
                  if ( lastExport[j] ==      iTime( pairs[j], PERIOD_CURRENT, 0 ) )                                            continue;   // .LOOP-NEXT INSTRUMENT
                  else lastExport[j]  =      iTime( pairs[j], PERIOD_CURRENT, 0 );
                  int         digits  = MarketInfo( pairs[j], MODE_DIGITS );
                  //----------------------------------------------------
                  int    logFH   = FileOpen(     fileNAME[j], FILE_CSV|FILE_WRITE, delimiter );
                  if (   logFH  == INVALID_HANDLE ) { Alert( StringFormat( "INF: Can not create / overwrite csv file(%s)! Errno(%d)", fileNAME[j], GetLastError() ) ); continue; } // !BLOCKS!
                  //----------------------------------------------------fileIO RTO:
                                                    FileWrite( logFH, "Date", "Time", "Open", "High", "Low", "Close", "Volume" );
                  for ( int i    = MathMin( BarCount,                                 iBars( pairs[j], PERIOD_CURRENT ) ); i >= 1; i-- )
                                                    FileWrite( logFH,  TimeToStr(     iTime( pairs[j], PERIOD_CURRENT, i ), TIME_DATE    ),
                                                                       TimeToStr(     iTime( pairs[j], PERIOD_CURRENT, i ), TIME_MINUTES ),
                                                                       DoubleToStr(   iOpen( pairs[j], PERIOD_CURRENT, i ), digits ),
                                                                       DoubleToStr(   iHigh( pairs[j], PERIOD_CURRENT, i ), digits ),
                                                                       DoubleToStr(    iLow( pairs[j], PERIOD_CURRENT, i ), digits ),
                                                                       DoubleToStr(  iClose( pairs[j], PERIOD_CURRENT, i ), digits ),
                                                                       DoubleToStr( iVolume( pairs[j], PERIOD_CURRENT, i ), 0      )
                                                                       );
                  //----------------------------------------------------fileIO DONE
                                                    FileClose( logFH );// @ a cost ~ 15-18 [ms]
            }
            //----------------------------------------------------------loopIO DONE
            Comment( StringFormat( "INF: Last loopIO-TAT took %d [us]. Will sleep past aNewBarEVENT( M1 )... ~ %s ( + %d [s] )",
                                    GetMicrosecondCount()-loopSTART,
                                    TimeToStr(      TimeLocal(), TIME_MINUTES ),
                                    ( 60 - MathMod( TimeLocal(), 60 ) )
                                    )
                     );
            Sleep( 250 + 1000 * ( 60 - MathMod( TimeLocal(), 60 ) ) );
            //----------------------------------------------------------loopWAIT past aNewBarEVENT
    }
    //------------------------------------------------------------------
    return( 0 );
}
//+------------------------------------------------------------------+
string GetTimeFrameName( int TimeFrame ) {
       switch( TimeFrame ) { case PERIOD_M1:  return(  "M1"  );
                             case PERIOD_M5:  return(  "M5"  );
                             case PERIOD_M15: return(  "M15" );
                             case PERIOD_M30: return(  "M30" );
                             case PERIOD_H1:  return(  "H1"  );
                             case PERIOD_H4:  return(  "H4"  );
                             case PERIOD_D1:  return(  "D1"  );
                             case PERIOD_W1:  return(  "W1"  );
                             case PERIOD_MN1: return( "MN1"  );
                             case 0:          return( GetTimeFrameName( Period() ) );
        }
}
//+------------------------------------------------------------------+
void Split( string buffer, string &splitted[], string separator ) {
     string value = "";
     int    index = 0;
     ArrayResize( splitted, 0 );
     if (  StringSubstr( buffer, StringLen( buffer ) - 1 ) != separator ) buffer = buffer + separator;
     for ( int i  = 0;
               i <  StringLen( buffer );
               i++ )
               if (  StringSubstr( buffer, i, 1 ) == separator          // a case, when (string) buffer[i] is actually a next (string) separator
                  || StringLen(    buffer )       == i                  // a case, when (string) buffer does not end with   a (string) separator
                     ){ //----------------------------------------------// ONCE A MANUAL FIX ENABLED TO PARSE A SINGLE-INSTRUMENT (string) buffer:
                     ArrayResize( splitted, index + 1 );
                     splitted[index] = StringTrimLeft( StringTrimRight( value ) );
                     index++;
                     value = "";
               }
               else
                     value = value + StringSubstr( buffer, i, 1 );

     /* **************************************************************** WORTH READING THE DOCUMENTATION:
     USING A BUILT-IN FUNCTION WOULD BE MUCH SIMPLER AND SAFER:
     >          
     >      int  StringSplit(  const string   string_value,             // A string to search in
     >                         const ushort   separator,                // A separator using which substrings will be searched
     >                         string        &result[]                  // An array passed by reference to get the found substrings
     >                         );

     int SplitINSTRUMENTs( string buffer, string &splitted[], string separator ){
         return( StringSplit(     buffer,
                                  StringGetCharacter( separator ),
                                  splitted
                                  )
                 ); // -------------------------------------------------WOULD LOVELY FIX THE WHOLE CIRCUS
     }
     */
}
//+------------------------------------------------------------------+
Другие вопросы по тегам