forex-python-trading-algoritmico-005

DTForex #5 – Trading su diverse Coppie di Valute

Sommario

Nel precedente articolo della serie sul trading algoritmico per il forex abbiamo descritto  alcune importanti modifiche al software DTForex. Questi aggiornamento hanno aumentato in modo signicativo le funzionalità del sistema, al punto che è quasi pronto per il backtesting con dati storici di tick su una gamma di coppie di valute.

In questo articolo descriviamo le seguenti modifiche apportate al sistema:

  • Ulteriori modifiche agli oggetti Position Portfolio per consentire lo scambio di più coppie di valute e valute non denominate nella valuta del conto, cioè con un conto nominato in EUR si può ora negoziare anche GBP/USD, ad esempio.
  • Revisione completa delle modalità con cui Position Portfolio calcolano di apertura, chiusura, aggiunta e rimozione di unità. L’oggetto Position esegue ora la maggior parte della logica, lasciando all’oggetto Portfolio la gestione ad alto livello.
  • Aggiunta della prima strategia non banale, ovvero la ben nota strategia Moving Average Crossover con una coppia di medie mobili semplici (SMA).
  • Modifica di backtest.py per renderlo single-threaded e deterministico. Nonostante il mio ottimismo sul fatto che un approccio multi-thread non sarebbe troppo dannoso per l’accuratezza della simulazione, ho trovato difficile ottenere risultati soddisfacenti di backtest con un approccio multi-thread.
  • Introduzione di uno script molto semplice di output basato su Matplotlib per visualizzare la curva di equity.

Gestione di coppie di valute multiple

Una caratteristica del sistema di trading che abbiamo discusso molte volte negli articoli di questa serie è la possibilità capacità di gestire più coppie di valute.

In questa articolo vediamo come modificare il software per consentire la gestione di conti nominati in valute diverse da EUR, che era la sola valuta codificata in precedenza. Descriviamo anche come poter negoziare altre coppie di valute, ad eccezione di quelle che consistono in una base o quotazione in Yen giapponese (JPY). La limitazione sullo Yen è dovuta alle modalità di calcolo delle dimensioni dei tick nelle coppie di valute con JPY.

Per ottenere questo è necessario modificare la logica di calcolo del profitto quando le unità vengono rimosse o la posizione viene chiusa. Di seguito vediamo il nuovo codice per calcolo dei pips, nel file position.py:

Se chiudiamo la posizione per realizzare un guadagno o una perdita, dobbiamo utilizzare il seguente codice per close_position, da inserire nel file position.py:

In primo luogo otteniamo i prezzi denaro e lettera sia per la coppia di valute negoziata che per la coppia di valute di base (“quote/home”). Ad esempio, per un conto denominato in EUR, dove stiamo negoziando GBP/USD, dobbiamo ottenere i prezzi per “USD/EUR”, poiché GBP è la valuta di base e USD è la quotazione.

In questa fase controlliamo se la posizione stessa è una posizione long o short e quindi calcoliamo il “prezzo di rimozione” per la coppia negoziata e il “prezzo di rimozione” per la coppia quote/home, che calcolati rispettivamente da remove_priceqh_close.

Quindi aggiorniamo i prezzi correnti e medi all’interno della posizione e infine calcoliamo il P&L moltiplicando i pip, il prezzo di rimozione per quote/home e quindi il numero di unità che stiamo chiudendo.

Abbiamo completamente eliminato la necessità di valutare la “esposizione”, che era una variabile ridondante. Questa formula fornisce quindi correttamente il P&L rispetto a qualsiasi scambio di coppie di valute (non denominate in JPY).

Revisione della posizione e gestione del portafoglio

Oltre alla possibilità di negoziare più coppie di valute, vediamo come perfezionare la logica in cui Position Portfolio “condividono” la responsabilità di aprire e chiudere le posizioni, nonché di aggiungere e sottrarre unità. In particolare, dobbiamo spostare molto del codice di gestione della posizione da portfolio.py a  position.py

Questo è più naturale poiché la posizione dovrebbe prendersi cura di se stessa e non delegarla al portafoglio!

In particolare, dobbiamo creare o migrare i metodi add_unitsremove_units close_position:

Negli ultimi due metodi si può vedere come è implementata la nuova formula per il calcolo del profitto. Di conseguenza, molte delle funzionalità della classe Portfolio sono state ridotte. In particolare, i metodi add_new_positionadd_position_unitsremove_position_unitsclose_position sono stati modificati a seguito dello spostamento del calcolo all’interno dell’oggetto Position:

In sostanza, tutti  i metodi (a parte add_new_position) controllano semplicemente se la posizione esiste per quella coppia di valute e quindi chiamano il corrispondente metodo in Position, tenendo conto del profitto se necessario.

Strategia di crossover della media mobile

In DataTrading abbiamo già descritto una strategia Moving Average Crossover, nel contesto del mercato azionario. È una strategia utile come banco di prova del sistema perché i calcoli sono facile da replicare, anche a mano (almeno a frequenze più basse!), al fine di verificare che il backtester si stia comportando come dovrebbe.

L’idea di base della strategia è la seguente:

  • Vengono creati due filtri separati di media mobile semplici, con periodi variabili della finestra, di una particolare serie temporale.
  • I segnali di acquisto dell’asset si verificano quando la media mobile più breve supera la media mobile più lunga.
  • Se la media più lunga successivamente supera la media più breve, l’asset viene venduto.

La strategia funziona bene quando una serie temporale entra in un periodo di forte tendenza e poi lentamente inverte la tendenza.

L’implementazione è semplice. In primo luogo, implementiamo un metodo calc_rolling_sma che ci consente di utilizzare in modo più efficiente il calcolo SMA del periodo di tempo precedente per generare quello nuovo, senza dover ricalcolare completamente l’SMA in ogni fase.

In secondo luogo, generiamo segnali in due casi. Nel primo caso generiamo un segnale se la SMA breve supera la SMA lunga e non siamo long nella coppia di valute. Nel secondo caso generiamo un segnale se la SMA lunga supera la SMA breve e siamo già long nello strumento.

In questo esempio abbiamo impostato il periodo della finestra a 500 tick per la SMA breve e 2.000 tick per la SMA lunga. Ovviamente in un ambiente di produzione questi parametri devono essere ottimizzati, ma funzionano bene per i nostri scopi di test.

Backtester a thread singolo

Un altro cambiamento importante è modificare il componente del backtest in modo da essere a singolo thread, anziché multi-thread.

Dobbiamo prevede questa modifica perché è molto complesso sincronizzare i thread da eseguire in un modo simile a quello che si avrebbe nel trading live senza introdurre errori e bias che comprometto i risultati del backtest. In particolare, con un backtester multi-thread si hanno i prezzi di entrata e di uscita molto irrealistici, perchè si verificano tipicamente dopo alcune ore (virtuali) l’effettiva ricezione del tick.

Per evitare questa criticità è sufficiente incorporare lo streaming dell’oggetto TickEventnel ciclo di backtest, come implementato nel seguente frammento di backtest.py:

Da notare la linea ticker.stream_next_tick(). Questo metodo viene chiamato prima di un polling della coda degli eventi e quindi ci assicuriamo che un nuovo evento tick venga elaborato prima che la coda venga nuovamente interrogata.

In questo modo un segnale è eseguito all’arrivo di nuovi dati di mercato, anche se c’è un certo ritardo nel processo di esecuzione degli ordini a causa dello slippage.

Abbiamo anche impostato un valore max_iters che controlla per quanto tempo continua il ciclo di backtest. In pratica questo dovrà essere abbastanza grande quando si tratta di più valute in più giorni. In questo caso è stato impostato su un valore predefinito che consente di elaborare i dati di un singolo giorno di una coppia di valute.

Il metodo stream_next_tick della classe del price handler è simile a, stream_to_queue tranne per il fatto che chiama manualmente il metodo iterativo next(), invece di eseguire il tick streaming in un ciclo for:

Da notare che si interrompe al ricevimento di un’eccezione StopIteration. Ciò consente al codice di riprendere l’esecuzione anziché bloccarsi.

Visualizzazione risultati con Matplotlib

Dobbiamo anche creare uno script di output, utilizzando Matplotlib in modo molto semplice per visualizzare la curva di equity. Il file output.py è inserito all’interno della directory backtest di DTForex ed il codice è riportato di seguito:.

