Trovare pattern personalizzati nelle coppie di valute in Python utilizzando MetaTrader 5
Introduzione all'analisi dei pattern nel Forex
Cosa vedono i principianti quando guardano per la prima volta i grafici delle coppie di valute? Molte fluttuazioni intraday, aumenti e diminuzioni della volatilità, cambiamenti dei trend e molto altro. Salite, discese, zig-zag - come capire tutto questo? Ho iniziato a conoscere il Forex immergendomi nello studio dell'analisi dei pattern dei prezzi.
Molte cose nel nostro mondo sembrano caotiche a prima vista. Ma ogni specialista esperto vede nella propria sfera personale schemi e possibilità che agli altri sembrano confusi. Lo stesso vale per i grafici delle coppie di valute. Se cerchiamo di sistematizzare questo caos, possiamo scoprire modelli nascosti che possono suggerire il futuro movimento dei prezzi.
Ma come trovarli? Come distinguere un pattern reale da un rumore casuale? Qui è dove inizia il divertimento. Ho deciso di creare un mio sistema di analisi dei pattern utilizzando Python e MetaTrader 5. Una sorta di simbiosi tra matematica e programmazione per conquistare il Forex.
L'idea era quella di studiare molti dati storici utilizzando un algoritmo che trovasse pattern ripetuti e ne valutasse le prestazioni. Sembra interessante? In realtà, l'implementazione non si è rivelata così semplice.
Impostazione dell'ambiente: installazione delle librerie necessarie e connessione a MetaTrader 5
Quindi, il nostro primo compito è quello di installare Python. Può essere scaricato dal sito web ufficiale python.org. Assicurarsi di selezionare la casella "Add Python to PATH".
Il passo successivo è quello delle librerie. Ne avremo bisogno di alcune. La principale è MetaTrader 5. Inoltre, c'è 'pandas' per lavorare con i dati. E eventualmente 'numpy'. Aprire la riga di comando e digitare:
pip install MetaTrader5 pandas numpy matplotlib pytz
La prima cosa da fare è installare MetaTrader 5 stessa. Scaricatela dal sito web ufficiale del vostro broker e installatela. Niente di complicato.
Ora dobbiamo trovare il percorso del terminale. In genere si tratta di qualcosa come "C:\Program Files\MetaTrader 5\terminal64.exe".
Ora aprite Python e digitate:
import MetaTrader5 as mt5 if not mt5.initialize(path="C:/Program Files/MetaTrader 5/terminal64.exe"): print("MetaTrader 5 initialization failed.") mt5.shutdown() else: print("MetaTrader 5 initialized successfully.")
Avviate. Se viene visualizzato il messaggio di inizializzazione del terminale riuscita, tutto è stato eseguito correttamente.
Volete assicurarvi che tutto funzioni? Cerchiamo di ottenere qualche dato:
import MetaTrader5 as mt5 import pandas as pd from datetime import datetime if not mt5.initialize(): print("Oops! Something went wrong.") mt5.shutdown() eurusd_ticks = mt5.copy_ticks_from("EURUSD", datetime.now(), 10, mt5.COPY_TICKS_ALL) ticks_frame = pd.DataFrame(eurusd_ticks) print("Look at this beauty:") print(ticks_frame) mt5.shutdown()
Se viene visualizzata una tabella di dati, congratulazioni! Avete appena fatto il primo passo nel mondo del trading algoritmico sul Forex utilizzando Python. Non è così difficile come sembra.
Struttura del codice: Funzioni di base e loro scopo
Iniziamo quindi ad analizzare la struttura del codice. Si tratta di un sistema completo per l'analisi dei pattern nel mercato dei cambi.
Inizieremo con l'elemento principale del sistema - la funzione find_patterns. Questa funzione esamina i dati storici, identificando i pattern di una determinata lunghezza. Dopo aver trovato i primi pattern, dobbiamo valutare la loro efficienza. Questa funzione memorizza anche l'ultimo pattern per un uso futuro.
La funzione successiva è calculate_winrate_and_frequency. Questa funzione analizza i pattern trovati - la frequenza di occorrenza, la percentuale di vincita e la classificazione dei pattern.
Anche la funzione process_currency_pair svolge un ruolo importante. Si tratta di un gestore piuttosto importante. Carica i dati, li esamina, cerca pattern di diversa lunghezza e fornisce anche i 300 modelli principali per vendite e acquisti. Per quanto riguarda l'inizio del codice, ecco l'inizializzazione, le impostazioni dei parametri, l'intervallo del grafico (TF) e il periodo di tempo (nel mio caso, dal 1990 al 2024).
Passiamo ora al ciclo di esecuzione del codice principale. Le caratteristiche dell'algoritmo di ricerca dei pattern includono diverse lunghezze di pattern, poiché quelli corti sono comuni ma non forniscono affidabilità, mentre quelli lunghi sono troppo rari, sebbene siano più efficaci. Dobbiamo considerare tutte le dimensioni.
Ottenere dati da MetaTrader 5: funzione copy_rates_range
La nostra prima funzione deve ricevere i dati dal terminale. Esaminiamo il codice:
import MetaTrader5 as mt5 import pandas as pd import time from datetime import datetime, timedelta import pytz # List of major currency pairs major_pairs = ['EURUSD'] # Setting up data request parameters timeframe = mt5.TIMEFRAME_H4 start_date = pd.Timestamp('1990-01-01') end_date = pd.Timestamp('2024-05-31') def process_currency_pair(symbol): max_retries = 5 retries = 0 while retries < max_retries: try: # Loading OHLC data rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date) if rates is None: raise ValueError("No data received") ohlc_data = pd.DataFrame(rates) ohlc_data['time'] = pd.to_datetime(ohlc_data['time'], unit='s') break except Exception as e: print(f"Error loading data for {symbol}: {e}") retries += 1 time.sleep(2) # Wait before retrying if retries == max_retries: print(f"Failed to load data for {symbol} after {max_retries} attempts") return # Further data processing...
Cosa succede in questo codice? Innanzitutto, definiamo le nostre coppie di valute. Al momento abbiamo solo EURUSD, ma è possibile aggiungerne altre. Quindi si imposta l'intervallo di tempo. H4 è di 4 ore. Questa è la temporizzazione ottimale.
Poi ci sono le date. Dal 1990 al 2024. Avremo bisogno di molte quotazioni storiche. Più dati abbiamo, più accurata sarà la nostra analisi. Passiamo ora alla cosa principale - la funzione process_currency_pair. Carica i dati utilizzando copy_rates_range.
Cosa otteniamo come risultato? Un DataFrame con dati storici. Ora, apertura, chiusura, massimo, minimo - tutto ciò che è necessario per lavorare.
Se qualcosa va storto, gli errori vengono identificati, visualizzati sullo schermo e si riprova.
Elaborazione delle serie temporali: Trasformazione dei dati OHLC in direzioni di movimento dei prezzi
Torniamo al nostro compito principale. Vogliamo trasformare le fluttuazioni caotiche del mercato Forex in qualcosa di più ordinato - tendenze e inversioni. Come possiamo farlo? Trasformeremo i prezzi in direzioni.
Ecco il nostro codice:
# Fill missing values with the mean ohlc_data.fillna(ohlc_data.mean(), inplace=True) # Convert price movements to directions ohlc_data['direction'] = np.where(ohlc_data['close'].diff() > 0, 'up', 'down')
Cosa sta succedendo qui? Per prima cosa, colmiamo i vuoti. I vuoti possono peggiorare notevolmente il risultato finale. Li riempiamo con valori medi.
E ora la parte più interessante. Creiamo una nuova colonna denominata "direzione". Qui traduciamo i dati di prezzo in dati che simulano il comportamento del trend. Funziona in modo elementare:
- Se il prezzo di chiusura attuale è superiore a quello precedente, si scrive "up".
- Se è al di sotto, scriviamo "down".
Una formulazione piuttosto semplice, ma molto efficace. Ora, invece di numeri complessi, abbiamo una semplice sequenza di "up" e "down". Questa sequenza è molto più semplice per la percezione umana. Ma perché ne abbiamo bisogno? Questi "up" e "down" sono gli elementi costitutivi dei nostri pattern. È da loro che raccoglieremo un quadro completo di ciò che sta accadendo sul mercato.
Algoritmo di ricerca dei pattern: funzione find_patterns
Abbiamo quindi una sequenza di "up" e "down". Successivamente, cercheremo i pattern che si ripetono in questa sequenza.
Ecco la funzione find_patterns:
def find_patterns(data, pattern_length, direction): patterns = defaultdict(list) last_pattern = None last_winrate = None last_frequency = None for i in range(len(data) - pattern_length - 6): pattern = tuple(data['direction'][i:i+pattern_length]) if data['direction'][i+pattern_length+6] == direction: patterns[pattern].append(True) else: patterns[pattern].append(False) # Check last prices for pattern match last_pattern_tuple = tuple(data['direction'][-pattern_length:]) if last_pattern_tuple in patterns: last_winrate = np.mean(patterns[last_pattern_tuple]) * 100 last_frequency = len(patterns[last_pattern_tuple]) last_pattern = last_pattern_tuple return patterns, last_pattern, last_winrate, last_frequency
Come funziona il tutto?
- Creiamo il dizionario "patterns". Questo servirà come una sorta di libreria in cui memorizzare tutti i pattern trovati.
- Poi iniziamo a iterare i dati. Prendiamo un campione di dati di pattern_length (che può essere 3, 4, 5, ecc. fino a 25) e osserviamo cosa succede 6 barre dopo di esso.
- Se dopo 6 barre il prezzo si muove nella direzione desiderata (al rialzo per i pattern di acquisto o al ribasso per i pattern di vendita), impostiamo True. In caso contrario - False.
- Questo viene fatto per tutti i possibili campioni di dati. Dovremmo ottenere pattern simili: "up-up-down" - True, "down-up-up" - False e così via.
- Successivamente, verifichiamo se uno dei pattern incontrati in precedenza è in corso di formazione. In caso affermativo, ne calcoliamo il tasso di vincita (percentuale di successi) e la frequenza di occorrenza.
È così che trasformiamo una semplice sequenza di "up" e "down" in uno strumento di previsione piuttosto potente. Ma non è tutto. Successivamente, ordineremo questi pattern, selezioneremo quelli più efficienti e li analizzeremo.
Calcolo delle statistiche dei pattern: Tasso di Vincita e frequenza di occorrenza
Ora che abbiamo una serie di pattern, dobbiamo selezionare i migliori.
Diamo un'occhiata al nostro codice:
def calculate_winrate_and_frequency(patterns): results = [] for pattern, outcomes in patterns.items(): winrate = np.mean(outcomes) * 100 frequency = len(outcomes) results.append((pattern, winrate, frequency)) results.sort(key=lambda x: x[1], reverse=True) return results
Qui prendiamo ogni pattern e i suoi risultati (prima li abbiamo definiti True e False) e poi calcoliamo il tasso di vincita, ovvero la nostra percentuale di produttività. Se un modello funziona 7 volte su 10, la sua percentuale di vincita è del 70%. Contiamo anche la frequenza, ovvero il numero di volte in cui il pattern si è verificato. Più spesso ricorre, più affidabili sono le nostre statistiche. Tutto questo viene inserito nell'elenco "results". E infine, l’ordinamento. Abbiamo messo i pattern migliori in cima alla lista.
Ordinamento dei risultati: Selezione di pattern significativi
Ora abbiamo dati sufficienti. Ma non ne avremo bisogno di tutti. Dobbiamo ordinarli.
filtered_buy_results = [result for result in all_buy_results if result[2] > 20] filtered_sell_results = [result for result in all_sell_results if result[2] > 20] filtered_buy_results.sort(key=lambda x: x[1], reverse=True) top_300_buy_patterns = filtered_buy_results[:300] filtered_sell_results.sort(key=lambda x: x[1], reverse=True) top_300_sell_patterns = filtered_sell_results[:300]
Abbiamo impostato l’ordinamento in questo modo. In primo luogo, eliminiamo tutti i pattern che ricorrono meno di 20 volte. Come dimostrano le statistiche, i pattern rari sono meno affidabili.
Ordiniamo quindi i pattern rimanenti in base alla percentuale di vincita. I più efficienti sono posti all'inizio dell'elenco. Di conseguenza, selezioniamo i 300 migliori. Questo è tutto ciò che dovrebbe rimanere di una moltitudine di pattern, il cui numero supera il migliaio.
Lavorare con pattern di diverse lunghezze: da 3 a 25
Ora dobbiamo selezionare le variazioni del pattern che statisticamente e costantemente produrranno profitti nel trading. Le opzioni differiscono per lunghezza. Possono essere costituiti da 3 o 25 movimenti di prezzo. Controlliamo tutti i possibili:
pattern_lengths = range(3, 25) # Pattern lengths from 3 to 25 all_buy_patterns = {} all_sell_patterns = {} for pattern_length in pattern_lengths: buy_patterns, last_buy_pattern, last_buy_winrate, last_buy_frequency = find_patterns(ohlc_data, pattern_length, 'up') sell_patterns, last_sell_pattern, last_sell_winrate, last_sell_frequency = find_patterns(ohlc_data, pattern_length, 'down') all_buy_patterns[pattern_length] = buy_patterns all_sell_patterns[pattern_length] = sell_patterns
Lanciamo il nostro filtro di ricerca dei pattern per ogni lunghezza da 3 a 25. Perché usiamo questa implementazione? I pattern con meno di tre movimenti sono troppo inaffidabili - ne abbiamo già parlato in precedenza. I pattern di lunghezza superiore a 25 sono troppo rari. Per ogni lunghezza, cerchiamo sia pattern di acquisto che di vendita.
Ma perché abbiamo bisogno di tante lunghezze diverse? I pattern brevi possono catturare rapide inversioni di mercato, mentre quelli lunghi possono mostrare le tendenze a lungo termine. Non sappiamo in anticipo cosa sarà più efficace, quindi testiamo tutto.
Analisi dei pattern di acquisto e vendita
Ora che abbiamo una selezione di pattern di varia lunghezze, è il momento di stabilire quali funzionano davvero.
Ecco il nostro codice in azione:
all_buy_results = [] for pattern_length, patterns in all_buy_patterns.items(): results = calculate_winrate_and_frequency(patterns) all_buy_results.extend(results) all_sell_results = [] for pattern_length, patterns in all_sell_patterns.items(): results = calculate_winrate_and_frequency(patterns) all_sell_results.extend(results)
Prendiamo ogni pattern, sia di acquisto che di vendita, e lo classifichiamo attraverso il nostro calcolatore di frequenza e tasso di vincita.
Ma non ci limitiamo a contare le statistiche. Cerchiamo la differenza tra i pattern di acquisto e di vendita. Perché è importante? Perché il mercato può comportarsi in modo differente quando sale e quando scende. A volte i pattern di acquisto funzionano meglio, mentre a volte i pattern di vendita diventano più affidabili.
Passiamo quindi alla fase successiva, confrontando pattern di lunghezze diverse tra loro. È possibile che i pattern corti funzionino meglio per determinare il punto di ingresso nel mercato, mentre quelli lunghi funzionano meglio per determinare la tendenza a lungo termine. Lo stesso può accadere al contrario. Per questo motivo analizziamo tutto e non scartiamo nulla a priori.
Alla fine di questa analisi, si formano i primi risultati: quali pattern funzionano meglio per l'acquisto, quali per la vendita, quale lunghezza di pattern è più efficace in diverse condizioni di mercato. Con questi dati, possiamo già condurre un'analisi dei prezzi sul mercato Forex.
Ma ricordate che anche il pattern migliore non è una garanzia di successo. Il mercato è pieno di sorprese. Il nostro compito è quello di aumentare le possibilità di successo, e questo è ciò che facciamo analizzando i pattern da tutti i lati.
In prospettiva: Previsioni basate su pattern recenti
Ora è il momento di fare qualche previsione. Diamo un'occhiata al codice del nostro predittore:
if last_buy_pattern: print(f"\nLast buy pattern for {symbol}: {last_buy_pattern}, Winrate: {last_buy_winrate:.2f}%, Frequency: {last_buy_frequency}") print(f"Forecast: Price will likely go up.") if last_sell_pattern: print(f"\nLast sell pattern for {symbol}: {last_sell_pattern}, Winrate: {last_sell_winrate:.2f}%, Frequency: {last_sell_frequency}") print(f"Forecast: Price will likely go down.")
Osserviamo l'ultimo pattern che si è formato e cerchiamo di prevedere il futuro e di eseguire la nostra analisi di trading.
Notare che stiamo considerando due scenari: un pattern di acquisto e uno di vendita. Perché? Perché il mercato è un eterno confronto tra tori e orsi, acquirenti e venditori. Dovremmo essere pronti ad affrontare qualsiasi evento.
Per ogni pattern, vengono forniti tre parametri chiave: il pattern stesso, la sua percentuale di vincita e la sua frequenza di occorrenza. Il tasso di vincita è particolarmente importante. Se un pattern di acquisto ha un tasso di vincita del 70%, significa che il 70% delle volte in cui è apparso questo modello, il prezzo è effettivamente salito. Si tratta di risultati piuttosto buoni. Ma ricordate che anche il 90% non è una garanzia. Nel mondo del Forex c'è sempre spazio per le sorprese.
Anche la frequenza gioca un ruolo importante. Un pattern che si verifica frequentemente è più affidabile di uno raro.
La parte più interessante è la nostra previsione. "Il prezzo probabilmente salirà" o "Il prezzo probabilmente scenderà". Queste previsioni portano una certa soddisfazione per il lavoro svolto. Ma ricordate che anche la previsione più accurata è solo una probabilità, non una garanzia. Il mercato Forex è piuttosto difficile da prevedere. Notizie, eventi economici, persino i tweet di persone influenti possono cambiare la direzione del movimento dei prezzi in pochi secondi.
Pertanto, il nostro codice non è una panacea, ma piuttosto un EA molto intelligente. Può essere interpretato come: "Sulla base dei dati storici, abbiamo ragione di credere che il prezzo salirà (o scenderà)". Spetta a voi decidere se entrare o meno nel mercato. L'applicazione di queste previsioni è un processo ponderato. Avete informazioni sui possibili movimenti, ma ogni passo deve essere fatto con saggezza, tenendo conto della situazione generale del mercato.
Disegnare il futuro: Visualizzazione dei pattern e delle previsioni migliori
Aggiungiamo un po' di magia di visualizzazione al nostro codice:
import matplotlib.pyplot as plt def visualize_patterns(patterns, title, filename): patterns = patterns[:20] # Take top 20 for clarity patterns.reverse() # Reverse the list to display it correctly on the chart fig, ax = plt.subplots(figsize=(12, 8)) winrates = [p[1] for p in patterns] frequencies = [p[2] for p in patterns] labels = [' '.join(p[0]) for p in patterns] ax.barh(range(len(patterns)), winrates, align='center', color='skyblue', zorder=10) ax.set_yticks(range(len(patterns))) ax.set_yticklabels(labels) ax.invert_yaxis() # Invert the Y axis to display the best patterns on top ax.set_xlabel('Winrate (%)') ax.set_title(title) # Add occurrence frequency for i, v in enumerate(winrates): ax.text(v + 1, i, f'Freq: {frequencies[i]}', va='center') plt.tight_layout() plt.savefig(filename) plt.close() # Visualize top buy and sell patterns visualize_patterns(top_300_buy_patterns, f'Top 20 Buy Patterns for {symbol}', 'top_buy_patterns.png') visualize_patterns(top_300_sell_patterns, f'Top 20 Sell Patterns for {symbol}', 'top_sell_patterns.png') # Visualize the latest pattern and forecast def visualize_forecast(pattern, winrate, frequency, direction, symbol, filename): fig, ax = plt.subplots(figsize=(8, 6)) ax.bar(['Winrate'], [winrate], color='green' if direction == 'up' else 'red') ax.set_ylim(0, 100) ax.set_ylabel('Winrate (%)') ax.set_title(f'Forecast for {symbol}: Price will likely go {direction}') ax.text(0, winrate + 5, f'Pattern: {" ".join(pattern)}', ha='center') ax.text(0, winrate - 5, f'Frequency: {frequency}', ha='center') plt.tight_layout() plt.savefig(filename) plt.close() if last_buy_pattern: visualize_forecast(last_buy_pattern, last_buy_winrate, last_buy_frequency, 'up', symbol, 'buy_forecast.png') if last_sell_pattern: visualize_forecast(last_sell_pattern, last_sell_winrate, last_sell_frequency, 'down', symbol, 'sell_forecast.png')
Abbiamo creato due funzioni: visualize_patterns e visualize_forecast. Il primo disegna un grafico a barre orizzontali informativo con i 20 pattern principali, le loro percentuali di vincita e la frequenza di occorrenza. Il secondo crea una rappresentazione visiva della nostra previsione basata sull'ultimo pattern.
Per i pattern, usiamo colonne orizzontali perché possono essere lunghi e questo li rende più facili da leggere. Il nostro colore è piacevole da percepire per l'occhio umano - blu cielo.

