English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Utilizzo di WinInet in MQL5.  Parte 2:  Richieste e file POST

Utilizzo di WinInet in MQL5. Parte 2: Richieste e file POST

MetaTrader 5Esempi | 11 gennaio 2022, 15:08
231 0
---
---


Introduzione

Nella lezione precedente, "Using WinInet.dll for Data Exchange between Terminals via the Internet", abbiamo imparato a lavorare con la libreria, aprire pagine web, inviare e ricevere informazioni tramite richieste GET.

In questa lezione impareremo a:

  • creare e inviare semplici richieste POST a un server;
  • inviare file a un server utilizzando il metodo di rappresentazione multipart/form-data;
  • lavorare con i cookie e leggere le informazioni dai siti web utilizzando il tuo nome utente.

Come in precedenza, consiglio vivamente di configurare un server proxy locale Charles; sarà necessario per il tuo studio e ulteriori esperimenti.


Richieste POST

Per inviare informazioni, avremo bisogno di quelle funzioni wininet.dll e della classe creata CMqlNet che sono state descritte in dettaglio nell'articolo precedente.

A causa dell'elevato numero di campi nei metodi CMqlNet::Request, abbiamo dovuto creare una struttura separata tagRequest che contiene tutti i campi richiesti per una richiesta. 

//------------------------------------------------------------------ struct tagRequest
struct tagRequest
{
  string stVerb;   // method of the request GET/POST/…
  string stObject; // path to an instance of request, for example "/index.htm" или "/get.php?a=1"  
  string stHead;   // request header
  string stData;   // addition string of data
  bool fromFile;   // if =true, then stData designates the name of a data file
  string stOut;    // string for receiving an answer
  bool toFile;     // if =true, then stOut designates the name of a file for receiving an answer

  void Init(string aVerb, string aObject, string aHead, 
            string aData, bool from, string aOut, bool to); // function of initialization of all fields
};
//------------------------------------------------------------------ Init
void tagRequest::Init(string aVerb, string aObject, string aHead, 
                      string aData, bool from, string aOut, bool to)
{
  stVerb=aVerb;     // method of the request GET/POST/…
  stObject=aObject; // path to the page "/get.php?a=1" or "/index.htm"
  stHead=aHead;     // request header, for example "Content-Type: application/x-www-form-urlencoded"
  stData=aData;     // addition string of data
  fromFile=from;    // if =true, the stData designates the name of a data file
  stOut=aOut;       // field for receiving an answer
  toFile=to;        // if =true, then stOut designates the name of a file for receiving an answer
}

Inoltre, dobbiamo sostituire l'intestazione del metodo CMqlNet::Request con una più corta:

//+------------------------------------------------------------------+
bool MqlNet::Request(tagRequest &req)
  {
   if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED))
     {
      Print("-DLL not allowed"); return(false);
     }
//--- checking whether DLLs are allowed in the terminal
   if(!MQL5InfoInteger(MQL5_DLLS_ALLOWED))
     {
      Print("-DLL not allowed");
      return(false);
     }
//--- checking whether DLLs are allowed in the terminal
   if(req.toFile && req.stOut=="")
     {
      Print("-File not specified ");
      return(false);
     }
   uchar data[]; 
    int hRequest,hSend;
   string Vers="HTTP/1.1"; 
    string nill="";

//--- read file to array
   if(req.fromFile)
     {
      if(FileToArray(req.stData,data)<0)
        {
         Print("-Err reading file "+req.stData);
         return(false);
        }
     }
   else StringToCharArray(req.stData,data);

   if(hSession<=0 || hConnect<=0)
     {
      Close();
      if(!Open(Host,Port,User,Pass,Service))
        {
         Print("-Err Connect");
         Close();
         return(false);
        }
     }
//--- creating descriptor of the request
   hRequest=HttpOpenRequestW(hConnect,req.stVerb,req.stObject,Vers,nill,0,
   INTERNET_FLAG_KEEP_CONNECTION|INTERNET_FLAG_RELOAD|INTERNET_FLAG_PRAGMA_NOCACHE,0);
   if(hRequest<=0)
     {
      Print("-Err OpenRequest");
      InternetCloseHandle(hConnect);
      return(false);
     }
