English Русский 中文 Español Deutsch 日本語 Português Türkçe
preview
Trovare pattern personalizzati nelle coppie di valute in Python utilizzando MetaTrader 5

Trovare pattern personalizzati nelle coppie di valute in Python utilizzando MetaTrader 5

MetaTrader 5Trading |
24 1
Yevgeniy Koshtenko
Yevgeniy Koshtenko

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

File allegati |
PredictPattern.py (9.23 KB)
AutoPattern.mq5 (18.98 KB)
PatternEA.mq5 (16.12 KB)
PatternEAMult.mq5 (9.59 KB)
Ultimi commenti | Vai alla discussione (1)
linfo2
linfo2 | 9 mag 2025 a 02:09
Grazie Yevgniy, un ottimo modello per valutare un'idea con python. molto apprezzato
Arriva il Nuovo MetaTrader 5 e MQL5 Arriva il Nuovo MetaTrader 5 e MQL5
Questa è solo una panoramica di MetaTrader 5. Non posso descrivere tutte le nuove funzionalità del sistema per un periodo di tempo così breve: i test sono iniziati il 09.09.2009. Questa è una data simbolica e sono sicuro che sarà un numero fortunato. Sono passati alcuni giorni da quando ho ricevuto la versione beta del terminale MetaTrader 5 e MQL5. Non sono riuscito a provare tutte le sue funzionalità, ma sono già sorpreso.
Sistema di trading di arbitraggio ad alta frequenza in Python utilizzando MetaTrader 5 Sistema di trading di arbitraggio ad alta frequenza in Python utilizzando MetaTrader 5
In questo articolo creeremo un sistema di arbitraggio che rimane lecito agli occhi dei broker, crea migliaia di prezzi sintetici sul mercato Forex, li analizza e opera con successo per ottenere profitti.
Utilizza i canali MQL5.community e le chat di gruppo Utilizza i canali MQL5.community e le chat di gruppo
Il sito web MQL5.com riunisce trader di tutto il mondo. Gli utenti pubblicano articoli, condividono codici gratuiti, vendono prodotti nel Market, offrono servizi da freelance e copiano segnali di trading. Puoi comunicare con loro sul Forum, nelle chat dei trader e nei canali MetaTrader.
I metodi di William Gann (Parte III): L'astrologia funziona? I metodi di William Gann (Parte III): L'astrologia funziona?
Le posizioni di pianeti e stelle influenzano i mercati finanziari? Armiamoci di statistiche e big data e intraprendiamo un viaggio emozionante nel mondo in cui stelle e grafici azionari si intersecano.