Files
-mql5_scripts/BinanceQuotesDownloader - script for MetaTrader 5/binancequotesdownloader.mq5
T

526 lines
35 KiB
Plaintext

//+------------------------------------------------------------------+
//| BinanceQuotesDownloader.mq5 |
//+------------------------------------------------------------------+
#property script_show_inputs
input string url="https://api.binance.com";
input uint marketViewUpdateInterval = 30000; // every n milliseconds must read marketView and check if symbols deleted or added
input uint quotesUpdateInterval = 5000; // every n milliseconds must update custom symbols quotes
input uint servicePauseInterval = 2500; // every n milliseconds service is sleeping
input datetime downloadHistoryFrom = D'2023.01.01 00:00'; // max number of bars that will be downloaded from Binance server for Every Custom symbol
input bool isForceReloadHistory = false;
uint LastTimeMarketViewRead = 0;
uint LastTimeQuotesUpdate = 0;
string binanceMarketViewSymbols[];
int marketViewSymbolsSize = 0;
bool forceReloaded = false;
void OnStart() {
// check if passed interval and run code
long currBarsHistory, seriesLastDate, seriesFirstDate;
uint currTime;
string symbolName, binanceSymbolName, customSymbolName, symbolPath;
MqlRates rates[];
string cookie=NULL,headers;
char post[],result[];
checkSymbols();
CBinanceClient client(url);
while(!_StopFlag){
currTime = GetTickCount();
if( (currTime-LastTimeMarketViewRead) > marketViewUpdateInterval ){
LastTimeMarketViewRead = currTime;
//Print("Read list of Binance Custom symbols that are in MarketView");
marketViewSymbolsSize = 0;
for(int i=0;i<SymbolsTotal(true);i++) {
symbolName = SymbolName(i,true);
if(StringGetCharacter(symbolName,0) =='b'){
Print(symbolName);
marketViewSymbolsSize+=1;
ArrayResize(binanceMarketViewSymbols, marketViewSymbolsSize, 10);
binanceMarketViewSymbols[marketViewSymbolsSize-1] = symbolName;
}
}
}
if( currTime-LastTimeQuotesUpdate >= quotesUpdateInterval ){
LastTimeQuotesUpdate = currTime;
// update Binance custom Symbols history that presented in marketView
for(int i=0; i<marketViewSymbolsSize;i++){
customSymbolName = binanceMarketViewSymbols[i];
binanceSymbolName = StringSubstr(customSymbolName, 1);
// download history
long t = downloadHistoryFrom*1000;
int s = 0;
seriesFirstDate = CCustomSymbol::getCustomSymbolBarOldestDate(customSymbolName);
currBarsHistory = CCustomSymbol::getCustomSymbolBarsCount(customSymbolName);
// download all history from historyOldest daytime
if( (currBarsHistory == 0 || t<seriesFirstDate*1000 || isForceReloadHistory)&&(forceReloaded == false) ){
Print("Need to download/reDownload all history");
do{
client.requestQuotes(binanceSymbolName, t, NULL, 1000, rates);
CustomRatesUpdate(customSymbolName, rates);
s = ArraySize(rates);
PrintFormat("download history RequestQuotes for '%s' from date:%s. Downloaded:%d, time:%s", binanceSymbolName, TimeToString(t/1000), s, TimeToString(rates[s-1].time) );
if(s>0){ t = rates[s-1].time*1000; }
}while(s>=1000);
}
// update recent last bars
seriesLastDate = CCustomSymbol::getCustomSymbolBarRecentDate(customSymbolName);
client.requestQuotes(binanceSymbolName, seriesLastDate, NULL, NULL, rates);
CustomRatesUpdate(customSymbolName, rates);
PrintFormat("Request %d Quotes for '%s' from %s", ArraySize(rates), binanceSymbolName, TimeToString(seriesLastDate/1000) );
}
forceReloaded = true;
}
Sleep(servicePauseInterval);
}
}
void checkSymbols(){
string symbolPath = SymbolInfoString("bBTCUSDT",SYMBOL_PATH);
if(StringFind(symbolPath,"BINANCE") >= 0) return;
string binanceSymbols[];
int binanceSymbolsSize = 0;
string cookie=NULL,headers;
char post[],result[];
Print("Update List of Symbols from Binance is started ...");
// 1. get list of all SYMBOLS that you can find in binance.com
CBinanceClient client(url);
client.webRequest("GET","/api/v3/ticker/price",cookie,NULL,500,post, 0);
string jsonStr = client.getResultAsString();
CJsonParser parser(jsonStr);
parser.toNextArray();
while( parser.toNextObject()!=-1){
ArrayResize(binanceSymbols, ++binanceSymbolsSize, 20);
binanceSymbols[binanceSymbolsSize-1] = parser.getValueAsString("symbol");
}
// 2. get array of symbols and create custom symbols in mt5
for(int i = 0; i<binanceSymbolsSize;i++){
CCustomSymbol::createCustomSymbol( "b"+binanceSymbols[i] );
}
for(int i=0; i<SymbolsTotal(true); i++){
if(SymbolSelect(SymbolName(i,true),false)) i--;
}
Print("Update List of Symbols from Binance is finished.");
}
//+------------------------------------------------------------------+
#include <Object.mqh>
class CBinanceClient : public CObject {
protected:
string serverUrl;
char result[];
string resultHeaders;
int res;
public:
CBinanceClient(string serverUrl);
~CBinanceClient(void);
int webRequest(const string method, // метод HTTP
const string apiUrl, // url-адрес
const string cookie, // cookie
const string referer, // referer
int timeout, // таймаут
const char &data[], // массив тела HTTP-сообщения
int data_size); // размер массива data[] в байтах
void saveResultAndHeadersInCommonTxtFile(string fileName);
void printResultAndHeadersInConsole(void);
void printResponceStatusInConsole(void);
string getResultAsString();
int requestQuotes(string binanceSymbolName, long startTime, long endTime, int limits, MqlRates &rates[]);
};
CBinanceClient::CBinanceClient(string srvUrl) {
this.serverUrl = srvUrl;
}
CBinanceClient::~CBinanceClient(void) {
}
int CBinanceClient::webRequest(
const string method, // метод HTTP
const string apiUrl, // url-адрес
const string cookie, // cookie
const string referer, // referer
int timeout, // таймаут
const char &data[], // массив тела HTTP-сообщения
int data_size) { // размер массива data[] в байтах
ResetLastError();
res = WebRequest( "GET",serverUrl + apiUrl,
cookie,referer,timeout,
data,data_size,
this.result,this.resultHeaders);
if(res==-1) {
Print("Ошибка в WebRequest. Код ошибки =",GetLastError());
//--- возможно, URL отсутствует в списке, выводим сообщение о необходимости его добавления
MessageBox("Необходимо добавить адрес '"+serverUrl+"' в список разрешенных URL во вкладке 'Советники'","Ошибка",MB_ICONINFORMATION);
} else {
if(res==200) {
PrintFormat("запрос успешен, размер %d байт.",ArraySize(this.result));
}
}
return res;
}
void CBinanceClient::saveResultAndHeadersInCommonTxtFile(string fileName) {
int filehandle=FileOpen(fileName,FILE_COMMON|FILE_TXT|FILE_WRITE);
if(filehandle!=INVALID_HANDLE) {
FileWriteString(filehandle, resultHeaders);
FileWriteString(filehandle,CharArrayToString(result));
//--- закрываем файл
FileClose(filehandle);
} else Print("Ошибка в FileOpen. Код ошибки =",GetLastError());
}
void CBinanceClient::printResultAndHeadersInConsole(void){
if(res==200) {
//--- успешная загрузка
PrintFormat("запрос успешен, размер %d байт.",ArraySize(this.result));
PrintFormat("%s", CharArrayToString(this.result) );
PrintFormat("Заголовки сервера: %s",this.resultHeaders);
}
}
void CBinanceClient::printResponceStatusInConsole(void){
PrintFormat("responce status: %d.", this.res);
}
string CBinanceClient::getResultAsString(){
return CharArrayToString(this.result);
}
/*
example of rest api web request
string jsonStr = "[
[1616362800000,\"0.03114900\",\"0.03114900\",\"0.03112200\",\"0.03112200\",\"321.55400000\",1616362859999,\"10.01102136\",357,\"104.64600000\",\"3.25810478\",\"0\"],
[1616362860000,\"0.03112200\",\"0.03113500\",\"0.03109300\",\"0.03112500\",\"244.69400000\",1616362919999,\"7.61258577\",365,\"29.91200000\",\"0.93052366\",\"0\"]
]";
https://api.binance.com/api/v3/klines?symbol=ETHBTC&interval=1m&endTime=1616362860000&limit=2
*/
int CBinanceClient::requestQuotes(string binanceSymbolName, long startTime, long endTime, int limits, MqlRates &rates[]){
double d;
long l;
string cookie=NULL;
char post[];
string strSymbol, strStartTime, strEndTime, strLimits;
strSymbol = "?symbol=" + binanceSymbolName;
if( startTime!=NULL){ strStartTime = "&startTime=" + (string)startTime; }
else{ strStartTime =""; }
if( endTime!=NULL){ strEndTime = "&endTime=" + (string)endTime; }
else{ strEndTime =""; }
if( limits!=NULL){ strLimits = "&limit=" + (string)limits; }
else{ strLimits =""; }
res = this.webRequest("GET","/api/v3/klines" + strSymbol + "&interval=1m" + strStartTime + strEndTime + strLimits, cookie,NULL,500,post, 0);
string jsonStr = this.getResultAsString();
CJsonParser parser(jsonStr);
parser.toNextArray();
int n = 0;
while( parser.toNextArray()!=-1){
ArrayResize(rates, n+1, 1000);
l = parser.getValueAsLong();
rates[n].time = l/1000;
parser.toNextComma();
d = parser.getValueAsDouble(8);
rates[n].open = d;
parser.toNextComma();
d = parser.getValueAsDouble(8);
rates[n].high = d;
parser.toNextComma();
d = parser.getValueAsDouble(8);
rates[n].low = d;
parser.toNextComma();
d = parser.getValueAsDouble(8);
rates[n].close = d;
parser.toNextComma();
d = parser.getValueAsDouble(8);
rates[n].real_volume = d;
parser.toNextComma();
l = parser.getValueAsLong();
parser.toNextComma();
d = parser.getValueAsDouble(8);
parser.toNextComma();
l = parser.getValueAsLong();
rates[n].tick_volume = l;
parser.toNextComma();
d = parser.getValueAsDouble(8);
parser.toNextComma();
d = parser.getValueAsDouble(8);
parser.toNextComma();
d = parser.getValueAsDouble(8);
rates[n].spread = 1; // set spread
n+=1;
}
ArrayResize(rates, n);
return this.res;
}
//+------------------------------------------------------------------+
class CCustomSymbol : public CObject {
public:
static void createCustomSymbol(string customSymbolName);
static long getCustomSymbolBarsCount(string customSymbolName);
static long getCustomSymbolBarRecentDate(string customSymbolName);
static long getCustomSymbolBarOldestDate(string customSymbolName);
};
// create custom symbol int metatrader5 if symbol with that name not exists yet
// with no data quotes downloaded.
// Data quotes must be downloaded separately
static void CCustomSymbol::createCustomSymbol(string customSymbolName){
bool isCustom = false;
bool isCreated = false;
bool isExist = false;
isExist = SymbolExist(customSymbolName, isCustom);
if(isExist==false || isCustom==false){
isCreated = CustomSymbolCreate(customSymbolName, "BINANCE", "EURUSD");
if(isCreated == false){ Print(GetLastError()); }
else {
CustomSymbolSetInteger(customSymbolName,SYMBOL_DIGITS,8);
}
}
}
// return number of all downloaded bars for current symbol
static long CCustomSymbol::getCustomSymbolBarsCount(string customSymbolName){
return SeriesInfoInteger(customSymbolName,PERIOD_M1,SERIES_BARS_COUNT);
}
// return long datetime number of milliseconds for custom symbol most recent bar
static long CCustomSymbol::getCustomSymbolBarRecentDate(string customSymbolName){
return SeriesInfoInteger(customSymbolName,PERIOD_M1,SERIES_LASTBAR_DATE)*1000;
}
// return long datetime number of milliseconds for custom symbol most old bar
static long CCustomSymbol::getCustomSymbolBarOldestDate(string customSymbolName){
return SeriesInfoInteger(customSymbolName,PERIOD_M1,SERIES_FIRSTDATE)*1000;
}
//+------------------------------------------------------------------+
class CJsonParser : public CObject {
protected:
string json;
int currPointer;
public:
CJsonParser(string str);
~CJsonParser(void);
int CJsonParser::toNextArray(); // move to next '[' return -1 no more
int CJsonParser::toEndOfArray(); // move to next ']' return -1 no more
int CJsonParser::toNextObject(); // move to next '{' return -1 no more
int CJsonParser::toEndOfObject(); // move to next '}' return -1 no more
int CJsonParser::toKey(string key); // move to string key "key" : "value" return -1 no more
int CJsonParser::toNextComma(); // move to next ',' return -1 no more
string CJsonParser::getValueAsString(string key); // find key and read value
double CJsonParser::getValueAsDouble(string key, int digits); // find key and read value
long CJsonParser::getValueAsLong(string key); // find key and read value
string CJsonParser::getValueAsString(); //read value where no key
double CJsonParser::getValueAsDouble(int digits); //read value where no key
long CJsonParser::getValueAsLong(); //read value where no key
void CJsonParser::printCurrentPointerChar();
};
CJsonParser::CJsonParser(string str) {
this.json = str;
this.currPointer = 0;
}
CJsonParser::~CJsonParser(void) {
}
int CJsonParser::toNextArray(){
int pos = StringFind(this.json, "[", this.currPointer);
if( pos!=-1){
this.currPointer = pos+1;
}
return pos;
}
int CJsonParser::toEndOfArray(){
int pos = StringFind(this.json, "]", this.currPointer);
if( pos!=-1){
this.currPointer = pos+1;
}
return pos;
}
int CJsonParser::toNextObject(){
int pos = StringFind(this.json, "{", this.currPointer);
if( pos!=-1){
this.currPointer = pos+1;
}
return pos;
}
int CJsonParser::toEndOfObject(){
int pos = StringFind(this.json, "}", this.currPointer);
if( pos!=-1){
this.currPointer = pos+1;
}
return pos;
}
int CJsonParser::toKey(string key){
int pos = StringFind(this.json, key, this.currPointer);
if( pos!=-1){
this.currPointer = pos;
}
return pos;
}
int CJsonParser::toNextComma(){
int pos = StringFind(this.json, ",", this.currPointer);
if( pos!=-1){
this.currPointer = pos+1;
}
return pos;
}
string CJsonParser::getValueAsString(string key){
int pos;
int posKey=-1;
int posDelimiter=-1;
int posValueBegin=-1;
int posValueEnd=-1;
string value = NULL;
pos = StringFind(this.json, key, this.currPointer);
if( pos!=-1){
posKey = pos;
}else return value;
pos = StringFind(this.json, ":", posKey);
if( pos!=-1){
posDelimiter = pos;
}else return value;
pos = StringFind(this.json, "\"", posDelimiter);
if( pos!=-1){
posValueBegin = pos+1;
}else return value;
pos = StringFind(this.json, "\"", posValueBegin);
if( pos!=-1){
posValueEnd = pos;
}else return value;
value = StringSubstr(this.json, posValueBegin, posValueEnd-posValueBegin);
return value;
}
long CJsonParser::getValueAsLong(string key){
int pos;
int posKey=-1;
int posDelimiter=-1;
int posValueBegin=-1;
int posValueEnd=-1;
int value = NULL;
string strValue = NULL;
pos = StringFind(this.json, key, this.currPointer);
if( pos!=-1){
posKey = pos;
}else return value;
pos = StringFind(this.json, ":", posKey);
if( pos!=-1){
posDelimiter = pos;
}else return value;
strValue = StringSubstr(this.json, posValueBegin, 20);
return (long)NormalizeDouble(StringToDouble(strValue), 0);
}
double CJsonParser::getValueAsDouble(string key, int digits){
string value = this.getValueAsString(key);
if(value!=NULL){
return NormalizeDouble(StringToDouble(value), digits);
}else return NULL;
}
string CJsonParser::getValueAsString(){
int pos;
int posValueBegin=-1;
int posValueEnd=-1;
string strValue = NULL;
pos = StringFind(this.json, "\"", this.currPointer);
if( pos!=-1){
posValueBegin = pos+1;
}else return strValue;
pos = StringFind(this.json, "\"", posValueBegin);
if( pos!=-1){
posValueEnd = pos;
}else return strValue;
strValue = StringSubstr(this.json, posValueBegin, posValueEnd-posValueBegin);
return strValue;
}
double CJsonParser::getValueAsDouble(int digits){
string value = NULL;
value = this.getValueAsString();
if(value!=NULL){
return NormalizeDouble(StringToDouble(value), digits);
}else return NULL;
}
long CJsonParser::getValueAsLong(){
string strValue = NULL;
strValue = StringSubstr(this.json, this.currPointer, 20);
if(strValue!=NULL){
return (long)NormalizeDouble(StringToDouble(strValue), 0);
}else return NULL;
}
void CJsonParser::printCurrentPointerChar(){
string currPointerStr = StringSubstr(this.json, this.currPointer, 20);
Print(currPointerStr);
}