Visualizzazione 3D senza librerie esterne: come MetaTrader 5 mostra i risultati dell'ottimizzazione tramite MQL5 + DX11
Da una bella immagine a uno strumento di lavoro
Quando i parametri della strategia sono più di dieci e il report del tester si trasforma in una tabella di più pagine con serie numeriche, trader e sviluppatori si trovano di fronte a un problema: il segnale utile si perde nel rumore. I grafici piatti e le mappe di calore bidimensionali sono efficaci fino a un certo punto, ma oltre tale limite, il cervello umano incontra inevitabilmente delle difficoltà a causa della multidimensionalità dei dati. Cerchiamo di tenere a mente la correlazione tra il periodo dell'indicatore, lo stop-loss, la volatilità e il drawdown massimo, ma il carico cognitivo cresce più velocemente dell'utilità delle informazioni.
La visualizzazione 3D e le interfacce interattive in MetaTrader 5 non sono un tentativo di trasformare il terminale di trading in un motore di gioco. Si tratta di una tecnica volta a ridurre l'attrito cognitivo. Una superficie 3D, in cui l'altezza corrisponde al valore atteso e la sfumatura di colore riflette la resilienza al drawdown, ci permette di evidenziare il "plateau di stabilità" e di eliminare in pochi secondi le "punte dell'overfitting". Ciò che richiede ore di confronto in formato tabellare, viene letto come uno schema nello spazio tridimensionale: pendenze del piano, variazioni di quota, plateau/aghi e zone di contrasto.
MetaTrader 5 offre una base tecnologica già pronta per questo compito. Il supporto nativo per DirectX 11, la pipeline di shader integrata e le classi CDXCanvas / CChartObjectDX sono integrate direttamente nell'ambiente MQL5. Lo sviluppatore non ha bisogno di collegare librerie grafiche esterne, scrivere wrapper in C++ o gestire dipendenze: l'intero ciclo, dalla creazione del dispositivo all'output del frame, è accessibile tramite l'API 3D standard e il caricamento di shader e texture avviene direttamente dalla cartella Files\DX\.
In questo articolo, illustreremo come trasformare gli array dei risultati di ottimizzazione, cluster di volumi e modelli di scenario in paesaggi 3D gestibili. Definiremo i criteri di appropriatezza: quando la grafica 3D aggiunge capacità di analisi dei dati e quando genera rumore visivo. Passeremo quindi all'architettura della pipeline DX, esaminando l'inizializzazione, il collegamento delle metriche ai buffer dei vertici, la meccanica della telecamera e i pannelli dell'interfaccia utente, per poi valutare i limiti tecnologici dell'ambiente.
Fig. 1. Testo vs visualizzazione
Quando il 3D e l'interattività sono davvero utili
Un trader e uno sviluppatore di strategie hanno a disposizione uno strumento potente, ma spesso sottovalutato: la percezione visiva. Le tabelle dei report, gli elenchi di metriche e i grafici bidimensionali sono strumenti efficaci per documentare i risultati. Ma quando si tratta di comprendere relazioni complesse, il cervello umano passa a una modalità diversa: cerca schemi sotto forma di immagini, volume e contesto. È qui che la visualizzazione 3D e l'interattività cessano di essere semplici elementi decorativi e diventano uno strumento analitico.
L'aggiunta di un terzo asse è giustificata quando nei dati è presente una chiara ipotesi analitica. Ad esempio, la dipendenza del valore atteso da due parametri chiave del sistema a un livello di rischio fisso. In questo caso, la superficie 3D trasforma le coordinate astratte in un terreno, dove le "montagne" verdi indicano aree di profitto stabile e gli "avvallamenti" rossi indicano zone di drawdown. La rappresentazione 3D funge da filtro: elimina il rumore e lascia solo le combinazioni significative.
Non per ragioni estetiche, ma per il valore analitico.
Non stiamo parlando di elementi grafici decorativi, bensì di livelli visivi che rivelano direttamente ciò che si cela all'interno delle righe del report di ottimizzazione. Una superficie piatta risponde alla domanda: "Qual è il risultato di questa combinazione di parametri?"
Una superficie 3D risponde alla domanda: "Come cambia il risultato con variazioni graduali di due parametri, e dove si trovano le zone di stabilità?" Si tratta di un livello di analisi strategica radicalmente diverso. La stabilità del risultato al variare del contesto esterno è l'aspetto principale da raggiungere quando si testa un'idea. Tutto il resto è secondario.
Esistono almeno tre scenari in cui la visualizzazione 3D offre un vantaggio:
1. Paesaggio di ottimizzazione
Immagina di dover selezionare due parametri: il periodo della media mobile e la dimensione dello stop loss. Nella tabella sono presenti 100 righe con i valori di Profitto e Drawdown. Su una superficie 3D con un gradiente di colore, vedrete immediatamente:
- Le zone di stabilità sono vaste aree verdi in cui la strategia risulta costantemente redditizia anche con parametri variabili;
- I picchi netti sono massimi locali ristretti che spesso sono un segno di overfitting;
- I pendii ripidi sono aree in cui una piccola variazione di un parametro si traduce in un forte aumento del drawdown.
Questa superficie 3D consente di prendere decisioni consapevoli sulla scelta di un insieme di parametri di lavoro, basandosi non sul massimo profitto, ma sulla robustezza.
2. La condizione del mercato come spazio multidimensionale
La classificazione dei regimi di mercato (trend, fase laterale, aumento della volatilità) spesso richiede l'analisi simultanea di più di tre indicatori. In questo caso la visualizzazione 3D consente:
- Rappresentare ciascun stato del mercato come un punto nello spazio delle caratteristiche;
- Codificare a colori l'efficacia della strategia in questa modalità (questo punto);
- Ruotare interattivamente la nuvola di punti per rivelare i raggruppamenti nascosti.
Ciò risulta particolarmente utile quando si impostano i parametri in modo adattivo: è possibile visualizzare in quali condizioni la strategia fallisce e dove invece funziona in modo affidabile.
3. Modellazione di scenari e stress test
Quando si esegue la strategia su una varietà di scenari sintetici (metodo Monte Carlo, generazione del percorso del prezzo, domande come: cosa succede se la volatilità aumenta del 30%?), la terza dimensione può visualizzare:
- Asse X - parametro della strategia;
- Asse Y - livello di stress del mercato (volatilità, gap);
- Asse Z / colore - metrica finale (fattore di recupero, valore atteso).
Di conseguenza, stiamo osservando non solo uno scenario peggiore, ma anche il limite di robustezza della strategia in condizioni di mercato in deterioramento.
Criteri di selezione: Quando il 3D è esagerato
Formuliamo una regola: l'utilizzo della rappresentazione 3D è giustificato quando si studia la dipendenza di un risultato da due o più variabili continue ed è importante visualizzare la geometria di tale dipendenza.
Se siete pronti a sostituire ore di analisi dettagliata dei report con un'analisi visiva intuitiva, iniziamo dalla pipeline di rendering.
Pipeline DX di base in MQL5: Dall'inizializzazione al primo frame
La creazione di grafica 3D in MetaTrader 5 è un processo sequenziale, in cui ogni fase ha uno scopo ben preciso. Comprendere l'architettura degli oggetti DirectX e il ciclo di rendering ci permette di evitare problemi comuni, come perdite di memoria, sfarfallio della grafica e degrado delle prestazioni quando si lavora con grandi quantità di dati.
Architettura degli oggetti DirectX in MQL5
La base della grafica 3D in MQL5 è la classe CCanvas3D, che incapsula tutta la complessità del lavoro con DirectX 11. Fornisce allo sviluppatore un'API di alto livello, nascondendo i dettagli di basso livello relativi alla creazione di dispositivi, contesti e buffer.
Struttura gerarchica degli oggetti:
- CCanvas3D — una classe che gestisce la scena, la telecamera e il rendering
- CDXMesh e i suoi derivati (CDXBox, CDXSphere, CDXTorus) — oggetti geometrici
- DXVertex — struttura del vertice contenente coordinate, normali, coordinate di texture e colore
struct DXVertex { DXVector4 position; // vertex coordinates DXVector4 normal; // normal vector DXVector2 tcoord; // texture coordinates DXColor vcolor; // color };
Questa struttura è compatibile con lo shader dei vertici standard, eliminando la necessità di scrivere shader personalizzati per le attività di base. Per progetti complessi, consigliamo di utilizzare i metodi e le utilità presentati nel file MQL5\Include\Canvas\DX\DXUtils.mqh.
Fig. 2. Gerarchia della grafica 3D in MQL5
Tutte le risorse (shader, texture, buffer) sono gestite tramite DXDispatcher. Ogni oggetto creato deve essere esplicitamente distrutto tramite Destroy() o Shutdown() nel distruttore. MetaTrader 5 non dispone di un garbage collector per le risorse DirectX e le perdite di memoria si accumulano fino al riavvio del terminale.
Inizializzazione della pipeline
Il primo passo consiste nel creare una superficie di disegno e impostare la matrice di proiezione:
//+------------------------------------------------------------------+ //+ canvas creation and projection matrix setup | //+------------------------------------------------------------------+ virtual bool Create(const int width, const int height) { //--- save canvas dimensions m_width = width; m_height = height; //--- create a canvas to render a 3D scene ResetLastError(); if(!m_canvas.CreateBitmapLabel("3D Sample_1", 0, 0, m_width, m_height, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Error creating canvas: ", GetLastError()); return(false); } //--- set the projection matrix parameters m_canvas.ProjectionMatrixSet((float)M_PI/6, (float)m_width/m_height, 0.1f, 100.0f); //--- create a 3D object if(!m_box.Create(m_canvas.DXDispatcher(), m_canvas.InputScene(),DXVector3(-1.0, -1.0, -1.0), DXVector3(1.0, 1.0, 1.0))) { m_canvas.Destroy(); return(false); } //--- add a cube to the scene m_canvas.ObjectAdd(&m_box); Redraw(); return(true); }
Principali parametri di proiezione:
- Field of view (FOV) - 30 gradi (M_PI/6), equilibrio tra prospettiva e distorsione;
- Aspect ratio - il rapporto tra larghezza e altezza (m_width/m_height); preservandolo si evita l'allungamento in altezza/larghezza;
- I piani di ritaglio sono 0,1f (più vicino) e 100,0f (più lontano), gli oggetti al di fuori di questi limiti non vengono renderizzati.
Questi parametri definiscono la piramide di visibilità: nella matrice di proiezione vengono inclusi solo gli oggetti situati tra i due piani di clipping e all'interno dell'angolo di visione orizzontale.
Compilazione degli shader - per le primitive standard, gli shader vengono compilati automaticamente. Se si applicano effetti personalizzati, utilizzare DXCompileShader() con gestione degli errori:
string error_log; if(!DXCompileShader("shader.hlsl", "vs_main", "vs_5_0", shader_blob, error_log)) { Print("Shader compilation failed: ", error_log); return(false); }
Dati → Vertici → Frame
Dopo l'inizializzazione, inizia il ciclo di rendering. Ogni frame passa attraverso una pipeline di trasformazione:
Modello dell'oggetto — Il modello tridimensionale è descritto da una rete di poligoni. In pratica, si utilizza la triangolazione: ogni poligono viene diviso in triangoli, poiché un triangolo descrive in modo univoco un piano nello spazio. Un cubo, ad esempio, ha 8 vertici, ma per una corretta illuminazione ne vengono creati 24 (4 per ogni faccia con normali univoche).
//--- preparing vertices and indices for the sphere DXVertex vertices[]; uint indices[]; if(!DXComputeSphere(0.3f, 50, vertices, indices)) return(false); //--- create the sphere object if(!m_sphere.Create(m_canvas.DXDispatcher(), m_canvas.InputScene(), vertices, indices)) { m_canvas.Destroy(); return(false); }
Trasformazioni — Ogni oggetto passa attraverso le matrici:
- Matrice del modello — coordinate locali → mondo (rotazione, scala, traslazione);
- Matrice di visualizzazione — coordinate del mondo → coordinate della telecamera;
- Matrice di proiezione — coordinate della telecamera → coordinate dello schermo.
//--- calculate the cube position and the transfer matrix DXMatrix rotation, translation; //--- rotate the cube sequentially along the X, Y and Z axes DXMatrixRotationYawPitchRoll(rotation, (float)M_PI/4, (float)M_PI/3, (float)M_PI/6); //--- shift the cube to the right-down-into the depth DXMatrixTranslation(translation, 1.0, -2.0, 5.0); //--- get the transformation matrix as a product of rotation and transfer DXMatrix transform; DXMatrixMultiply(transform, rotation, translation); //--- set the transformation matrix m_box.TransformMatrixSet(transform);
Illuminazione (modello Phong) — per ottenere un'immagine realistica è necessario calcolare tre componenti di illuminazione:
- Ambiente (sfondo) — una componente costante che simula la luce diffusa;
- Diffusa (dispersa) — dipende dall'angolo di incidenza della luce sulla superficie;
- Speculare (a specchio) — riflessi che creano un effetto lucido.
//--- set the source color to yellow and direct it from top to bottom m_canvas.LightColorSet(DXColor(1.0, 1.0, 0.0, 0.8f)); m_canvas.LightDirectionSet(DXVector3(0.0, -1.0, 0.0)); //--- set the blue color for the ambient light m_canvas.AmbientColorSet(DXColor(0.0, 0.0, 1.0, 0.4f)); //--- set the cube color to white with a green glow m_box.DiffuseColorSet(DXColor(1.0, 1.0, 1.0, 1.0)); m_box.EmissionColorSet(DXColor(0.0, 1.0, 0.0, 0.2f));
Rendering e aggiornamento della superficie di disegno - tutte le operazioni vengono eseguite nel metodo Render(). Dopodiché, l'immagine viene trasferita sulla superficie di disegno tramite Update():
//--- calculate the 3D scene m_canvas.Render(DX_CLEAR_COLOR | DX_CLEAR_DEPTH, ColorToARGB(clrBlack)); //--- update the picture on the canvas in accordance with the current scene m_canvas.Update();
Gestione della memoria - ogni oggetto DX consuma memoria video. Quando si creano scene dinamiche (ad esempio, aggiornando la superficie di ottimizzazione), è necessario:
- Cancellare i vecchi buffer prima di crearne di nuovi;
- Utilizzare DXResourceRelease() per rilasciare esplicitamente le risorse;
- Controllare il numero di vertici: sono consentiti migliaia di poligoni per gli oggetti statici, centinaia per quelli animati (il numero esatto dipende dalle prestazioni del computer).
Diagnostica e controllo degli errori - controllare sempre i valori restituiti e usare GetLastError() per la diagnostica quando si verificano errori:
if(!m_canvas.CreateBitmapLabel(...)) { int err = GetLastError(); Print("DX Error code: ", err); //--- typical errors: //--- 1: device does not support DX11 //--- 2: insufficient video memory //--- 3: shader compilation error }
Accelerazione hardware o rendering software:
MT5 rileva automaticamente le capacità della vostra scheda video. Se DirectX 11 non è disponibile, la grafica non verrà visualizzata. Controllo dei requisiti:
- Scheda video con supporto per DX11 e versione shader 5.0;
- Driver pertinenti (le versioni obsolete potrebbero causare artefatti);
- Attivare l'accelerazione hardware nelle impostazioni del terminale.
Per scoprire quale versione di DirectX è installata, eseguire i seguenti passaggi:
- Nella casella di ricerca della barra degli strumenti di Windows, digitare "dxdiag". Quindi selezionare "dxdiag" dall'elenco dei risultati;
- Nello strumento di diagnostica DirectX, selezionare la scheda Sistema - la versione di DirectX deve essere almeno la 11.
Fig. 3. Verifica della versione di DirectX
Nota:
La prima volta che si utilizza lo strumento di diagnostica DirectX, potrebbe essere richiesto di verificare la presenza di firme digitali sui driver. Si consiglia di selezionare Sì per garantire che i driver siano firmati da un editore che ne abbia verificato l'autenticità.
Ottimizzazione del consumo delle risorse del computer per il ciclo di rendering:
- OnTimer() o OnChartEvent() — per le scene statiche, chiamare la funzione Redraw() solo quando i dati cambiano, non a ogni tick;
- Level of Detail (LOD) — riduzione del dettaglio degli oggetti all’aumentare della distanza dalla telecamera;
- Instancing — per più oggetti identici (cluster, marcatori), utilizzare una mesh con matrici di trasformazione diverse.
La pipeline di base è pronta. Abbiamo creato una superfice di disegno, impostato una proiezione, definito gli oggetti con vertici e normali, applicato trasformazioni e illuminazione. Il passo successivo consiste nell'arricchire questo framework con contenuti analitici: trasformare i risultati dell'ottimizzazione in superfici 3D, i cluster di liquidità in strutture volumetriche e i modelli di scenario in report interattivi.
Visualizzazione di dati complessi - superfici, cluster, scenari
Una volta configurata la pipeline DX di base, disponiamo di uno spazio vuoto pronto per visualizzare i dati. Il compito successivo consiste nel trasformare i numeri del report del tester in primitive geometriche comprensibili all'occhio umano. Passiamo dalle matrici grezze ai paesaggi visivi.
La creazione di una superficie di ottimizzazione 3D è probabilmente il modo più utile per utilizzare la grafica 3D nel trading. Lo strumento di test della strategia genera una tabella in cui le righe rappresentano le esecuzioni e le colonne i parametri e le metriche. La visualizzazione in 3D dei risultati dell'ottimizzazione trasforma questa tabella in un terreno.
Semplici calcoli di visualizzazione: gli assi X e Z diventano i parametri della strategia (ad esempio, periodo della media mobile e dimensione dello stop loss), e l'asse Y (altezza) diventa la funzione obiettivo (ad esempio, fattore di recupero, valore atteso). Per costruire una superficie di questo tipo, dobbiamo generare una mesh triangolare (Mesh) basata sull'array dei risultati.
Algoritmo di costruzione:
- Raccolta dei dati - esaminare gli array dei risultati di ottimizzazione, selezionare e strutturare i dati necessari;
- Normalizzazione - normalizzare i valori dei parametri su una scala comune in modo che il grafico non appaia appiattito;
- Generazione dei vertici - per ogni punto dati creiamo il vertice DXVertex;
- Triangolazione - collegamento di vertici adiacenti con indici a formare dei triangoli.
//+------------------------------------------------------------------+ //| example of creating a surface from a data array | //+------------------------------------------------------------------+ void CreateOptimizationSurface(const double &data[],const int width,const int height) { DXVertex vertices[]; uint indices[]; int v_count = 0; int i_count = 0; //--- allocate memory for vertices (width*height of points) ArrayResize(vertices,width*height); for(int y=0;y<height;y++) { for(int x=0;x<width;x++) { int idx=y*width+x; //--- X and Z are the coordinates of the parameters, Y is the value of the metric (profit) vertices[idx].position=DXVector3(x, data[idx], y); vertices[idx].normal=DXVector3(0, 1, 0); // simplified normal vertices[idx].vcolor=ColorToVector4(ColorFromValue(data[idx])); // color depends on value vertices[idx].tcoord=DXVector2((float)x/width, (float)y/height); } } //--- next, we form an array of indices for triangles and create an object using m_mesh.Create(...) }
Su una superficie di questo tipo, gli "altipiani" sono immediatamente visibili - ampie zone in cui la strategia opera in modo relativamente stabile al variare dei parametri. Al contrario, sono chiaramente visibili picchi di profitto ristretti (simili ad aghi), che indicano un overfitting. Eliminare visivamente un simile "ago" è molto più facile che individuarlo in una tabella di 1000 righe, dato che le dipendenze multidimensionali sono difficilmente visibili in forma tabellare.
Analisi dei cluster nello spazio
I dati di mercato includono non solo prezzo e tempo, ma anche volume. In 2D, di solito disegniamo un istogramma del volume nella parte inferiore dello schermo, ma in 3D il volume diventa una dimensione a tutti gli effetti. Invece di barre piatte, possiamo utilizzare blocchi di volume (voxel) o cilindri, la cui altezza e spessore dipendono dal volume di trading o dalla densità del cluster:
- Asse X - Tempo;
- Asse Z - Prezzo;
- Asse Y - Volume / Numero di trade (raggio del voxel).
Visualizzazione della liquidità - utilizzando DXComputeSphere o mesh personalizzate, è possibile rappresentare grandi cluster di trade come sfere di dimensioni e trasparenza variabili. Sovrapposti l'uno all'altro, creano una nuvola di liquidità, dove il trader vede "vuoti" (zone in cui il prezzo si è mosso rapidamente) e "grumi" (livelli di supporto/resistenza).
//--- setting color based on volume (heatmap effect) //--- the larger the volume, the redder the color (from blue to red) color cluster_color=ColorInterpolate(clrBlue,clrRed,volume_ratio); m_cluster.MeshColorSet(ColorToVector4(cluster_color,0.7)); // 0.7 - transparency
Generazione della superficie tramite DXComputeSurface()
Per le tipiche attività di visualizzazione di dati bidimensionali (mappe di calore, risultati di ottimizzazione, matrici di correlazione), la libreria DirectX MQL5 fornisce la funzione modello predefinita DXComputeSurface(). La funzione si trova nel file DXUtils.mqh.
Dichiarazione:
//+------------------------------------------------------------------+ //| Surface | //| TVertex must have | //| DXVector4 position, DXVector4 normal and DXVector2 tcoord members| //+------------------------------------------------------------------+ template <typename TVertex> bool DXComputeSurface(double &data[],uint data_width,uint data_height,double data_range, const DXVector3 &from,const DXVector3 &to,DXVector2 &texture_size, bool two_sided,bool use_normals, TVertex &vertices[],uint &indices[])
Obiettivo:
Genera automaticamente vertici e indici per una superficie 3D a partire da un array di dati 2D. Questa funzione libera lo sviluppatore dalla necessità di calcolare manualmente coordinate, triangolazione della mesh e normali.
Inputs:
| Parametro | Tipo | Descrizione |
|---|---|---|
| [in] data[] | double& | Array unidimensionale di valori di altezza (dati di ottimizzazione, correlazioni, ecc.) |
| [in] data_width | uint | Larghezza della matrice di dati originale (numero di colonne) |
| [in] data_height | uint | Altezza della matrice di dati originale (numero di righe) |
| [in] data_range | double | Intervallo di scala per i valori lungo l'asse Z (altezza) |
| [in] from | const DXVector3& | Coordinate dell'angolo inferiore sinistro della superficie nello spazio 3D |
| [in] to | const DXVector3& | Coordinate dell'angolo superiore destro della superficie nello spazio 3D |
| [in] texture_size | DXVector2& | Dimensione delle coordinate di texture (per la mappatura delle texture o dei gradienti) |
| [in] two_sided | bool | Flag di rendering a doppia faccia (utile per superfici trasparenti) |
| [in] use_normals | bool | Flag di calcolo normale per un'illuminazione corretta |
| [out] vertices[] | TVertex& | Array dei vertici di output (riempito dalla funzione) |
| [out] indices[] | uint& | Array di output degli indici per la triangolazione (riempito dalla funzione) |
Requisiti di tipo TVertex: il parametro del modello TVertex deve essere una struttura contenente almeno tre campi:
struct MyVertex { DXVector4 position; // vertex coordinates (x,y,z,w) DXVector4 normal; // normal vector (nx,ny,nz,0) DXVector2 tcoord; // texture coordinates (u,v) // additional fields as needed: color, binormal, etc. };
Valore restituito
- true — la superficie è stata generata con successo;
- false — errore (verificare le dimensioni degli array e la validità dei dati di input).
Esempio di utilizzo:
Supponiamo di avere i risultati dell'ottimizzazione della strategia sotto forma di una matrice 20x20, dove ogni valore rappresenta il valore atteso di un trade. Vogliamo visualizzare questi dati come una superficie 3D.
//+------------------------------------------------------------------+ //| Generate optimization surface via DXComputeSurface | //+------------------------------------------------------------------+ void BuildOptimizationSurface(double &optimization_data[],uint width,uint height) { //--- define the vertex type with an additional color field struct SVertex { DXVector4 position; DXVector4 normal; DXVector2 tcoord; DXColor vcolor; // vertex color for gradient }; SVertex vertices[]; uint indices[]; //--- surface parameters DXVector3 from(-10.0,-5.0,0.0); // lower left corner DXVector3 to(10.0,5.0,1.0); // upper right corner DXVector2 texture_size(1.0,1.0); // full coverage of texture coordinates double data_range=2.0; // height scaling //--- generate geometry if(!DXComputeSurface(optimization_data,width,height,data_range, from,to,texture_size, false,true, // one-sided, with normals vertices,indices)) { Print("surface generation error: ",GetLastError()); return; } //--- color the vertices by height (gradient: red → green) for(uint i=0;i<ArraySize(vertices);i++) { double t=MathMax(0.0,MathMin(1.0,vertices[i].position.z/data_range)); vertices[i].vcolor=DXColor(1.0f-(float)t,(float)t,0.2f,1.0f); } //--- create a mesh and add it to the scene if(!mesh.Create(canvas.DXDispatcher(),canvas.InputScene(),vertices,indices)) Print("error creating mesh: ",GetLastError()); mesh.DiffuseColorSet(DXColor(1.0f,1.0f,1.0f,1.0f)); canvas.ObjectAdd(&mesh); }
Vantaggi di DXComputeSurface():
- Velocità di sviluppo - non è necessario scrivere manualmente i cicli di triangolazione e di calcolo della normale;
- Ottimizzazione - l'implementazione interna utilizza algoritmi efficienti per la generazione di mesh;
- Flessibilità - il tipo di vertice del modello consente di aggiungere qualsiasi attributo aggiuntivo (colore, texture, dati personalizzati).
Limitazioni di DXComputeSurface():
- Solo griglie regolari - la funzione presuppone che i dati siano una matrice rettangolare. Per nuvole di punti irregolari o topologie arbitrarie, sarà necessaria la generazione manuale;
- Interpolazione predefinita - le altezze dei vertici vengono interpolate linearmente tra i nodi della griglia. Potrebbe essere necessario un pre-filtraggio dei dati per levigare le superfici frastagliate;
- Memoria - la funzione alloca dinamicamente array di vertici e indici. Quando si lavora con matrici di grandi dimensioni (ad esempio 100x100 e superiori), è importante monitorare il consumo di memoria e la frequenza di ricostruzione della superficie.
Integrazione con il controllo interattivo:
Poiché DXComputeSurface() genera una geometria statica, gli aggiornamenti dinamici (ad esempio, quando si modificano i parametri di ottimizzazione in tempo reale) richiedono:
- Salvare un riferimento all'oggetto CDXMesh,
- Quando i dati cambiano, chiamare mesh.Shutdown() per liberare i vecchi buffer,
- Richiamare DXComputeSurface() e mesh.Create() con i nuovi dati,
- Chiamare RedrawScene() per visualizzare le modifiche.
Per un'animazione fluida, si consiglia di spostare le operazioni più complesse nel gestore OnTimer, come avviene nell'EA DX_OptimizationSurface (vedere la descrizione qui sotto). L'utilizzo di DXComputeSurface() in molti casi tipici riduce la quantità di codice del 30-40% nella costruzione di superfici standard, consentendoci di concentrarci sulla logica analitica piuttosto che sulla grafica di basso livello.
Modellazione di scenari e report interattivi
Uno dei vantaggi di DirectX in MQL5 è la possibilità di aggiornare dinamicamente la scena senza ricaricare l'indicatore. Questo apre la strada alla modellazione di scenari "cosa succederebbe se?". Ad esempio, il pannello in cui si sposta il cursore etichettato "Volatilità" o "Commissione". L'array data[] viene ricalcolato in tempo reale e la funzione di aggiornamento del buffer dei vertici VertexSet() modifica dinamicamente la superficie 3D.
//+------------------------------------------------------------------+ //| dynamic update example | //+------------------------------------------------------------------+ void OnSliderChange(const double new_vol) { //--- recalculate the strategy metrics with the new volatility RecalculateStrategyMetrics(new_vol); //--- update only the Y-coordinate of the vertices (height), without recreating the mesh for(int i=0;i<ArraySize(m_vertices);i++) { m_vertices[i].position.y=m_new_metrics[i]; //--- recalculate normals for correct lighting m_vertices[i].normal=CalculateNormal(i); } //--- send updated data to video memory m_mesh.VertexSet(m_vertices); Redraw(); }
Questo permette al trader di valutare la sostenibilità della strategia - se una piccola variazione della volatilità trasforma un grafico redditizio in uno in perdita, è improbabile che la strategia sia valida a lungo termine.
Gli shader come filtro analitico
Gli shader standard di MetaTrader 5 sono responsabili dell'illuminazione (modello Phong). Ma per l'analisi abbiamo bisogno di shader che lavorino con i dati. Possiamo scrivere un semplice Pixel Shader che fungerà da filtro.
Ad esempio, sul grafico 3D è necessario evidenziare solo le zone in cui il drawdown supera il 20% o in cui il fattore di profitto è inferiore a uno:
//+------------------------------------------------------------------+ //| example of logic in HLSL shader | //+------------------------------------------------------------------+ float4 PS_Main(PS_INPUT input) : SV_TARGET { //--- input.color contains the metric information encoded in the vertex float profit=input.color.r; //--- if the profit is below the threshold, make the object transparent if(profit<threshold) return float4(0.5,0.5,0.5,0.2); // gray and transparent else return float4(0.0,1.0,0.0,1.0); // bright green }
Nello shader, controlliamo il valore di un attributo del vertice (come l'altezza o il colore passato) e se non soddisfa i criteri, rendiamo il pixel trasparente o grigio. Questo approccio ci permette di rimuovere il "rumore" dal grafico, lasciando solo le aree significative. Questo è importante quando si analizzano grandi quantità di dati di ottimizzazione.
Interattività - mouse, pulsanti e controllo della telecamera
Un'immagine 3D statica è bellissima, ma è di scarsa utilità nel trading se non è possibile ruotarla, ingrandirla o applicare filtri. Il vero vantaggio di DirectX in MQL5 emerge quando abbiamo l'opportunità di esplorare attivamente i dati. Vediamo come dare vita a una scena collegando gli eventi del mouse e della tastiera con i calcoli della telecamera.
Gestione degli eventi in MQL5 - dare vita alla superficie di disegno
Per impostazione predefinita, la superficie di disegno (CDXCanvas) è semplicemente un'immagine raster che non risponde al cursore. Per consentire l'interazione con l'utente, è necessario iscriversi agli eventi del grafico. Questo viene fatto tramite i modificatori CHART_EVENT_MOUSE_... nella funzione OnInit():
//+------------------------------------------------------------------+ //| subscribe to mouse events | //+------------------------------------------------------------------+ int OnInit() { //--- enable receiving mouse events ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,1); ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1); //--- timer for animation EventSetMillisecondTimer(16); return(INIT_SUCCEEDED); }
Dopodiché, tutti i movimenti del cursore verranno elaborati dal gestore OnChartEvent(). Il compito successivo consiste nel filtrare gli ID richiesti (CHARTEVENT_MOUSE_MOVE, CHARTEVENT_MOUSE_WHEEL) e passare le coordinate alla parte del codice che si occupa del rendering.
Osservazioni:
Nel metodo OnDeinit(), assicuratevi di disabilitare questi eventi (chiamando ChartSetInteger(..., 0)), altrimenti, dopo aver eliminato l'indicatore, il terminale continuerà a generarli, creando un carico aggiuntivo.
Controllo della telecamera - rotazione e zoom
Il metodo più comune per interagire con la grafica 3D consiste nel ruotare attorno a un oggetto (orbita) e ingrandire/rimpicciolire l'immagine. In DirectX, questo risultato non si ottiene ruotando l'oggetto stesso, ma spostando la telecamera su una sfera attorno al centro della scena.
In CCanvas3D vengono utilizzati tre metodi interconnessi per controllare la telecamera:
- ViewPositionSet() — imposta la posizione dell'occhio dell'osservatore (coordinate della telecamera);
- ViewTargetSet() — imposta il punto in cui l'osservatore sta guardando (di solito il centro della scena è 0,0,0);
- ViewUpDirectionSet() — imposta la direzione della parte superiore dell'inquadratura (in modo che la telecamera non si capovolga).
Implementazione della rotazione
La rotazione si effettua spostando il cursore del mouse tenendo premuto il pulsante sinistro del mouse. Per ruotare la scena, tracciamo il delta del movimento del cursore: la differenza tra la posizione attuale e quella precedente. Convertiamo questo delta in angoli di rotazione attorno agli assi X e Y.
//+------------------------------------------------------------------+ //| mouse movement event handler | //+------------------------------------------------------------------+ void OnMouseMove(int x, int y, uint flags) { //--- check that the left mouse button is pressed if((flags&1)==1) { //--- calculate the change in angles based on the mouse offset //--- divide by the coefficient (for example, 300.0f) for sensitivity m_camera_angles.y+=(x-m_mouse_x)/300.0f; m_camera_angles.x+=(y-m_mouse_y)/300.0f; //--- limit the vertical angle if(m_camera_angles.x<-DX_PI*0.49f) m_camera_angles.x=-DX_PI*0.49f; if(m_camera_angles.x>DX_PI*0.49f) m_camera_angles.x=DX_PI*0.49f; //--- recalculate the camera position UpdateCameraPosition(); Redraw(); } //--- save the current coordinates for the next step m_mouse_x=x; m_mouse_y=y; }
Nella funzione UpdateCameraPosition(), utilizziamo le matrici di rotazione DXMatrixRotationX e DXMatrixRotationY per ruotare il vettore di direzione della telecamera e aggiornarne la posizione nello spazio.
Implementazione dello Zoom
Ruotando la rotellina del mouse si modifica la distanza tra la telecamera e il centro della scena (m_camera_distance).
//+------------------------------------------------------------------+ //| mouse wheel rotation event handler | //+------------------------------------------------------------------+ void OnMouseWheel(double delta) { //--- decrease or increase the distance m_camera_distance*=1.0-delta*0.001; //--- limit the zoom to reasonable values if(m_camera_distance>50.0) m_camera_distance=50.0; if(m_camera_distance<3.0) m_camera_distance=3.0; UpdateCameraPosition(); Redraw(); }
Pannello dell'interfaccia utente sovrapposto alla scena 3D
La grafica 3D si occupa della visualizzazione dei dati, ma per controllare i parametri abbiamo bisogno di elementi di interfaccia MQL5 standard e familiari. Per evitare di reinventare la ruota, utilizzate le classi della libreria di controlli standard MQL5 (CAppDialog, CSlider, CComboBox ecc.), che disegnano sopra o a lato dell'area grafica. È possibile utilizzare anche altre classi per costruire l'interfaccia.
Quando il valore del cursore cambia (ad esempio, "Soglia di profitto"), viene chiamata una funzione che aggiorna i dati nel buffer dei vertici o modifica le variabili dello shader, dopodiché viene chiamata la funzione Redraw(). Questo ci permette di creare uno strumento analitico completo. Ad esempio: a sinistra è presente un paesaggio 3D interattivo che mostra i risultati dell'ottimizzazione, a destra si trova un pannello di controllo con filtri.
Ottimizzazione del ciclo interattivo
L'interattività non dovrebbe ridurre in modo significativo le prestazioni del terminale e del computer nel suo complesso. Se si chiama la funzione Redraw() ad ogni micro movimento del mouse senza tenere premuto il pulsante, l'interfaccia del terminale inizierà a rallentare.
Formuliamo le regole di base per ottimizzare l'interattività:
- Chiamare Redraw() solo quando necessario - chiamare l’aggiornamento della scena solo all'interno dei blocchi di controllo: if((flags & 1) == 1) (controlla quando viene premuto il pulsante) o quando si modificano i parametri dell'interfaccia utente;
- Filtraggio degli eventi non necessari: se i dati per il ridisegno arrivano troppo spesso (ad esempio, dai tick nel gestore OnTick()), aggiornare il buffer dei vertici non più di 10-15 volte al secondo;
- Asincronia dei calcoli complessi - se la modifica di un parametro richiede il ricalcolo di migliaia di vertici (ad esempio, la ricostruzione dell'intera superficie di ottimizzazione), eseguirla nel gestore eventi del timer OnTimer() oppure spostarla in un thread separato tramite DLL per evitare il blocco dell'interfaccia.
Expert di test per la validazione di tecnologie 3D
Svilupperemo un EA che implementerà le tecnologie 3D descritte. L'EA è uno strumento per la visualizzazione tridimensionale dei dati analitici nel terminale di trading MetaTrader 5. Lo scopo principale è trasformare i risultati numerici dell'ottimizzazione delle strategie di trading in superfici tridimensionali, consentendo al trader e/o allo sviluppatore di valutare rapidamente la qualità e la stabilità della strategia.
L'EA risolve i seguenti problemi:
- La visualizzazione della superficie di ottimizzazione consiste nella rappresentazione grafica della dipendenza della funzione obiettivo (profitto, valore atteso, indice di Sharpe) da due parametri della strategia ottimizzata, sotto forma di un paesaggio tridimensionale.
- Analisi della stabilità della strategia - identificazione del "plateau di stabilità" (aree con variazioni graduali dei risultati) e "picchi di overfitting" (picchi isolati che indicano un overfitting rispetto ai dati storici).
- Esplorazione interattiva dei dati - offre la possibilità di ruotare, ingrandire ed esplorare la superficie in dettaglio con il mouse.
- Confronto tra modalità - passare da una modalità di visualizzazione all'altra (ad esempio, profitto e rumore) per valutare la qualità dell'ottimizzazione.
Caratteristiche:
Visualizzazione 3D — l'EA crea una superficie di disegno 3D interattiva sul grafico, di dimensioni 640×480 pixel, che visualizza una superficie generata in base alla funzione analitica. La superficie è una mesh di vertici 25x25, triangolata per una visualizzazione corretta.
Codifica a colori — è stato applicato il seguente schema di colori:
- La parte superiore (valori massimi) è di un verde intenso;
- Base (valori minimi) - colore rosso intenso;
- I valori intermedi presentano una gradazione graduale con una transizione attraverso tonalità giallo-arancio.
Questa codifica ci permette di identificare con sicurezza le zone di massimo profitto (le "colline" verdi) e le zone di perdita (gli "avvallamenti" rossi).
Controllo della telecamera — è stato implementato il controllo completo del punto di vista.
- Rotazione - tenendo premuto il pulsante sinistro del mouse e muovendo il cursore, la telecamera ruota attorno al centro della scena descrivendo un'orbita sferica;
- Zoom - scorrendo la rotellina del mouse si ingrandisce o si rimpicciolisce la telecamera (distanza da 5,0 a 40,0 unità);
- Limitazioni - l'angolo verticale è limitato a ±0,9×π/2 per evitare che la telecamera si ribalti.
Controllo interattivo
Sulla superficie di disegno è presente un pulsante per cambiare modalità:
- Modalità profitto - visualizza una superficie a curva gaussiana uniforme (simulando una strategia stabile);
- Modalità rumore - visualizza la superficie con l'aggiunta di rumore casuale (simulando una strategia sovra-ottimizzata).
Quando si cambia modalità, il testo del pulsante viene aggiornato automaticamente.
Illuminazione e materiali
Per creare un'immagine volumetrica realistica, è stato utilizzato un modello di illuminazione Phong con tre componenti:
- Sorgente luminosa direzionale - colore giallo (1.0, 0.95, 0.8), direzione (0.5, -0.8, 0.3);
- Illuminazione ambientale - colore grigio (0.55, 0.55, 0.55) con intensità aumentata per una migliore visibilità delle zone d'ombra;
- Materiale diffuso - colore bianco (1.0, 1.0, 1.0, 1.0) per una corretta miscelazione con i colori dei vertici.
Caratteristiche di implementazione
Gestione asincrona degli eventi:
- Reazione ai movimenti del cursore del mouse e alla rotazione della rotellina - il programma riceve la chiamata alla funzione OnChartEvent() e la gestisce immediatamente (vengono modificati l'orientamento del modello 3D, le sue dimensioni, ecc.). Non implica calcoli complessi.
- Reazione alla pressione del pulsante di cambio modalità - il programma riceve la chiamata alla funzione OnChartEvent() e imposta il flag need_rebuild. La funzione BuildSurface(), responsabile della distruzione della vecchia mesh, della generazione di una nuova superficie 3D e del rendering, viene chiamata all'interno della funzione del gestore OnTimer(), che controlla lo stato del flag need_rebuild ogni 50 ms.
Questo approccio garantisce che, anche se il ricalcolo del modello 3D richiede risorse della CPU, il grafico del terminale e i controlli rimangono pienamente reattivi.
La libreria MQL5\Include\Canvas\DX\DXMath.mqh viene utilizzata per calcolare correttamente l'illuminazione e l'orientamento della telecamera:
- Normali - per ogni vertice della superficie, il vettore normale viene calcolato utilizzando la funzione di prodotto vettoriale DXVec3Cross(). Ciò è necessario affinché la superficie abbia volume e reagisca alla sorgente luminosa, creando ombre e luci realistiche.
- Telecamera - la posizione dell'osservatore viene calcolata utilizzando coordinate sferiche, il che consente una rotazione intuitiva attorno ad un oggetto ("orbita") senza l'effetto di ribaltamento della telecamera, limitando l'angolo verticale.
Il codice sorgente completo dell'EA è contenuto nel file DX_OptimizationSurface.mq5 allegato all'articolo.
Esempi del funzionamento dell'EA in diverse modalità sono illustrati nelle immagini seguenti:
Fig. 4. Modalità di profitto
Fig. 5. Modalità rumore
Ora disponiamo di una serie completa di strumenti: possiamo costruire una scena, riempirla con dati di ottimizzazione e dare all'utente la possibilità di esplorare questi dati ruotando e ingrandendo l'immagine. Ma prima di affrettarci a implementare il 3D ovunque possibile, vediamo innanzitutto dove questa tecnologia è superflua e dove è meglio attenersi ai grafici convenzionali.
Quando è meglio attenersi ai grafici tradizionali?
Uno strumento è utile solo quando risolve un problema più velocemente e con maggiore precisione rispetto al suo predecessore. Abbiamo esplorato l'architettura della pipeline DX, imparato a generare superfici e a controllare la telecamera. Tuttavia, nel trading, è necessario distinguere tra una visualizzazione analitica utile e il caos del rumore visivo. L'integrazione della grafica 3D in MetaTrader 5 non è fine a se stessa, ma un modo per risolvere dei problemi. Consideriamo i casi in cui abbandonare il 3D a favore del classico 2D risulti tecnicamente ed economicamente giustificato.
Regola della dimensionalità dei dati
Formuliamo una regola per la necessità della visualizzazione 3D - la terza dimensione deve veicolare informazioni univoche:
- Il 3D è inutile se si cerca di visualizzare una serie temporale (Prezzo/Tempo) in uno spazio tridimensionale. Aggiungere un asse Z che duplica il prezzo o il tempo non farebbe altro che complicare la percezione, costringendo il trader a ruotare la scena per visualizzare la normale linea di tendenza.
- La visualizzazione 3D è necessaria per analizzare i risultati dell'ottimizzazione. Qui abbiamo due input indipendenti (ad esempio, il periodo della media mobile e la dimensione dello stop loss) e un output (profitto o fattore di profitto). Senza una superficie 3D, vedremo solo un insieme di punti. La grafica 3D ci permette di visualizzare la relazione tra due variabili e il risultato.
Se il vostro set di dati si riduce a una coppia di coordinate, un normale grafico 2D (DRAW_LINE, DRAW_HISTOGRAM) sarà più efficiente.
Limitazioni hardware e VPS
La grafica 3D in MQL5 si basa su DirectX 11. Ciò richiede il supporto di Shader Model 5.0 e un certo livello di prestazioni della CPU/GPU.
La maggior parte degli sviluppatori professionisti esegue i propri Expert Advisor (EA) su server virtuali privati (VPS). Molte configurazioni VPS standard non dispongono di schede grafiche potenti o di accelerazione hardware DirectX. In tali ambienti, la chiamata CreateBitmapLabel() potrebbe causare un errore e/o un arresto anomalo dell'EA.
Pertanto, se il programma è progettato per funzionare in modalità "cieca", ovvero senza grafica, o su un server economico, il modulo 3D dovrebbe essere disabilitato o sostituito con la registrazione dei dati su un file/database. La CPU è in grado di renderizzare facilmente la grafica 2D anche senza una scheda video.
Velocità di percezione - superficie 3D contro mappa di calore
Una delle principali alternative a una superficie 3D è una heat map bidimensionale (HeatMap). Le differenze fondamentali tra di loro sono le seguenti:
- 3D — mostra l'altezza (Z) come elevazione fisica. Richiede la rotazione per analizzare accuratamente tutti i lati e le pendenze nascoste.
- HeatMap — mostra il valore (Z) come colore di una cella sul piano (X, Y).
Se l'obiettivo è trovare rapidamente l'ottimizzazione massima (la zona più verde), la HeatMap nell'indicatore standard funzionerà più velocemente e richiederà meno codice.
Interattività vs Monitoraggio
Le scene 3D interattive (come il nostro EA DX_OptimizationSurface) richiedono la partecipazione attiva dell'utente. Dovete posizionare il cursore del mouse sopra di esso, tenere premuto il pulsante e ruotare.
Se lo scopo del grafico è il monitoraggio (il tracciamento in background della situazione), allora la grafica 3D interattiva può essere fonte di distrazione e rallentare il processo.
Se l'obiettivo è una presentazione o un'analisi approfondita prima del lancio di una strategia, allora il 3D è indispensabile.
Conclusioni
Utilizzare la modellazione 3D solo quando il volume dei dati o la loro struttura rendono impossibile l'analisi in 2D. Se il vostro obiettivo è trovare rapidamente il massimo in una tabella di ottimizzazione, una mappa di calore sarà più adatta allo scopo. La grafica 3D è necessaria quando si ha bisogno di vedere la forma della relazione, e non solo il suo valore.
In altri casi, le classi MQL5 standard (CCanvas per oggetti 2D o grafici) rappresenteranno una soluzione più semplice.
Conclusioni
Siamo passati dalla giustificazione teorica della necessità della grafica 3D alla creazione di un EA interattivo completo. L'EA DX_OptimizationSurface dimostra che MetaTrader 5 non è solo un terminale per l'esecuzione degli ordini, ma una potente piattaforma per l'analisi visiva dei dati.
Principali risultati:
- Efficienza cognitiva - la visualizzazione 3D ci permette di trasformare infinite sequenze di numeri in paesaggi visivi. Ciò riduce il carico cognitivo sul trader, consentendogli di distinguere visivamente i plateau di profitto stabili dai pericolosi picchi di overfitting.
- Accessibilità tecnologica - il supporto integrato per DirectX 11 e le classi native CCanvas3D e CDXMesh offrono agli sviluppatori gli strumenti per risolvere i problemi di trading. Non è necessario scrivere codice C++ o includere librerie esterne - tutto è già integrato in MQL5. Non resta che approfittarne.
- L'importanza dell'architettura - come abbiamo visto, l'integrazione della grafica richiede un'attenta considerazione del ciclo di vita delle risorse e dell'asincronia. Spostare le operazioni di calcolo in un timer e liberare correttamente la memoria garantisce che non ci saranno problemi di prestazioni del terminale.
La visualizzazione è una lente, non un ornamento. Dovrebbe rispondere a una specifica domanda analitica.
Abbiamo creato questo strumento non per ottenere un impatto esterno, ma per velocizzare il processo decisionale. Se la grafica 3D vi aiuta a comprendere rapidamente il comportamento di una strategia e a scartare le opzioni non valide, allora ha raggiunto il suo scopo. Se si tratta solo di una distrazione, sentitevi liberi di tornare ai collaudati metodi 2D. Uno strumento è prezioso non per il suo aspetto, ma per le decisioni che contribuisce ad accelerare.
Prossimi passi - per chi desidera approfondire l'argomento, consigliamo quanto segue:
- Esplorazione della libreria DXMath per lavorare con i quaternioni e le trasformazioni complesse.
- Sviluppo di shader personalizzati (HLSL) per creare effetti di rendering unici.
- Integrazione con Python tramite MetaTrader 5 per la pre-elaborazione dei dati prima della visualizzazione nel terminale.
MetaTrader 5 fornisce le basi tecnologiche. Gli strumenti analitici che si possono costruire su questa base dipendono unicamente dalla propria immaginazione e dalla necessità di cercare "perle" in un'infinità di dati di mercato.
Risorse consigliate per imparare a lavorare con DirectX in MetaTrader 5:
- Guida di MetaEditor per DirectX,
- Come creare grafica 3D utilizzando DirectX in MetaTrader 5,
- Tutorial su DirectX (Parte I): Disegnare il primo triangolo (in Inglese),
- Visualizzare una strategia nel tester di MetaTrader 5.
Elenco dei file allegati all'articolo:
| Nome del File | Descrizione |
|---|---|
| DX_OptimizationSurface.mq5 | File contenente il codice di un Expert Advisor (EA) di prova con visualizzazione 3D interattiva. |
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/22393
Avvertimento: Tutti i diritti su questi materiali sono riservati a MetaQuotes Ltd. La copia o la ristampa di questi materiali in tutto o in parte sono proibite.
Arriva il Nuovo MetaTrader 5 e MQL5
MetaTrader 5: Costruire un mercato su misura per la propria strategia — grafici Renko, Range ed Equal-Volume, simboli sintetici e stress test con simboli personalizzati.
Utilizza i canali MQL5.community e le chat di gruppo
Architettura basata sugli eventi in MQL5: Come convertire un Expert Advisor in un sistema di trading a tutti gli effetti
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso
Wow. È davvero fantastico! Grazie. Lo smonterò...
FANTASTICO..........