La Componente di Portafoglio – Trading System sul Forex Parte 2

Componente di Portafoglio

Nella prima lezione di questo corso sul trading automatico sul forex abbiamo spiegato come creare un sistema di trading automatico collegato all’API del broker OANDA. Abbiamo anche indicato che i passaggi successivi prevedevano l’implementazione della componente di portafoglio e di una copertura per gestire il rischio su tutti i  segnali suggeriti e generati dalla componente Strategy. In questa lezione descriviamo come costruire il portafoglio per il Forex, realizzando una classe Portfolio completa e funzionante.

Questa componente risulta essenziale per costruire un motore di backtest delle strategie forex, seguendo un approccio simile a quello mostrato nel corso “Trading System Event-Driven” per il mercato azionario. In particolare, vogliamo un ambiente con differenze minime tra il trading live e il sistema di backtest. Per questo motivo costruiamo una componente di portafoglio capace di riflettere (per quanto possibile) lo stato attuale del conto di trading fornito da OANDA.

Obbiettivi e Limitazioni

La logica base richiede che il conto di trading “practice” e le componenti del portafoglio locale presentino valori simili, se non uguali, per attributi come saldo del conto, profitto e perdita (P&L) non realizzati, conto economico realizzato e posizioni aperte.

Se raggiungiamo questo obiettivo ed eseguiamo alcune strategie di test tramite questa componente di portafoglio, e se gli attributi mostrano valori uguali sia nel portfolio locale sia in OANDA, allora possiamo fidarci della capacità del backtester di produrre risultati più realistici, molto simili a quelli che le strategie avrebbero ottenuto in “live”.

Quali sono i limiti attuali di questa implementazione?

  • La valuta di base, e quindi l’esposizione, risulta codificata su EUR. Serve modificarla per consentire la scelta di qualsiasi valuta di base.
  • Attualmente ho testato solo EUR / USD, poiché utilizzo EUR come valuta di base. In seguito modificherò i calcoli di esposizione per supportare qualsiasi coppia di valute.
  • Sebbene alcuni unit test abbiano indicato che aggiunta e rimozione di posizioni e unità funzionano correttamente, non ho ancora condotto test estesi.
  • Finora ho provato solo l’apertura e la chiusura di posizioni long senza testare posizioni short. Dovrò scrivere alcuni unit test specifici per gestire anche le posizioni short.

Descriviamo questa componente nonostante presenti queste limitazioni perchè vogliamo evidenziare quanto sia impegnativo creare sistemi di trading algoritmico e quanto serva attenzione ai dettagli. Esiste un ampio margine per introdurre bug e comportamenti errati. Vogliamo mostrare come costruire i sistemi nel “mondo reale” e come possiamo testare e correggere gli errori.

Iniziamo descrivendo come abbiamo costruito l’attuale configurazione del portafoglio e come l’abbiamo integrata nel sistema di trading demo descritto nella precedente lezione. Successivamente analizzeremo i punti in cui penso possano emergere delle differenze.

La Componente di Portafoglio per il Forex

Per generare un oggetto Portfolio dobbiamo descrivere come eseguiamo le negoziazioni in valuta, poiché si comportano in modo sostanzialmente diverso rispetto alle azioni.

Calcolo di Pips e Unità

Nelle altre classi di attività, chiamiamo il più piccolo incremento di variazione del prezzo dell’asset “tick”. Nel trading Forex lo definiamo “pip” (Price Interest Point). Si tratta dell’incremento più piccolo in qualsiasi coppia di valute ed equivale (di solito) a 1/100 di centesimo, noto anche come punto base. Poiché la maggior parte delle principali coppie di valute presenta un prezzo a quattro cifre decimali, la variazione più piccola avviene sull’ultimo punto decimale.

In EUR/USD, ad esempio, un movimento da 1,1184 a 1,1185 rappresenta un pip (4 cifre decimali), quindi un pip equivale a 0,0001. Per le valute basate sullo yen giapponese usiamo due cifre decimali, quindi un pip corrisponde a 0,01.

Ora possiamo porci una domanda: a quanto equivale in euro (EUR) un movimento di 20 pips (20 x 0,0001 = 0,002) per una quantità fissa di unità di EUR/USD? Se consideriamo 2.000 unità della valuta di base (ad esempio 2.000 euro), calcoliamo il P&L in euro così:

