Nuova Interfaccia di Backtest – Trading System sul Forex Parte 7

Nuova Interfaccia di Backtest

In questa lezione del corso per il trading automatico sul forex illustriamo la nuova interfaccia di backtest. L’aggiornamento permette di semplificare l’interfaccia per costruire un nuovo backtest, incapsulando gran parte del codice “boilerplate” all’interno di una nuova classe Backtest.
Inoltre abbiamo effettuato una ulteriore modifica per gestire più coppie di valute. Infine testiamo la nuova interfaccia utilizzando la consueta strategia di esempio di Moving Average Crossover, sia su GBP/USD che su EUR/USD.

Nuova Interfaccia di Backtest

Abbiamo creato una nuova interfaccia di backtest che permette di semplificare la gestione di un’istanza di Backtest e popolarla con i componenti di trading. In questo modo non c’è più bisogno di costruire un file backtest.py personalizzato come in precedenza.

Il modo migliore per iniziare con il nuovo approccio consiste nell’aprire la directory examples/ e consultare il file mac.py.

				
					from backtest import Backtest
from execution import SimulatedExecution
from portfolio import Portfolio
from settings import settings
from strategy import MovingAverageCrossStrategy
from data.price import HistoricCSVPriceHandler

if __name__ == "__main__":
    # Trading su GBP/USD e EUR/USD
    pairs = ["GBPUSD", "EURUSD"]

    # Crea i parametri della strategia per MovingAverageCrossStrategy
    strategy_params = {
        "short_window": 500,
        "long_window": 2000
    }

    # Crea ed esegue il backtest
    backtest = Backtest(
        pairs, HistoricCSVPriceHandler,
        MovingAverageCrossStrategy, strategy_params,
        Portfolio, SimulatedExecution,
        equity=settings.EQUITY
    )
    backtest.simulate_trading()
				
			

Il codice della nuova interfeccia per il backtest risulta relativamente semplice. Per prima cosa importiamo i componenti necessari: Backtest, SimulatedExecution, Portfolio, MovingAverageCrossStrategy e HistoricCSVPriceHandler.

Successivamente definiamo le coppie di valute da negoziare e creiamo un dizionario chiamato strategy_params. Questo contiene tutti gli argomenti delle key words che vogliamo passare alla strategia. Nel caso di un Moving Average Crossover impostiamo le lunghezze dei periodi delle medie mobili. Questi valori si esprimono in termini di “tick”.

Infine creiamo un’istanza di Backtest e passiamo tutti gli oggetti come parametri. A questo punto eseguiamo il backtest vero e proprio.

All’interno del nuovo file backtest.py richiamiamo questo metodo:

				
					
# backtest.py
..
..
    def simulate_trading(self):
        """
        Simula il backtest e calcola le performance del portfolio
        """
        self._run_backtest()
        self._output_performance()
        print("Backtest complete.")
				
			

Eseguiamo il calcolo del backtest, aggiornando il portafoglio ad ogni arrivo dei tick, e calcoliamo le performance salvandole in equity.csv.

Come abbiamo spiegato nelle lezioni precedenti, possiamo ancora produrre un grafico dell’output usando lo script backtest/output.py. Utilizzeremo la nuova interfaccia di backtest di seguito quando tratteremo l’implementazione su più coppie di valute.

Gestione di più Coppie di Valute

Finalmente possiamo testare la nostra prima strategia di trading non banale su dati di tick ad alta frequenza per più coppie di valute!

Per farlo dobbiamo modificare le modalità di gestione all’interno di MovingAverageCrossStrategy.

Ecco il codice completo:

				
					

class MovingAverageCrossStrategy(object):
    """
    Una strategia base di Moving Average Crossover che genera
    due medie mobili semplici (SMA), con finestre predefinite
    di 500 tick per la SMA  breve e 2.000 tick per la SMA
    lunga.

    La strategia è "solo long" nel senso che aprirà solo una
    posizione long una volta che la SMA breve supera la SMA
    lunga. Chiuderà la posizione (prendendo un corrispondente
    ordine di vendita) quando la SMA lunga incrocia nuovamente
    la SMA breve.

    La strategia utilizza un calcolo SMA a rotazione per
    aumentare l'efficienza eliminando la necessità di chiamare due
    calcoli della media mobile completa su ogni tick.
    """
    def __init__(
            self, pairs, events,
            short_window=500, long_window=2000
    ):
        self.pairs = pairs
        self.pairs_dict = self.create_pairs_dict()
        self.events = events
        self.short_window = short_window
        self.long_window = long_window

    def create_pairs_dict(self):
        attr_dict = {
            "ticks": 0,
            "invested": False,
            "short_sma": None,
            "long_sma": None
        }
        pairs_dict = {}
        for p in self.pairs:
            pairs_dict[p] = copy.deepcopy(attr_dict)
        return pairs_dict

    def calc_rolling_sma(self, sma_m_1, window, price):
        return ((sma_m_1 * (window - 1)) + price) / window

    def calculate_signals(self, event):
        if event.type == 'TICK':
            pair = event.instrument
            price = event.bid
            pd = self.pairs_dict[pair]
            if pd["ticks"] == 0:
                pd["short_sma"] = price
                pd["long_sma"] = price
            else:
                pd["short_sma"] = self.calc_rolling_sma(
                    pd["short_sma"], self.short_window, price
                )
                pd["long_sma"] = self.calc_rolling_sma(
                    pd["long_sma"], self.long_window, price
                )
            # Si avvia la strategia solamente dopo aver creato una 
            # accurata finestra di breve periodo
            if pd["ticks"] > self.short_window:
                if pd["short_sma"] > pd["long_sma"] and not pd["invested"]:
                    signal = SignalEvent(pair, "market", "buy", event.time)
                    self.events.put(signal)
                    pd["invested"] = True
                if pd["short_sma"] < pd["long_sma"] and pd["invested"]:
                    signal = SignalEvent(pair, "market", "sell", event.time)
                    self.events.put(signal)
                    pd["invested"] = False
            pd["ticks"] += 1
				
			

Essenzialmente creiamo un dizionario degli attributi attr_dict che memorizza il numero di tick trascorsi e se la strategia è “a mercato” per quella particolare coppia.

In calculate_signals aspettiamo di ricevere un TickEvent e quindi calcoliamo le medie mobili semplici per il breve e lungo periodo. Una volta che la SMA breve incrocia al rialzo la SMA lunga per una particolare coppia, la strategia va long ed esce quando incrocia al ribasso, sebbene lo faccia separatamente per ciascuna coppia.

Risultati

Abbiamo utilizzato 2 mesi di dati sia per GBP/USD che per EUR/USD e il backtest richiede un po ‘di tempo per essere eseguito. Tuttavia, una volta completato il backtest, siamo in grado di utilizzare backtest/output.py per produrre il seguente grafico delle prestazioni:

trading-algoritmico-forex-7-mac-results

Le prestazioni non sono eccezionali, poiché la strategia rimane quasi interamente “sott’acqua” col passare del tempo. Detto questo, non dobbiamo aspettarci molto da una strategia di base sui dati tick ad alta frequenza. In futuro, esploreremo approcci molto più sofisticati per il trading su questa scala temporale.

Ci auguriamo che questa nuova interfaccia di backtest possa rappresentare un utile punto di partenza per lo sviluppo di strategie più avanzate. 

Conclusione

In questa serie di lezioni, abbiamo visto le basi di un sistema di trading automatico sul mercato del Forex, implementato in Python. Nonostante il sistema sia completo di funzionalità per il backtest e il paper/live trading, ci sono ancora molti aspetti su cui possiamo lavorare.

In particolare, possiamo rendere il sistema molto più veloce, permettendo ricerche di parametri in tempi ragionevoli. Sebbene Python sia un ottimo strumento, uno svantaggio è che è relativamente lento rispetto a C / C++. Per questo motivo, possiamo concentrarci sul migliorare la velocità di esecuzione sia del backtest che dei calcoli delle prestazioni.

Inoltre, un altro aspetto che merita di essere implementato riguarda la gestione di altri tipi di ordine rispetto al semplice ordine di mercato. Per attuare strategie HFT efficaci sul broker OANDA, dovremo utilizzare gli ordini limite. Ciò richiederà probabilmente una rielaborazione del modo in cui il sistema esegue attualmente le operazioni, ma ci permetterà di sviluppare un universo molto più ampio di strategie di trading.

Il codice completo presentato in questa lezione, basato sul sistema di trading automatico sul Forex TQforex, è disponibile nel seguente repository GitHub: https://github.com/tradingquant-it/TQforex.”

Torna in alto