Как заставить терминал MetaTrader4 экспортировать в CSV в реальном времени?
Я только что закончил сборку своего алгоритма, но теперь мне нужно каждую минуту экспортировать данные из терминала MetaTrader в файл CSV, в котором мой алгоритм может читать и выполнять прогнозы.
Есть несколько способов онлайн-экспорта данных MetaTrader в CSV-файл в режиме реального времени, но я не могу найти ничего, что позволило бы мне экспортировать даже только цену открытия новой свечи, как только она сформируется.
Я хотел бы экспортировать последние 10 свечей OHLC на минутном таймфрейме и цену открытия текущей 11-й свечи. Цена открытия текущей свечи, которая все еще формируется и еще не закрылась. Мне просто нужна цена открытия для этого, как только начнется свеча.
Есть идеи? Я застрял здесь
ОБНОВИТЬ
Я добавил код.
Этот текущий код представляет собой скрипт MetaTrader, который выбирает последние 10 свечей OHLCV и 11-ю свечу, как я уже упоминал.
Однако у меня есть три проблемы с этим сценарием:
- Это не позволяет мне перезаписать существующий CSV.
- Он не работает в режиме реального времени и постоянно обновляется.
- 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
}
*/
}
//+------------------------------------------------------------------+