Salviamo i nostri capolavori in file PNG.
Test e backtesting del sistema di analisi dei pattern
Abbiamo creato il nostro sistema di analisi dei pattern, ma come facciamo a sapere se funziona davvero? A tal fine, è necessario eseguire un test sui dati storici.
Ecco il codice necessario per questo compito:
def simulate_trade(data, direction, entry_price, take_profit, stop_loss): for i, row in data.iterrows(): current_price = row['close'] if direction == "BUY": if current_price >= entry_price + take_profit: return {'profit': take_profit, 'duration': i} elif current_price <= entry_price - stop_loss: return {'profit': -stop_loss, 'duration': i} else: # SELL if current_price <= entry_price - take_profit: return {'profit': take_profit, 'duration': i} elif current_price >= entry_price + stop_loss: return {'profit': -stop_loss, 'duration': i} # If the loop ends without reaching TP or SL, close at the current price last_price = data['close'].iloc[-1] profit = (last_price - entry_price) if direction == "BUY" else (entry_price - last_price) return {'profit': profit, 'duration': len(data)} def backtest_pattern_system(data, buy_patterns, sell_patterns): equity_curve = [10000] # Initial capital $10,000 trades = [] for i in range(len(data) - max(len(p[0]) for p in buy_patterns + sell_patterns)): current_data = data.iloc[:i+1] last_pattern = tuple(current_data['direction'].iloc[-len(buy_patterns[0][0]):]) matching_buy = [p for p in buy_patterns if p[0] == last_pattern] matching_sell = [p for p in sell_patterns if p[0] == last_pattern] if matching_buy and not matching_sell: entry_price = current_data['close'].iloc[-1] take_profit = 0.001 # 10 pips stop_loss = 0.0005 # 5 pips trade_result = simulate_trade(data.iloc[i+1:], "BUY", entry_price, take_profit, stop_loss) trades.append(trade_result) equity_curve.append(equity_curve[-1] + trade_result['profit'] * 10000) # Multiply by 10000 to convert to USD elif matching_sell and not matching_buy: entry_price = current_data['close'].iloc[-1] take_profit = 0.001 # 10 pips stop_loss = 0.0005 # 5 pips trade_result = simulate_trade(data.iloc[i+1:], "SELL", entry_price, take_profit, stop_loss) trades.append(trade_result) equity_curve.append(equity_curve[-1] + trade_result['profit'] * 10000) # Multiply by 10000 to convert to USD else: equity_curve.append(equity_curve[-1]) return equity_curve, trades # Conduct a backtest equity_curve, trades = backtest_pattern_system(ohlc_data, top_300_buy_patterns, top_300_sell_patterns) # Visualizing backtest results plt.figure(figsize=(12, 6)) plt.plot(equity_curve) plt.title('Equity Curve') plt.xlabel('Trades') plt.ylabel('Equity ($)') plt.savefig('equity_curve.png') plt.close() # Calculating backtest statistics total_profit = equity_curve[-1] - equity_curve[0] win_rate = sum(1 for trade in trades if trade['profit'] > 0) / len(trades) if trades else 0 average_profit = sum(trade['profit'] for trade in trades) / len(trades) if trades else 0 print(f"\nBacktest Results:") print(f"Total Profit: ${total_profit:.2f}") print(f"Win Rate: {win_rate:.2%}") print(f"Average Profit per Trade: ${average_profit*10000:.2f}") print(f"Total Trades: {len(trades)}")
Cosa sta succedendo qui? La funzione simulate_trade è il nostro simulatore di un singolo trade. Monitora il prezzo e chiude l'operazione quando viene raggiunto il take profit o lo stop loss.
backtest_pattern_system è una funzione più importante. Esamina i dati storici, passo dopo passo, giorno dopo giorno, verificando se si è formato uno dei nostri pattern. Avete trovato un pattern di acquisto? Allora compriamo. Ne avete trovato uno da vendere? Vendiamo.
Utilizziamo un take profit fisso di 100 punti e uno stop loss di 50 punti. Dobbiamo stabilire i limiti per un profitto soddisfacente - non troppo, in modo da non rischiare oltre il limite, ma nemmeno troppo poco, in modo che il profitto possa crescere.
Dopo ogni trade aggiorniamo la nostra curva dell’equity. Alla fine del nostro lavoro otteniamo i seguenti risultati: quanto abbiamo guadagnato in totale, quale percentuale di transazioni è stata redditizia, qual è il profitto medio per trade. E, naturalmente, visualizziamo i risultati.