//--- sending the request
   hSend=HttpSendRequestW(hRequest,req.stHead,StringLen(req.stHead),data,ArraySize(data));
//--- sending the file
   if(hSend<=0)
     {
      int err=0;
      err=GetLastError(err);
      Print("-Err SendRequest= ",err);
     }
//--- reading the page
   if(hSend>0) ReadPage(hRequest,req.stOut,req.toFile);
//--- closing all handles
   InternetCloseHandle(hRequest); InternetCloseHandle(hSend);

   if(hSend<=0)
     {
      Close();
      return(false);
     }
   return(true);
  }

Ora iniziamo a lavorare.


Invio di dati a un sito web del tipo "application/x-www-form-urlencoded"

Nella lezione precedente abbiamo analizzato l'esempio del MetaArbitrage (monitoraggio delle quotazioni).

Ricordiamo che l'EA invia i prezzi Bid del suo simbolo utilizzando una richiesta GET; come risposta riceve i prezzi di altri broker che vengono inviati allo stesso modo al server da altri terminali.

Per modificare una richiesta GET in una richiesta POST è sufficiente "nascondere" la riga di richiesta stessa nel corpo della richiesta che segue la sua intestazione.

BOOL HttpSendRequest(
  __in  HINTERNET hRequest,
  __in LPCTSTR lpszHeaders,
  __in DWORD dwHeadersLength,
  __in  LPVOID lpOptional,
  __in DWORD dwOptionalLength
);

  • hRequest [in]
    Handle restituito da HttpOpenRequest.
  • lpszHeaders [in]
    Puntatore a una riga contenente le intestazioni da aggiungere alla richiesta. Questo parametro potrebbe essere vuoto.
  • dwHeadersLength [in]
    Dimensione dell'intestazione in byte.
  • lpOptional [in]
    Puntatore a un array con dati uchar che viene inviato subito dopo l'intestazione. In genere, questo parametro viene utilizzato per le operazioni POST e PUT.
  • dwOptionalLength [in]
    Dimensione dei dati in byte. Il parametro può essere = 0; significa che non viene inviata alcuna informazione aggiuntiva.

Dalla descrizione della funzione, possiamo capire che i dati vengono inviati come un byte uchar-array (il quarto parametro della funzione). Questo è tutto ciò che dobbiamo sapere in questa fase.

Nell'esempio MetaArbitrage, la richiesta GET ha il seguente aspetto:

www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794


La richiesta stessa è evidenziata con il colore rosso. Quindi, se abbiamo bisogno di fare una richiesta POST, dovremmo spostare il suo testo nell'array di dati lpOptional.

Creiamo uno script chiamato MetaSwap, il quale invierà e riceverà informazioni sugli scambi di un simbolo. 

#include <InternetLib.mqh>

string Server[];        // array of server names
double Long[], Short[]; // array for swap information
MqlNet INet;           // class instance for working

//------------------------------------------------------------------ OnStart
void OnStart()
{
//--- opening a session
  if (!INet.Open("www.fxmaster.de", 80, "", "", INTERNET_SERVICE_HTTP)) return;
 
//--- zeroizing arrays
  ArrayResize(Server, 0); ArrayResize(Long, 0); ArrayResize(Short, 0);
//--- the file for writing an example of swap information
  string file=Symbol()+"_swap.csv";
//--- sending swaps
  if (!SendData(file, "GET")) 
  { 
    Print("-err RecieveSwap"); 
    return; 
  }
//--- read data from the received file
  if (!ReadSwap(file)) return; 
//--- refresh information about swaps on the chart
  UpdateInfo();               
}

Il funzionamento dello script è molto semplice.

Innanzitutto viene aperta la sessione Internet INet.Open. Quindi la funzione SendData invia informazioni sugli scambi del simbolo corrente. Quindi, se viene inviato con successo, gli swap ricevuti vengono letti utilizzando ReadSwap e visualizzati sul grafico utilizzando UpdateInfo.

In questo momento ci interessa solo la funzione SendData.

