Gestione del Portafoglio nel Trading Quantitativo con DataTrader

Per implementare il sistema di trading quantitativo avanzato DataTrader, descriviamo sia la classe di posizione che la classe di portafoglio, due componenti fondamentali per un backtesting solido e un sistema di trading in tempo reale. In questo articolo esploriamo la gestione del portafoglio in un sistema di trading e implementiamo la classe Portfolio Handler, completando così la descrizione dell’Order Management System (OMS).

L’OMS rappresenta la spina dorsale di ogni infrastruttura di trading quantitativo. Tracciamo le posizioni aperte e chiuse sugli asset, le raggruppiamo in un portafoglio che include anche la liquidità e modifichiamo tale portafoglio con nuovi segnali di trading, sovrapposizioni di gestione del rischio e regole per il dimensionamento delle posizioni.

In questo articolo analizziamo la classe PortfolioHandler. Questa classe gestisce un oggetto Portfolio e decide se aprire o chiudere posizioni in base alle informazioni ricevute dalle classi Strategy, PositionSizer, RiskManager e ExecutionHandler. Consideriamo questa classe essenziale perché connette tutti gli altri componenti del sistema.

Il codice che presentiamo qui proviene da DataTrader, un motore open-source pensato per il backtesting e il trading live. Lo rilasciamo sotto licenza MIT open-source, e pubblichiamo l’ultima versione sempre su https://github.com/tradingquant-it/DataTrader.

Nel precedente articolo abbiamo fornito un promemoria dei componenti del sistema, spiegando nel dettaglio come colleghiamo insieme tutti gli elementi di DataTrader. Ti invitiamo a rileggerlo per rinfrescare la memoria su queste interazioni.

Ora concentriamo la nostra attenzione sulla classe PortfolioHandler e analizziamo come interagisce con l’oggetto Portfolio.

La Gestione del Portafoglio

La prima questione che affrontiamo riguarda la motivazione che ci ha spinti ad abbandonare l’approccio precedente basato sulla classe Portfolio di TQForex, sostituendolo con una nuova implementazione della gestione del portafoglio nella classe Portfolio per calcoli complessi sulle Position, e con una classe PortfolioHandler semplificata.

Abbiamo scelto questa architettura per ottenere un oggetto Portfolio più pulito, responsabile solo del monitoraggio di saldo, patrimonio e posizioni aperte. Con questo approccio possiamo, almeno in teoria, creare molteplici versioni del portafoglio (generate dal PositionSizer o dal RiskManager) e costruire una serie di trade necessari a trasformare il portafoglio corrente in quello desiderato.

Troviamo più semplice gestire questo processo quando la classe Portfolio si limita a rappresentare un insieme di oggetti Position e un saldo di conto aggiornato. L’oggetto Portfolio non interagisce direttamente con la coda degli eventi, il PositionSizer, il RiskManager o il PriceHandler. Demandiamo queste interazioni al nuovo oggetto PortfolioHandlerAbbiamo definito l’oggetto PortfolioHandler nel file portfolio_handler.py e nel seguito presentiamo il codice sorgente completo per consultarlo facilmente. 

Nota: uno qualsiasi dei listati che riportiamo può subire modifiche, poiché aggiorniamo il progetto in modo continuo.

				
					from order import SuggestedOrder
from portfolio import Portfolio


class PortfolioHandler(object):
    def __init__(
        self, initial_cash, events_queue,
        price_handler, position_sizer, risk_manager
    ):
        """
        PortfolioHandler è progettato per interagire con un
        backtest o trading dal vivo, in generale una architettura
        basato sugli eventi. Espone due metodi, on_signal e
        on_fill, che gestiscono come gli oggetti SignalEvent
        e FillEvent vengono trattati.

        Ogni PortfolioHandler contiene un oggetto Portfolio,
        che memorizza gli effettivi oggetti Posizione.

        Il PortfolioHandler accetta un handle per un oggetto
        PositionSizer che determina un meccanismo, basato sul
        Portfolio corrente, per dimensionare un nuovo Ordine.

        PortfolioHandler prende anche un handle per il
        RiskManager, che viene utilizzato per modificare qualsiasi
        Ordine in modo da rimanere in linea con i parametri di rischio.
        """
        self.initial_cash = initial_cash
        self.events_queue = events_queue
        self.price_handler = price_handler
        self.position_sizer = position_sizer
        self.risk_manager = risk_manager
        self.portfolio = Portfolio(price_handler, initial_cash)

    def _create_order_from_signal(self, signal_event):
        """
        Prende un oggetto SignalEvent e lo usa per creare un oggetto
        SuggestedOrder. Questi non sono oggetti OrderEvent,
        poiché devono ancora essere inviati all'oggetto RiskManager.
        In questa fase sono semplicemente "suggerimenti" che il
        RiskManager verificherà, modificherà o eliminerà.
        """
        order = SuggestedOrder(
            signal_event.ticker, signal_event.action
        )
        return order

    def _place_orders_onto_queue(self, order_list):
        """
        Una volta che il RiskManager ha verificato, modificato o eliminato
        ogni oggetto Ordine, vengono inseriti nella coda degli eventi,
        per essere infine eseguiti dal ExecutionHandler.
        """
        for order_event in order_list:
            self.events_queue.put(order_event)

    def _convert_fill_to_portfolio_update(self, fill_event):
        """
        Al ricevimento di un FillEvent, PortfolioHandler converte
        l'evento in una transazione che viene archiviata nell'oggetto
        Portafoglio. Ciò garantisce che il broker e il portafoglio locale
        siano "sincronizzati".

        Inoltre, a fini di backtest, il valore del portafoglio può
        essere stimato in modo realistico, semplicemente modificando
        il modo in cui l'oggetto ExecutionHandler gestisce lo slippage,
        i costi di transazione, la liquidità e l'impatto sul mercato.
        """
        action = fill_event.action
        ticker = fill_event.ticker
        quantity = fill_event.quantity
        price = fill_event.price
        commission = fill_event.commission
        # Crea o modifa la posizione dalle informazioni di portafoglio
        self.portfolio.transact_position(
            action, ticker, quantity,
            price, commission
        )

    def on_signal(self, signal_event):
        """
        Questo è chiamato dal backtester o dall'architettura del trading live
        per formare gli ordini iniziali dal SignalEvent.

        Questi ordini vengono ridimensionati dall'oggetto PositionSizer e quindi
        inviato al RiskManager per verificarlo, modificarlo o eliminarlo.

        Una volta ricevuti dal RiskManager vengono convertiti in
        oggetti OrderEvent completi e rinviati alla coda degli eventi.
        """

        # Crea la lista dell'ordine iniziale da un segnale £vent
        initial_order = self._create_order_from_signal(signal_event)
        # Dimensiona la quantità dell'ordine iniziale
        sized_order = self.position_sizer.size_order(
            self.portfolio, initial_order
        )
        # Affina o elimina l'ordine tramite l'overlay del gestore del rischio
        order_events = self.risk_manager.refine_orders(
            self.portfolio, sized_order
        )
        # Inserisce ordini nella coda degli eventi
        self._place_orders_onto_queue(order_events)

    def on_fill(self, fill_event):
        """
        Questo è chiamato dal backtester o dall'architettura del trading live
        per prendere un FillEvent e aggiornare l'oggetto Portfolio con le nuovi
        posizioni o le posizioni modificate.

        In un ambiente di backtest, questi FillEvents verranno simulati
        da un modello che rappresenta l'esecuzione, mentre nel trading dal vivo
        provengono direttamente da un broker (come Interactive Broker).
        """
        self._convert_fill_to_portfolio_update(fill_event)
				
			

La Classe PortfolioHandler

La gestione del portafoglio è implementata nella classe PortfolioHandler. In questa classe importiamo l’oggetto SuggestedOrder e l’oggetto Portfolio. Il primo è un oggetto differente rispetto a OrderEvent perché non ha attraversato il processo di dimensionamento della posizione o di gestione del rischio. Una volta che un ordine ha superato entrambi i processi, diventa un OrderEvent completo.

