La Logica del Portafoglio Quantitativo con DataTrader

Nell’articolo precedente del tutorial per implementare un Sistema di Trading  Quantitativo Avanzato, abbiamo mostrato il codice e i test iniziali della classe Position, che contiene i dati di ogni trade. In questo articolo descriviamo come implementare la logica del portafoglio nella classe Portfolio, dove manteniamo un elenco di istanze di Position e il saldo complessivo del conto.

Ultimamente abbiamo fatto notevoli progressi su DataTrader, il sistema open source per backtest e trading live descritto in questa serie. Abbiamo completato la prima versione completa del codice end-to-end, usando una strategia semplice e volutamente non redditizia per validare la piattaforma. Documentiamo tutto con articoli sequenziali per spiegare ogni modulo.

Attualmente abbiamo solo questi articoli come base, ma per rendere DataTrader una libreria affidabile per il backtest servono guide dettagliate. Una volta stabilizzato il codice, produrremo documentazione completa e tutorial per facilitare il backtest su qualsiasi sistema operativo o frequenza operativa.

Ribadiamo che manteniamo il progetto disponibile su https://github.com/tradingquant-it/DataTrader con licenza open source MIT.

Promemoria per la progettazione dei componenti

Nell’articolo precedente abbiamo presentato i moduli principali di DataTrader. Ora estendiamo l’elenco per includere tutte le componenti essenziali per realizzare un backtest completo.

Molti moduli risultano noti agli utenti di TQForex e del backtester TQTradingSystem. Rispetto a quei progetti, includiamo test unitari e abbiamo aggiunto molte nuove funzionalità.

Progettiamo DataTrader con questa struttura:

  • Position – gestiamo tutti i dati relativi a una posizione aperta, tracciando PnL realizzato e non realizzato, inclusi i costi.
  • Portfolio – manteniamo una lista di oggetti Position, saldo del conto, equity e PnL totale.
  • PositionSizer – definiamo come dimensionare le posizioni quando riceviamo un segnale dalla strategia.
  • RiskManager – controlliamo, modifichiamo o blocchiamo un trade proposto dal PositionSizer, valutando rischio e composizione del portafoglio.
  • PortfolioHandler – coordiniamo Portfolio, interagiamo con RiskManager e PositionSizer ed emettiamo ordini tramite ExecutionHandler.
  • Event – usiamo Event e le sue sottoclassi per inviare messaggi tra moduli, utilizzando una coda Python per gestire eventi.
  • Strategy – generiamo segnali di trading basati sui prezzi e li inviamo al PortfolioHandler.
  • ExecutionHandler – riceviamo OrderEvent e produciamo FillEvent, basandoci su dati reali o simulati.
  • PriceHandler – colleghiamo sorgenti dati come CSV, HDF5, RDBMS, MongoDB o API di streaming live.
  • Backtest – integriamo tutti i moduli per eseguire il backtest; in futuro sostituiremo alcuni per attivare il live trading.

Ci manca ancora un modulo per calcolare statistiche e visualizzare i risultati del trading. Includeremo metriche come Sharpe Ratio, Maximum Drawdown, curve di equity, rendimenti e drawdown. Invece di accoppiare queste metriche al PortfolioHandler, come facevamo in TQForex e TQTradingSystem, creiamo una classe Result o Statistic a parte. Essa calcolerà e conterrà le metriche del backtest, permettendoci di integrare interfacce web o GUI per visualizzare risultati.

Inoltre, non abbiamo ancora moduli per robustezza, storage e monitoraggio. In un motore professionale per backtesting e live trading consideriamo fondamentali questi aspetti. Aggiungeremo quei moduli nei prossimi sviluppi. Prevediamo che quei componenti si appoggeranno a infrastrutture server o cloud, come Amazon Web Services o servizi equivalenti da altri provider.

Ora ci concentriamo sulla classe Portfolio. Nei prossimi articoli esploreremo come funziona PortfolioHandler e come interagisce con PositionSizer e RiskManager.

La logica del Portafolio