//------------------------------------------------------------------ SendData bool SendData(string file, string mode) {   string smb=Symbol();   string Head="Content-Type: application/x-www-form-urlencoded"; // header   string Path="/mt5swap/metaswap.php"; // path to the page   string Data="server="+AccountInfoString(ACCOUNT_SERVER)+               "&pair="+smb+               "&long="+DTS(SymbolInfoDouble(smb, SYMBOL_SWAP_LONG))+               "&short="+DTS(SymbolInfoDouble(smb, SYMBOL_SWAP_SHORT));   tagRequest req; // initialization of parameters   if (mode=="GET")  req.Init(mode, Path+"?"+Data, Head, "",   false, file, true);   if (mode=="POST") req.Init(mode, Path,          Head, Data, false, file, true);   return(INet.Request(req)); // sending request to the server }

In questo script vengono dimostrati due metodi di invio delle informazioni: utilizzando GET e POS per farti notare la differenza tra loro.

Descriviamo una per una le variabili della funzione:

  • Head - intestazione della richiesta che descrive il tipo dei suoi contenuti. In realtà, questa non è l'intera intestazione della richiesta. Gli altri campi dell'intestazione sono creati dalla libreria wininet.dll. Tuttavia, possono essere modificati utilizzando la funzione HttpAddRequestHeaders.
  • Percorso - questo è il percorso dell'istanza della richiesta relativamente al dominio iniziale www.fxmaster.de. In altre parole, è il percorso di uno script php che elaborerà la richiesta. A proposito, non è necessario richiedere solo uno script php. Può essere una normale pagina html (abbiamo anche provato a richiedere un file mq5 durante la nostra prima lezione).
  • Dati - queste sono le informazioni che vengono inviate al server. I dati vengono scritti secondo le regole di passaggio del parameter name=value. Il segno "&" viene utilizzato come separatore di dati.

E la cosa principale - prestare attenzione alla differenza tra fare richieste GET e POST in tagRequest::Init.

Nel metodo GET, il percorso viene inviato insieme al corpo della richiesta (unito dal segno "?") e il campo dati lpOptional (denominato stData nella struttura) viene lasciato vuoto.
Nel metodo POST
, il percorso esiste da solo e il corpo della richiesta viene spostato in lpOptional.

Come puoi vedere, la differenza non è significativa. Lo script del server metaswap.php che riceve la richiesta è allegato all'articolo.


Invio di dati "multipart/form-data"

In realtà, le richieste POST non sono analoghe alle richieste GET (altrimenti non sarebbero necessarie). Le richieste POST hanno un vantaggio significativo: utilizzandole, puoi inviare file con contenuto binario.

Si tratta di una richiesta di tipo urlencoded che è autorizzata a inviare un set limitato di simboli. In caso contrario, i simboli "non consentiti" verranno sostituiti con codici. Pertanto, quando si inviano dati binari, saranno distorti. Quindi non sei in grado di inviare nemmeno un piccolo file gif utilizzando una richiesta GET.

Per risolvere questo problema vengono elaborate regole speciali per descrivere una richiesta; consentono lo scambio con file binari oltre a quelli di testo.

Per raggiungere questo obiettivo, il corpo della richiesta è suddiviso in sezioni. La cosa principale è che ogni sezione può avere il proprio tipo di dati. Ad esempio, il primo è un testo, il successivo è un'immagine/jpeg, ecc. In altre parole, una richiesta inviata al server può contenere più tipi di dati contemporaneamente.

Diamo un'occhiata alla struttura di tale descrizione con l'esempio dei dati passati dallo script MetaSwap.

L'intestazione della richiesta Head avrà il seguente aspetto:

Tipo di contenuto: multipart/form-data; boundary=SEPARATOR\r\n


La parola chiave SEPARATOR – è un insieme casuale di simboli. Tuttavia, questo è ciò che devi osservare per essere al di fuori dei dati della richiesta. In altre parole, questa linea deve essere unica - alcuni abracadabra come hdsJK263shxaDFHLsdhsDdjf9 o qualsiasi altra cosa ti venga in mente :). In PHP, tale linea viene formata utilizzando il codice MD5 di un tempo corrente.

La richiesta POST stessa si presenta come segue (per una più facile comprensione, i campi sono evidenziati secondo il significato generale):

\r\n
--SEPARATOR\r\n

Content-Disposition: form-data; name="Server"\r\n
\r\n
MetaQuotes-Demo

