In questo articolo descriviamo come semplificare l’interfaccia per costruire un nuovo backtest, incapsulando molto del codice “boilerplate” in una nuova classe Backtest. Inoltre vediamo come modificare il sistema per poter gestire più coppie di valute.

Infine vediamo come testare la nuova interfaccia tramite la solita strategia di esempio di Moving Average Crossover, sia su GBP/USD che su EUR/USD.

Nuova Interfaccia di Backtest

Abbiamo modificato l’interfaccia di backtest in modo tale da creare semplicemente un’istanza di Backteste popolarla con i componenti di trading, invece di dover creare un file  backtest.py file personalizzato come in precedenza.

Il modo migliore per iniziare con il nuovo approccio è dare un’occhiata alla directory  examples/ e aprire 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 è relativamente semplice. In primo luogo il codice importa i componenti necessari, ovvero il BacktestSimulatedExecutionPortfolioMovingAverageCrossStrategy HistoricCSVPriceHandler.

In secondo luogo, definiamo le coppie di valute da negoziare e quindi creiamo un dizionario noto come strategy_params. Questo contiene essenzialmente qualsiasi argomento delle key words che potremmo voler passare alla strategia. Nel caso di un Moving Average Crossover dobbiamo impostare le lunghezze dei periodi delle medie mobili. Questi valori sono in termini di “tick”.

Infine creiamo un’istanza Backtest e passiamo tutti gli oggetti come parametri. Quindi, eseguiamo il backtest stesso.

All’interno del nuovo backtest.py chiamiamo 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.")

Esegue il calcolo del backtest (ovvero l’aggiornamento del portafoglio all’arrivo dei tick), nonché il calcolo e l’output delle prestazioni in equity.csv.

Come descritto negli articoli precedenti, possiamo ancora produrre un grafico dell’output con lo script backtest/output.py. Useremo questo script di seguito quando discuteremo dell’implementazione di più coppie di valute.

Gestione di più Coppie di Valute

Siamo finalmente in grado di  testare la prima strategia di trading (non banale) su dati di tick ad alta frequenza per più coppie di valute!

A tale scopo è necessario modificare le modalità di gestione all’interno di MovingAverageCrossStrategy.

Di seguito 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 nel modo visto nei precedenti articoli, sebbene lo faccia separatamente per ciascuna coppia.

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

Chiaramente le prestazioni non sono eccezionali in quanto la strategia rimane quasi interamente “sott’acqua” col passare del tempo. Detto questo, non dovremmo aspettarci molto da una strategia di base sui dati tick ad alta frequenza. In futuro esamineremo approcci molto più sofisticati al trading su questa scala temporale.

Si spera che questo sistema possa fornire un utile punto di partenza per lo sviluppo di strategie più sofisticate. Non vedo l’ora di scoprire cosa inventeranno gli altri nel prossimo futuro!

Conclusioni

In questa serie di articoli 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 lavorare.

In particolare si può rendere il sistema molto più veloce, in modo da permettere di effettuare ricerche di parametri in tempi ragionevoli. Sebbene Python sia un ottimo strumento, uno svantaggio è che è relativamente lento rispetto a C / C ++. Quindi si può lavorare sul cercare di migliorare la velocità di esecuzione sia del backtest che dei calcoli delle prestazioni.

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

 

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

Se si desidera leggere gli altri articoli di questa serie, sono disponibili ai seguenti link:

Recommended Posts