Vediamo la logica del portafoglio che vogliamo implementare in un sistema di trading quantitativo avanzato. Ricordiamo che in questo progetto la classe Portfolio differisce significativamente da quelle di TQForex o  TQTradingSystem. Nel progetto DataTrader separiamo il portfolio in due classi: Portfolio e PortfolioHandler.

Perché adottiamo questo approccio? Vogliamo una classe Portfolio leggera, che gestisca solo il valore in contanti e l’elenco di Position. L’unico metodo pubblico che usiamo è transact_position, che aggiorna una posizione in una specifica equity. Noi eseguiamo tutti i calcoli relativi a profitti e perdite, aggiornando il PnL realizzato e il PnL non realizzato.

In questo modo, la classe PortfolioHandler gestisce altre attività, come interfacciarsi con RiskManager e PositionSizer, mentre noi affidiamo i calcoli finanziari a Portfolio. Questo facilita i test, perché una classe calcola, l’altra interagisce con i moduli esterni.

Descriviamo i listati di codice di portfolio.py e portfolio_test.py e spieghiamo il funzionamento di ciascuno. Ricordiamo che miglioriamo costantemente il progetto, quindi quei codici potrebbero cambiare. Aspettiamo che molti di voi contribuiscano con richieste pull al codebase.

la classe Portfolio

				
					from decimal import Decimal
from position import Position


class Portfolio(object):
    def __init__(self, price_handler, cash):
        """
        Alla creazione, l'oggetto Portfolio non contiene posizioni
        e tutti i valori vengono "ripristinati" con il capitale
        iniziale e senza PnL - realizzato o non realizzato.
        """
        self.price_handler = price_handler
        self.init_cash = cash
        self.cur_cash = cash
        self.positions = {}
        self._reset_values()

    def _reset_values(self):
        """
        Questo viene chiamato dopo ogni aggiunta di
        posizione o modifica. Permette che i calcoli
        siano eseguito "da zero" in modo da minimizzare
        errori.

        Tutto il contanti venie ripristinato ai valori
        iniziali e il PnL è impostato a zero.
        """
        self.cur_cash = self.init_cash
        self.equity = self.cur_cash
        self.unrealised_pnl = Decimal('0.00')
        self.realised_pnl = Decimal('0.00')

    def _update_portfolio(self):
        """
         Aggiorna i valori totali del portafoglio (contanti, capitale,
        PnL non realizzato, PnL realizzato, costo base ecc.)
        su i valori correnti per tutti i ticker.

        Questo metodo viene chiamato dopo ogni modifica della posizione.
        """
        for ticker in self.positions:
            pt = self.positions[ticker]
            self.unrealised_pnl += pt.unrealised_pnl
            self.realised_pnl += pt.realised_pnl
            self.cur_cash -= pt.cost_basis
            pnl_diff = pt.realised_pnl - pt.unrealised_pnl
            self.cur_cash += pnl_diff
            self.equity += (
                pt.market_value - pt.cost_basis + pnl_diff
            )

    def _add_position(self, action, ticker, quantity, price, commission):
        """
        Aggiunge un nuovo oggetto Position al Portfolio. Questo
        richiede di ottenere il miglior prezzo bid / ask dal
        gestore del prezzo al fine di calcolare un ragionevole
        " valore di mercato ".

        Una volta aggiunta la posizione, i valori del portafoglio
        vengono aggiornati.
        """
        self._reset_values()
        if ticker not in self.positions:
            bid, ask = self.price_handler.get_best_bid_ask(ticker)
            position = Position(
                action, ticker, quantity,
                price, commission, bid, ask
            )
            self.positions[ticker] = position
            self._update_portfolio()
        else:
            print(
                "Ticker %s is already in the positions list. " \
                "Could not add a new position." % ticker
            )

    def _modify_position(self, action, ticker, quantity, price, commission):
        """
        Modifica un oggetto Posizione corrente nel Portafoglio.
        Ciò richiede di ottenere il miglior prezzo bid / ask dal
        gestore del prezzo al fine di calcolare un ragionevole
        " valore di mercato ".

        Una volta modificata la posizione, il portafoglio valorizza
        vengono aggiornati.
        """
        self._reset_values()
        if ticker in self.positions:
            self.positions[ticker].transact_shares(
                action, quantity, price, commission
            )
            bid, ask = self.price_handler.get_best_bid_ask(ticker)
            self.positions[ticker].update_market_value(bid, ask)
            self._update_portfolio()
        else:
            print(
                "Ticker %s not in the current position list. " \
                "Could not modify a current position." % ticker
            )

    def transact_position(self, action, ticker, quantity, price, commission):
        """
        Gestisce qualsiasi nuova posizione o modifica a 
        una posizione corrente, chiamando il rispettivo
        metodi _add_position e _modify_position. 

        Quindi, questo singolo metodo verrà chiamato da 
        PortfolioHandler per aggiornare il Portfolio stesso.
        """
        if ticker not in self.positions:
            self._add_position(
                action, ticker, quantity,
                price, commission
            )
        else:
            self._modify_position(
                action, ticker, quantity,
                price, commission
            )
				
			

