Integrando o terminal do cliente do MetaTrader 4 com o MS SQL SERVER
Introdução
O uso de integrações com outros produtos oferece um desafio adicional no trading.
Pode haver muitos usos do mesmo, então darei alguns deles abaixo.
Você pode coletar ticks e passá-los para análise mais abrangente no MS SQL SERVER. Tendo um histórico grande de ticks você pode coletar qualquer período começando do menor período até períodos não padronizados. Tendo cotações de ticks reais, você pode depurar estratégias dependentes de dados de tick conhecidas como 'scalpers'.
Você pode usar uma loja para uma análise rápida de dados tirados de outros aplicativos, por exemplo, do MS Excel ou outro software de terceiros ou dos seus próprios produtos.
Por exemplo, você pode descarregar todo o histórico do seu centro de histórico do terminal para o MS SQL. Então, você não vai precisar armazenar o histórico no MT4. Isso vai ajudar a aliviar a memória do terminal.
Você pode calcular as redes neurais usando cotações armazenadas no MS SQL SERVER: por exemplo, STATISTICA - 7.8 permite você baixar cotações do SQL e podem ser resolvidas em tempo real transmitindo os sinais de rede para o MT4.
Você pode desenvolver seu próprio programa em outra língua e para outro símbolo e transmitir sinais usando o servidor do MS SQL, tendo sobrado apenas funções de execução para o terminal do cliente e o livrando de sérios cálculos.
Os seguintes produtos de software foram usados para esse projeto
- MS SQL SERVER 2000 Developer - BASE
- VISUAL C++ 6.0 SP5 - para criar DLL "YZMSSQLExpertSample.dll"
- MDAC 7
A configuração mínima que precisa estar instalada:
1 MS SQL SERVER 2000 Developer
2 MDAC 7
Depurei o programa usando MDAC 7. Entretanto, é possível que tudo funcione bem em algumas versões antigas. Se você não for compilar o DLL, você não precisa instalar ou ter o Visual C++ 6.0 instalado. Você pode usar um DLL pronto. Entretanto, fiz uma conexão física com o nome de usuário, o nome do DSN e as conexões. Então, você terá que repetir tudo o que foi listado acima na sua versão do programa. Não vou descrever aqui como instalar o MS SQL SERVER ou o Visual C++ 6.0. Esses assuntos estão fora do escopo do artigo específico.
Depois que os produtos de software necessários foram instalados, devemos criar um DSN:
dsn=MT4_SQL_BASE;", "yuraz", "qwerty"
Exemplo de recebimento de tick no MS SQL
Todos os experimentos foram conduzidos com o MS SQL SERVER 2000 Developer. No Visual C++ 6.0, o YZMSSQLExpertSample.DLL foi criado usando o método de acesso ao MS SQL via ADO. O MDAC 7 ou MDAC 8 deve estar instalado. Somente descreverei os exemplos de como criar procedimentos e tabelas. A configuração mínima para o que temos que criar no MS SQL é a base, tabelas e procedimentos. Vamos considerar a tabela e os procedimentos de trabalho com cotações de tick. Você pode adicionar algumas outras funções, se quiser.
É necessário criar uma base e tabelas no MS SQL. Criei uma base nova chamada MT4TRADE. Então, devemos criar tabelas nela:
MT4TICK - Tabela de Ticks
//----------------------------------------------------------------------------------- // // Structure of MT4TICK Base // // idc - formed automatically, unique number of record // ServerDateTime - Filled out automatically, when adding a record // Server local time - time when the quote was placed in the table // (it doesn't have anything in common with the date and time passed by MT4) // it is the time counted by the server itself - it will be the same as the time // of the machine, on which the server has been launched. //--- // iDateTime - date and time in MT4 format, passed from MT4 // sSymbol - symbol // cAsk - quote Ask // cBid - quote Bid // CREATE TABLE [dbo].[MT4TICK] ( [idc] [bigint] IDENTITY (1, 1) NOT NULL , [ServerDateTime] [datetime] NULL , [iDateTime] [bigint] NULL , [sSymbol] [char] (6) COLLATE SQL_Latin1_General_CP1251_CI_AS NULL , [cAsk] [numeric](18, 4) NULL , [cBid] [numeric](18, 4) NULL ) ON [PRIMARY] GO --- Include automated filling out the ServerDateTime field with the date and time of server MS SQL ALTER TABLE [dbo].[MT4TICK] ADD CONSTRAINT [DF_MT4TICK_ServerDateTime] DEFAULT (getdate()) FOR [ServerDateTime] GO
Abaixo está como o recebimento de tick e o procedimento de tabulação aparecem:
// // @RetCode int out --- used for returning // ,@psSymbol char(6) --- symbol // ,@piDateTime bigint --- date and time of tick arrival // ,@pdAsk float --- Ask // ,@pdBid float --- Bid // // The procedure just returns 0 // if we analyze the code of returning to MQL4, we can see that the quote has reached the procedure and has been tabulated // // CREATE PROCEDURE dbo.YZ_MT4_TICK @RetCode int out ,@psSymbol char(6) ,@piDateTime bigint ,@pdAsk float ,@pdBid float AS insert into MT4TICK ( sSymbol, iDateTime, cAsk, cBid ) values ( @psSymbol , @piDateTime, @pdAsk , @pdBid ) select @RetCode=0 return @RetCode
Podemos ver pela descrição acima quais procedimentos e para quais propósitos são usados.
@RetCode - não suporta qualquer funcionalidade quando está sendo transmitido do DLL, serve para recebimento do código de terminação apenas.
A configuração do MS SQL SERVER terminou. Um script para criar uma configuração padrão anexada a esse artigo.
Vamos fantasiar: Possíveis soluções e adicionais
Podemos criar um armazenamento de dados e colocar/extrair informação dele. Dessa forma, podemos livrar o terminal do cliente do MT4 da necessidade de armazenar históricos de cotações. Agora, o histórico de cotações é armazenado no servidor do MS SQL e podemos operar com essa informação, extraí-la mais cedo e exportá-la para outros aplicativos. Podemos usar os dados para ser analisados em pacotes NEURAL, a maioria deles pode trabalhar com armazenamentos no SQL.
Em tempo real, o terminal pode continuar formando sinais a partir dos indicadores, transmitindo-os para o armazenamento e fixando-os dessa forma. Um aplicativo externo pode extrair o sinal e o histórico em tempo real, analisá-los e formar sinais fixando a execução e o armazenamento do registro no servidor MS SQL, e enviá-los para o terminal executar.
Assim, podemos obter integração e distribuição funcional entre os aplicativos envolvidos no complexo de trading automatizado.
Bem, se não há mais necessidade de armazenar cotações de histórico, podemos configurá-lo da seguinte forma. Configure as barras mínimas em Tools(Ferramentas)>Options(Opções)>Charts (Gráficos), por exemplo, para 5000. O terminal começa a funcionar mais rápido uma vez que não precisa compartilhar memória para um histórico grande.
Textos fonte
Código DLL:
//+------------------------------------------------------------------+ //| Sample DLL for MQL4 | //| Copyright c 2004-2008, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ // // YURAZ 2008 YZMSSQLExpertSample // // Example DLL Integrating MT4 with MS SQL 2000 // // ADO MS SQL SERVER // // software used // // VISUAL C++ 6 , SP5 , MDAC 7 , MS SQL2000 + SP4 // //+------------------------------------------------------------------+ #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include <windows.h> #include <stdlib.h> #include <stdio.h> //---- #define MT4_EXPFUNC __declspec(dllexport) //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #pragma pack(push,1) struct RateInfo { unsigned int ctm; double open; double low; double high; double close; double vol; double vol1; double vol2; double vol3; double vol4; double vol5; }; #pragma pack(pop) struct MqlStr { int len; char *string; }; static int CompareMqlStr(const void *left,const void *right); static int SQLexecProcedure( char *nprc ); static int SQLexecProcedureSignal( char *sSymbol, char* sProcedure ); // static int _YZSQLsqlstrinsql( char *Symbol , unsigned int DateTime , double Ask, double Bid, char *NamePrc ); static int _YZSQLprocedure ( char *sSymbol, unsigned int pDateTime, double Ask, double Bid, char *NamePrc ); static int _YZSQLprocedureHISTORYPut(char *Symbol,unsigned int Period, unsigned int DateTime,double Open, double High,double Low, double Close ,double Volume, unsigned int Bar ,char *Procedure); //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) { //---- switch(ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } //---- return(TRUE); } // place ticks in MS SQL // call the procedure as an SQL line passing parameters "exec YZ_MT4_TICK ?,?,?,?" /* MT4_EXPFUNC int __stdcall SQLsqlstringTickPut(char *Symbol,unsigned int DateTime,double Ask,double Bid,char *sSQLstring) { int ccc = _YZSQLsqlstrinsql( Symbol , DateTime , Ask , Bid , sSQLstring ); return(ccc); } */ // call as a procedure passing parameters MT4_EXPFUNC int __stdcall SQLProcedureTickPut(char *Symbol,unsigned int DateTime,double Ask,double Bid,char *Procedure) { int ccc = _YZSQLprocedure( Symbol , DateTime , Ask , Bid ,Procedure ); return(ccc); } // place a specific candlestick in MS SQL history MT4_EXPFUNC int __stdcall SQLProcedureHistoryPut(char *Symbol,unsigned int Period , unsigned int DateTime, double Open,double High,double Low, double Close ,double Volume,unsigned int Bar ,char *Procedure) { int ccc = _YZSQLprocedureHISTORYPut(Symbol,Period,DateTime,Open,High,Low,Close,Volume,Bar,Procedure); return(ccc); } // call procedure sProcedure // // return -1 error // MT4_EXPFUNC int __stdcall SQLProcedureGetInt(char *sProcedure) { int Ret = SQLexecProcedure( sProcedure ); return((int)Ret); } MT4_EXPFUNC int __stdcall SQLProcedureGetSignal (char *sSymbol, char *sProcedure) { int Ret = SQLexecProcedureSignal( sSymbol, sProcedure ); return((int)Ret); } ////////////////////////////////// #include "stdafx.h" #include <stdio.h> #import "C:\Program Files\Common Files\System\ado\msado20.tlb" \ rename("EOF","ADOEOF") rename("BOF","ADOBOF") using namespace ADODB; inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); }; // procedure call method int _YZSQLprocedure( char *sSymbol, unsigned int pDateTime, double Ask, double Bid, char *NamePrc ) { HRESULT hr = S_OK; _CommandPtr pCmd = NULL; _ConnectionPtr pConnection = NULL; _bstr_t strMessage, strAuthorID; ::CoInitialize(NULL); long codRet = -1; try { _ParameterPtr Par1; _ParameterPtr Par2; _ParameterPtr Par3; _ParameterPtr Par4; _ParameterPtr Par5; TESTHR(pConnection.CreateInstance(__uuidof(Connection))); hr = pConnection->Open("dsn=MT4_SQL_BASE;", "yuraz", "qwerty", adConnectUnspecified); pConnection->CursorLocation = adUseClient; TESTHR(pCmd.CreateInstance(__uuidof(Command))); pCmd->CommandText = NamePrc; // procedure name pCmd->CommandType = adCmdStoredProc; Par1 = pCmd->CreateParameter( _bstr_t("@P1"), adInteger, adParamOutput,0, codRet ); pCmd->Parameters->Append( Par1 ); Par1 = pCmd->CreateParameter("@psSymbol",adChar, adParamInput, strlen(sSymbol) ,sSymbol ); pCmd->Parameters->Append(Par1); Par2 = pCmd->CreateParameter("@piDateTime", adDouble , adParamInput, sizeof(double) , (double)pDateTime ); pCmd->Parameters->Append(Par2); Par3 = pCmd->CreateParameter("@pdAsk", adDouble, adParamInput, 4, Ask ); pCmd->Parameters->Append(Par3); Par4 = pCmd->CreateParameter("@pdBid", adDouble, adParamInput, 4, Bid ); pCmd->Parameters->Append(Par4); pCmd->ActiveConnection = pConnection; int hr = pCmd->Execute( 0, 0, adCmdStoredProc ); if( FAILED(hr) ) { codRet = -1; } else { Par1 = pCmd->Parameters->GetItem(_bstr_t("@P1")); // obtain from the procedure codRet = Par1->GetValue(); } } catch(_com_error ) { // // if necessary, process the execution error // codRet = -1; } if (pConnection) if (pConnection->State == adStateOpen) pConnection->Close(); ::CoUninitialize(); return((int)codRet); } // place in history Symbol , Period . DateTime, Open , High , Low , Close , Value , Bar int _YZSQLprocedureHISTORYPut(char *pSymbol,unsigned int pPeriod, unsigned int pDateTime,double pOpen,double pHigh, double pLow, double pClose ,double pVolume, unsigned int pBar ,char *pProcedure ) { HRESULT hr = S_OK; _CommandPtr pCmd = NULL; _ConnectionPtr pConnection = NULL; _bstr_t strMessage, strAuthorID; ::CoInitialize(NULL); long codRet = -1; try { _ParameterPtr ParReturn; // _ParameterPtr Par1; // SYMBOL _ParameterPtr Par2; // PERIOD _ParameterPtr Par3; // DATETIME _ParameterPtr Par4; // OPEN _ParameterPtr Par5; // HIGH _ParameterPtr Par6; // LOW _ParameterPtr Par7; // CLOSE _ParameterPtr Par8; // VOLUME _ParameterPtr Par9; // BAR TESTHR(pConnection.CreateInstance(__uuidof(Connection))); hr = pConnection->Open("dsn=MT4_SQL_BASE;", "yuraz", "qwerty", adConnectUnspecified); pConnection->CursorLocation = adUseClient; TESTHR(pCmd.CreateInstance(__uuidof(Command))); pCmd->CommandText = pProcedure; // procedure name pCmd->CommandType = adCmdStoredProc; ParReturn = pCmd->CreateParameter( _bstr_t("@P1"), adInteger, adParamOutput,0, codRet ); pCmd->Parameters->Append( ParReturn ); Par1 = pCmd->CreateParameter("@psSymbol",adChar, adParamInput, strlen(pSymbol) ,pSymbol ); pCmd->Parameters->Append(Par1); Par2 = pCmd->CreateParameter("@piDateTime", adDouble , adParamInput, sizeof(double) , (double)pPeriod ); pCmd->Parameters->Append(Par2); Par3 = pCmd->CreateParameter("@piDateTime", adDouble , adParamInput, sizeof(double) , (double)pDateTime ); pCmd->Parameters->Append(Par3); Par4 = pCmd->CreateParameter("@pdOpen", adDouble, adParamInput, 4, pOpen ); pCmd->Parameters->Append(Par4); Par5 = pCmd->CreateParameter("@pdHigh", adDouble, adParamInput, 4, pHigh ); pCmd->Parameters->Append(Par5); Par6 = pCmd->CreateParameter("@pdLow", adDouble, adParamInput, 4, pLow ); pCmd->Parameters->Append(Par6); Par7 = pCmd->CreateParameter("@pdClose", adDouble, adParamInput, 4, pClose ); pCmd->Parameters->Append(Par7); Par8 = pCmd->CreateParameter("@pdVolume", adDouble, adParamInput, 4, pVolume ); pCmd->Parameters->Append(Par8); Par9 = pCmd->CreateParameter("@piBar", adDouble , adParamInput, sizeof(double) , (double)pBar ); pCmd->Parameters->Append(Par9); pCmd->ActiveConnection = pConnection; int hr = pCmd->Execute( 0, 0, adCmdStoredProc ); if( FAILED(hr) ) { codRet = -1; } else { ParReturn = pCmd->Parameters->GetItem(_bstr_t("@P1")); // obtain from the procedure codRet = ParReturn->GetValue(); } } catch(_com_error ) { // // if necessary, process the execution error // codRet = -1; } if (pConnection) if (pConnection->State == adStateOpen) pConnection->Close(); ::CoUninitialize(); return((int)codRet); } // // return the value returned by the procedure // int SQLexecProcedure( char *nprc ) { HRESULT hr = S_OK; _CommandPtr pcmd = NULL; _ConnectionPtr pConnection = NULL; _bstr_t strMessage, strAuthorID; ::CoInitialize(NULL); long codRet = -1; try { TESTHR(pConnection.CreateInstance(__uuidof(Connection))); hr = pConnection->Open("dsn=MT4_SQL_BASE;", "yuraz", "qwerty", adConnectUnspecified); pConnection->CursorLocation = adUseClient; TESTHR(pcmd.CreateInstance(__uuidof(Command))); pcmd->CommandText = nprc; // procedure name pcmd->CommandType = adCmdStoredProc; _ParameterPtr pParm1 = pcmd->CreateParameter( _bstr_t("@P1"), adInteger, adParamOutput,0, codRet ); pcmd->Parameters->Append( pParm1 ); pcmd->ActiveConnection = pConnection; int hr = pcmd->Execute( 0, 0, adCmdStoredProc ); if( FAILED(hr) ) { codRet = -1; } else { pParm1 = pcmd->Parameters->GetItem(_bstr_t("@P1")); // obtain from the procedure codRet = pParm1->GetValue(); } } catch(_com_error ) { // // if necessary, process the execution error // codRet = -1; } if (pConnection) if (pConnection->State == adStateOpen) pConnection->Close(); ::CoUninitialize(); return((int)codRet); } // // // int SQLexecProcedureSignal( char *sSymbol, char* sProcedure ) { HRESULT hr = S_OK; _CommandPtr pcmd = NULL; _ConnectionPtr pConnection = NULL; _bstr_t strMessage; _bstr_t strAuthorID; ::CoInitialize(NULL); long codRet = 0; try { TESTHR(pConnection.CreateInstance(__uuidof(Connection))); hr = pConnection->Open("dsn=MT4_SQL_BASE;", "yuraz", "qwerty", adConnectUnspecified); pConnection->CursorLocation = adUseClient; TESTHR(pcmd.CreateInstance(__uuidof(Command))); pcmd->CommandText = sProcedure; // procedure name pcmd->CommandType = adCmdStoredProc; _ParameterPtr pParm1 = pcmd->CreateParameter("@psSymbol",adChar, adParamInput, strlen(sSymbol) ,sSymbol ); pcmd->Parameters->Append(pParm1); _ParameterPtr pParm2 = pcmd->CreateParameter( _bstr_t("@P1"), adInteger, adParamOutput,0, codRet ); pcmd->Parameters->Append( pParm2 ); pcmd->ActiveConnection = pConnection; int hr = pcmd->Execute( 0, 0, adCmdStoredProc ); if( FAILED(hr) ) { bool bSuccess = false; } pParm2 = pcmd->Parameters->GetItem(_bstr_t("@P1")); // obtain from the procedure codRet = pParm2->GetValue(); // printf("\n [%d] \n",codRet ); // OBTAINING from the procedure } catch(_com_error ) { // // if necessary, process the execution error // } if (pConnection) if (pConnection->State == adStateOpen) pConnection->Close(); ::CoUninitialize(); return((int)codRet); }
Exemplo de ligação de MQL4 -
// Comments are reduced to make it appear simpler, the comments in the attached files are complete //+------------------------------------------------------------------+ //| | //| Copyright c 1999-2006, MetaQuotes Software Corp. | //| http://www.metaquotes.ru | //| YZMSSQLSample.mq4 | //| Yuriy Zaitsev | //+------------------------------------------------------------------+ // Example of integrating with MS SQL | //+------------------------------------------------------------------+ #property copyright "YURAZ Copyright(C) 2008" #property link "yzy @ mail.ru" //+------------------------------------------------------------------+ // DLL function library //+------------------------------------------------------------------+ #import "YZMSSQLExpertSample.dll" // Performing any actions on MS SQL Server, procedure is called SQLProcedureGetInt // Collecting ticks int SQLProcedureTickPut( string, int , double , double ,string ); int Prc = 0; int init() { // // SQLProcedureGetInt The function, once having called a certain procedure, // will return into MT4 int value, for example, parameters // stored on MS SQL server, formed by another software // Prc = SQLProcedureGetInt ("YZ_MT4_T1"); return(0); } int start() { int a; int RetCode = SQLProcedureTickPut( Symbol(), TimeCurrent() , Ask, Bid ,"YZ_MT4_TICK"); // call to the tick collecting procedure Print(" SQLProcedureTickPut (YZ_MT4_NEWDAY)"+ RetCode ); // Example: // on MS SQL server, you can filter signals formed using third-party software // neural networks // other software products // /* int Signal = SQLProcedureGetSignal (Symbol() , "YZ_MT4_SIGNAL" ); // procedure MS SQL , will return signal Print(" SQLProcedureGetSignal (Symbol() , YZ_MT4_SIGNAL )"+ Signal ); if ( Signal == OP_BUY ) { // the procedure has returned the signal and is recommending to buy } if ( Signal == OP_SELL ) { // the procedure has returned the signal and is recommending to sell } */ return(0); }
Script carregando histórico para o servidor MS SQL:
// // YURAZ 2008 yzh @ mail.ru // // script loading history onto MS SQL // reload all history for all currency pairs and for all TIMEFRAMES // in MS SQL // #import "YZMSSQLExpertSample.dll" int SQLProcedureHistoryPut( string, int , int, double , double ,double , double ,double ,int, string ); static int mPeriod[8]={PERIOD_M1,PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1,PERIOD_W1,PERIOD_MN1}; void start() { PutHistor("EURUSD"); PutHistor("USDCHF"); Comment(" LOADING COMPLETE " ); } void PutHistor(string sSymbol) { for ( int iPeriod = 0; iPeriod <= 8 ; iPeriod++ ) { int pPERIOD_XX = mPeriod[iPeriod]; int Bar = iBars(sSymbol,pPERIOD_XX ); // obtain the depth of history for the given timeframe // no progress bar organized for ( int iBar = Bar; iBar >= 0 ; iBar--) { Comment( "WAIT TIMEFRAME "+pPERIOD_XX+" SYMBOL "+sSymbol+" BARS "+iBar ); double o = iOpen (sSymbol,pPERIOD_XX,iBar); double h = iHigh (sSymbol,pPERIOD_XX,iBar); double l = iLow (sSymbol,pPERIOD_XX,iBar); double c = iClose (sSymbol,pPERIOD_XX,iBar); double v = iVolume(sSymbol,pPERIOD_XX,iBar); datetime d = iTime (sSymbol,pPERIOD_XX,iBar); int RetCode = SQLProcedureHistoryPut( sSymbol,pPERIOD_XX,d,o,h,l,c,v,iBar, "YZ_MT4_HISTORY"); // Print ( " YZ_MT4_HITSRY "+RetCode); } } }
Atenção: Infelizmente, todo o histórico é carregado lentamente usando o script, mas fixa o número da barra claramente e com alta qualidade.
A melhor solução seria descarregar as cotações em arquivos texto e carregá-las no MS SQL através do IMPRT EXPORT DTS. Carregar o histórico M1 de 1999-2008 para cada símbolo vai levar alguns minutos.
O índice da barra não é descarregado ao descarregar para um arquivo texto. Se você decidir que o índice da barra será apenas o número de linha, você terá o problema de barras perdidas, se modificar ou recarregar, os números das barras não descarregadas podem ser diferentes no MS SQL e no MT4. Ainda não resolvi esse problema, mas suponho que pode ser resolvido através de recarregamento do histórico depois de uma atualização de alta qualidade do histórico no próprio MT4.
Descrição dos arquivos em anexo
CreateSQLallDate.txt (9.0 Kb)- Script no formato SQL como exemplo de como criar bases, tabelas, procedimentos no servidor MS SQL Server.
SQLGETHISTORY.mq4 (1.4 Kb)- Script para carregar o histórico no MS SQL
YZMSSQLExpertSample.rar (89.9 Kb)
Projeto DLL
Para serem anexados aos gráficos de símbolo como EA, os ticks que devem ser coletados, você pode anexá-los a qualquer timeframe.
Conclusão
A integração com outros produtos de software vai expandir a funcionalidade do MetaTrader 4 e permitir a distribuição de tarefas e funções de um sistema de trading automatizado com mais eficiência.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1533
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso