forex-python-trading-algoritmico-002

DTForex #2 – Aggiunta di un Portafoglio al Sistema di Trading Automatico sul Forex

Sommario

Nel primo articolo della serie sul trading algoritmico sul Forex (link) abbiamo descritto come creare un sistema di trading automatico che si collega all’API del broker OANDA. Abbiamo anche menzionato che i passaggi successivi includevano la costruzione di un portafoglio e una copertura per la gestione del rischio da applicare per tutti i segnali suggeriti e generati dalla componente Strategy. In questo articolo descriviamo come costruire una componente di Portfolio completa e funzionante.

Questa componente è necessaria se vogliamo costruire un motore di backtest per le strategie forex in modo analogo a quanto descritto in precedenza con il mercato azionario tramite il backtester guidato dagli eventi. In particole si vuole un ambiente che presenti una differenza minima tra il trading live e il sistema di backtest. Per questo motivo dobbiamo realizzare una componente di portafoglio che riflettesse (per quanto possibile) lo stato attuale del conto di trading fornito da OANDA.

La logica base prevede che il conto di trading “practice” e le componenti del portafoglio locale dovrebbero avere valori simili, se non uguali, per attributi come il saldo del conto, il profitto e la perdita (P&L) non realizzati, il conto economico realizzato e qualsiasi posizione aperta . Se raggiungiamo questo obiettivo ed eseguiamo alcune strategie di test tramite questa componente di portafoglio, e se gli attributi risultino con valori uguali sia nel portfolio locale che in OANDA, allora potremmo essere fiduciosi nella capacità del backtester di produrre risultati più realistici, molto simili a quelli che si le strategie avrebbero avuto se fossero state in “live”.

Ho passato gli ultimi due giorni a tentare di implementare un tale oggetto Portfolio e credo di esserci quasi riuscito. Vedo ancora alcune differenze tra il saldo del portafoglio locale e il saldo del conto OANDA dopo che sono state effettuate diverse operazioni.

Quali sono i limiti attuali di questa implementazione?

  • La valuta di base, e quindi l’esposizione, è codificata per essere EUR. Deve essere cambiato per consentire la scelta di qualsiasi valuta di base.
  • Attualmente l’ho testato solo per EUR / USD, poiché la mia valuta di base è EUR Successivamente modificherò i calcoli dell’esposizione per consentire qualsiasi coppia di valute.
  • Sebbene alcuni unit test abbiano suggerito che l’aggiunta e la rimozione di posizioni e unità sta funzionando come previsto, non è stato ancora testato.
  • Finora l’ho provato solo con l’apertura e la chiusura di posizioni long, non ho testato posizioni short. Avrò bisogno di scrivere alcuni unit test per gestire le posizioni short.

Ci si potrebbe ragionevolmente chiedere perché sto descrivendo questa componente se presenta tutte queste limitazioni? In questo modo vorrei che qualsiasi lettore di questo articolo possa essere consapevole di come la creazione di sistemi di trading algoritmico è un lavoro duro e richiede molta attenzione ai dettagli! C’è un notevole margine di manovra per introdurre bug e comportamenti scorretti. Voglio delineare come vengono costruiti i sistemi del “mondo reale” e mostrarvi come testare questi errori e correggerli.

Inizieremo descrivendo come ho costruito l’attuale configurazione del portafoglio e poi l’ho integrato nel sistema di trading demo che abbiamo esaminato nel precedente articolo.  Successivamente vedremo i punti in cui penso ci possano essere differenze.

Il seguente codice “così com’è” sotto il disclaimer che ho indicato nel precedente articolo.

Creazione del Portfolio

Per generare un oggetto Portfolio è necessario descrivere come vengono eseguite le negoziazioni in valuta, poiché differiscono in modo sostanziale dalle azioni.

Calcolo di Pips e Unità

In altre classi di attività, il più piccolo incremento di una variazione del prezzo dell’asset è noto come “tick”. Nel trading in Forex è noto come “pip” (Price Interest Point). È l’incremento più piccolo in qualsiasi coppia di valute ed è (di solito) 1/100 di centesimo, noto anche come punto base. Dato che la maggior parte delle principali coppie di valute ha un prezzo di quattro cifre decimali, la variazione più piccola si verifica sull’ultimo punto decimale.

In EUR / USD, ad esempio, un movimento da 1.1184 a 1.1185 è un pip (4 cifre decimali) e quindi un pip è uguale a 0.0001. Qualsiasi valuta basata sullo yen giapponese utilizza due punti decimali, quindi un pip sarebbe uguale a 0,01. 

Una domanda che possiamo ora porci è: 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 prendiamo 2.000 unità della valuta di base (ad esempio 2.000 euro), possiamo calcolare il P&L in euro come segue: 

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

Con OANDA siamo liberi di scegliere il numero di quote negoziate (e quindi la generica esposizione). Dal momento che ho un conto in euro (EUR) e sto negoziando EUR/USD (in questo esempio) l’esposizione sarà sempre uguale al numero di unità. Questo è attualmente “codificato” nel sistema sottostante. Nel caso si vuole gestire più coppie di valute, è indispensabile modificare il calcolo dell’esposizione per tenere conto delle diverse valute di base.

Poiché il valore del profitto sopra descritto è piuttosto piccolo e le valute non oscillano molto (tranne quando lo fanno!), di solito è necessario introdurre la leva finanziaria nel conteggio. Discuteremo di questo negli articoli successivi. Per ora, non  dobbiamo preoccuparcene.

Panoramica del sistema di backtesting / trading

Il sistema attuale è costituito dai 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 il concetto di una “posizione” Forex, ovvero un “long” o uno “short” in una coppia di valute con associata una quantità di unità.
  • Portfolio: il componente Portfolio contiene più oggetti Position, uno per ciascuna coppia di valute negoziata. Tiene traccia dell’attuale P&L di ciascuna posizione, anche dopo successivi incrementi e riduzioni di unità.
  • Strategy – L’oggetto Strategy prende le informazioni delle serie temporali (tick delle coppie di valute) e quindi calcola e invia gli eventi di segnale al portafoglio, che decide come agire su di essi.
  • Streaming Forex Price: questo componente si collega a OANDA tramite un web-socket in streaming e riceve dati tick-by-tick in tempo reale (ovvero bid / ask) da qualsiasi coppia di valute sottoscritta.
  • Esecution: si prende gli eventi  di tipo “Ordine” e li invia a OANDA per essere eseguiti.
  • Trading Loop – Il trading loop avvolge insieme tutti i componenti descritti sopra ed esegue due thread: uno per i prezzi di streaming e uno per il gestore di eventi.

Per ottenere maggiori informazioni su come il sistema è collegato insieme, vale la pena leggere il precedente articolo di questa serie.

Implementazione in Python

Discuteremo ora come implementare in Python il sistema appena descritto.

Position

Il primo componente è l’oggetto Position. È progettato 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 negoziare, ad es. “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 ci sono \(P\) acquisti, il prezzo medio viene calcolato come \(\frac{\sum_{p=1} ^ Pc_pu_p} {\sum_{p=1}^{P}u_p}\), dove \(c_p\) è il costo di acquisto \(p\) e \(u_p\) sono le unità acquisite per l’acquisto \(p\).
  • Current: il prezzo di vendita corrente.
  • Profit (BASE) – L’attuale P&L nella valuta base della posizione.
  • Profit (%): l’attuale percentuale di P&L della posizione.

Come è evidente nel codice seguente, questi attributi sono stati riflessi come membri della classe Position, ad eccezione di “Type”, che ho rinominato “side”, poiché type è una parola riservata in Python!

La classe ha quattro metodi (esclusa l’inizializzazione): calculate_pips, calculate_profit_base, calculate_profit_perc e update_position_price.

Il primo metodo, calculate_pips, determina il numero di pips che sono stati generati dalla posizione da quando è stata aperta (tenendo conto di eventuali nuove unità aggiunte alla posizione). Il secondo metodo, calculate_profit_base, calcola il profitto (o la perdita!) corrente sulla posizione. Il terzo metodo, calculate_profit_perc, determina la percentuale di profitto sulla posizione. Infine, update_position_price aggiorna 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, ci sarà un’istanza di classe per ogni mercato che viene negoziato. Come accennato in precedenza, al momento il Portfoilo gestisce solamente EUR come valuta di base e EUR/USD come strumento di trading. Negli articoli futuri vedremo In articoli futuri estenderò l’oggetto Portfolio per gestire più valute di base e più coppie di valute. Parliamo ora di come configurare un ambiente virtuale di base per Python e quindi di come funziona il Portfolio.

Symlink per l'ambiente virtuale

Nel seguente modulo dell’oggetto Portfolio ho modificato il modo in cui vengono gestite le importazioni. Ho creato un ambiente virtuale, per cui ho aggiunto un collegamento simbolico alla mia directory DTForex. Ciò mi consente di fare riferimento a una gerarchia annidata di file di progetto all’interno di ogni modulo Python. Il codice per realizzare questo in Ubuntu è simile al seguente:

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

Ovviamente si deve sostituire le posizioni del tuo ambiente virtuale e la posizione del codice sorgente. Normalmente memorizzo i miei ambienti virtuali nella directory home in ~/venv/. Memorizzo i miei progetti nella directory home in ~/sites/. Questo mi consente di fare riferimento, ad esempio, a dtforex.event.event import OrderEvent da qualsiasi file all’interno del progetto.

Portfolio

Il costruttore __init__ del Portfolio richiede i seguenti argomenti:

  • ticker – il gestore del ticker dei prezzi forex in streaming. Viene utilizzato per ottenere gli ultimi prezzi bid / ask.
  • event: la coda degli eventi, in cui il portfolio deve inserire gli eventi.
  • base – la valuta di base, nel mio caso è EUR.
  • leverage – il fattore di leva. Attualmente è 1:20.
  • equity – la quantità di patrimonio netto effettivo nel conto, che ho impostato per default a 100.000.
  • risk_per_trade – la percentuale del patrimonio netto del conto da poter rischiare per ogni operazione, che ho impostato di default al 2%. Ciò significa che le unità di scambio saranno pari a 2.000 per una dimensione del conto iniziale di 100.000.

All’inizializzazione la classe calcola le trade_units, che sono la quantità massima di unità consentite per posizione, oltre a dichiarare il dizionario delle positions (ogni mercato è una chiave) che contiene tutte le posizioni aperte all’interno del 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” è piuttosto semplice! Nel seguente metodo calc_risk_position_size ci assicuriamo solamente che l’esposizione di ciascuna posizione non superi il risk_per_trade% del capitale del conto. Il valore predefinito del risk_per_trade è 2% come argomento della parola chiave, sebbene questo possa ovviamente essere modificato. Quindi per un conto di 100.000 euro, il rischio per operazione non supererà 2.000 euro per posizione.

Nota che questa cifra non si ridimensionerà dinamicamente con la dimensione del saldo del conto, utilizzerà solo il saldo del conto iniziale. Le implementazioni successive incorporeranno logiche più sofisticate di gestione del rischio e dimensionamento della posizione.

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

Il successivo metodo, add_new_position, richiede i parametri necessari per aggiungere una nuova posizione al Portfolio. In particolare, richiede add_price e remove_price. Non si utilizza direttamente i prezzi bid/ask perché i prezzi dipenderanno dal fatto che il lato sia “long” o “short”. Quindi dobbiamo specificare correttamente quale prezzo considerare in modo da ottenere 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
        

Abbiamo anche bisogno di un metodo, add_position_units, che consente di aggiungere unità ad una posizione, solamente dopo aver precedentemente creato la posizione. Per fare ciò dobbiamo calcolare il nuovo prezzo medio delle unità acquistate. Ricorda che questo viene calcolato dalla seguente espressione:

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

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

Una volta calcolato il nuovo prezzo medio, le unità vengono aggiornate nella posizione e quindi viene ricalcolato 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 le unità da una posizione (ma non per chiuderla completamente). Questo è implementato da remove_position_units. Una volta che le unità e l’esposizione sono state ridotte, il conto economico viene calcolato per le unità rimosse e quindi aggiunto (o sottratto!) 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. Questo è implementato in close_position. È simile a remove_position_units tranne per il fatto che la posizione viene eliminata 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
        

La maggior parte del lavoro di questa classe viene eseguita dal metodo execute_signal. Il metodo rende gli oggetti SignalEvent creati dagli oggetti Strategy e li utilizza per generare oggetti OrderEvent da reinserire nella coda degli eventi.

La logica di base è la seguente:

  • Se non esiste una posizione corrente per questa coppia di valute, creane una.
  • Se una posizione esiste già, controlla se sta aggiungendo o sottraendo unità.
  • Se sta aggiungendo unità, aggiungi semplicemente la quantità corretta di unità.
  • Se non sta aggiungendo unità, controlla se la nuova riduzione di unità avversaria chiude lo scambio, in tal caso fallo.
  • Se le unità di riduzione sono inferiori alle unità di posizione, rimuovere semplicemente quella quantità dalla posizione.
  • Tuttavia, se le unità riducenti superano la posizione corrente, è necessario chiudere la posizione corrente dalle unità riducenti e quindi creare una nuova posizione opposta con le unità rimanenti. Non l’ho ancora testato ampiamente, quindi potrebbero esserci ancora dei bug!

Il codice per execute_signal segue:

                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
        

Questo conclude il codice per la classe Portfolio. Ora discutiamo della gestione degli eventi.

Event