Analogamente al codice di position.py dell’articolo precedente, utilizziamo ampiamente il modulo Decimal di Python. Come abbiamo già detto, consideriamo questo modulo essenziale nei calcoli finanziari. Altrimenti rischiamo errori di arrotondamento causati dalla matematica delle operazioni in virgola mobile.

Inizializzazione della classe

Nel metodo di inizializzazione della classe Portfolio forniamo due parametri di input: un PriceHandler e un saldo iniziale in cassa (di tipo Decimal, non float). Con questi due elementi creiamo l’istanza Portfolio senza necessità di ulteriori argomenti o configurazioni extra. Nel metodo definiamo due valori: la liquidità iniziale e quella corrente. In seguito costruiamo un dizionario che rappresenta le posizioni attive.

Infine richiamiamo il metodo _reset_values, che azzera tutti i calcoli di cassa e reimposta i valori PnL su zero.

				
					from decimal import Decimal
from position import Position


class Portfolio(object):
    def __init__(self, price_handler, cash):
        """
        Alla creazione, l'oggetto Portfolio non contiene posizioni
        e tutti i valori vengono "ripristinati" con il capitale
        iniziale e senza PnL - realizzato o non realizzato.
        """
        self.price_handler = price_handler
        self.init_cash = cash
        self.cur_cash = cash
        self.positions = {}
        self._reset_values()
				
			

Come accennato in precedenza, _reset_values è richiamato durante l’inizializzazione, ma è anche durante la modifica di ogni posizione. Può sembrare ingombrante, ma riduce notevolmente gli errori nel processo di calcolo. Reimposta semplicemente i valori di liquidità e di capitale correnti al valore iniziale e quindi azzera i valori PnL:

				
					

    def _reset_values(self):
        """
        Questo viene chiamato dopo ogni aggiunta di
        posizione o modifica. Permette che i calcoli
        siano eseguito "da zero" in modo da minimizzare
        errori.

        Tutto il contanti venie ripristinato ai valori
        iniziali e il PnL è impostato a zero.
        """
        self.cur_cash = self.init_cash
        self.equity = self.cur_cash
        self.unrealised_pnl = Decimal('0.00')
        self.realised_pnl = Decimal('0.00')
				
			

 

Il metodo successivo è _update_portfolio. Questo metodo viene chiamato anche dopo ogni modifica della posizione (cioè transazione). Per ogni ticker nel Portfolio, ogni PnL delle posizioni sono aggiunti al il PnL non realizzato e realizzato dell’intero portafoglio, mentre la liquidità disponibile corrente viene ridotta in base al costo base delle posizioni . Infine, la differenza tra PnL realizzato e non realizzato viene applicata alla liquidità corrente e si rettifica l’equity totale del portafoglio:

				
					    
    def _update_portfolio(self):
        """
         Aggiorna i valori totali del portafoglio (contanti, capitale,
        PnL non realizzato, PnL realizzato, costo base ecc.)
        su i valori correnti per tutti i ticker.

        Questo metodo viene chiamato dopo ogni modifica della posizione.
        """
        for ticker in self.positions:
            pt = self.positions[ticker]
            self.unrealised_pnl += pt.unrealised_pnl
            self.realised_pnl += pt.realised_pnl
            self.cur_cash -= pt.cost_basis
            pnl_diff = pt.realised_pnl - pt.unrealised_pnl
            self.cur_cash += pnl_diff
            self.equity += (pt.market_value - pt.cost_basis + pnl_diff)
				
			

Anche se può sembrare complesso, abbiamo progettato questi calcoli per riflettere la logica del portafoglio prevista per gestire i principali brokers, come Interactive Brokers. In questo modo, il motore di backtesting genera valori molto simili a quelli del live trading, assumendo uno slippage e costi di transazione realistici.

Gestione delle posizioni

Ora analizziamo i due metodi successivi: _add_position e _modify_position. In origine li abbiamo creati come metodi pubblici, utili per generare nuove posizioni e modificarle successivamente. In un secondo momento abbiamo deciso di semplificare la gestione dell’aggiunta o modifica di una posizione per l’utente. Per farlo, abbiamo introdotto un metodo wrapper chiamato transact_position, che seleziona il metodo corretto in base alla presenza del ticker nel portafoglio.

Il metodo _add_position riceve in input un’azione (acquisto o vendita), un ticker, una quantità, il prezzo di esecuzione e la commissione. Prima ripristiniamo i valori del portafoglio, poi otteniamo il miglior prezzo bid e ask dal gestore prezzi. A questo punto creiamo la nuova Position usando quei valori, così calcoliamo un valore di mercato aggiornato. Infine, inseriamo l’istanza Position nel dizionario delle posizioni utilizzando il ticker come chiave.

In questa fase chiediamo a _update_portfolio di aggiornare tutti i valori di mercato. Gestiamo anche il caso in cui la posizione esista già, stampando informazioni sulla console. In futuro sostituiremo ogni stampa console con un sistema di logging più robusto.

*Questa scelta comporta implicazioni progettuali future, come la rinomina dei ticker, la gestione di classi azionarie multiple e azioni societarie. Tuttavia, ora usiamo il ticker perché ci garantisce un’identificazione unica.

				
					
    def _add_position(self, action, ticker, quantity, price, commission):
        """
        Aggiunge un nuovo oggetto Position al Portfolio. Questo
        richiede di ottenere il miglior prezzo bid / ask dal
        gestore del prezzo al fine di calcolare un ragionevole
        " valore di mercato ".

        Una volta aggiunta la posizione, i valori del portafoglio
        vengono aggiornati.
        """
        self._reset_values()
        if ticker not in self.positions:
            bid, ask = self.price_handler.get_best_bid_ask(ticker)
            position = Position(
                action, ticker, quantity,
                price, commission, bid, ask
            )
            self.positions[ticker] = position
            self._update_portfolio()
        else:
            print(
                "Ticker %s is already in the positions list. " \
                "Could not add a new position." % ticker
            )
				
			

_modify_position è simile ad “add_position” tranne per il fatto che richiama transact_shares della classe Position invece di creare una nuova posizione:

				
					
    def _modify_position(self, action, ticker, quantity, price, commission):
        """
        Modifica un oggetto Posizione corrente nel Portafoglio.
        Ciò richiede di ottenere il miglior prezzo bid / ask dal
        gestore del prezzo al fine di calcolare un ragionevole
        " valore di mercato ".

        Una volta modificata la posizione, il portafoglio valorizza
        vengono aggiornati.
        """
        self._reset_values()
        if ticker in self.positions:
            self.positions[ticker].transact_shares(
                action, quantity, price, commission
            )
            bid, ask = self.price_handler.get_best_bid_ask(ticker)
            self.positions[ticker].update_market_value(bid, ask)
            self._update_portfolio()
        else:
            print(
                "Ticker %s not in the current position list. " \
                "Could not modify a current position." % ticker
            )
				
			