Da notare che  settings.py deve ora prevedere la nuova variabile OUTPUT_RESULTS_DIR, che deve essere presente e valorizzata nelle impostazioni. In questo esempio abbiamo impostato a una directory temporanea fuori dalla struttura del progetto in modo da non aggiungere accidentalmente nessun risultato di backtest al codice base del progetto!

La curva di equity è costruita  aggiungendo un valore di portafoglio (“balance”) a una lista di dizionari, con un dizionario corrispondente a una marca temporale.

Una volta completato il backtest, l’elenco dei dizionari viene convertito in un DataFrame di pandas e il metodo to_csv viene utilizzato per l’output equity.csv.

Questo script di output legge semplicemente il file e visualizza il grafico della colonna balance del DataFrame.

Di seguito il codice per i metodi append_equity_row output_results della classe Portfolio:

Ogni volta che viene chiamato execute_signal, si richiamata il metodo precedente e si aggiunge il valore di timestamp / saldo al membro equity.

Alla fine del backtest viene chiamato output_results che semplicemente converte l’elenco dei dizionari in un DataFrame e quindi l’output nella directory specificata in OUTPUT_RESULTS_DIR.

Sfortunatamente, questo non è un modo particolarmente appropriato per creare una curva di equity poiché si verifica solo quando viene generato un segnale. Ciò significa che non tiene conto del P&L non realizzato .

Anche se questo è il modo in cui avviene realmente il trading (non si fa effettivamente un profitto/perdita  fino a quando non si chiude una posizione!), Significa che la curva dell’equità rimarrà completamente piatta tra gli aggiornamenti del saldo del portafoglio. Peggio ancora, Matplotlib per impostazione predefinita esegue l’interpolazione lineare tra questi punti, fornendo così la falsa impressione del P&L non realizzato.

La soluzione a questo problema è creare un tracker P&L non realizzato per la classe Position che si aggiorna correttamente ad ogni tick. Questo è un po ‘più costoso dal punto di vista computazionale, ma consente una curva di equity più utile e realistica. Descriveremo questa funzione in un prossimo articolo!

Prossimi Passi

La prossima funzionalità da prevedere per DTForex è la possibilità di effettuare il backtesting con dati relativi a molti giorni. Attualmente l’oggetto HistoricCSVPriceHandler carica solo il valore di un singolo giorno di dati tick DukasCopy per qualsiasi coppia di valute specificata.

Per consentire backtest per periodi che coprono più giorni, sarà necessario caricare e trasmettere sequenzialmente un singolo giorno in modo da evitare di riempire la RAM con l’intera cronologia dei dati dei tick. Ciò richiederà una modifica al funzionamento del metodo stream_next_tick. Una volta completato, consentirà il backtesting della strategia a lungo termine su più coppie.

Un altro compito è migliorare l’output della curva di equity Per calcolare una qualsiasi delle normali metriche di performance (come lo Sharpe Ratio ), avremo bisogno di calcolare i rendimenti percentuali in un determinato periodo di tempo. Tuttavia, ciò richiede di raggruppare i dati del tick in barre per calcolare un rendimento di periodo di tempo.

Tale binning deve avvenire su una frequenza di campionamento che è simile alla frequenza di negoziazione o lo Sharpe Ratio non rifletterà il vero rischio / rendimento della strategia. Questo raggruppamento non è un esercizio banale in quanto ci sono molti presupposti che contribuiscono a generare un “prezzo” per ogni campione.

Una volta completate queste due attività e acquisiti dati sufficienti, saremo in grado di eseguire il backtest di un’ampia gamma di strategie forex basate sui dati tick e di produrre curve azionarie al netto della maggior parte dei costi di transazione. Inoltre, sarà estremamente semplice testare queste strategie sul conto di paper trading fornito da OANDA.

Ciò dovrebbe consentire di prendere decisioni più precise sull’opportunità di eseguire una strategia, rispetto ai test effettuati con un sistema di backtesting più “orientato alla ricerca”.

 

Per il codice completo riportato in questo articolo, utilizzando il modulo di backtesting event-driven per il forex (DTForex) si può consultare il seguente repository di github:
https://github.com/datatrading-info/DTForex

Benvenuto su DataTrading!

Sono Gianluca, ingegnere software e data scientist. Sono appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato DataTrading per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

ARTICOLI

TUTORIAL

Torna in alto
Scroll to Top