Profitto (EUR) = Pip x Esposizione / EURUSD = 0,002 x 2.000 / 1,1185 = 3,57

Con OANDA scegliamo liberamente il numero di quote da negoziare (e quindi la nostra esposizione). Poiché possediamo un conto in euro (EUR) e negoziamo EUR/USD (in questo esempio), l’esposizione corrisponde sempre al numero di unità. Attualmente abbiamo codificato questo comportamento nel sistema. Se vogliamo gestire più coppie di valute, dobbiamo modificare il calcolo dell’esposizione per considerare le diverse valute di base.

Poiché il valore del profitto che abbiamo descritto risulta piuttosto piccolo e le valute non oscillano molto (tranne quando lo fanno!), di solito introduciamo la leva finanziaria nel conteggio. Tratteremo questo aspetto nelle lezioni successive. Per ora, possiamo trascurarlo.

Panoramica del sistema di backtesting / trading

Il sistema attuale comprende i seguenti componenti:

  • Event – I componenti Evento trasportano i “messaggi” (come tick, segnali e ordini) tra gli oggetti Strategy, Portfolio ed Esecution.
  • Position – La componente Position rappresenta una “posizione” Forex, ovvero un “long” o uno “short” in una coppia di valute con associata una quantità di unità.
  • Portfolio – Il componente Portfolio raccoglie più oggetti Position, uno per ogni coppia di valute negoziata. Tiene traccia dell’attuale P&L di ciascuna posizione, anche dopo incrementi e riduzioni successivi di unità.
  • Strategy – L’oggetto Strategy elabora le informazioni delle serie temporali (tick delle coppie di valute) e invia gli eventi di segnale al portafoglio, che decide come agire di conseguenza.
  • Streaming Forex Price – Questo componente si collega a OANDA tramite un web-socket in streaming e riceve dati tick-by-tick in tempo reale (bid/ask) da qualsiasi coppia di valute sottoscritta.
  • Esecution – Gestiamo gli eventi di tipo “Ordine” e li inviamo a OANDA per eseguirli.
  • Trading Loop – Il trading loop collega tutti i componenti descritti sopra e avvia due thread: uno per i prezzi di streaming e uno per il gestore di eventi.

Per approfondire come colleghiamo il sistema, vale la pena leggere la precedente lezione di questo corso.

Implementazione in Python

Vediamo ora come implementare in Python la componente di portafoglio del sistema appena descritto.

Gestione delle Posizioni

Il primo componente è l’oggetto Position. Lo costruiamo per replicare il comportamento di una posizione aperta nel sistema fxTrade Practice di OANDA. La scheda Position nel software fxTrade contiene 8 colonne:

  • Type: indica se la posizione è “long” o “short”
  • Market: quale coppia di valute negoziamo, ad esempio “EUR/USD”
  • Unit: il numero di unità della valuta (vedi sopra)
  • Exposure (BASE): l’esposizione nella valuta base della posizione
  • Avg. Price: il prezzo medio raggiunto per più acquisti. Se effettuiamo \(P\) acquisti, calcoliamo il prezzo medio come \(\frac{\sum_{p=1}^P c_p u_p}{\sum_{p=1}^{P} u_p}\), dove \(c_p\) rappresenta il costo di acquisto \(p\) e \(u_p\) le unità acquistate per \(p\).
  • Current: il prezzo di vendita corrente.
  • Profit (BASE): l’attuale profitto o perdita nella valuta base della posizione.
  • Profit (%): la percentuale attuale di profitto o perdita della posizione.

Nel seguente codice riflettiamo questi attributi come membri della classe Position, tranne “Type” che rinominiamo “side”, poiché type è una parola riservata in Python. La classe comprende quattro metodi principali (oltre all’inizializzazione): calculate_pips, calculate_profit_base, calculate_profit_perc e update_position_price.

Con il primo metodo, calculate_pips, calcoliamo il numero di pips generati dalla posizione dalla sua apertura (considerando eventuali nuove unità aggiunte). Nel secondo metodo, calculate_profit_base, determiniamo il profitto o la perdita attuale sulla posizione. Il terzo metodo, calculate_profit_perc, misura la percentuale di profitto della posizione. Infine, con update_position_price aggiorniamo i due valori precedenti in base ai dati di mercato correnti.

            class Position(object):
    def __init__(
        self, side, market, units,
        exposure, avg_price, cur_price
    ):
        self.side = side
        self.market = market
        self.units = units
        self.exposure = exposure
        self.avg_price = avg_price
        self.cur_price = cur_price
        self.profit_base = self.calculate_profit_base()
        self.profit_perc = self.calculate_profit_perc()

    def calculate_pips(self):
        mult = 1.0
        if self.side == "SHORT":
            mult = -1.0
        return mult * (self.cur_price - self.avg_price)

    def calculate_profit_base(self):
        pips = self.calculate_pips()
        return pips * self.exposure / self.cur_price

    def calculate_profit_perc(self):
        return self.profit_base / self.exposure * 100.0

    def update_position_price(self, cur_price):
        self.cur_price = cur_price
        self.profit_base = self.calculate_profit_base()
        self.profit_perc = self.calculate_profit_perc()
        

Poiché un portafoglio può contenere più posizioni, creiamo un’istanza di classe per ogni mercato che negoziamo. Come abbiamo accennato in precedenza, al momento gestiamo soltanto EUR come valuta di base e EUR/USD come strumento di trading. Nelle prossime lezioni estenderemo l’oggetto Portfolio per gestire più valute di base e più coppie di valute. Ora configuriamo insieme un ambiente virtuale di base per Python e vediamo come funziona la componente di portafoglio nel trading algoritmico con il Portfolio.

Symlink per l’ambiente virtuale

Nel modulo successivo dell’oggetto Portfolio modifichiamo il modo in cui gestiamo le importazioni. Creiamo un ambiente virtuale e aggiungiamo un collegamento simbolico alla nostra directory DTForex. Questo approccio ci permette di fare riferimento a una gerarchia annidata di file di progetto all’interno di ogni modulo Python. Il codice che utilizziamo su Ubuntu è simile al seguente:

            cd /PATH/TO/YOUR/VIRTUALENV/DIRECTORY/lib/python3/site-packages/
ln -s /PATH/TO/YOUR/DTFOREX/DIRECTORY/ROOT/ DTForex
        

Ovviamente dobbiamo sostituire i percorsi del nostro ambiente virtuale e del codice sorgente. Normalmente memorizziamo i nostri ambienti virtuali nella directory home in ~/venv/. Salviamo i progetti nella directory home in ~/sites/. Questo approccio ci permette di fare riferimento, ad esempio, a dtforex.event.event import OrderEvent da qualsiasi file all’interno del progetto.

La classe Portfolio

Per implementare correttamente la componente di portafoglio per il forex, il costruttore __init__ della classe Portfolio richiede i seguenti argomenti:

  • ticker – il gestore del ticker dei prezzi forex in streaming, che utilizziamo per ottenere gli ultimi prezzi bid/ask.
  • event – la coda degli eventi, nella quale il portfolio inserisce gli eventi.
  • base – la valuta di base, che nel nostro caso è EUR.
  • leverage – il fattore di leva, attualmente impostato a 1:20.
  • equity – la quantità di patrimonio netto effettivo nel conto, impostata di default a 100.000.
  • risk_per_trade – la percentuale di patrimonio netto da rischiare per ogni operazione, impostata di default al 2%. Con un conto iniziale di 100.000, le unità di scambio saranno pari a 2.000.

Quando inizializziamo la classe, calcoliamo le trade_units, ovvero la quantità massima di unità consentite per ogni posizione, e dichiariamo il dizionario delle positions (dove ogni mercato è una chiave), che contiene tutte le posizioni aperte nel portafoglio.

            from copy import deepcopy

from event import OrderEvent
from portfolio import Position


class Portfolio(object):
    def __init__(
        self, ticker, events, base="EUR", leverage=20, 
        equity=100000.0, risk_per_trade=0.02
    ):
        self.ticker = ticker
        self.events = events
        self.base = base
        self.leverage = leverage
        self.equity = equity
        self.balance = deepcopy(self.equity)
        self.risk_per_trade = risk_per_trade
        self.trade_units = self.calc_risk_position_size()
        self.positions = {}
        

In questa fase la gestione del rischio risulta piuttosto semplice! Nel metodo calc_risk_position_size, ci assicuriamo che l’esposizione di ogni posizione non superi il risk_per_trade% del capitale del conto. Impostiamo il valore predefinito del risk_per_trade al 2% come argomento della parola chiave, ma possiamo naturalmente modificarlo. Quindi, per un conto di 100.000 euro, il rischio massimo per ogni operazione sarà di 2.000 euro per posizione.