Il metodo che viene effettivamente chiamato esternamente è transact_position. Comprende sia la creazione che la modifica di un oggetto Position. Sceglie semplicemente il metodo corretto tra _add_position e _modify_position quando si effettua una nuova transazione:
				
					

    def transact_position(self, action, ticker, quantity, price, commission):
        """
        Gestisce qualsiasi nuova posizione o modifica a 
        una posizione corrente, chiamando il rispettivo
        metodi _add_position e _modify_position. 

        Quindi, questo singolo metodo verrà chiamato da 
        PortfolioHandler per aggiornare il Portfolio stesso.
        """
        if ticker not in self.positions:
            self._add_position(
                action, ticker, quantity,
                price, commission
            )
        else:
            self._modify_position(
                action, ticker, quantity,
                price, commission
            )
				
			

Questo conclude la classe Portfolio. Fornisce un robusto meccanismo autonomo per raggruppare le classi Position con un saldo di cassa.

Per completezza puoi trovare il codice completo per la classe Portfolio su Github.

Esecuzione dei test

Per verificare la corretta implementazione della logica del portafoglio dobbiamo prevedere dei unit test. Sicuramente è necessario fare più lavoro per controllare portafogli più grandi e diversificati, ma almeno possiamo assicurarci che il sistema stia calcolando i valori come dovrebbe.

Come per i test per la classe Position, questi sono stati confrontati con i valori prodotti da Interactive Brokers utilizzando l’account demo di Trader Workstation. Come prima, in futuro è sempre possibile trovare nuovi casi limite e bug, ma speriamo che l’attuale controllo di integrità e test di calcolo dovrebbero fornire fiducia nel funzionamento del Portfolio.

Il listato completo di position_test.py è il seguente:

				
					from decimal import Decimal
import unittest

from portfolio import Portfolio


class PriceHandlerMock(object):
    def __init__(self):
        pass

    def get_best_bid_ask(self, ticker):
        prices = {
            "GOOG": (Decimal("705.46"), Decimal("705.46")),
            "AMZN": (Decimal("564.14"), Decimal("565.14")),
        }
        return prices[ticker]


class TestAmazonGooglePortfolio(unittest.TestCase):
    """
    Prova un portafoglio composto da Amazon e
    Google con vari ordini per creare
    "round-trip" per entrambi.

    Questi ordini sono stati eseguiti in un conto demo
    di Interactive Brokers e verificata l'uguaglianza
    per contanti, equità e PnL.
    """

    def setUp(self):
        """
        Imposta l'oggetto Portfolio che memorizzerà una
        raccolta di oggetti Position, prevedendo
        $500.000,00 USD per il saldo iniziale del conte
        """
        ph = PriceHandlerMock()
        cash = Decimal("500000.00")
        self.portfolio = Portfolio(ph, cash)


    def test_calculate_round_trip(self):
        """
        Acquisto/vendita più lotti di AMZN e GOOG
        a vari prezzi / commissioni per controllare
        il calcolo e la gestione dei costi.
        """
        # Acquista 300 AMZN su due transazion
        self.portfolio.transact_position(
            "BOT", "AMZN", 100,
            Decimal("566.56"), Decimal("1.00")
        )
        self.portfolio.transact_position(
            "BOT", "AMZN", 200,
            Decimal("566.395"), Decimal("1.00")
        )
        # Acquista 200 GOOG su una transazione
        self.portfolio.transact_position(
            "BOT", "GOOG", 200,
            Decimal("707.50"), Decimal("1.00")
        )
        # Aggiunge 100 azioni sulla posizione di AMZN
        self.portfolio.transact_position(
            "SLD", "AMZN", 100,
            Decimal("565.83"), Decimal("1.00")
        )
        # Aggiunge 200 azioni alla posizione di GOOG
        self.portfolio.transact_position(
            "BOT", "GOOG", 200,
            Decimal("705.545"), Decimal("1.00")
        )
        # Vende 200 azioni di AMZN
        self.portfolio.transact_position(
            "SLD", "AMZN", 200,
            Decimal("565.59"), Decimal("1.00")
        )
        # Transazioni Multiple costruite in una (in IB)
        # Vendi 300 GOOG dal portfolio
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.92"), Decimal("1.00")
        )
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.90"), Decimal("0.00")
        )
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.92"), Decimal("0.50")
        )
        # Infine vendiamo le rimanenti 100 azioni di GOOG
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.78"), Decimal("1.00")
        )

        # I numeri seguenti sono derivati dall'account demo di 
        # Interactive Brokers usando i seguenti trade con i 
        # prezzi forniti dal loro feed dati in demo.
        self.assertEqual(self.portfolio.cur_cash, Decimal("499100.50"))
        self.assertEqual(self.portfolio.equity, Decimal("499100.50"))
        self.assertEqual(self.portfolio.unrealised_pnl, Decimal("0.00"))
        self.assertEqual(self.portfolio.realised_pnl, Decimal("-899.50"))