Per inizializzare un PortfolioHandler è necessario un saldo di iniziale e i riferimenti alla coda degli eventi, al gestore del prezzo, al sizer delle posizioni e al gestore del rischio. Infine creiamo l’oggetto Portfolio associato al suo interno . Si noti che esso stesso richiede l’accesso al gestore dei prezzi e al saldo iniziale:

				
					from order import SuggestedOrder
from portfolio import Portfolio


class PortfolioHandler(object):
    def __init__(
        self, initial_cash, events_queue,
        price_handler, position_sizer, risk_manager
    ):
        """
        PortfolioHandler è progettato per interagire con un
        backtest o trading dal vivo, in generale una architettura
        basato sugli eventi. Espone due metodi, on_signal e
        on_fill, che gestiscono come gli oggetti SignalEvent
        e FillEvent vengono trattati.

        Ogni PortfolioHandler contiene un oggetto Portfolio,
        che memorizza gli effettivi oggetti Posizione.

        Il PortfolioHandler accetta un handle per un oggetto
        PositionSizer che determina un meccanismo, basato sul
        Portfolio corrente, per dimensionare un nuovo Ordine.

        PortfolioHandler prende anche un handle per il
        RiskManager, che viene utilizzato per modificare qualsiasi
        Ordine in modo da rimanere in linea con i parametri di rischio.
        """
        self.initial_cash = initial_cash
        self.events_queue = events_queue
        self.price_handler = price_handler
        self.position_sizer = position_sizer
        self.risk_manager = risk_manager
        self.portfolio = Portfolio(price_handler, initial_cash)
				
			
Nel seguente metodo, _create_order_from_signal creiamo l’oggetto SuggestedOrder dal ticker e dal tipo di operazione. In questa fase si prevede di gestire solo gli ordini a mercato. Gli ordini limite e le forme di esecuzione più esotiche sono oggetto di successive implementazioni:
				
					
    def _create_order_from_signal(self, signal_event):
        """
        Prende un oggetto SignalEvent e lo usa per creare un oggetto
        SuggestedOrder. Questi non sono oggetti OrderEvent,
        poiché devono ancora essere inviati all'oggetto RiskManager.
        In questa fase sono semplicemente "suggerimenti" che il
        RiskManager verificherà, modificherà o eliminerà.
        """
        order = SuggestedOrder(
            signal_event.ticker, signal_event.action
        )
        return order
				
			
Il metodo _place_orders_onto_queue è un metodo di supporto che accetta un elenco di oggetti OrderEvent e li aggiunge alla coda degli eventi:
				
					
    def _place_orders_onto_queue(self, order_list):
        """
        Una volta che il RiskManager ha verificato, modificato o eliminato
        ogni oggetto Ordine, vengono inseriti nella coda degli eventi,
        per essere infine eseguiti dal ExecutionHandler.
        """
        for order_event in order_list:
            self.events_queue.put(order_event)
				
			

Il seguente metodo _convert_fill_to_portfolio_update, accetta un FillEvent e quindi aggiorna l’oggetto  Portfolio interno per tenere conto della transazione di riempimento, necessario in una corretta gestione del portafoglio. Come si può vedere, mostra che il PortfolioHandler non esegue calcoli matematici, ma delega i calcoli alla classe Portfolio:

				
					
    def _convert_fill_to_portfolio_update(self, fill_event):
        """
        Al ricevimento di un FillEvent, PortfolioHandler converte
        l'evento in una transazione che viene archiviata nell'oggetto
        Portafoglio. Ciò garantisce che il broker e il portafoglio locale
        siano "sincronizzati".

        Inoltre, a fini di backtest, il valore del portafoglio può
        essere stimato in modo realistico, semplicemente modificando
        il modo in cui l'oggetto ExecutionHandler gestisce lo slippage,
        i costi di transazione, la liquidità e l'impatto sul mercato.
        """
        action = fill_event.action
        ticker = fill_event.ticker
        quantity = fill_event.quantity
        price = fill_event.price
        commission = fill_event.commission
        # Crea o modifa la posizione dalle informazioni di portafoglio
        self.portfolio.transact_position(
            action, ticker, quantity,
            price, commission
        )
				
			

Gestione degli ordini

Il metodo on_signal lega insieme alcuni dei metodi precedenti. Crea l’ordine suggerito iniziale, quindi lo invia all’oggetto PositionSizer (insieme al portfolio) per essere dimensionato. Una volta restituito l’ordine dimensionato, si invia al RiskManager per gestire qualsiasi rischio associato agli impatti del nuovo ordine sul portafoglio corrente.

Il gestore del rischio restituisce quindi un elenco di ordini. Perché una lista? Ebbene, bisogna considerare il fatto che un trade generato può indurre il gestore del rischio a creare un ordine di copertura in un altro titolo. Quindi è necessario eventualmente restituire più di un ordine.

Una volta creato l’elenco degli ordini, vengono tutti inseriti nella coda degli eventi:

				
					
    def on_signal(self, signal_event):
        """
        Questo è chiamato dal backtester o dall'architettura del trading live
        per formare gli ordini iniziali dal SignalEvent.

        Questi ordini vengono ridimensionati dall'oggetto PositionSizer e quindi
        inviato al RiskManager per verificarlo, modificarlo o eliminarlo.

        Una volta ricevuti dal RiskManager vengono convertiti in
        oggetti OrderEvent completi e rinviati alla coda degli eventi.
        """

        # Crea la lista dell'ordine iniziale da un segnale £vent
        initial_order = self._create_order_from_signal(signal_event)
        # Dimensiona la quantità dell'ordine iniziale
        sized_order = self.position_sizer.size_order(
            self.portfolio, initial_order
        )
        # Affina o elimina l'ordine tramite l'overlay del gestore del rischio
        order_events = self.risk_manager.refine_orders(
            self.portfolio, sized_order
        )
        # Inserisce ordini nella coda degli eventi
        self._place_orders_onto_queue(order_events)
				
			

Il metodo finale del PortfolioHandler è il on_fill. Questo richiama semplicemente il metodo precedente _convert_fill_to_portfolio_update. Questi due metodi sono stati separati, poiché nelle versioni successive di DataTrader potrebbe essere necessaria una logica più sofisticata. Non desideriamo modificare l’interfaccia on_fill del PortfolioHandler a meno che non sia assolutamente necessario. Questo aiuta a mantenere la compatibilità con le versioni precedenti :

				
					
 def on_fill(self, fill_event):
        """
        Questo è chiamato dal backtester o dall'architettura del trading live
        per prendere un FillEvent e aggiornare l'oggetto Portfolio con le nuovi
        posizioni o le posizioni modificate.

        In un ambiente di backtest, questi FillEvents verranno simulati
        da un modello che rappresenta l'esecuzione, mentre nel trading dal vivo
        provengono direttamente da un broker (come Interactive Broker).
        """
        self._convert_fill_to_portfolio_update(fill_event)
				
			

Con questo concludiamo la descrizione della classe PortfolioHandler. Per completezza, mettiamo a disposizione su GitHub il codice completo della classe PortfolioHandler.

Esecuzione dei test

Ora che abbiamo implementato la gestione del portafoglio nella classe PortfolioHandler, dobbiamo testare il suo funzionamento. Per fortuna, la maggior parte dei test matematici si trova già nelle classi Position e Portfolio. Tuttavia, verifichiamo comunque che il PortfolioHandler risponda correttamente ai segnali generati dalla strategia e ai riempimenti prodotti dall’esecuzione.

Pur sembrando “banali”, i test seguenti risultano fondamentali. Garantiamo un sistema funzionante man mano che aggiungiamo complessità, anche se scrivere codice per unit test può risultare noioso. Uno degli aspetti più frustranti nello sviluppo software consiste nell’evitare i test unitari per ottenere una risposta veloce, salvo poi trovarci di fronte a un bug senza sapere dove cercarlo in una vasta base di codice!

Eseguiamo i test unitari mentre sviluppiamo i moduli per ridurre al minimo questo rischio. Quando individuiamo un bug, lo rintracciamo molto più facilmente. Il tempo che dedichiamo ai test delle unità non lo consideriamo mai sprecato!