\r\n
--SEPARATOR\r\n

Content-Disposition: form-data; name="Pair"\r\n
\r\n
EURUSD

\r\n
--SEPARATOR\r\n

Content-Disposition: form-data; name="Long"\r\n
\r\n
1.02

\r\n
--SEPARATOR\r\n

Content-Disposition: form-data; name="Short"\r\n
\r\n
-0.05

\r\n
--SEPARATOR--\r\n


Specifichiamo esplicitamente gli spazi per i feed di riga "\r\n", perché sono simboli obbligatori in una richiesta. Come puoi vedere, nella richiesta vengono passati gli stessi quattro campi, quindi viene eseguita nel solito modo testuale.

Le importanti peculiarità del posizionamento dei separatori:

  • Due simboli "--" sono posti prima del separatore.
  • Per il separatore di chiusura vengono aggiunti due simboli aggiuntivi "--" dopo di esso.


Nel prossimo esempio, puoi vedere un metodo corretto per passare i file in una richiesta.

Immagina che un Expert Advisor crei un'istantanea del grafico e un rapporto dettagliato sul conto in un file di testo quando chiude una posizione.

\r\n
--SEPARATOR\r\n

Content-Disposition: form-data; name="ExpertName"\r\n
\r\n
MACD_Sample

\r\n
--SEPARATOR\r\n

Content-Disposition: file; name="screen"; filename="screen.gif"\r\n
Tipo di contenuto: image/gif\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
......contenuto del file gif.....

\r\n
--SEPARATOR\r\n

Content-Disposition: form-data; name="statement"; filename="statement.csv"\r\n
Tipo di contenuto: application/octet-stream\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
......contenuto del file csv.....

\r\n
--SEPARATOR--\r\n


Nella richiesta vengono visualizzate due nuove intestazioni:

Content-Type - descrive il tipo di contenuto. Tutti i possibili tipi sono accuratamente descritti nello standard RFC[2046]. Abbiamo usato due tipi: image/gif e application/octet-stream.

Due varianti di scrittura Content-Disposition: file e form-data sono equivalenti e vengono elaborati correttamente da PHP in entrambi i casi. Quindi puoi usare file o form-data a tua scelta. Puoi vedere meglio la differenza tra le loro rappresentazioni in Charles.

Content-Transfer-Encoding - descrive la codifica del contenuto. Può essere assente per i dati di testo.

Per consolidare il materiale, scriviamo lo script ScreenPost, il quale invia gli screenshot al server:

#include <InternetLib.mqh>

MqlNet INet; // class instance for working

//------------------------------------------------------------------ OnStart
void OnStart()
{
  // opening session
  if (!INet.Open("www.fxmaster.de", 80, "", "", INTERNET_SERVICE_HTTP)) return;

  string giffile=Symbol()+"_"+TimeToString(TimeCurrent(), TIME_DATE)+".gif"; // name of file to be sent
 
  // creating screenshot 800х600px
  if (!ChartScreenShot(0, giffile, 800, 600)) { Print("-err ScreenShot "); return; }
 
  // reading gif file to the array
  int h=FileOpen(giffile, FILE_ANSI|FILE_BIN|FILE_READ); if (h<0) { Print("-err Open gif-file "+giffile); return; }
  FileSeek(h, 0, SEEK_SET);
  ulong n=FileSize(h); // determining the size of file
  uchar gif[]; ArrayResize(gif, (int)n); // creating uichar array according to the size of data
  FileReadArray(h, gif); // reading file to the array
  FileClose(h); // closing the file
 
  // creating file to be sent
  string sendfile="sendfile.txt";
  h=FileOpen(sendfile, FILE_ANSI|FILE_BIN|FILE_WRITE); if (h<0) { Print("-err Open send-file "+sendfile); return; }
  FileSeek(h, 0, SEEK_SET);

  // forming a request
  string bound="++1BEF0A57BE110FD467A++"; // separator of data in the request
  string Head="Content-Type: multipart/form-data; boundary="+bound+"\r\n"; // header
  string Path="/mt5screen/screen.php"; // path to the page
 
  // writing data
  FileWriteString(h, "\r\n--"+bound+"\r\n");
  FileWriteString(h, "Content-Disposition: form-data; name=\"EA\"\r\n"); // the "name of EA" field
  FileWriteString(h, "\r\n");
  FileWriteString(h, "NAME_EA");
  FileWriteString(h, "\r\n--"+bound+"\r\n");
  FileWriteString(h, "Content-Disposition: file; name=\"data\"; filename=\""+giffile+"\"\r\n"); // field of the gif file
  FileWriteString(h, "Content-Type: image/gif\r\n");
  FileWriteString(h, "Content-Transfer-Encoding: binary\r\n");
  FileWriteString(h, "\r\n");
  FileWriteArray(h, gif); // writing gif data
  FileWriteString(h, "\r\n--"+bound+"--\r\n");
  FileClose(h); // closing the file

  tagRequest req; // initialization of parameters
  req.Init("POST", Path, Head, sendfile, true, "answer.htm", true);
 
  if (INet.Request(req)) Print("-err Request"); // sending the request to the server
  else Print("+ok Request");
} 