Affinché questo Portfolio funzioni con le nuove logiche di generazione di segnali e ordini è necessario modificare event.py. In particolare abbiamo aggiunto la componente SignalEvent, che ora è generato dall’oggetto Strategy, invece di un OrderEvent. Indica semplicemente se andare long o short su un particolare “strumento”, cioè una coppia di valute. L’order_type si riferisce al fatto che l’ordine sia un ordine di mercato o un ordine limite. Non ho abbiamo ancora implementato quest’ultimo, quindi per ora sarà sempre valorizzato come “market”:

            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        
        

Strategy

Dopo aver definito l’oggetto SignalEvent, dobbiamo modificare la logica di funzionamento della classe Strategy. In particolare, ora deve generare eventi SignalEvent invece di OrderEvents.

Dobbiamo effettivamente cambiare la logica base della “strategia”. Invece di creare segnali casuali di acquisto o vendita, ora genera un ordine di acquisto ogni 5 tick e quindi il sistema diventa “investito”. Al 5° tick successivo, se è investito, si effettua una vendita e diventa “non investito”. Questo processo si ripete in un ciclo infinito:

            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
        

StreamingForexPrices

L’oggetto Portfolio richiede un oggetto ticker che contienea i prezzi ask/bid più recenti. Abbiamo semplicemente modificato StreamingForexPrices nel file streaming.py per contenere due attributi extra:

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

Questi attributo 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 oggetto di questo articolo, il codice completo può essere trovato nel seguente repository di github: github.com/datatrading-info/DTForex

Trading

L’ultima parte delle modifiche è relativo al file trading.py. Per prima cosa si modificano le importazioni per tenere conto della struttura della directory e del fatto che ora stiamo importando un 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
        

Quindi modifichiamo 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 aggiustiamo trade_thread per prendere 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

Nell’articolo precedente abbiamo menzionato che non è una buona idea memorizzare le password o altre informazioni di autenticazione, inclusi il token API, all’interno del codice sorgente. Quindi si può modificare 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 possono essere memorizzati in un file di ambiente che viene caricato all’avvio del sistema. In Ubuntu, puoi usare il file .bash_profile nascosto nella tua directory home. Ad esempio, usando l’editor di testo preferito, si può digitare:

            emacs ~/.bash_profile
        
Si aggiungono 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'
        
Potrebbe essere necessario assicurarsi 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 è necessario assicurarsi che l’ambiente virtuale sia correttamente impostato. Lo si può verificare eseguendo il seguente comando (attenzione e a specificare la directory corretta):
            source ~/venv/qsforex/bin/activate
        

Si dovrà anche installare la libreria requests, se non è statp fatto durante l’articolo precedente:

            pip install requests
        
Infine, si può 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 sismtea di trading! Come affermato nell’articolo precedente, è molto facile perdere denaro con un sistema di questo tipo collegato a un conto di trading live! Assicurati di visualizzare il disclaimer nel post e di essere estremamente attento con gli oggetti Strategy. Consiglio vivamente di provarlo sulla sandbox o sugli account di esercitazione prima di un’implementazione live.

Tuttavia, prima di procedere con l’implementazione di strategie personalizzate, vorrei discutere da dove credo derivino 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 che siano stati introdotti bug. Si possono 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 precisi unit test. È necessario prevedere ulteriore lavoro per effettuare gli unit test  di tutte le classi
  • Errori di arrotondamento: poiché si utilizza variabili a virgola mobile per memorizzare tutti i dati finanziari, si verificheranno errori nell’arrotondamento. Il modo per aggirare questo è usare il tipo Decimal di Python. Le implementazioni successive utilizzeranno il valore Decimal.
  • Slippage – Lo slippage è la differenza tra il prezzo che l’oggetto Strategy ha definito quando ha deciso di acquistare o vendere e il prezzo effettivo raggiunto quando il broker esegue un ordine. Data la natura multi-threaded del programma, è estremamente probabile che lo slippage sia una delle cause delle differenze tra il saldo locale e il saldo del conto OANDA.

Studierò questi problemi mentre continuo a lavorare sul sistema forex. Nella prossimo articolo della serie vedremo i miei progressi.

Prossimi Passi

Negli articoli successivi discuteremo i seguenti miglioramenti:

  • Saldi contabili differenti – Il primo compito è determinare perché i saldi contabili differiscono tra OANDA e questa implementazione locale. 
  • Strategie reali: di recente ho letto alcuni articoli su come applicare l’apprendimento automatico ai mercati forex. Convertire alcune di questi teorie in strategie effettive di cui possiamo il bakctest sarebbe interessante (e divertente!).
  • Valute multiple – Aggiunta di più coppie di valute e valute di base alternative.
  • Costi di transazione – Gestione realistica dei costi di transazione, oltre che lo 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 spero che possa aiutarti nel tuo trading automatico

 

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...

Scroll to Top