Nell’attuale serie sull’infrastruttura di trading avanzata abbiamo descritto sia la classe di posizione che la classe di portafoglio , due componenti essenziali di un solido backtesting e di un sistema di live trading. In questo articolo estenderemo la discussione alla classe Portfolio Handler , che completerà la descrizione del portfolio Order Management System (OMS).

L’OMS è la spina dorsale di qualsiasi infrastruttura di trading quantitativo. Deve tenere traccia delle posizioni aperte (e chiuse) negli asset, deve raggruppare insieme quelle posizioni all’interno di un portafoglio (con liquidità) e deve modificare quel portafoglio con nuovi segnali di trading, sovrapposizioni di gestione del rischio e regole di dimensionamento delle posizioni.

In questo articolo discuteremo la classe PortfolioHandler. Questa specifica classe ha il compito di gestire un oggetto Portfolio, dicendogli se aprire/chiudere le posizioni in base alle informazioni che riceve dalle classi StrategyPositionSizerRiskManager ExecutionHandler. Questa classe è estremamente importante in quanto lega insieme il resto dei componenti.

Il seguente codice presentato in questo articolo proviene da DataTrader, motore open-source per il backtesting e il live trading. E’ rilasciato con una licenza MIT open source  e l’ultima versione è sempre disponibile su https://github.com/datatrading-info/DataTrader

Nel precedente articolo abbiamo elencato un promemoria dei componenti del sistema che descriveva in dettaglio come tutti i componenti di DataTrader si collegano insieme. Potete darci un’occhiata per ricordarsi come interagiscono.

Rivolgiamo ora la nostra attenzione alla classe PortfolioHandler e vediamo come interagisce con l’oggetto Portfolio.

PortfolioHandler

La prima questione da discutere è il motivo per cui il vecchio approccio della classe  Portfolio implementato in DTForex è stato ora sostituito con una classe  Portfolio che implementa calcoli complessi per monitorare le Position, e con una classe PortfolioHandler meno complessa.

Abbiamo adottato questo approccio in modo da vare un oggetto Portfolio più pulito e preveda solo il monitoraggio del saldo/patrimonio e delle posizioni aperte. Lo scopo principale di questo approccio è quello di consentire “teoricamente” la creazione di diversi oggetti “portafoglio” (ad esempio dal PositionSizer o dal RiskManager), e successivamente creare un insieme di trade necessari per “trasformare” il portafoglio corrente nel portafoglio teoricamente desiderato.

Questo processo è molto più semplice se la classe Portfolio gestisce semplicemente di un raggruppamento di oggetti Position e di un saldo del conto.

L’oggetto Portfolio non prevede l’interazione con gli eventi in coda, il PositionSizer, il RiskManager e il PriceHandler. Queste interazioni vengono gestite dal nuovo oggetto, il PortfolioHandler.

Abbiamo creato l’oggetto PortfolioHandler nel file portfolio_handler.py e di seguito si può consultare il listato completo. di seguito.

Nota che uno qualsiasi di questi elenchi è soggetto ad aggiornamenti, poiché il progetto è soggetto a continue modifiche e miglioramenti.

portfolio_handler.py

            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)
        

Il PortfolioHandler importa 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 semplicemente il 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
        

_place_orders_onto_queue è un semplice 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. 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
        )
        

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)
        

Questo completa la descrizione della classe PortfolioHandler. Per completezza puoi trovare il codice completo per la classe PortfolioHandler su Github.

portfolio_handler_test.py

Ora che abbiamo creato il PortfolioHandler, dobbiamo testarlo. Per fortuna, la maggior parte dei test matematici si verifica nelle classi Position e Portfolio. Tuttavia, è ancora necessario verificare che il PortfolioHandler “faccia la cosa giusta” quando riceve segnali generati dalla strategia e i riempimenti generati dall’esecuzione.

Sebbene i seguenti test possano sembrare “banali”, posso assicurare che è assolutamente vitale assicurarsi di avere un sistema funzionante man mano che viene aggiunta più complessità, anche se può essere abbastanza noioso scrivere codice di unit test. Uno degli aspetti più frustranti dello sviluppo del software è non fare gli unit test per così “ottenere rapidamente una risposta” e poi rendersi conto di avere un bug e non avere idea di dove si trovi in una vasta raccolta di moduli!

Eseguendo gli unit test mentre scriviamo i singoli moduli si evita questo problema il più possibile. Se viene scoperto un bug, di solito è molto più semplice rintracciarlo. Il tempo speso per testare le unità non è mai sprecato!

Di seguito è riportato un listato completo di portfolio_handler_test.py. Dopo il listato si analizza i singoli oggetti e metodi, come 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
        

Dobbiamo creare tre oggetti “fittizi” (vedere l’ articolo precedente per una descrizione degli oggetti fittizi), uno per ciascuno PriceHandlerPositionSizer RiskManager. 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. Viene chiamata la classe che li esegue TestSimpleSignalOrderFillCycleForPortfolioHandler. 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.

Per fare ciò, 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)
        

Possiamo vedere chiaramente che ci sono molti più test da fare qui. Abbiamo solo scalfito la superficie con il tipo di situazioni che possono verificarsi. Tuttavia, è sempre utile disporre di una serie di controlli di integrità. Il framework di unit test è altamente estensibile e quando ci imbattiamo in nuove situazioni / bug, possiamo semplicemente scrivere nuovi test e correggere il problema.

Per completezza puoi trovare il codice completo per il test PortfolioHandler su Github.

Prossimi Passi

Abbiamo ora coperto tre degli oggetti principali per il sistema di gestione degli ordini, vale a dire il Position, il Portfolio e il PortfolioHandler. Questi sono gli aspetti matematici “centrali” del codice e come tali dobbiamo essere sicuri che funzionino come previsto.

Anche se discutere di questi oggetti non è così eccitante come costruire un oggetto Strategy, o anche un RiskManager, è fondamentale che funzionino, altrimenti il ​​resto dell’infrastruttura di backtesting e live trading sarà, nella migliore delle ipotesi, inutile e, nel peggiore dei casi, altamente non redditizia!

Abbiamo molti altri componenti da descrivere oltre a quelli menzionati sopra, tra cui il PriceHandler, la classe Backtest, i vari ExecutionHandler che potrebbero collegarsi a Interactive Brokers o OANDA , così come l’implementazione di un oggetto Strategy non banale .

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

Recommended Posts