Ricordiamo che questa cifra non si ridimensiona dinamicamente con il saldo del conto: utilizziamo sempre il saldo iniziale. Nelle implementazioni successive introdurremo logiche più avanzate di gestione del rischio e di dimensionamento della posizione.

             def calc_risk_position_size(self):
        return self.equity * self.risk_per_trade
        

Di seguito implementiamo il metodo add_new_position, che richiede i parametri necessari per aggiungere una nuova posizione al Portfolio. In particolare, specifichiamo add_price e remove_price. Non usiamo direttamente i prezzi bid/ask perché dipendono dal fatto che la posizione sia “long” o “short”. Dobbiamo quindi indicare correttamente quale prezzo utilizzare per garantire un backtest realistico.

                def add_new_position(
        self, side, market, units, exposure,
        add_price, remove_price
    ):
        ps = Position(side, market, units, exposure,
                      add_price, remove_price
                     )
        
        self.positions[market] = ps
        

Calcolo delle posizioni nel portafoglio

Abbiamo bisogno di un metodo, add_position_units, che ci permetta di aggiungere unità a una posizione già esistente. Per farlo, calcoliamo il nuovo prezzo medio delle unità acquistate. Ricordiamo che possiamo ottenerlo utilizzando la seguente espressione:

\(\begin{eqnarray}\frac{\sum_{p=1}^{P} c_p u_p} {\sum_{p = 1} ^ {P} u_p} \end{eqnarray}\)

Dove \(P\) rappresenta il numero di acquisti, \(c_p\) indica il costo di acquisto per ciascun acquisto \(p\) e \(u_p\) rappresenta le unità acquistate con l’acquisto \(p\).

Dopo aver calcolato il nuovo prezzo medio, aggiorniamo le unità nella posizione e ricalcoliamo il P&L associato alla posizione.

                def add_position_units(
        self, market, units, exposure, 
        add_price, remove_price
    ):
        if market not in self.positions:
            return False
        else:
            ps = self.positions[market]
            new_total_units = ps.units + units
            new_total_cost = ps.avg_price*ps.units + add_price*units
            ps.exposure += exposure
            ps.avg_price = new_total_cost/new_total_units
            ps.units = new_total_units
            ps.update_position_price(remove_price)
            return True
        

Allo stesso modo, abbiamo bisogno di un metodo per rimuovere unità da una posizione senza chiuderla completamente. Lo realizziamo con remove_position_units. Una volta ridotte le unità e l’esposizione, calcoliamo il conto economico delle unità rimosse e lo aggiungiamo (o lo sottraiamo!) dal saldo del portafoglio:

                def remove_position_units(
        self, market, units, remove_price
    ):
        if market not in self.positions:
            return False
        else:
            ps = self.positions[market]
            ps.units -= units
            exposure = float(units)
            ps.exposure -= exposure
            ps.update_position_price(remove_price)
            pnl = ps.calculate_pips() * exposure / remove_price 
            self.balance += pnl
            return True
        

Abbiamo anche bisogno di un modo per chiudere completamente una posizione. Usiamo close_position per farlo. Questo metodo funziona in modo simile a remove_position_units, con la differenza che elimina la posizione dal dizionario positions:

                def close_position(
            self, market, remove_price
    ):
        if market not in self.positions:
            return False
        else:
            ps = self.positions[market]
            ps.update_position_price(remove_price)
            pnl = ps.calculate_pips() * ps.exposure / remove_price
            self.balance += pnl
            del [self.positions[market]]
            return True
        

Gestione degli ordini

Gran parte del lavoro di questa classe si concentra nel metodo execute_signal. Usiamo questo metodo per prendere gli oggetti SignalEvent generati dagli oggetti Strategy e creare nuovi oggetti OrderEvent da inserire nella coda degli eventi.

La logica di base della lezione è la seguente:

  • Se non esiste una posizione corrente per questa coppia di valute, ne creiamo una.
  • Se una posizione esiste già, verifichiamo se dobbiamo aggiungere o sottrarre unità.
  • Se aggiungiamo unità, incrementiamo semplicemente la quantità corretta di unità.
  • Se non aggiungiamo unità, controlliamo se la riduzione delle unità chiude la posizione; in tal caso, la chiudiamo.
  • Se la riduzione riguarda meno unità rispetto alla posizione, rimuoviamo solo la quantità necessaria.
  • Se invece le unità ridotte superano quelle della posizione corrente, chiudiamo la posizione esistente e apriamo una nuova posizione opposta con le unità residue. Non abbiamo ancora testato ampiamente questo scenario, quindi potrebbero esserci dei bug!