Implementiamo la ricerca di pattern utilizzando il linguaggio MQL5. Ecco il nostro codice:
//+------------------------------------------------------------------+ //| PatternProbabilityIndicator| //| Copyright 2024 | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Your Name Here" #property link "https://www.mql5.com" #property version "1.06" #property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 //--- plot BuyProbability #property indicator_label1 "BuyProbability" #property indicator_type1 DRAW_LINE #property indicator_color1 clrGreen #property indicator_style1 STYLE_SOLID #property indicator_width1 2 //--- plot SellProbability #property indicator_label2 "SellProbability" #property indicator_type2 DRAW_LINE #property indicator_color2 clrRed #property indicator_style2 STYLE_SOLID #property indicator_width2 2 //--- input parameters input int InpPatternLength = 5; // Pattern Length (3-10) input int InpLookback = 1000; // Lookback Period (100-5000) input int InpForecastHorizon = 6; // Forecast Horizon (1-20) //--- indicator buffers double BuyProbabilityBuffer[]; double SellProbabilityBuffer[]; //--- global variables int g_pattern_length; int g_lookback; int g_forecast_horizon; string g_patterns[]; int g_pattern_count; int g_pattern_occurrences[]; int g_pattern_successes[]; int g_total_bars; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- validate inputs if(InpPatternLength < 3 || InpPatternLength > 10) { Print("Invalid Pattern Length. Must be between 3 and 10."); return INIT_PARAMETERS_INCORRECT; } if(InpLookback < 100 || InpLookback > 5000) { Print("Invalid Lookback Period. Must be between 100 and 5000."); return INIT_PARAMETERS_INCORRECT; } if(InpForecastHorizon < 1 || InpForecastHorizon > 20) { Print("Invalid Forecast Horizon. Must be between 1 and 20."); return INIT_PARAMETERS_INCORRECT; } //--- indicator buffers mapping SetIndexBuffer(0, BuyProbabilityBuffer, INDICATOR_DATA); SetIndexBuffer(1, SellProbabilityBuffer, INDICATOR_DATA); //--- set accuracy IndicatorSetInteger(INDICATOR_DIGITS, 2); //--- set global variables g_pattern_length = InpPatternLength; g_lookback = InpLookback; g_forecast_horizon = InpForecastHorizon; //--- generate all possible patterns if(!GeneratePatterns()) { Print("Failed to generate patterns."); return INIT_FAILED; } g_total_bars = iBars(_Symbol, PERIOD_CURRENT); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check for rates total if(rates_total <= g_lookback + g_pattern_length + g_forecast_horizon) { Print("Not enough data for calculation."); return 0; } int start = (prev_calculated > g_lookback + g_pattern_length + g_forecast_horizon) ? prev_calculated - 1 : g_lookback + g_pattern_length + g_forecast_horizon; if(ArraySize(g_pattern_occurrences) != g_pattern_count) { ArrayResize(g_pattern_occurrences, g_pattern_count); ArrayResize(g_pattern_successes, g_pattern_count); } ArrayInitialize(g_pattern_occurrences, 0); ArrayInitialize(g_pattern_successes, 0); // Pre-calculate patterns for efficiency string patterns[]; ArrayResize(patterns, rates_total); for(int i = g_pattern_length; i < rates_total; i++) { patterns[i] = ""; for(int j = 0; j < g_pattern_length; j++) { patterns[i] += (close[i-j] > close[i-j-1]) ? "U" : "D"; } } // Main calculation loop for(int i = start; i < rates_total; i++) { string current_pattern = patterns[i]; if(StringLen(current_pattern) != g_pattern_length) continue; double buy_probability = CalculateProbability(current_pattern, true, close, patterns, i); double sell_probability = CalculateProbability(current_pattern, false, close, patterns, i); BuyProbabilityBuffer[i] = buy_probability; SellProbabilityBuffer[i] = sell_probability; } // Update Comment with pattern statistics if total bars changed if(g_total_bars != iBars(_Symbol, PERIOD_CURRENT)) { g_total_bars = iBars(_Symbol, PERIOD_CURRENT); UpdatePatternStatistics(); } return(rates_total); } //+------------------------------------------------------------------+ //| Generate all possible patterns | //+------------------------------------------------------------------+ bool GeneratePatterns() { g_pattern_count = (int)MathPow(2, g_pattern_length); if(!ArrayResize(g_patterns, g_pattern_count)) { Print("Failed to resize g_patterns array."); return false; } for(int i = 0; i < g_pattern_count; i++) { string pattern = ""; for(int j = 0; j < g_pattern_length; j++) { pattern += ((i >> j) & 1) ? "U" : "D"; } g_patterns[i] = pattern; } return true; } //+------------------------------------------------------------------+ //| Calculate probability for a given pattern | //+------------------------------------------------------------------+ double CalculateProbability(const string &pattern, bool is_buy, const double &close[], const string &patterns[], int current_index) { if(StringLen(pattern) != g_pattern_length || current_index < g_lookback) { return 50.0; // Return neutral probability on error } int pattern_index = ArraySearch(g_patterns, pattern); if(pattern_index == -1) { return 50.0; } int total_occurrences = 0; int successful_predictions = 0; for(int i = g_lookback; i > g_pattern_length + g_forecast_horizon; i--) { int historical_index = current_index - i; if(historical_index < 0 || historical_index + g_pattern_length + g_forecast_horizon >= ArraySize(close)) { continue; } if(patterns[historical_index] == pattern) { total_occurrences++; g_pattern_occurrences[pattern_index]++; if(is_buy && close[historical_index + g_pattern_length + g_forecast_horizon] > close[historical_index + g_pattern_length]) { successful_predictions++; g_pattern_successes[pattern_index]++; } else if(!is_buy && close[historical_index + g_pattern_length + g_forecast_horizon] < close[historical_index + g_pattern_length]) { successful_predictions++; g_pattern_successes[pattern_index]++; } } } return (total_occurrences > 0) ? (double)successful_predictions / total_occurrences * 100 : 50; } //+------------------------------------------------------------------+ //| Update pattern statistics and display in Comment | //+------------------------------------------------------------------+ void UpdatePatternStatistics() { string comment = "Pattern Statistics:\n"; comment += "Pattern Length: " + IntegerToString(g_pattern_length) + "\n"; comment += "Lookback Period: " + IntegerToString(g_lookback) + "\n"; comment += "Forecast Horizon: " + IntegerToString(g_forecast_horizon) + "\n\n"; comment += "Top 5 Patterns:\n"; int sorted_indices[]; ArrayResize(sorted_indices, g_pattern_count); for(int i = 0; i < g_pattern_count; i++) sorted_indices[i] = i; // Use quick sort for better performance ArraySort(sorted_indices); for(int i = 0; i < 5 && i < g_pattern_count; i++) { int idx = sorted_indices[g_pattern_count - 1 - i]; // Reverse order for descending sort double win_rate = g_pattern_occurrences[idx] > 0 ? (double)g_pattern_successes[idx] / g_pattern_occurrences[idx] * 100 : 0; comment += g_patterns[idx] + ": " + "Occurrences: " + IntegerToString(g_pattern_occurrences[idx]) + ", " + "Win Rate: " + DoubleToString(win_rate, 2) + "%\n"; } Comment(comment); } //+------------------------------------------------------------------+ //| Custom function to search for a string in an array | //+------------------------------------------------------------------+ int ArraySearch(const string &arr[], string value) { for(int i = 0; i < ArraySize(arr); i++) { if(arr[i] == value) return i; } return -1; }
Ecco come appare sul grafico:

Creazione di un EA per il rilevamento dei pattern e il trading
Successivamente, ho verificato gli sviluppi nel tester di MetaTrader 5, poiché i test in Python erano andati a buon fine. Anche il codice sottostante è allegato all'articolo. Il codice è un'implementazione pratica del concetto di analisi dei pattern nel mercato dei cambi. Incarna l'idea che i pattern storici dei prezzi possano fornire informazioni statisticamente significative sui futuri movimenti del mercato.
Componenti chiave dell’EA:
- Generazione di pattern - L'EA utilizza una rappresentazione binaria dei movimenti di prezzo (al rialzo o al ribasso), creando tutte le possibili combinazioni per una determinata lunghezza del pattern.
- Analisi statistica - L'EA esegue un'analisi retrospettiva, valutando la frequenza di occorrenza di ciascun pattern e la sua efficacia predittiva.
- Adattamento dinamico - L'EA aggiorna continuamente le statistiche dei pattern per adattarsi alle mutevoli condizioni di mercato.
- Prendere decisioni di trading - In base ai pattern più efficaci identificati per l'acquisto e la vendita, l'EA apre, chiude o mantiene le posizioni.
- Parametrizzazione - L'EA consente di personalizzare i parametri chiave, come la lunghezza del pattern, il periodo di analisi, l'orizzonte di previsione e il numero minimo di occorrenze del pattern da considerare.
In totale, ho realizzato 4 versioni dell'EA: la prima si basa sul concetto dell'articolo, apre operazioni basate sui pattern e le chiude quando viene rilevato un nuovo pattern migliore nella direzione opposta. Il secondo è lo stesso, ma multivaluta: funziona con le 10 coppie Forex più liquide, secondo le statistiche della Banca Mondiale. Il terzo è lo stesso, ma chiude le operazioni quando il prezzo supera un numero di barre superiore all'orizzonte di previsione. L'ultimo chiude con il take profit e lo stop.
Ecco il codice per il primo EA, il resto sarà nei file allegati:
//+------------------------------------------------------------------+ //| PatternProbabilityExpertAdvisor | //| Copyright 2024, Evgeniy Koshtenko | //| https://www.mql5.com/en/users/koshtenko | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, Evgeniy Koshtenko" #property link "https://www.mql5.com/en/users/koshtenko" #property version "1.00" #include <Trade\Trade.mqh> // Include the CTrade trading class //--- input parameters input int InpPatternLength = 5; // Pattern Length (3-10) input int InpLookback = 1000; // Lookback Period (100-5000) input int InpForecastHorizon = 6; // Forecast Horizon (1-20) input double InpLotSize = 0.1; // Lot Size input int InpMinOccurrences = 30; // Minimum Pattern Occurrences //--- global variables int g_pattern_length; int g_lookback; int g_forecast_horizon; string g_patterns[]; int g_pattern_count; int g_pattern_occurrences[]; int g_pattern_successes[]; int g_total_bars; string g_best_buy_pattern = ""; string g_best_sell_pattern = ""; CTrade trade; // Use the CTrade trading class //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- validate inputs if(InpPatternLength < 3 || InpPatternLength > 10) { Print("Invalid Pattern Length. Must be between 3 and 10."); return INIT_PARAMETERS_INCORRECT; } if(InpLookback < 100 || InpLookback > 5000) { Print("Invalid Lookback Period. Must be between 100 and 5000."); return INIT_PARAMETERS_INCORRECT; } if(InpForecastHorizon < 1 || InpForecastHorizon > 20) { Print("Invalid Forecast Horizon. Must be between 1 and 20."); return INIT_PARAMETERS_INCORRECT; } //--- set global variables g_pattern_length = InpPatternLength; g_lookback = InpLookback; g_forecast_horizon = InpForecastHorizon; //--- generate all possible patterns if(!GeneratePatterns()) { Print("Failed to generate patterns."); return INIT_FAILED; } g_total_bars = iBars(_Symbol, PERIOD_CURRENT); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!IsNewBar()) return; UpdatePatternStatistics(); string current_pattern = GetCurrentPattern(); if(current_pattern == g_best_buy_pattern) { if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { trade.PositionClose(_Symbol); } if(!PositionSelect(_Symbol)) { trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "Buy Pattern: " + current_pattern); } } else if(current_pattern == g_best_sell_pattern) { if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { trade.PositionClose(_Symbol); } if(!PositionSelect(_Symbol)) { trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "Sell Pattern: " + current_pattern); } } } //+------------------------------------------------------------------+ //| Generate all possible patterns | //+------------------------------------------------------------------+ bool GeneratePatterns() { g_pattern_count = (int)MathPow(2, g_pattern_length); if(!ArrayResize(g_patterns, g_pattern_count)) { Print("Failed to resize g_patterns array."); return false; } for(int i = 0; i < g_pattern_count; i++) { string pattern = ""; for(int j = 0; j < g_pattern_length; j++) { pattern += ((i >> j) & 1) ? "U" : "D"; } g_patterns[i] = pattern; } return true; } //+------------------------------------------------------------------+ //| Update pattern statistics and find best patterns | //+------------------------------------------------------------------+ void UpdatePatternStatistics() { if(ArraySize(g_pattern_occurrences) != g_pattern_count) { ArrayResize(g_pattern_occurrences, g_pattern_count); ArrayResize(g_pattern_successes, g_pattern_count); } ArrayInitialize(g_pattern_occurrences, 0); ArrayInitialize(g_pattern_successes, 0); int total_bars = iBars(_Symbol, PERIOD_CURRENT); int start = total_bars - g_lookback; if(start < g_pattern_length + g_forecast_horizon) start = g_pattern_length + g_forecast_horizon; double close[]; ArraySetAsSeries(close, true); CopyClose(_Symbol, PERIOD_CURRENT, 0, total_bars, close); string patterns[]; ArrayResize(patterns, total_bars); ArraySetAsSeries(patterns, true); for(int i = 0; i < total_bars - g_pattern_length; i++) { patterns[i] = ""; for(int j = 0; j < g_pattern_length; j++) { patterns[i] += (close[i+j] > close[i+j+1]) ? "U" : "D"; } } for(int i = start; i >= g_pattern_length + g_forecast_horizon; i--) { string current_pattern = patterns[i]; int pattern_index = ArraySearch(g_patterns, current_pattern); if(pattern_index != -1) { g_pattern_occurrences[pattern_index]++; if(close[i-g_forecast_horizon] > close[i]) { g_pattern_successes[pattern_index]++; } } } double best_buy_win_rate = 0; double best_sell_win_rate = 0; for(int i = 0; i < g_pattern_count; i++) { if(g_pattern_occurrences[i] >= InpMinOccurrences) { double win_rate = (double)g_pattern_successes[i] / g_pattern_occurrences[i]; if(win_rate > best_buy_win_rate) { best_buy_win_rate = win_rate; g_best_buy_pattern = g_patterns[i]; } if((1 - win_rate) > best_sell_win_rate) { best_sell_win_rate = 1 - win_rate; g_best_sell_pattern = g_patterns[i]; } } } Print("Best Buy Pattern: ", g_best_buy_pattern, " (Win Rate: ", DoubleToString(best_buy_win_rate * 100, 2), "%)"); Print("Best Sell Pattern: ", g_best_sell_pattern, " (Win Rate: ", DoubleToString(best_sell_win_rate * 100, 2), "%)"); } //+------------------------------------------------------------------+ //| Get current price pattern | //+------------------------------------------------------------------+ string GetCurrentPattern() { double close[]; ArraySetAsSeries(close, true); CopyClose(_Symbol, PERIOD_CURRENT, 0, g_pattern_length + 1, close); string pattern = ""; for(int i = 0; i < g_pattern_length; i++) { pattern += (close[i] > close[i+1]) ? "U" : "D"; } return pattern; } //+------------------------------------------------------------------+ //| Custom function to search for a string in an array | //+------------------------------------------------------------------+ int ArraySearch(const string &arr[], string value) { for(int i = 0; i < ArraySize(arr); i++) { if(arr[i] == value) return i; } return -1; } //+------------------------------------------------------------------+ //| Check if it's a new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time = 0; datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0); if(current_time != last_time) { last_time = current_time; return true; } return false; }
I risultati del test su EURUSD sono i seguenti:

E in dettaglio:

Non male, e la grafica è bellissima. Altre versioni di EA si aggirano intorno allo zero o vanno incontro a lunghi drawdown. Anche l'opzione migliore non corrisponde ai miei criteri. Preferisco gli EA con un fattore di profitto superiore a 2 e un rapporto di Sharpe superiore a 1. Mi è venuto in mente che nel tester Python era necessario prendere in considerazione sia la commissione di negoziazione, sia lo spread e lo swap.
Potenziali miglioramenti: Ampliamento dei timeframe e aggiunta di indicatori
Continuiamo le nostre riflessioni. Il sistema mostra certamente dei risultati positivi, ma come si può migliorare ed è realistico?
Ora guardiamo il timeframe a 4 ore. Proviamo a guardare oltre. Dovremmo aggiungere un grafico giornaliero, settimanale e forse anche mensile. Con questo approccio, saremo in grado di vedere tendenze più globali e modelli su larga scala. Espandiamo il codice per coprire tutte queste scale temporali:
timeframes = [mt5.TIMEFRAME_H4, mt5.TIMEFRAME_D1, mt5.TIMEFRAME_W1, mt5.TIMEFRAME_MN1]
for tf in timeframes:
ohlc_data = get_ohlc_data(symbol, tf, start_date, end_date)
patterns = find_patterns(ohlc_data)
Più dati, più rumore. Dobbiamo imparare a separare questo rumore per ottenere dati più chiari.
Ampliamo la gamma di caratteristiche analizzate. Nel mondo del trading, questa è l'aggiunta degli indicatori tecnici. RSI, MACD e Bande di Bollinger sono gli strumenti più utilizzati.
def add_indicators(data): data['RSI'] = ta.RSI(data['close']) data['MACD'] = ta.MACD(data['close']).macd() data['BB_upper'], data['BB_middle'], data['BB_lower'] = ta.BBANDS(data['close']) return data ohlc_data = add_indicators(ohlc_data)
Gli indicatori possono aiutarci a confermare i nostri segnali dei pattern. Oppure, possiamo cercare anche pattern sugli indicatori.
Conclusioni
Abbiamo quindi terminato il nostro lavoro di ricerca e analisi dei modelli. Abbiamo creato un sistema che cerca pattern nel caos del mercato. Abbiamo imparato a visualizzare i nostri risultati, a condurre backtest, a pianificare i miglioramenti futuri. Ma soprattutto abbiamo imparato a pensare come trader analitici. Non ci limitiamo a seguire la massa, ma cerchiamo il nostro percorso, i nostri pattern, le nostre possibilità.
Tenete presente che il mercato è il prodotto delle azioni di persone vive. Cresce e cambia. E il nostro compito è quello di cambiare con essa. I pattern di oggi potrebbero non funzionare domani, ma questo non è un motivo per disperarsi. È un'opportunità per imparare, adattarsi e crescere. Utilizzate questo sistema come punto di partenza. Sperimentate, migliorate, create il vostro. Forse troverete proprio quel pattern che vi aprirà le porte al trading di successo!
In bocca al lupo per questo emozionante viaggio! Fate in modo che i vostri pattern siano sempre profittevoli e che le perdite siano solo lezioni per la strada del successo. A presto nel mondo del Forex!
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/15965
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.
Questo articolo è stato scritto da un utente del sito e riflette le sue opinioni personali. MetaQuotes Ltd non è responsabile dell'accuratezza delle informazioni presentate, né di eventuali conseguenze derivanti dall'utilizzo delle soluzioni, strategie o raccomandazioni descritte.
Arriva il Nuovo MetaTrader 5 e MQL5
Sistema di trading di arbitraggio ad alta frequenza in Python utilizzando MetaTrader 5
Utilizza i canali MQL5.community e le chat di gruppo
I metodi di William Gann (Parte III): L'astrologia funziona?
- 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