Lo script del server che riceve le informazioni:

<?php
$ea=$_POST['EA'];
$data=file_get_contents($_FILES['data']['tmp_name']); // information in the file
$file=$_FILES['data']['name'];
$h=fopen(dirname(__FILE__)."/$ea/$file", 'wb'); // creating a file in the EA folder
fwrite($h, $data); fclose($h); // saving data
?>

Si consiglia vivamente di familiarizzare con le regole di ricezione dei file dal server per evitare problemi di sicurezza!


Lavorare con i cookie

Questo argomento verrà brevemente descritto come un'aggiunta alla lezione precedente e uno spunto di riflessione sulle sue caratteristiche.

Come sapete, i cookie hanno lo scopo di evitare la richiesta continua di dati personali da parte dei server. Una volta che un server riceve i dettagli personali richiesti per la sessione di lavoro corrente da un utente, lascia un file di testo con tali informazioni sul computer dell'utente. Inoltre, quando l'utente si sposta tra le pagine, il server non richiede più tali informazioni all'utente; prende automaticamente le informazioni dalla cache del browser.

Ad esempio, quando abiliti l'opzione "Ricordami" mentre dai l’autorizzazione sul server www.mql5.com, salvi un Cookie con i tuoi dettagli sul tuo computer. Alla successiva visita del sito, il browser passerà il Cookie al server senza che te lo chieda.

Se sei interessato, puoi aprire la cartella (WinXP) C:\Documents and Settings\<Utente>\Cookies e visualizzare i contenuti dei diversi siti web che hai visitato.

Per quanto riguarda le nostre esigenze, i cookie possono essere utilizzati per leggere le tue pagine del forum MQL5. In altre parole, leggerai le informazioni come se avessi l’autorizzazione nel sito con il tuo nome utente e poi analizzerai le pagine ottenute. La variante ottimale è analizzare i cookie utilizzando un server proxy locale Charles. Mostra informazioni dettagliate su tutte le richieste ricevute/inviate, inclusi i Cookie.