Di seguito mostriamo il listato completo di portfolio_handler_test.py. Dopo il codice, analizziamo gli oggetti e i metodi uno per uno, come fatto in precedenza:

				
					
import datetime
from decimal import Decimal
import queue
import unittest

from event import FillEvent, OrderEvent, SignalEvent
from portfolio import PortfolioHandler


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

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


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

    def size_order(self, portfolio, initial_order):
        """
        Questo oggetto PositionSizerMock modifica semplicemente
        la quantità per essere 100 per qualsiasi azione negoziata.
        """
        initial_order.quantity = 100
        return initial_order


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

    def refine_orders(self, portfolio, sized_order):
        """
        Questo oggetto RiskManagerMock consente semplicemente
        la verifica dell'ordine, crea il corrispondente
        OrderEvent e lo aggiunge ad un elenco.
        """
        order_event = OrderEvent(
            sized_order.ticker,
            sized_order.action,
            sized_order.quantity
        )
        return [order_event]


class TestSimpleSignalOrderFillCycleForPortfolioHandler(unittest.TestCase):
    """
    Verifica un semplice ciclo di segnale, ordine e riempimento per il
    PortfolioHandler. Questo è, in effetti, un controllo di integrità.
    """
    def setUp(self):
        """
        Impostare l'oggetto PortfolioHandler fornendolo
        $ 500.000,00 USD di capitale iniziale.
        """
        initial_cash = Decimal("500000.00")
        events_queue = queue.Queue()
        price_handler = PriceHandlerMock()
        position_sizer = PositionSizerMock()
        risk_manager = RiskManagerMock()
        # Create the PortfolioHandler object from the rest
        self.portfolio_handler = PortfolioHandler(
            initial_cash, events_queue, price_handler,
            position_sizer, risk_manager
        )

    def test_create_order_from_signal_basic_check(self):
        """
        Verifica il metodo "_create_order_from_signal"
        per il controllo di integrità.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        order = self.portfolio_handler._create_order_from_signal(signal_event)
        self.assertEqual(order.ticker, "MSFT")
        self.assertEqual(order.action, "BOT")
        self.assertEqual(order.quantity, 0)

    def test_place_orders_onto_queue_basic_check(self):
        """
        Verifica il metodo "_place_orders_onto_queue"
        per il controllo di integrità.
        """
        order = OrderEvent("MSFT", "BOT", 100)
        order_list = [order]
        self.portfolio_handler._place_orders_onto_queue(order_list)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)

    def test_convert_fill_to_portfolio_update_basic_check(self):
        """
        Verifica il metodo "_convert_fill_to_portfolio_update"
        per il controllo di integrità.
        """
        fill_event_buy = FillEvent(
            datetime.datetime.utcnow(), "MSFT", "BOT",
            100, "ARCA", Decimal("50.25"), Decimal("1.00")
        )
        self.portfolio_handler._convert_fill_to_portfolio_update(fill_event_buy)

        # Controlla i valori di Portfolio all'interno di PortfolioHandler
        port = self.portfolio_handler.portfolio
        self.assertEqual(port.cur_cash, Decimal("494974.00"))

    def test_on_signal_basic_check(self):
        """
        Verifica il metodo "on_signal"
        per il controllo di integrità.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        self.portfolio_handler.on_signal(signal_event)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)


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

La prima operazione è importare i moduli corretti. Usiamo il modulo Decimal, come negli articoli precedenti, così come il modulo unittest . Abbiamo anche bisogno di importare vari oggetti Event utilizzati da PortfolioHandler per comunicare. Infine importiamo lo stesso PortfolioHandler:

				
					
import datetime
from decimal import Decimal
import queue
import unittest

from event import FillEvent, OrderEvent, SignalEvent
from portfolio import PortfolioHandler
				
			

Simulazione dei prezzi

Per testare la gestione del portafoglio dobbiamo creare tre oggetti “fittizi”, uno per ciascuno PriceHandlerPositionSizerRiskManager. Il primo, PriceHandlerMock ci fornisce i prezzi denaro / lettera statici per tre azioni: MSFT, GOOG e AMZN. Essenzialmente vogliamo simulare il metodo get_best_bid_ask per i nostri unit test ripetibili:

				
					
