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
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)
..
..
__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)
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.”