Per esempio:

  • Un Expert Advisor (o un'applicazione esterna) che richiede una volta all'ora la pagina https://www.mql5.com/en/job e riceve l'elenco delle nuove offerte di lavoro.
  • Inoltre richiede un ramo, ad esempio https://www.mql5.com/en/forum/53, e controlla se ci sono nuovi messaggi.
  • Inoltre, può controllare se ci sono nuovi "messaggi privati" nei forum.

Per impostare un cookie in una richiesta, viene utilizzata la funzione InternetSetCookie.

BOOL InternetSetCookie(
  __in LPCTSTR lpszUrl,
  __in LPCTSTR lpszCookieName,
  __in  LPCTSTR lpszCookieData
);

  • lpszUrl [in] - Nome di un server, ad esempio www.mql5.com
  • lpszCookieName [in]- Nome di un Cookie
  • lpszCookieData [in] - Dati per il cookie

Per impostare più Cookie, richiama questa funzione per ciascuno di essi.

Una caratteristica interessante: si può effettuare una chiamata a InternetSetCookie in qualsiasi momento, anche quando non si è connessi al server.


Conclusione

Abbiamo conosciuto un altro tipo di richieste HTTP, ottenuto la possibilità di inviare file binari, cosa che permette di estendere le possibilità di lavorare con i vostri server, e abbiamo appreso i metodi per lavorare con i cookie.

Possiamo creare il seguente elenco di direzioni di ulteriori sviluppi:

  • Organizzazione dell'archiviazione remota dei report;
  • Scambio di file tra utenti, aggiornamento delle versioni di Expert Advisor/indicatori;
  • Creazione di scanner personalizzati che funzionano con il tuo account e monitorano l'attività in un sito web.


Link utili:

  1. Un server proxy per la visualizzazione delle intestazioni inviate - http://www.charlesproxy.com/
  2. Descrizione di WinHTTP - http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
  3. Descrizione della sessione HTTP - http://msdn.microsoft.com/en-us/library/aa384322%28VS.85% 29.aspx
  4. Il toolkit Denwer per un'installazione locale di Apache+PHP - http://www.denwer.ru/
  5. Tipi di intestazioni di richiesta - http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
  6. Tipi di richieste - http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
  7. Tipi di richieste - ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types.
  8. Struttura dell'utilizzo di HINTERNET - http://msdn.microsoft.com/en-us/library/aa383766%28VS.85% 29.aspx
  9. Utilizzo dei file: http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
  10. Tipi di dati da passare a MQL - http://msdn.microsoft.com/en-us/library/aa383751%28VS .85%29.aspx


Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/276

File allegati |
metaswap.zip (0.66 KB)
screenpost.zip (0.33 KB)
internetlib.mqh (12.82 KB)
screenpost.mq5 (2.63 KB)
metaswap.mq5 (4.84 KB)
Pagamenti e metodi di pagamento Pagamenti e metodi di pagamento
I servizi MQL5.community offrono grandi opportunità per i trader e per gli sviluppatori di applicazioni per il terminale MetaTrader. In questo articolo spieghiamo come vengono eseguiti i pagamenti per i servizi MQL5, come è possibile prelevare il denaro guadagnato e come viene garantita la sicurezza dell'operazione.
L'implementazione dell'analisi automatica delle onde di Elliott in MQL5 L'implementazione dell'analisi automatica delle onde di Elliott in MQL5
Uno dei metodi più popolari di analisi di mercato è il principio dell'onda di Elliott. Tuttavia, questo processo è piuttosto complicato, il che ci porta all'uso di strumenti aggiuntivi. Uno di questi strumenti è il marcatore automatico. Questo articolo descrive la creazione di un analizzatore automatico delle onde di Elliott nel linguaggio MQL5.
Applicazione della trasformata di Fisher e della trasformata inversa di Fisher all'analisi dei mercati su MetaTrader 5 Applicazione della trasformata di Fisher e della trasformata inversa di Fisher all'analisi dei mercati su MetaTrader 5
Ora sappiamo che la funzione di densità di probabilità (PDF) di un ciclo di mercato non ricorda un ciclo gaussiano, ma piuttosto un PDF di un'onda sinusoidale e che la maggior parte degli indicatori presuppone che il ciclo di mercato PDF sia gaussiano quindi abbiamo bisogno di un modo per "correggerlo". La soluzione è utilizzare la trasformata di Fisher. La trasformata di Fisher cambia il PDF di qualsiasi forma d'onda in una curva approssimativamente gaussiana. Questo articolo descrive la matematica dietro la trasformata di Fisher e la trasformata di Fisher inversa e la loro applicazione nel trading. Viene presentato e valutato un modulo di segnale di trading proprietario basato sulla trasformata di Fisher inversa.
Diminuzione del consumo di memoria tramite indicatori ausiliari Diminuzione del consumo di memoria tramite indicatori ausiliari
Se un indicatore utilizza i valori di molti altri indicatori per i suoi calcoli, consuma molta memoria. L'articolo descrive diversi metodi per ridurre il consumo di memoria quando si utilizzano indicatori ausiliari. La memoria salvata consente di aumentare il numero di coppie di valute, indicatori e strategie utilizzate contemporaneamente nel terminale del cliente. Aumenta l'affidabilità del portafoglio. Una così semplice cura delle risorse tecniche del tuo computer può trasformarsi in risorse monetarie sul tuo deposito.