class PriceHandlerMock(object):
    
    def __init__(self):
        pass
    
    def get_best_bid_ask(self, ticker):
        prices = {
            "MSFT": (Decimal("50.28"), Decimal("50.31")),
            "GOOG": (Decimal("705.46"), Decimal("705.46")),
            "AMZN": (Decimal("564.14"), Decimal("565.14")),
        }
        return prices[ticker]
    
				
			

Il secondo oggetto fittizio è il PositionSizerMock. Imposta semplicemente la quantità dell’ordine pari a 100, che è una scelta arbitraria, ma è necessario fissarla per i test unitari. Simula il metodo size_order che si troverà nella “vera” classe PositionSizer, quando sarà completa:

				
					
class PositionSizerMock(object):

    def __init__(self):
        pass

    def size_order(self, portfolio, initial_order):
        """
        Questo oggetto PositionSizerMock modifica semplicemente
        la quantità per essere 100 per qualsiasi azione negoziata.
        """
        initial_order.quantity = 100
        return initial_order
				
			

L’ultimo oggetto fittizio è il RiskManagerMock. Non fa altro che creare un oggetto OrderEvent e inserirlo in un elenco. Fondamentalmente, non esiste una vera gestione del rischio! Anche se questo può sembrare artificioso, ci consente di eseguire un “controllo di integrità” per verificare che PortfolioHandler può semplicemente effettuare le transazioni più elementari di ordini, esecuzioni e segnali. Man mano che creiamo oggetti RiskManager più sofisticati ,  crescerà la lista di unit test, al fine di testare la nuova funzionalità. In questo modo ci assicuriamo continuamente che la base di codice funzioni come previsto:

				
					
class RiskManagerMock(object):

    def __init__(self):
        pass

    def refine_orders(self, portfolio, sized_order):
        """
        Questo oggetto RiskManagerMock consente semplicemente
        la verifica dell'ordine, crea il corrispondente
        OrderEvent e lo aggiunge ad un elenco.
        """
        order_event = OrderEvent(
            sized_order.ticker,
            sized_order.action,
            sized_order.quantity
        )
        return [order_event]
				
			

Ora che abbiamo definito i tre oggetti fittizi, possiamo creare gli specifici unit test. Creiamo la classe TestSimpleSignalOrderFillCycleForPortfolioHandler. per gestire ed eseguire questi oggetti. Sebbene dettagliata, ci dice esattamente per quale test è stata progettata, vale a dire testare un semplice ciclo di segnale-ordine-riempimento all’interno del gestore del portafoglio.

Implementazione dei test

Per simulare la gestione del portafoglio creiamo un saldo di cassa iniziale di 500.000 USD, una coda di eventi e i tre oggetti fittizi sopra menzionati. Infine, creiamo lo stesso PortfolioHandler e lo colleghiamo alla classe di test:

				
					

class TestSimpleSignalOrderFillCycleForPortfolioHandler(unittest.TestCase):
    """
    Verifica un semplice ciclo di segnale, ordine e riempimento per il
    PortfolioHandler. Questo è, in effetti, un controllo di integrità.
    """
    def setUp(self):
        """
        Impostare l'oggetto PortfolioHandler fornendolo
        $ 500.000,00 USD di capitale iniziale.
        """
        initial_cash = Decimal("500000.00")
        events_queue = queue.Queue()
        price_handler = PriceHandlerMock()
        position_sizer = PositionSizerMock()
        risk_manager = RiskManagerMock()
        # Create the PortfolioHandler object from the rest
        self.portfolio_handler = PortfolioHandler(
            initial_cash, events_queue, price_handler,
            position_sizer, risk_manager
        )
				
			