if __name__ == "__main__":
    unittest.main()
				
			

Il primo compito è eseguire le  corrette importazioni. Importiamo il modulo unittest e l’oggetto Portfolio stesso:

				
					from decimal import Decimal
import unittest

from portfolio import Portfolio
				
			

Simulazione dei prezzi

Per creare una classe Portfolio funzionante , abbiamo bisogno di una classe PriceHandler per fornire valori bid e ask per ogni ticker. Tuttavia, non abbiamo ancora codificato alcun oggetto di gestione dei prezzi, quindi cosa dobbiamo fare?

A quanto pare, questo è un modello comune negli unit test. Per superare questa difficoltà, possiamo creare un oggetto fittizio . In sostanza, un mock-object è una classe che simula il comportamento della sua controparte reale, consentendo così di testare la funzionalità su altre classi che ne fanno uso. Quindi è necessario creare una classe PriceHandlerMock che fornisca la stessa interfaccia di a PriceHandler, ma restituisca solo valori preimpostati, invece di eseguire calcoli su prezzi “reali”.

L’oggetto PriceHandlerMock ha un metodo di inizializzazione vuoto, ma espone il metodo get_best_bid_ask che si trova sul reale PriceHandler. Restituisce semplicemente valori bid/ask preimpostati per le azioni GOOG e AMZN con cui effettueremo le transazioni nei seguenti ulteriori unit test:

				
					
class PriceHandlerMock(object):
    def __init__(self):
        pass

    def get_best_bid_ask(self, ticker):
        prices = {
            "GOOG": (Decimal("705.46"), Decimal("705.46")),
            "AMZN": (Decimal("564.14"), Decimal("565.14")),
        }
        return prices[ticker]
				
			

Simulazione di un portafoglio

Gli unit test consistono nel creare una nuova classe chiamata in modo piuttosto parlante TestAmazonGooglePortfolio che, come tutti gli unit test in Python, è derivato dalla classe unittest.TestCase.

Nel metodo setUp impostiamo l’oggetto fittizio del gestore del prezzo, il saldo iniziale e creiamo un Portfolio:

				
					class TestAmazonGooglePortfolio(unittest.TestCase):
    """
    Prova un portafoglio composto da Amazon e
    Google con vari ordini per creare
    "round-trip" per entrambi.

    Questi ordini sono stati eseguiti in un conto demo
    di Interactive Brokers e verificata l'uguaglianza
    per contanti, equità e PnL.
    """

    def setUp(self):
        """
        Imposta l'oggetto Portfolio che memorizzerà una
        raccolta di oggetti Position, prevedendo
        $500.000,00 USD per il saldo iniziale del conte
        """
        ph = PriceHandlerMock()
        cash = Decimal("500000.00")
        self.portfolio = Portfolio(ph, cash)
				
			

L’unico metodo di unit test che creiamo è chiamato test_calculate_round_trip. Il suo obiettivo è calcolare i trade round-trip per AMZN e GOOG, assicurandosi che i calcoli finanziari delle classi Position e Portfolio siano corretti.  In questo caso “corretto” significa che corrispondono ai valori calcolati da Interactive Brokers quando abbiamo eseguito questa situazione in Trader Workstation. Abbiamo codificato questi valori negli unit test.

