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 Strategy
, PositionSizer
, RiskManager
e 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()
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 PriceHandler
, PositionSizer
e 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
e 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.