Segue il codice della funzione execute_signal:

                def execute_signal(self, signal_event):
        side = signal_event.side
        market = signal_event.instrument
        units = int(self.trade_units)

        # Controlla il lato per il corretto prezzo bid/ask
        # TODO: Supporta solo i long
        add_price = self.ticker.cur_ask
        remove_price = self.ticker.cur_bid
        exposure = float(units)

        # Se non c'è una posizione, si crea una nuova
        if market not in self.positions:
            self.add_new_position(
                side, market, units, exposure,
                add_price, remove_price
            )
            order = OrderEvent(market, units, "market", "buy")
            self.events.put(order)
        # Se la posizione esiste, si aggiunge o rimuove unità
        else:
            ps = self.positions[market]
            # controlla se il lato è coerente con il lato della posizione
            if side == ps.side:
                # aggiunge unità alla posizione
                self.add_position_units(market, units, exposure,
                                        add_price, remove_price
                                        )
            else:
                # Controlla se ci sono unità nella posizione
                if units == ps.units:
                    # Chiude la posizione
                    self.close_position(market, remove_price)
                    order = OrderEvent(market, units, "market", "sell")
                    self.events.put(order)
                elif units < ps.units:
                    # Rimuove unità dalla posizione
                    self.remove_position_units(
                        market, units, remove_price
                    )
                else:  # units > ps.units
                    # Chiude la posizione e crea una nuova posizione
                    # nel lato opposto con le unità rimanenti
                    new_units = units - ps.units
                    self.close_position(market, remove_price)

                    if side == "buy":
                        new_side = "sell"
                    else:
                        new_side = "sell"
                    new_exposure = float(units)
                    self.add_new_position(
                        new_side, market, new_units,
                        new_exposure, add_price, remove_price
                    )
        print
        "Balance: %0.2f" % self.balance
        

Abbiamo concluso l’implementazione della classe Portfolio. Ora analizziamo insieme come gestire gli eventi.

Gestione degli Eventi

Per far funzionare la componente di portafoglio con le nuove logiche di generazione di segnali e ordini, modifichiamo il file event.py. In particolare, aggiungiamo la componente SignalEvent, che ora l’oggetto Strategy genera direttamente, invece di creare un OrderEvent.

Questo evento indica semplicemente se entrare long o short su un particolare “strumento”, come una coppia di valute. Il campo order_type specifica se l’ordine è di tipo market o limit. Per ora lavoriamo solo con ordini di mercato, dato che non abbiamo ancora implementato gli ordini limite:

            class Event(object):
    pass


class TickEvent(Event):
    def __init__(self, instrument, time, bid, ask):
        self.type = 'TICK'
        self.instrument = instrument
        self.time = time
        self.bid = bid
        self.ask = ask


class SignalEvent(Event):
    def __init__(self, instrument, order_type, side):
        self.type = 'SIGNAL'
        self.instrument = instrument
        self.order_type = order_type
        self.side = side        


class OrderEvent(Event):
    def __init__(self, instrument, units, order_type, side):
        self.type = 'ORDER'
        self.instrument = instrument
        self.units = units
        self.order_type = order_type
        self.side = side        
        

Gestione della Strategia

Dopo aver definito l’oggetto SignalEvent, modifichiamo la logica di funzionamento della classe Strategy. Ora la strategia deve generare eventi SignalEvent invece di OrderEvent.

Modifichiamo effettivamente la logica di base della strategia: invece di creare segnali casuali di acquisto o vendita, generiamo un ordine di acquisto ogni 5 tick. Quando il sistema risulta “investito”, effettuiamo una vendita al 5° tick successivo e torniamo “non investiti”. Ripetiamo questo processo in un ciclo continuo:

            from event import SignalEvent

class TestStrategy(object):
    def __init__(self, instrument, events):
        self.instrument = instrument
        self.events = events
        self.ticks = 0
        self.invested = False

    def calculate_signals(self, event):
        if event.type == 'TICK':
            self.ticks += 1
            if self.ticks % 5 == 0:
                if self.invested == False:
                    signal = SignalEvent(self.instrument, "market", "buy")
                    self.events.put(signal)
                    self.invested = True
                else:
                    signal = SignalEvent(self.instrument, "market", "sell")
                    self.events.put(signal)
                    self.invested = False
        

