Come integrare il sentiment delle news per proteggere la tua strategia in MQL5
23 min
Il problema: la mia strategia viene continuamente interrotta da news improvvise
Nel trading automatizzato di oggi, anche la logica più robusta può essere invalidata runtime da notizie dell’ultima ora. I mercati finanziari si muovono rapidamente e, nell’attuale clima geopolitico, eventi inaspettati possono stravolgere anche la strategia di trading più sofisticata nel giro di pochi minuti. Ad esempio, un’improvvisa escalation in Medio Oriente o cambiamenti repentini nelle prospettive macroeconomiche possono innescare istantaneamente volatilità, oscillazioni valutarie e reazioni a catena sui mercati globali. Al giorno d’oggi integrare un meccanismo per mitigare questo rischio non è più opzionale.
Il concetto chiave: il sentiment come filtro dinamico
La maggior parte dei trader utilizza i calendari economici per evitare di operare in prossimità di eventi macroeconomici importanti. Tuttavia, la direzione dell’impatto di una notizia è spesso più preziosa della semplice volatilità. Utilizzando un’API che aggrega e analizza le notizie finanziarie globali in tempo reale, possiamo creare un filtro che:
- Convalida gli ingressi: apre una posizione solo se la direzione è coerente con il sentiment.
- Protegge i profitti / minimizza le perdite: chiude una posizione esistente se il sentiment si inverte contro l’operazione in corso (segnale di inversione).
Quindi, l’idea di base per evitare interruzioni della strategia causate dalle notizie in arrivo è: (1) usare le news come conferma aggiuntiva per gli ingressi, e (2) includerle tra le condizioni utilizzate per valutare le uscite.
Come funziona in pratica
Per capire come si comporta il filtro del sentiment in uno scenario di trading reale, analizziamo un esempio concreto.
Supponiamo di avere un EA che entra long quando una media mobile veloce incrocia al rialzo una media mobile lenta, ed esce quando un indicatore RSI diventa ipercomprato. Senza alcun filtro del sentiment, questo EA aprirebbe una posizione ogni volta che le medie mobili si incrociano, indipendentemente dal contenuto delle news.
Ora aggiungiamo il filtro sul sentiment. Poniamo che il sentiment attuale per lo strumento sia “bearish” (ribassista) con una confidenza di 85%. Anche se la condizione di incrocio delle medie mobili è soddisfatta, l’EA non aprirà una posizione long perché il sentiment non conferma un orientamento rialzista. L’operazione viene semplicemente saltata. Questo è particolarmente utile durante i periodi in cui gli indicatori tecnici forniscono segnali contraddetti da forti notizie negative.
Supponiamo poi che l’EA abbia già una posizione long aperta. Il mercato si sta muovendo a nostro favore e siamo in profitto. All’improvviso si verifica un importante evento geopolitico o macroeconomico e il sentiment per l’instrument passa a “bearish” con un livello di confidenza del 72%. Il nostro EA rileva questo cambiamento e attiva un’uscita anticipata, anche se l’RSI ancora non è in ipercomprato e lo Stop Loss è lontano. Di conseguenza, evitiamo un drawdown più profondo che si sarebbe verificato se l’EA avesse continuato a mantenere ciecamente la posizione.
In uno scenario diverso, immaginiamo che il sentiment sia “neutral” (neutrale) o presenti una confidenza molto bassa. In questo caso, la maggior parte delle implementazioni ignorerà semplicemente il sentiment sia per gli ingressi che per le uscite e l’EA farà trading basandosi solo sui segnali tecnici. Ciò impedisce all’EA di reagire in modo eccessivo a notizie rumorose o ambigue.
Il punto chiave da tener presente è che il filtro sul sentiment non sostituisce la logica di trading di base dell’EA. Agisce invece come un livello aggiuntivo di conferma e protezione, aiutando a:
- Evitare di entrare in operazioni quando il sentiment delle news contraddice chiaramente la direzione desiderata.
- Uscire anticipatamente dalle posizioni quando notizie improvvise iniziano a muovere il mercato contro l’operazione.
- Rimanere fuori dal mercato durante periodi di alta incertezza o sentiment misto.
Questo approccio a doppio livello è prezioso nei mercati che si muovono rapidamente: gli indicatori tecnici possono mostrare un ritardo rispetto alla price action e la volatilità guidata dalle notizie può causare ampie oscillazioni in poco tempo. Da considerare comunque che il sentiment delle news non si traduce automaticamente nella direzione del prezzo. Infatti una headline “bullish” (rialzista) potrebbe non spingere il prezzo più in alto se il mercato lo ha già scontato (“buy the rumour, sell the news”). In alcuni casi gli investitori reagiscono in modo non lineare (ad esempio, “bad news is good news”), pertanto l’analisi del sentiment deve essere considerata come uno strumento complementare da combinare con l’analisi di prezzo, volume, liquidità, contesto macro (tempistica degli eventi, aspettative, posizionamento), ecc. prima di prendere decisioni di trading.
L’implementazione: integrare l’analisi del sentiment in un Expert Advisor
Integrare un tale meccanismo in un Expert Advisor è piuttosto semplice.
L’integrazione si basa sulla funzione WebRequest di MQL5 per comunicare con un’API REST. L’API restituisce un payload JSON contenente il sentiment attuale, che può essere “bullish”, “bearish” o “neutral”, insieme a un punteggio di confidenza.
Preparare i dati
Per cominciare, a livello globale definiamo una struttura per memorizzare il sentiment delle notizie e i dati necessari per mantenere le informazioni sul sentiment durante il ciclo di vita dell’EA. Creiamo a questo scopo una struttura SentimentData per memorizzare il titolo (headline), la previsione del sentiment tra “bullish”, “bearish” o “neutral”, il punteggio di confidenza che può variare in un intervallo da 0.0 a 1.0 e la fonte di ciascuna notizia che può essere “live” o “cache”. Dichiariamo inoltre un array a dimensione fissa per conservare sia il sentiment attuale che una breve cronologia delle notizie precedenti. Infine, definiamo lo strumento che vogliamo analizzare (ad es. “EURUSD”), l’URL dell’API e la chiave API. Questa configurazione è minima ma sufficiente per comunicare con l’API del sentiment e memorizzare i dati in modo strutturato.
struct SentimentData {
string headline;
string prediction;
double confidence;
string source;
};
#define NEWS_ITEM_NUM 10 // this size accommodates both the current news item and historical items
SentimentData sentimentHistory[NEWS_ITEM_NUM];
const string newsInstrument = "EURUSD";
const string apiUrl = "https://trading-sentiment-aggregator-ai.p.rapidapi.com/analyze";
const string apiKey = "your-api-key";
Recuperare il sentiment
In questo passaggio viene fatta la chiamata effettiva all’API esterna per il recupero del sentiment più recente relativo all’instrument di interesse. L’API che restituisce il news sentiment attuale si chiama Trading Sentiment Aggregator AI ed è disponibile con un piano gratuito sul portale RapidAPI. Questa API raccoglie automaticamente le notizie finanziarie da molteplici fonti e include un modello di IA specializzato in finanza che predice istantaneamente se le headline segnalano un orientamento di mercato rialzista o ribassista, fornendo anche un punteggio di confidenza.
È possibile chiamare l’API per qualsiasi instrument (forex, indici, azioni, ecc.) purché sia disponibile nella fonte principale utilizzata dall’API per ottenere le news, che è Yahoo Finance RSS. Il sentiment viene analizzato sulle singole news le quali vengono elaborate in lingua inglese non appena disponibili dalle fonti. Se la news disponibile non è cambiata dall’ultima richiesta, viene restituita sempre la stessa. Un meccanismo di caching lato API garantisce risposte rapide per ridurre al minimo i blocchi quando le chiamate vengono effettuate in modo sincrono.
Utilizziamo la funzione WebRequest di MQL5 per inviare una richiesta POST all’API, includendo l’instrument e il numero di elementi che desideriamo nello storico della response. L’API restituisce un payload JSON che analizzeremo tramite la libreria JSON Serialization and Deserialization (native MQL) — library for MetaTrader 5 . Se la richiesta va a buon fine (HTTP 200), estraiamo il sentiment attuale insieme all’array opzionale dello storico e li memorizziamo nel nostro array globale sentimentHistory. Controlliamo inoltre se l’headline appena scaricata è nuova rispetto all’ultima chiamata e, in tal caso, stampiamo un messaggio di log. Siccome WebRequest è una chiamata sincrona, per evitare il blocco dell’EA ad ogni tick utilizzeremo la funzione OnTimer impostata a intervalli regolari (es. ogni 4 ore).
bool fetchSentimentData() {
char postData[], result[];
string resultHeaders;
string headers = "Content-Type: application/json\r\n" +
"x-rapidapi-host: trading-sentiment-aggregator-ai.p.rapidapi.com\r\n" +
"x-rapidapi-key: " + apiKey + "\r\n";
CJAVal payload;
payload["instrument"] = newsInstrument;
payload["lookback"] = NEWS_ITEM_NUM - 1; // max number of past news we want in addition to the current one
string jsonPayload = payload.Serialize();
StringToCharArray(jsonPayload, postData, 0, WHOLE_ARRAY, CP_UTF8);
ArrayResize(postData, ArraySize(postData) - 1);
ResetLastError();
int res = WebRequest("POST", apiUrl, headers, 5000, postData, result, resultHeaders);
if(res == 200) {
string responseText = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
CJAVal response;
if(response.Deserialize(responseText)) {
string prevHeadline = sentimentHistory[0].headline;
// reset sentiment history
for(int i=0; i < ArraySize(sentimentHistory); i++) {
sentimentHistory[i].headline = "";
sentimentHistory[i].prediction = "none";
sentimentHistory[i].confidence = 0.0;
sentimentHistory[i].source = "";
}
// manage current news data
sentimentHistory[0].headline = response["current"]["headline"].ToStr();
sentimentHistory[0].prediction = response["current"]["prediction"].ToStr();
sentimentHistory[0].confidence = response["current"]["confidence"].ToDbl();
sentimentHistory[0].source = response["current"]["source"].ToStr();
if(StringCompare(prevHeadline, sentimentHistory[0].headline, false) != 0) {
Print(__FUNCTION__,
" > New sentiment: \"", sentimentHistory[0].headline, "\". Prediction=", sentimentHistory[0].prediction, "; confidence=", sentimentHistory[0].confidence, "; source=", sentimentHistory[0].source
);
}
// manage historical news data
if(response.HasKey("history")) {
CJAVal historyArray = response["history"];
int downloadedHistorySize = historyArray.Size();
int sentimentHistoryArrayIdx = 1;
for(int i = 0; i < downloadedHistorySize && sentimentHistoryArrayIdx < ArraySize(sentimentHistory); i++, sentimentHistoryArrayIdx++) {
sentimentHistory[sentimentHistoryArrayIdx].headline = historyArray[i]["headline"].ToStr();
sentimentHistory[sentimentHistoryArrayIdx].prediction = historyArray[i]["prediction"].ToStr();
sentimentHistory[sentimentHistoryArrayIdx].confidence = historyArray[i]["confidence"].ToDbl();
sentimentHistory[sentimentHistoryArrayIdx].source = historyArray[i]["source"].ToStr();
}
}
return true;
}
} else {
Print(__FUNCTION__, " > Could not retrieve sentiment for instrument ", newsInstrument, " (last error: ", GetLastError(), ", http code: ", res, ")");
}
return false;
}
Una piccola funzione di utilità
Per aiutare il nostro EA a prendere decisioni “informate” vogliamo che il sentiment delle notizie sia chiaramente rialzista o ribassista. Poiché le notizie scaricate possono anche essere neutrali, includiamo una funzione di utilità che restituisce il primo evento significativo, se presente, altrimenti un elemento vuoto.
SentimentData getInstrumentSentiment() {
SentimentData retData = {"", "none", 0.0, ""};
for(int i = 0; i < ArraySize(sentimentHistory); i++) {
if(sentimentHistory[i].prediction != "neutral") {
retData = sentimentHistory[i];
break;
}
}
return retData;
}
Filtrare le entrate
Una volta ottenuto il sentiment più recente, possiamo usarlo come filtro dinamico per un ingresso a mercato. L’idea è semplice: anche se tutte le condizioni tecniche per l’entry sono soddisfatte (es. un segnale da indicatori come l’incrocio di medie mobili), l’ordine verrà inoltrato al broker solo se il sentiment attuale è coerente con la direzione prevista e presenta una confidenza sufficiente. In questo modo i dati del sentiment funzionano da semaforo verde finale. Ad esempio, per un ordine di acquisto (buy) richiediamo che il sentiment sia “bullish” con una confidenza superiore a una determinata soglia come ad es. 0.7. Per un ordine di vendita (sell) vogliamo che sia “bearish” con una confidenza minima simile. Questo meccanismo impedisce all’EA di entrare a mercato quando il sentiment delle notizie contraddice chiaramente il segnale tecnico, il che può essere particolarmente utile durante eventi macroeconomici importanti o improvvisi cambiamenti di mercato.
bool buyCondition = true; // <-- placeholder for your strategy
const double minConfidence = 0.7;
SentimentData sentiment = getInstrumentSentiment();
bool newsSentimentOk = (sentiment.prediction == "bullish" && sentiment.confidence >= minConfidence);
if(buyCondition && newsSentimentOk) {
// Proceed with the trade
}
Valutare le uscite
Valutare il momento giusto per chiudere le posizioni è uno dei compiti più difficili nel trading, sia manuale che automatizzato. Gli stessi dati di sentiment possono essere utilizzati anche per migliorare la gestione delle uscite dai trade. Invece di affidarci solo a livelli fissi di take-profit e stop-loss o a segnali di uscita puramente tecnici, possiamo chiudere una posizione esistente se il sentiment si inverte contro l’operazione in corso. Ad esempio, se siamo esposti al rialzo (long) e il sentiment diventa improvvisamente fortemente ribassista, possiamo uscire anticipatamente per limitare le perdite potenziali. Viceversa, se siamo esposti al ribasso (short) e il sentiment diventa fortemente rialzista possiamo chiudere la posizione short. Questo approccio aiuta a proteggere i profitti e a ridurre i drawdown quando le notizie iniziano a muovere il mercato nella direzione opposta a quella dell’operazione. Il sentiment per le uscite viene valutato utilizzando una funzione separata che controlla un’inversione (“sentiment reversal”) rispetto alla direzione della posizione corrente.
Ecco come potrebbe apparire una funzione di questo tipo.
bool determineSentimentReversal() {
SentimentData currentSentiment = getInstrumentSentiment();
if(currentSentiment.prediction == "none" || currentSentiment.prediction == "neutral") {
return false;
}
ulong tkt = 0;
bool positionExists = false;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
tkt = PositionGetTicket(i);
if(tkt > 0 && PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) {
positionExists = true;
break;
}
}
if(!positionExists) return false;
const double minConfidenceReversal = 0.55;
bool isLong = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
bool isBearishReversal = (isLong && currentSentiment.prediction == "bearish" && currentSentiment.confidence >= minConfidenceReversal);
bool isBullishReversal = (!isLong && currentSentiment.prediction == "bullish" && currentSentiment.confidence >= minConfidenceReversal);
return (isBearishReversal || isBullishReversal);
}
Questa funzione può essere utilizzata facilmente in questo modo:
bool exitCondition = true; // <-- placeholder for your strategy
bool isSentimentReversal = determineSentimentReversal();
if(exitCondition && isSentimentReversal) {
// Exit trade
}
EA di esempio
Ora diamo un’occhiata a un’istanza di EA funzionante. Come esempio inseriamo tutto quanto detto sopra in un EA molto semplice che utilizza l’incrocio di una Media Mobile Semplice (SMA) come segnale di ingresso e l’oscillatore RSI come segnale di uscita. Sia i segnali di ingresso che quelli di uscita sono accoppiati al sentiment attuale delle notizie come criterio di verifica aggiuntivo.
//+------------------------------------------------------------------+
//| Example EA - News Sentiment.mq5 |
//+------------------------------------------------------------------+
#define VERSION "1.00"
#property version VERSION
#property strict
#include <Trade/Trade.mqh>
#include <Indicators/Oscilators.mqh>
#include <JAson.mqh>
// input parameters
input group "<--- Trade Management --->";
input double inpLots = 0.1; // Lots
input int maFastLen = 20; // Length of Fast SMA
input int maSlowLen = 40; // Length of Slow SMA
input group "<--- Profit Management --->";
input double tpPricePerc = 0.0; // TP Price % (0: disabled)
input int rsiPeriod = 7; // RSI Period (0: disabled)
input int rsiOverbought = 80; // RSI Overbought
input int rsiOversold = 20; // RSI Oversold
input group "<--- Risk Management --->";
input double slPricePerc = 1.0; // SL Price % (0: disabled)
input group "<--- Sentiment Settings --->";
input string sentimentInstrument = ""; // Sentiment Instrument (empty: disabled)
input ENUM_TIMEFRAMES sentimentUpdateFreq = PERIOD_H4; // Sentiment Update Frequency
input double minConfSentEntry = 0.7; // Min. Confidence for Sentiment Entry
input double minConfSentExit = 0.5; // Min. Confidence for Sentiment Exit
input group "<--- Other Settings --->";
input ulong magicNumber = 0; // Magic Number
// globals
CTrade trade;
// SMA variables
int hFast = INVALID_HANDLE, hSlow = INVALID_HANDLE;
double maFasts[], maSlows[];
// RSI indicator providing exit signals
CiRSI rsi;
// data for news sentiment
struct SentimentData {
string headline;
string prediction;
double confidence;
string source;
};
#define NEWS_ITEM_NUM 10
SentimentData sentimentHistory[NEWS_ITEM_NUM];
const string apiUrl = "https://trading-sentiment-aggregator-ai.p.rapidapi.com/analyze";
const string apiKey = "your-api-key"; // Note: protect your API key. Keeping it in source code is not a good security practice
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
trade.SetExpertMagicNumber(magicNumber);
hFast = iMA(_Symbol, PERIOD_CURRENT, maFastLen, 0, MODE_SMA, PRICE_CLOSE);
if(hFast == INVALID_HANDLE){
Print(__FUNCTION__, " > Could not initialize Fast MA");
return INIT_FAILED;
}
hSlow = iMA(_Symbol, PERIOD_CURRENT, maSlowLen, 0, MODE_SMA, PRICE_CLOSE);
if(hSlow == INVALID_HANDLE){
Print(__FUNCTION__, " > Could not initialize Slow MA");
return INIT_FAILED;
}
if(rsiPeriod > 0) {
if(!rsi.Create(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE)) {
Print(__FUNCTION__, " > Could not initialize RSI indicator");
return INIT_FAILED;
}
}
ArraySetAsSeries(maFasts, true);
ArraySetAsSeries(maSlows, true);
if(StringLen(sentimentInstrument) > 0 && !MQLInfoInteger(MQL_TESTER)) {
int timerSeconds = PeriodSeconds(sentimentUpdateFreq);
EventSetTimer(timerSeconds);
fetchSentimentData();
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
if(hFast != INVALID_HANDLE) {
IndicatorRelease(hFast);
hFast = INVALID_HANDLE;
}
if(hSlow != INVALID_HANDLE) {
IndicatorRelease(hSlow);
hSlow = INVALID_HANDLE;
}
if(rsiPeriod > 0) rsi.FullRelease(true);
ArrayFree(maFasts);
ArrayFree(maSlows);
if(StringLen(sentimentInstrument) > 0 && !MQLInfoInteger(MQL_TESTER)) EventKillTimer();
}
//+------------------------------------------------------------------+
//| Expert OnTimer function |
//+------------------------------------------------------------------+
void OnTimer() {
if(!MQLInfoInteger(MQL_TESTER)) fetchSentimentData();
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
static datetime lastBarTime = 0;
datetime curBarTime = iTime(_Symbol, PERIOD_M1, 0);
if(lastBarTime != curBarTime) {
lastBarTime = curBarTime;
return; // run logic once per new 1-min bar
}
if(positionsTotal() <= 0) { // currently not trading. Manage entry
// determine entry conditions
bool longCondition = false, shortCondition = false;
if(CopyBuffer(hFast, 0, 0, maFastLen, maFasts) != -1 && CopyBuffer(hSlow, 0, 0, maSlowLen, maSlows) != -1) {
longCondition = crossover(maFasts, maSlows);
shortCondition = crossunder(maFasts, maSlows);
}
// get latest sentiment for configured instrument
SentimentData curSentiment = {"", "none", 0.0, ""};
if(StringLen(sentimentInstrument) > 0) {
curSentiment = getInstrumentSentiment();
}
bool newsSentimentOk = true;
if(longCondition) {
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double slPrice = slPricePerc > 0 ? NormalizeDouble(ask - ask * (slPricePerc / 100), _Digits) : 0;
double tpPrice = tpPricePerc > 0 ? NormalizeDouble(ask + ask * (tpPricePerc / 100), _Digits) : 0;
if(!MQLInfoInteger(MQL_TESTER) && StringLen(sentimentInstrument)) {
newsSentimentOk = curSentiment.prediction == "bullish" && curSentiment.confidence >= minConfSentEntry;
}
if(newsSentimentOk) trade.Buy(inpLots, _Symbol, ask, slPrice, tpPrice);
} else if(shortCondition) {
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double slPrice = slPricePerc > 0 ? NormalizeDouble(bid + bid * (slPricePerc / 100), _Digits) : 0;
double tpPrice = tpPricePerc > 0 ? NormalizeDouble(bid - bid * (tpPricePerc / 100), _Digits) : 0;
if(!MQLInfoInteger(MQL_TESTER) && StringLen(sentimentInstrument)) {
newsSentimentOk = curSentiment.prediction == "bearish" && curSentiment.confidence >= minConfSentEntry;
}
if(newsSentimentOk) trade.Sell(inpLots, _Symbol, bid, slPrice, tpPrice);
}
} else { // currently trading. Manage exit
// determine exit conditions for RSI-based TP
if(rsiPeriod > 0) {
rsi.Refresh();
bool exitLongCond = rsi.Main(0) > rsiOverbought;
bool exitShortCond = rsi.Main(0) < rsiOversold;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
ulong tkt = PositionGetTicket(i);
if(tkt > 0 && PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) {
if(PositionGetDouble(POSITION_PROFIT) > 0.0) {
if(exitLongCond || exitShortCond) {
closeOpenPositions();
return;
}
}
}
}
}
if(StringLen(sentimentInstrument) > 0) {
if(determineSentimentReversal()) {
closeOpenPositions();
}
}
}
}
//+------------------------------------------------------------------+
//| Count EA's positions |
//+------------------------------------------------------------------+
int positionsTotal() {
int totalPos = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
if(PositionGetTicket(i) != 0) {
if(PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) totalPos++;
}
}
return totalPos;
}
//+-------------------------------------------------------------------+
//| Close all EA's positions |
//+-------------------------------------------------------------------+
void closeOpenPositions() {
CTrade trd;
for(int i = PositionsTotal()-1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if(ticket > 0) {
if(PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) {
trd.SetExpertMagicNumber(magicNumber);
trd.PositionClose(ticket);
}
}
}
}
//+----------------------------------------------------------------------+
//| Determine whether a crossover happened between two series of values |
//+----------------------------------------------------------------------+
bool crossover(const double &series1[], const double &series2[]) {
if(ArraySize(series1) < 2 || ArraySize(series2) < 2) return false;
return series1[ArraySize(series1) - 1] > series2[ArraySize(series2) - 1] &&
series1[ArraySize(series1) - 2] < series2[ArraySize(series2) - 2];
}
//+----------------------------------------------------------------------+
//| Determine whether a crossunder happened between two series of values |
//+----------------------------------------------------------------------+
bool crossunder(const double &series1[], const double &series2[]) {
if(ArraySize(series1) < 2 || ArraySize(series2) < 2) return false;
return series1[ArraySize(series1) - 1] < series2[ArraySize(series2) - 1] &&
series1[ArraySize(series1) - 2] > series2[ArraySize(series2) - 2];
}
//+------------------------------------------------------------------+
//| Get news sentiment data |
//+------------------------------------------------------------------+
bool fetchSentimentData() {
char postData[], result[];
string resultHeaders;
string headers = "Content-Type: application/json\r\n" +
"x-rapidapi-host: trading-sentiment-aggregator-ai.p.rapidapi.com\r\n" +
"x-rapidapi-key: " + apiKey + "\r\n";
CJAVal payload;
payload["instrument"] = sentimentInstrument;
payload["lookback"] = NEWS_ITEM_NUM - 1; // max number of past news we want in addition to the current one
string jsonPayload = payload.Serialize();
StringToCharArray(jsonPayload, postData, 0, WHOLE_ARRAY, CP_UTF8);
ArrayResize(postData, ArraySize(postData) - 1);
ResetLastError();
int res = WebRequest("POST", apiUrl, headers, 5000, postData, result, resultHeaders);
if(res == 200) {
string responseText = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
CJAVal response;
if(response.Deserialize(responseText)) {
string prevHeadline = sentimentHistory[0].headline;
// reset sentiment history
for(int i=0; i < ArraySize(sentimentHistory); i++) {
sentimentHistory[i].headline = "";
sentimentHistory[i].prediction = "none";
sentimentHistory[i].confidence = 0.0;
sentimentHistory[i].source = "";
}
// manage current news data
sentimentHistory[0].headline = response["current"]["headline"].ToStr();
sentimentHistory[0].prediction = response["current"]["prediction"].ToStr();
sentimentHistory[0].confidence = response["current"]["confidence"].ToDbl();
sentimentHistory[0].source = response["current"]["source"].ToStr();
if(prevHeadline != sentimentHistory[0].headline) {
Print(__FUNCTION__,
" > New sentiment: \"", sentimentHistory[0].headline, "\". Prediction=", sentimentHistory[0].prediction, "; confidence=", sentimentHistory[0].confidence, "; source=", sentimentHistory[0].source
);
}
// manage historical news data
if(response.HasKey("history")) {
CJAVal historyArray = response["history"];
int downloadedHistorySize = historyArray.Size();
int sentimentHistoryArrayIdx = 1;
for(int i = 0; i < downloadedHistorySize && sentimentHistoryArrayIdx < ArraySize(sentimentHistory); i++, sentimentHistoryArrayIdx++) {
sentimentHistory[sentimentHistoryArrayIdx].headline = historyArray[i]["headline"].ToStr();
sentimentHistory[sentimentHistoryArrayIdx].prediction = historyArray[i]["prediction"].ToStr();
sentimentHistory[sentimentHistoryArrayIdx].confidence = historyArray[i]["confidence"].ToDbl();
sentimentHistory[sentimentHistoryArrayIdx].source = historyArray[i]["source"].ToStr();
}
}
return true;
}
} else {
Print(__FUNCTION__, " > Could not retrieve sentiment for instrument ", sentimentInstrument, " (last error: ", GetLastError(), ", http code: ", res, ")");
}
return false;
}
//+------------------------------------------------------------------+
//| Get the first meaningful item from the sentiment array, if any |
//+------------------------------------------------------------------+
SentimentData getInstrumentSentiment() {
SentimentData retData = {"", "none", 0.0, ""};
for(int i = 0; i < ArraySize(sentimentHistory); i++) { // loop starts from the first element which contains the current news item
if(sentimentHistory[i].prediction != "neutral") {
retData = sentimentHistory[i];
break;
}
}
return retData;
}
//+---------------------------------------------------------------------------------+
//| Check if the latest sentiment is mismatching direction of the current position |
//+---------------------------------------------------------------------------------+
bool determineSentimentReversal() {
SentimentData currentSentiment = getInstrumentSentiment();
if(currentSentiment.prediction == "none" || currentSentiment.prediction == "neutral") {
return false;
}
// select position currently open by this EA, if any
ulong tkt = 0;
bool positionExists = false;
for(int i = PositionsTotal() - 1; i >= 0; i--) {
tkt = PositionGetTicket(i);
if(tkt > 0 && PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) {
positionExists = true;
break;
}
}
if(!positionExists) return false;
bool isLong = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
bool isBearishReversal = (isLong && currentSentiment.prediction == "bearish" && currentSentiment.confidence >= minConfSentExit);
bool isBullishReversal = (!isLong && currentSentiment.prediction == "bullish" && currentSentiment.confidence >= minConfSentExit);
return (isBearishReversal || isBullishReversal);
}
Nota: questo EA viene fornito esclusivamente a scopo didattico e non è idoneo per l’uso in produzione.
Errori comuni e suggerimenti pratici
Quando integri il filtro del sentiment nel tuo Expert Advisor ci sono diversi errori comuni e consigli pratici che possono farti risparmiare tempo ed evitare comportamenti imprevisti.
Assicurarsi che lo strumento del sentiment sia impostato correttamente
Nell’esempio di EA sopra riportato, l’input sentimentInstrument determina quale strumento verrà analizzato dall’API. Secondo la documentazione di Trading Sentiment Aggregator AI, il feed RSS di Yahoo Finance è una delle fonti utilizzate per i titoli. Per assicurarti di impostare lo strumento corretto, apri nel browser l’indirizzo https: //feeds.finance.yahoo.com/rss/2.0/headline?s=“INSTRUMENT”®ion=US&lang=en-US
sostituendo “INSTRUMENT” con il tuo strumento. Ad esempio:
https://feeds.finance.yahoo.com/rss/2.0/headline?s=EURUSD=X®ion=US&lang=en-US
In questo modo verifichi che EURUSD esista effettivamente nel feed RSS di Yahoo Finance e possa essere gestito dall’API del sentiment.
La scelta dello strumento dipende dalla coppia di simboli su cui stai facendo trading e la response di questo URL ti permette anche di verificare che l’argomento generale delle news sia adatto alla tua strategia. Ad esempio, se fai trading sulla coppia Forex USDJPY puoi impostare il simbolo completo come strumento o utilizzare solo la valuta base (in questo caso “USD”), purché sia presente su Yahoo Finance. Puoi cercare il tuo instrument e verificare se è supportato qui: https://finance.yahoo.com/lookup/
In alternativa, nell’EA di esempio puoi disattivare completamente il filtro del sentiment lasciando vuoto l’input sentimentInstrument ("").
Scegliere una frequenza di aggiornamento del sentiment ragionevole
Il parametro sentimentUpdateFreq controlla la frequenza con cui l’EA chiama l’API del sentiment. L’uso di una frequenza troppo breve (ad esempio M1 o M5) può comportare:
- Un numero eccessivo di chiamate API che esauriscono rapidamente il piano gratuito su RapidAPI.
- Traffico di rete superfluo che non aggiunge valore poiché il sentiment potrebbe non cambiare così velocemente.
Nell’EA di esempio chiamiamo l’API ogni 4 ore tramite OnTimer, il che rappresenta un buon equilibrio tra reattività e controllo dei costi.
Capire come si comporta la funzione WebRequest nello Strategy Tester
Come specificato nella documentazione di WebRequest, questa funzione non può essere eseguita all’interno dello Strategy Tester di MetaTrader 5. Di conseguenza:
- Il filtro del sentiment non funzionerà durante i backtest.
- L’EA di esempio farà trading basandosi solo sui segnali tecnici quando viene eseguito nel tester, ignorando il sentiment.
- Le metriche osservate nel tester non rifletteranno la strategia combinata (tecnica + sentiment) e non potranno, da sole, convalidare i miglioramenti apportati dal sentiment.
Per consentire l’esecuzione di test standardizzati e ripetibili (walk‑forward, Monte Carlo, out‑of‑sample), è necessario simulare l’interazione con l’API all’interno del tester. Di seguito sono riportate alcune opzioni pratiche per creare un “feed storico del sentiment” deterministico che l’EA in esecuzione nel tester possa leggere.
Proxy / feed registrato (consigliato)
Durante un forward testing salva le risposte reali dell’API in un file (CSV o JSON), includendo timestamp, instrument, punteggio di confidenza/direzione del sentiment e tutti i metadati utilizzati dall’EA.
Poi in vista dell’esecuzione nel tester modifica l’EA per leggere quel file e restituire il valore di sentiment registrato per la candela corrispondente invece di chiamare WebRequest. Questo approccio ricrea il comportamento reale dell’API e ne preserva la tempistica; consente inoltre di replicare scenari identici per i test walk-forward e Monte Carlo (tramite il rimescolamento o il campionamento degli eventi registrati).
Feed manuale
Crea manualmente un file di testo/CSV con eventi di sentiment sintetici, ad esempio una riga per timestamp: AAAA‑MM‑DD HH:MM, instrument, punteggio_sentiment, etichetta_sentiment, ecc.
Utilizza questo file per sottoporre l’EA a uno stress test in condizioni controllate (sequenze rialziste/ribassiste estreme, segnali misti, notizie scarse). Questa opzione è utile per un’analisi della sensibilità e per i casi limite in cui si desidera un controllo preciso sul flusso di input.
Snapshot generato da Curl
Usa uno script (Bash, Python, PowerShell, ecc.) con Curl per chiamare l’API un certo numero di volte e registrare le risposte a un file:
curl --request POST \
--url https://trading-sentiment-aggregator-ai.p.rapidapi.com/analyze \
--header 'Content-Type: application/json' \
--header 'x-rapidapi-host: trading-sentiment-aggregator-ai.p.rapidapi.com' \
--header 'x-rapidapi-key: <your-api-key>' \
--data '{"instrument":"USD","lookback": 10}'
Chiama l’API dallo script ad orari desiderati per raccogliere le headline insieme all’analisi del sentiment e salva i campi rilevanti e i timestamp in formato CSV/JSON per una successiva riproduzione nel tester. Questo permette di creare rapidamente un set di dati più ampio e realistico senza dover fare trading effettivo.
Per integrare una di queste opzioni aggiungerai un flag di configurazione nell’EA, come ad esempio sentimentMode = LIVE|CACHE. Quando è in modalità CACHE, l’EA leggerà i valori di sentiment dal file locale e ignorerà le WebRequest.
Suggerimenti pratici per test affidabili:
- Includi il timestamp e l’identificatore dell’instrument nel feed salvato per evitare disallineamenti dovuti a fusi orari, convenzioni sui simboli o cambi di ora legale.
- Includi contesto aggiuntivo nella registrazione (es. testo della headline, successo/fallimento della chiamata API, ecc.) per poter riprodurre casi limite e fare il debug delle discrepanze.
- Per una significatività statistica esegui test out-of-sample e walk-forward utilizzando più periodi registrati; per i test Monte Carlo campiona casualmente o rimescola a blocchi gli eventi registrati, preservando l’ordine intraday se necessario.
- Prima di attivare un trading live confronta i dati salvati durante un forward testing su conto demo con le decisioni prese dall’EA nel tester per rilevare eventuali errori di implementazione.
Aggiungendo un semplice livello di registrazione e riproduzione otterrai la possibilità di testare le strategie potenziate dal sentiment in modo scientifico e riproducibile, riservando WebRequest live solo per le dimostrazioni o l’esecuzione in produzione.
Calibrare attentamente le soglie di confidenza
I parametri minConfSentEntry e minConfSentExit sono le soglie minime per il livello di confidenza che il sentiment deve avere per influenzare le entry e le exit.
- Se imposti minConfSentEntry su un valore troppo alto (ad esempio 0.9), l’EA potrebbe entrare raramente a mercato anche quando il segnale tecnico è forte.
- Se imposti minConfSentExit su un valore troppo basso (ad esempio 0.3), l’EA potrebbe uscire troppo facilmente a causa di segnali di sentiment deboli o rumorosi.
Inizia con valori moderati (ad esempio 0.6–0.7 per gli ingressi, 0.5–0.6 per le uscite) e regolali in base al comportamento dell’EA in condizioni reali di mercato.
Proteggere la key dell’API
Nel codice di esempio l’api-key viene memorizzata direttamente nel sorgente come:
const string apiKey = "your-api-key";
Questo è accettabile a scopi didattici e test ma non è consigliato per la produzione o la distribuzione. In un EA reale dovresti:
- Memorizzare la chiave dell’API all’esterno del codice sorgente (ad esempio in un file di configurazione o in una variabile d’ambiente).
- Recuperare la chiave da un server a runtime dopo l’autenticazione e l’autorizzazione.
- Evitare di condividere il codice sorgente con la chiave inclusa (ad esempio su un repository Git remoto).
Evitare l’overfitting sul sentiment
Tieni presente che il sentiment è un segnale probabilistico, non un predittore deterministico. Evita di:
- Cercare di ottimizzare la strategia per “adattarla perfettamente” ai segnali del sentiment.
- Aspettarti che il filtro del sentiment funzioni perfettamente su ogni singola operazione.
Valuta invece le prestazioni statisticamente nel tempo e su un numero elevato di operazioni. L’obiettivo è migliorare il rendimento complessivo, non eliminare ogni singola perdita.
Conclusioni
Integrando un meccanismo per ottenere il sentiment attuale sull’instrument trattato dall’EA, andiamo oltre la pura price action e l’analisi tecnica incorporando il bias attuale e i fondamentali del mercato. Questo approccio a doppio livello aiuta a evitare falsi ingressi e uscite premature durante i cambiamenti macroeconomici e fornisce un meccanismo di filtro intelligente che gli indicatori tecnici spesso non riescono a cogliere.
Note Importanti
- Responsabilità dell’utilizzatore: Le informazioni contenute in questo articolo devono essere considerate come uno strumento di supporto decisionale e non come una consulenza finanziaria. La responsabilità finale di qualsiasi decisione di trading o di investimento spetta interamente all’utilizzatore. Raccomandiamo vivamente di utilizzare questi dati come uno dei molteplici input all’interno di una strategia solida di gestione del rischio.
- Per utilizzare questa funzionalità in uno dei tuoi EA è necessario aggiungere l’URL https://trading-sentiment-aggregator-ai.p.rapidapi.com/ all’elenco degli URL consentiti nel terminale MT5, sotto il menu Tools > Options > Expert Advisors.
Disclaimer sull’Intelligenza Artificiale
- L’API utilizzata in questo articolo si basa su un modello di IA per l’analisi finanziaria e deve essere usata in modo responsabile.
- Natura probabilistica: il modello utilizza la statistica per categorizzare il sentiment del mercato, pertanto i risultati sono probabilistici e non deterministici. I modelli di IA prevedono pattern basati sul linguaggio e non rappresentano l’assoluta verità.
- Potenziali errori di interpretazione: i modelli di IA possono occasionalmente produrre allucinazioni interpretando in modo errato il gergo finanziario complesso, l’ironia o il contesto di mercato locale. Sebbene questi modelli puntino a un’elevata accuratezza, i singoli dati possono comunque contenere errori.
- Logica di trading: l’utente dovrebbe approcciarsi a questi risultati con una mentalità probabilistica, il che è una pratica comune nel trading professionale. Ciò significa valutare le prestazioni statisticamente nel tempo piuttosto che affidarsi a una singola previsione dell’IA.