Il primo test genera semplicemente un falso SignalEvent per acquistare Microsoft. Verifichiamo quindi che sia stato generato l’ordine corretto. Notare che una quantità non è stata impostata in questa fase (è zero). Controlliamo tutte le proprietà per assicurarci che l’ordine sia stato creato correttamente:

				
					
    def test_create_order_from_signal_basic_check(self):
        """
        Verifica il metodo "_create_order_from_signal"
        per il controllo di integrità.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        order = self.portfolio_handler._create_order_from_signal(signal_event)
        self.assertEqual(order.ticker, "MSFT")
        self.assertEqual(order.action, "BOT")
        self.assertEqual(order.quantity, 0)
				
			

Il prossimo test consiste nel verificare se gli ordini sono stati inseriti correttamente nella coda (e recuperati). Si noti che dobbiamo racchiudere il OrderEvent in un elenco, in quanto RiskManager produce un elenco di ordini, a causa della suddetta necessità di coprire eventualmente o aggiungere ulteriori ordini oltre a quelli suggeriti dalla Strategy. Infine, affermiamo che l’ordine restituito (che viene prelevato dalla coda) contiene le informazioni appropriate:

				
					

    def test_place_orders_onto_queue_basic_check(self):
        """
        Verifica il metodo "_place_orders_onto_queue"
        per il controllo di integrità.
        """
        order = OrderEvent("MSFT", "BOT", 100)
        order_list = [order]
        self.portfolio_handler._place_orders_onto_queue(order_list)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)
				
			

Il seguente test crea un FillEvent, come se fosse stato appena ricevuto da un oggetto ExecutionHandler. Al gestore del portafoglio viene quindi chiesto di convertire il riempimento in un effettivo aggiornamento del portafoglio (ovvero registrare la transazione all’interno dell’oggetto Portfolio).

Il test consiste nel verificare che il saldo corrente all’interno del Portfolio sia effettivamente corretto:

				
					

    def test_convert_fill_to_portfolio_update_basic_check(self):
        """
        Verifica il metodo "_convert_fill_to_portfolio_update"
        per il controllo di integrità.
        """
        fill_event_buy = FillEvent(
            datetime.datetime.utcnow(), "MSFT", "BOT",
            100, "ARCA", Decimal("50.25"), Decimal("1.00")
        )
        self.portfolio_handler._convert_fill_to_portfolio_update(fill_event_buy)

        # Controlla i valori di Portfolio all'interno di PortfolioHandler
        port = self.portfolio_handler.portfolio
        self.assertEqual(port.cur_cash, Decimal("494974.00"))
				
			

Il test finale verifica semplicemente il metodo on_signal creando un oggetto SignalEvent, posizionandolo in coda e quindi recuperandolo per verificare che i valori dell’ordine siano quelli previsti. Questo verifica la gestione di base “end to end” degli oggetti PositionSizer RiskManager:

				
					

    def test_on_signal_basic_check(self):
        """
        Verifica il metodo "on_signal"
        per il controllo di integrità.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        self.portfolio_handler.on_signal(signal_event)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)
				
			

Vediamo chiaramente che ci aspettano ancora molti test. Abbiamo solo iniziato a esplorare le situazioni possibili. Tuttavia, disporre di una serie di controlli di integrità ci aiuta sempre. Il framework di unit test si estende facilmente e, quando incontriamo nuove situazioni o bug, scriviamo subito nuovi test e risolviamo il problema.

Per completezza, carichiamo il codice completo del test PortfolioHandler su GitHub.

Conclusioni

Con la gestione del portafoglio abbiamo trattato tre degli oggetti principali nel sistema di gestione degli ordini: il Position, il Portfolio e il PortfolioHandler. Questi rappresentano i componenti matematici fondamentali del nostro codice e, proprio per questo, verifichiamo sempre che funzionino correttamente.

Anche se parlare di questi oggetti non entusiasma quanto costruire un oggetto Strategy o un RiskManager, garantire il loro corretto funzionamento rimane essenziale. Senza questi elementi, il resto della nostra infrastruttura per il backtest e il trading live risulterebbe, nel migliore dei casi, inutile e, nel peggiore, addirittura dannosa per la redditività!

Dobbiamo ancora descrivere molti altri componenti, come il PriceHandler, la classe Backtest, i vari ExecutionHandler che possiamo collegare a Interactive Brokers o OANDA, oltre a un’implementazione non banale di un oggetto Strategy.

Nel prossimo articolo analizzeremo una o più di queste classi.

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