Gestione del flusso dati

Il Portfolio richiede un oggetto ticker che contenga i prezzi ask/bid più recenti. Modifichiamo quindi l’oggetto StreamingForexPrices nel file streaming.py aggiungendo due attributi extra:

            ..
..
        self.cur_bid = None
        self.cur_ask = None
..
..
        

Questi attributi sono valorizzati nel metodo stream_to_queue:

            ..
..
                if msg.has_key("instrument") or msg.has_key("tick"):
                    print msg
                    instrument = msg["tick"]["instrument"]
                    time = msg["tick"]["time"]
                    bid = msg["tick"]["bid"]
                    ask = msg["tick"]["ask"]
                    self.cur_bid = bid
                    self.cur_ask = ask
                    tev = TickEvent(instrument, time, bid, ask)
                    self.events_queue.put(tev)
        

Come per ogni lezione di questo corso, il codice completo è disponibile nel seguente repository GitHub: github.com/tradingquant-it/TQforex

Logica di Trading

L’ultima parte delle modifiche per gestire la componente di portafoglio per il forex riguarda il file trading.py. Iniziamo modificando le importazioni per riflettere la struttura della directory e per importare l’oggetto Portfolio:

            from execution import Execution
from portfolio import Portfolio
from settings import STREAM_DOMAIN, API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID
from strategy import TestStrategy
from data import StreamingForexPrices
        
Poi, aggiorniamo il gestore della coda degli eventi per indirizzare SignalEvents all’istanza di Portfolio:
            ..
..
    while True:
        try:
            event = events.get(False)
        except Queue.Empty:
            pass
        else:
            if event is not None:
                if event.type == 'TICK':
                    strategy.calculate_signals(event)
                elif event.type == 'SIGNAL':
                    portfolio.execute_signal(event)
                elif event.type == 'ORDER':
                    execution.execute_order(event)
        time.sleep(heartbeat)
..
..
        
Infine, modifichiamo la funzione __main__ per creare il Portfolio e regoliamo trade_thread per passare il Portfolio come argomento:
                ..
    ..
    # Crea un oggetto Portfolio che sarà usato per 
    # confrontare le posizioni OANDA con quelle locali
    # in modo da verificare l'integrità del backtesting.
    portfolio = Portfolio(prices, events, equity=100000.0)

    # Crea due threads separati: Uno per il ciclo di trading
    # e l'altro per lo streaming dei prezzi di mercato
    trade_thread = threading.Thread(
        target=trade, args=(
            events, strategy, portfolio, execution
        )
    )
    ..
    ..
        

Variabili d’ambiente nelle impostazioni

Nella lezione precedente abbiamo sottolineato che non è sicuro memorizzare le password o altre informazioni di autenticazione, come il token API, nel codice sorgente. Per questo motivo, modifichiamo il file delle impostazioni in questo modo:
            import os

ENVIRONMENTS = { 
    "streaming": {
        "real": "stream-fxtrade.oanda.com",
        "practice": "stream-fxpractice.oanda.com",
        "sandbox": "stream-sandbox.oanda.com"
    },
    "api": {
        "real": "api-fxtrade.oanda.com",
        "practice": "api-fxpractice.oanda.com",
        "sandbox": "api-sandbox.oanda.com"
    }
}

DOMAIN = "practice"
STREAM_DOMAIN = ENVIRONMENTS["streaming"][DOMAIN]
API_DOMAIN = ENVIRONMENTS["api"][DOMAIN]
ACCESS_TOKEN = os.environ.get('OANDA_API_ACCESS_TOKEN', None)
ACCOUNT_ID = os.environ.get('OANDA_API_ACCOUNT_ID', None)
        
Nello specifico, le seguenti due righe:
            ACCESS_TOKEN = os.environ.get('OANDA_API_ACCESS_TOKEN', None)
ACCOUNT_ID = os.environ.get('OANDA_API_ACCOUNT_ID', None)
        