La prima parte del metodo esegue più transazioni sia per GOOG che per AMZN a vari prezzi e costi di commissione. Abbiamo preso questi prezzi direttamente da quelli calcolati da Interactive Brokers (IB) quando abbiamo effettuato questi trade nel conto demo. “BOT” è la terminologia IB per l’acquisto di un’azione, mentre “SLD” è la terminologia per la vendita di un’azione.

Una volta completata la serie completa di transazioni, le posizioni vengono entrambe compensate con quantità zero. Non avranno alcun PnL non realizzato, ma avranno un PnL realizzato, così come modifiche alla liquidità corrente e al valore del patrimonio netto totale:

				
					    def test_calculate_round_trip(self):
        """
        Acquisto/vendita più lotti di AMZN e GOOG
        a vari prezzi / commissioni per controllare
        il calcolo e la gestione dei costi.
        """
        # Acquista 300 AMZN su due transazion
        self.portfolio.transact_position(
            "BOT", "AMZN", 100,
            Decimal("566.56"), Decimal("1.00")
        )
        self.portfolio.transact_position(
            "BOT", "AMZN", 200,
            Decimal("566.395"), Decimal("1.00")
        )
        # Acquista 200 GOOG su una transazione
        self.portfolio.transact_position(
            "BOT", "GOOG", 200,
            Decimal("707.50"), Decimal("1.00")
        )
        # Aggiunge 100 azioni sulla posizione di AMZN
        self.portfolio.transact_position(
            "SLD", "AMZN", 100,
            Decimal("565.83"), Decimal("1.00")
        )
        # Aggiunge 200 azioni alla posizione di GOOG
        self.portfolio.transact_position(
            "BOT", "GOOG", 200,
            Decimal("705.545"), Decimal("1.00")
        )
        # Vende 200 azioni di AMZN
        self.portfolio.transact_position(
            "SLD", "AMZN", 200,
            Decimal("565.59"), Decimal("1.00")
        )
        # Transazioni Multiple costruite in una (in IB)
        # Vendi 300 GOOG dal portfolio
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.92"), Decimal("1.00")
        )
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.90"), Decimal("0.00")
        )
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.92"), Decimal("0.50")
        )
        # Infine vendiamo le rimanenti 100 azioni di GOOG
        self.portfolio.transact_position(
            "SLD", "GOOG", 100,
            Decimal("704.78"), Decimal("1.00")
        )

        # I numeri seguenti sono derivati dall'account demo di 
        # Interactive Brokers usando i seguenti trade con i 
        # prezzi forniti dal loro feed dati in demo.
        self.assertEqual(self.portfolio.cur_cash, Decimal("499100.50"))
        self.assertEqual(self.portfolio.equity, Decimal("499100.50"))
        self.assertEqual(self.portfolio.unrealised_pnl, Decimal("0.00"))
        self.assertEqual(self.portfolio.realised_pnl, Decimal("-899.50"))


if __name__ == "__main__":
    unittest.main()
				
			

Chiaramente c’è spazio per produrre molti più unit test per questa classe, specialmente quando vengono utilizzate posizioni più esotiche, come quelle con forex, futures o opzioni. Tuttavia, in questa fase vogliamo semplicemente gestire azioni ed ETF, il che significa una gestione delle posizioni più diretta.

Conclusioni

Ora che abbiamo implementato la logica del portafoglio nelle classi PositionPortfolio dobbiamo approfondire il PortfolioHandler. Questa è la classe che interagisce con PositionSizerRiskManager per produrre ordini e ricevere esecuzioni che alla fine determinano il portafoglio azionario (e quindi la redditività!).

Dal momento che siamo molto più avanti con l’effettivo sviluppo effettivo del software di DataTrader rispetto agli articoli che spiegano come funziona, presenteremo al più presto alcune strategie di trading avanzate utilizzando questo software, piuttosto che aspettare che tutti gli articoli siano stati completati.

Il codice completo presentato in questo articolo, basato sul framework di trading quantitativo event-driven DataTrader, è disponibile nel seguente repository GitHub: https://github.com/tradingquant-it/DataTrader.

Benvenuto su TradingQuant!

Sono Gianluca, ingegnere software e data scientist. Sono appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato TradingQuant per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

TradingQuant vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Torna in alto