Abbiamo utilizzato la libreria os per recuperare due variabili di ambiente (ENVVARS). Il primo è il token di accesso API e il secondo è l’ID account OANDA. Questi parametri possono essere memorizzati in un file di ambiente che viene caricato all’avvio del sistema. In Ubuntu, possiamo usare il file nascosto .bash_profile nella directory home. Ad esempio, usando l’editor di testo preferito, possiamo digitare:

            emacs ~/.bash_profile
        

Aggiungiamo le seguenti righe, assicurandosi di sostituire le variabili con i dettagli di un account “practice”:

            export OANDA_API_ACCESS_TOKEN='1234567890abcdef1234567890abcdef1234567890abcdef'
export OANDA_API_ACCOUNT_ID='12345678'
        

Dobbiamo anche assicurarci che il terminale abbia accesso a queste variabili eseguendo quanto segue da riga di comando:

            source ~/.bash_profile
        

Esecuzione del Codice

Per eseguire il codice dobbiamo impostare correttamente l’ambiente virtuale. A tale scopo possiamo eseguire il seguente comando (attenzione a specificare la directory corretta):

            source ~/venv/qsforex/bin/activate
        

Dobbiamo anche installare la libreria requests:

            pip install requests
        

Infine, possiamo eseguire il codice (assicurandosi di adattare il percorso al codice sorgente del progetto):

            python dtforex/trading/trading.py
        

A questo punto, stiamo effettuando il nostro primo sistema di trading con la componente di portafoglio per il forex! Come abbiamo affermato nella lezione precedente, è facile perdere denaro con un sistema di questo tipo collegato a un conto di trading live! Assicurati di visualizzare il disclaimer e di essere estremamente attento con gli oggetti Strategy. Ti consigliamo di provarlo prima sulla sandbox o sugli account demo, prima di passare a una vera implementazione live.

Tuttavia, prima di procedere con l’implementazione di strategie personalizzate, vogliamo discutere da dove potrebbero derivare alcune delle differenze tra il saldo del conto OANDA e il saldo calcolato.

Possibili fonti di errore

Man mano che l’implementazione dei sistemi diventa più complessa, aumenta il rischio di introdurre bug. Possiamo utilizzare alcuni unit test per verificare se gli oggetti Position e Portfolio si comportano come previsto, ma ci sono ancora discrepanze tra il portafoglio locale e il saldo del conto OANDA. Le possibili ragioni includono:

  • Bug: Ovviamente, i bug possono insinuarsi ovunque. Il modo migliore per eliminarli è definire in anticipo delle solide specifiche su ciò che il programma dovrebbe fare e creare unit test precisi. È necessario prevedere ulteriore lavoro per effettuare gli unit test di tutte le classi.
  • Errori di arrotondamento: Poiché utilizziamo variabili a virgola mobile per memorizzare tutti i dati finanziari, si verificheranno errori nell’arrotondamento. Per aggirare questo problema, è possibile usare il tipo Decimal di Python. Le implementazioni successive utilizzeranno il valore Decimal.
  • Slippage: Lo slippage è la differenza tra il prezzo definito dall’oggetto Strategy al momento dell’acquisto o della vendita e il prezzo effettivo raggiunto quando il broker esegue l’ordine. Data la natura multi-threaded del programma, è molto probabile che lo slippage sia una delle cause delle differenze tra il saldo locale e il saldo del conto OANDA.

Studieremo questi problemi mentre continuiamo a lavorare sul sistema forex. Nella prossima lezione del corso, vedremo i nostri progressi.

Conclusioni

In questa lezione, abbiamo descritto come implementare la componente di portafoglio sul forex all’interno di un sistema di trading event-driven.

Nelle prossime lezioni discuteremo i seguenti miglioramenti:

  • Saldi contabili differenti – Il primo compito è determinare perché i saldi contabili differiscono tra OANDA e questa implementazione locale.
  • Strategie reali: Recentemente, abbiamo letto alcuni articoli su come applicare l’apprendimento automatico ai mercati forex. Sarebbe interessante (e divertente!) convertire alcune di queste teorie in strategie effettive da testare tramite backtest.
  • Valute multiple – Aggiunta di più coppie di valute e valute di base alternative.
  • Costi di transazione – Gestione realistica dei costi di transazione, oltre allo spread denaro-lettera. Ciò includerà una migliore modellazione dello slippage e un impatto sul mercato.

Ci sono anche molti altri miglioramenti da apportare. Questo progetto migliorerà continuamente e speriamo che possa aiutarti nel tuo trading automatico.

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