Modellare i Costi di Transazione nel sistema di trading DataInvestor

Nel precedente articolo di questa serie abbiamo descritto la gerarchia di classi Asset che costituisce la base degli strumenti negoziabili all’interno del framework di backtesting DataInvestor. In questo articolo iniziamo a esaminare le classi che implementano il componente di broker simulato del framework e descriviamo come modellare i costi di transazione. Questo componente di DataInvestor rappresenta un broker e si occupa di tracciare tutta la contabilità associata alle transazioni di asset. Tiene anche traccia delle remunerazioni societarie conseguenti al possesso materiale dei titoli azionari, come esborsi in contanti o dividendi su azioni ordinarie.

Implementare questa logica all’interno di una classe ci offre un vantaggio importante: se definiamo l’interfaccia in modo appropriato, possiamo sostituire il componente di broker simulato con uno per il trading dal vivo, senza modificare le altre logiche di quant trading. In questo modo non dobbiamo cambiare nulla nell’implementazione dell’algoritmo della strategia tra backtesting e trading live.

Il broker è un componente complesso e per funzionare sfrutta diversi sottocomponenti: una gerarchia di classi FeeModel, un componente Portfolio e un componente Transaction. In questo articolo ci concentriamo sulla classe FeeModel.

Modellare i costi di transazione

Uno degli aspetti più importanti nella simulazione di una strategia di trading sistematico consiste nel modellare i costi di transazione e tracciarli. Questi costi possono assumere molte forme, comprese le commissioni e le tasse di intermediazione, così come costi indiretti come lo slippage e l’impatto sul mercato. Modellare questi aspetti è complesso e lo approfondiremo in articoli futuri. In questo articolo ci focalizziamo sulle commissioni e le tasse di intermediazione, che possiamo modellare utilizzando la gerarchia delle classi FeeModel.

Abbiamo progettato FeeModel per implementare la struttura dei costi di un broker utilizzato per il trading. Questi costi possono diventare molto complessi, a seconda dell’area geografica, della classe di asset, del volume scambiato e del periodo temporale considerato nella simulazione.

Oltre alle commissioni del broker, includiamo anche le imposte e le tasse, che variano in base alla giurisdizione e al tipo di broker utilizzato.

Attualmente, DataInvestor supporta due modelli commissionali semplici. Il primo è ZeroFeeModel, che non applica alcuna commissione o tassa alle transazioni. Ci serve per creare una simulazione di base da confrontare con modelli più realistici. Il secondo è PercentFeeModel, che applica una commissione e/o una tassazione percentuale sull’importo negoziato (noto come “corrispettivo”). Descriviamo di seguito entrambi questi modelli.

FeeModel

FeeModel è la classe base astratta da cui ereditiamo tutte le sottoclassi dei modelli commissionali. Definiamo tre metodi astratti principali: _calc_commission, _calc_tax e calc_total_cost. Il carattere di sottolineatura nei primi due indica che si tratta di metodi pseudo-privati, utili internamente alla classe. L’ultimo è il metodo dell’interfaccia pseudo-pubblica, che utilizziamo per calcolare il costo totale della transazione partendo dal corrispettivo negoziato.

Notiamo che il linguaggio Python non distingue formalmente tra metodi privati e pubblici, a differenza di C++ o Java. In Python, il singolo carattere di sottolineatura segnala ai client dei moduli quali metodi fanno parte dell’interfaccia (nessuna sottolineatura) e quali sono specifici dell’implementazione (con sottolineatura).

Abbiamo separato i metodi _calc_commission e _calc_tax per calcolare separatamente ciascun tipo di costo. Questo approccio ci consente, ad esempio, di applicare una commissione decrescente, mentre l’imposta può restare fissa in percentuale, indipendentemente dall’importo del corrispettivo.

Calcolo dei costi totali

Il metodo calc_total_cost accetta tre argomenti obbligatori e uno opzionale. I primi tre sono asset, quantity e consideration. Usiamo il parametro asset perché le diverse classi di asset possono avere commissioni diverse. Il parametro consideration rappresenta il prezzo unitario dell’asset moltiplicato per la quantità, e fornisce il valore del corrispettivo in valuta, indipendentemente dalla classe di asset.

Potremmo pensare che bastino asset e corrispettivo (dato che la commissione si calcola sul valore scambiato), ma esistono broker che calcolano la commissione sulla quantità scambiata e non sul suo valore in dollari. Per questo motivo abbiamo incluso il parametro quantità nel design di DataInvestor, così da supportare anche queste logiche.

Il parametro finale è un riferimento all’istanza del broker (simulato o attivo). Ci serve per ottenere ulteriori informazioni utili al calcolo della commissione. Un esempio? Possiamo ricavare la data effettiva della transazione, dato che i broker possono modificare la struttura delle commissioni nel tempo. Per modellare correttamente questi cambiamenti, dobbiamo conoscere la data della negoziazione.

Qui sotto mostriamo il codice per FeeModel. Si tratta di un’implementazione piuttosto semplice della classe base astratta:

				
					from abc import ABCMeta, abstractmethod


class FeeModel(object):
    """
    Classe astratta per gestire il calcolo delle
    commissioni del broker e delle tasse.
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def _calc_commission(self, asset, quantity, consideration, broker=None):
        raise NotImplementedError(
            "Should implement _calc_commission()"
        )

    @abstractmethod
    def _calc_tax(self, asset, quantity, consideration, broker=None):
        raise NotImplementedError(
            "Should implement _calc_tax()"
        )

    @abstractmethod
    def calc_total_cost(self, asset, quantity, consideration, broker=None):
        raise NotImplementedError(
            "Should implement calc_total_cost()"
        )

				
			

ZeroFeeModel

La prima classe che implementa questa interfaccia astratta è la sottoclasse derivata ZeroFeeModel. È una classe estremamente semplice che restituisce 0.0 per entrambi _calc_tax e _calc_commission. Questi due valori vengono quindi aggiunti al calc_total_cost per produrre 0.0 come costo totale dell’operazione.

Perché una classe del genere dovrebbe essere utilizzata in una simulazione reale? Il motivo principale è permettere il confronto tra vari modelli di commissioni all’interno di una simulazione di backtest. L’utilizzo di ZeroFeeModel permette a un ricercatore quantitativo di analizzare i risultati del backtest senza costi e confrontarlo con vari modelli di commissioni di intermediazione e tasse. In questo modo è possibile  verificare se una strategia rimane redditizia anche dopo aver applicato i costi di transazione.

Il codice della classe ZeroFeeModel è riportato di seguito. La maggior parte delle righe di codice per questo modello sono docstring. L’implementazione effettiva è minima:

				
					from datainvestor.broker.fee_model.fee_model import FeeModel


class ZeroFeeModel(FeeModel):
    """
    Una sottoclasse di FeeModel che produce nessuna commisione o
    tasse. Questo è il modello default delle commission per
    simulare il brokerages con datainvestor.
    """

    def _calc_commission(self, asset, quantity, consideration, broker=None):
        """
        Returns zero commission.

        Parameters
        ----------
        asset : `str`
            Stringa del simbolo dell'asset.
        quantity : `int`
            La quantità di asset (necessaria per i calcoli
            in stile InteractiveBrokers).
        consideration : `float`
            Prezzo moltiplicato per quantità dell'ordine.
        broker : `Broker`, optional
            Riferimento ad un broker (opzionale).

        Returns
        -------
        `float`
            Le commissione a costo zero.
        """
        return 0.0

    def _calc_tax(self, asset, quantity, consideration, broker=None):
        """
        Restituisce le tasse a zero.

        Parameters
        ----------
        asset : `str`
            Stringa del simbolo dell'asset.
        quantity : `int`
            La quantità di asset (necessaria per i calcoli
            in stile InteractiveBrokers).
        consideration : `float`
            Prezzo moltiplicato per quantità dell'ordine.
        broker : `Broker`, optional
            Riferimento ad un broker (opzionale).

        Returns
        -------
        `float`
            Le tasse a costo zero.
        """
        return 0.0

    def calc_total_cost(self, asset, quantity, consideration, broker=None):
        """
        Calcola il totale di qualsiasi commissione e/o tassa
        per il trade della dimensione 'corrispettiva'.

        Parameters
        ----------
        asset : `str`
            Stringa del simbolo dell'asset.
        quantity : `int`
            La quantità di asset (necessaria per i calcoli
            in stile InteractiveBrokers).
        consideration : `float`
            Prezzo moltiplicato per quantità dell'ordine.
        broker : `Broker`, optional
            Riferimento ad un broker (opzionale).

        Returns
        -------
        `float`
            Totale delle commission e tasse a costo zero.
        """
        commission = self._calc_commission(asset, quantity, consideration, broker)
        tax = self._calc_tax(asset, quantity, consideration, broker)
        return commission + tax

				
			

PercentFeeModel

Un modello leggermente più realistico dei costi di transazione è fornito dalla classe PercentFeeModel. Questa classe prevede un  metodo __init__ di inizializzazione con due parametri. Il primo è il commission_pct, che corrisponde alla commissione a percentuale fissa del broker da applicare al corrispettivo. Il secondo è tax_pct, che è la percentuale fissa di tassazione da applicare al corrispettivo. Entrambi questi valori sono specificati nell’intervallo [0.0, 1.0]. Cioè, 1.0 equivale a 100%.

Entrambi i metodi si limitano ad applicare tale percentuale al valore assoluto del corrispettivo. Questo evita commissioni negative quando si vendono asset! Ad esempio, il calcolo per la commissione del broker è simile a: return self.commission_pct * abs(consideration). L’implementazione è quasi identica per il metodo della tassazione.

Il codice della classe PercentFeeModel è riportato di seguito. Come per ZeroFeeModel la maggior parte delle righe di codice per questo modello sono docstring. L’implementazione effettiva è minima:

				
					from datainvestor.broker.fee_model.fee_model import FeeModel


class PercentFeeModel(FeeModel):
    """
    Una sottoclasse FeeModel che produce un costo percentuale
    per tasse e commissioni.

    Parameters
    ----------
    commission_pct : `float`, optional
        La commissione percentuale applicata al corrispettivo.
        0-100% è nell'intervallo [0,0, 1,0]. Quindi, ad es. 0,1% è 0,001
    tax_pct : `float`, optional
        L'imposta percentuale applicata al corrispettivo.
        0-100% è nell'intervallo [0,0, 1,0]. Quindi, ad es. 0,1% è 0,001
    """

    def __init__(self, commission_pct=0.0, tax_pct=0.0):
        super().__init__()
        self.commission_pct = commission_pct
        self.tax_pct = tax_pct

    def _calc_commission(self, asset, quantity, consideration, broker=None):
        """
        Restituisce la commissione percentuale dal corrispettivo.

        Parameters
        ----------
        asset : `str`
            Stringa del simbolo dell'asset.
        quantity : `int`
            La quantità di asset (necessaria per i calcoli
            in stile InteractiveBrokers).
        consideration : `float`
            Prezzo moltiplicato per quantità dell'ordine.
        broker : `Broker`, optional
            Riferimento ad un broker (opzionale).

        Returns
        -------
        `float`
            La percentuale di commssione.
        """
        return self.commission_pct * abs(consideration)

    def _calc_tax(self, asset, quantity, consideration, broker=None):
        """
        Restituisce la tassa percentuale dal corrispettivo.

        Parameters
        ----------
        asset : `str`
            Stringa del simbolo dell'asset.
        quantity : `int`
            La quantità di asset (necessaria per i calcoli
            in stile InteractiveBrokers).
        consideration : `float`
            Prezzo moltiplicato per quantità dell'ordine.
        broker : `Broker`, optional
            Riferimento ad un broker (opzionale).

        Returns
        -------
        `float`
            La percentuale di tasse.
        """
        return self.tax_pct * abs(consideration)

    def calc_total_cost(self, asset, quantity, consideration, broker=None):
        """
        Calcola il totale di qualsiasi commissione e/o tassa
        per il trade della dimensione 'corrispettiva'.

        Parameters
        ----------
        asset : `str`
            Stringa del simbolo dell'asset.
        quantity : `int`
            La quantità di asset (necessaria per i calcoli
            in stile InteractiveBrokers).
        consideration : `float`
            Prezzo moltiplicato per quantità dell'ordine.
        broker : `Broker`, optional
            Riferimento ad un broker (opzionale).

        Returns
        -------
        `float`
            Totale di commissioni e tasse.
        """
        commission = self._calc_commission(asset, quantity, consideration, broker)
        tax = self._calc_tax(asset, quantity, consideration, broker)
        return commission + tax

				
			

Notiamo che questa classe, per come l’abbiamo implementata, non consente di applicare una percentuale a “scala mobile” basata sul valore del corrispettivo. Nella maggior parte dei broker nel mondo reale, si applicano percentuali diverse al crescere del corrispettivo. Questa scala mobile può variare anche in base alla classe di asset e alla regione geografica.

Conclusioni

Per modellare i costi di transazione è necessario definire una logica altamente specifica per ogni broker, quindi tentare di codificare tutte le possibili strutture commissionali sarebbe un compito molto impegnativo. Tuttavia, progettiamo la logica della classe in modo abbastanza generico da supportare un’ampia gamma di strutture dei costi di intermediazione. Nei prossimi articoli ci occuperemo dell’implementazione di costi di trading più realistici, utilizzando la stessa gerarchia di classi descritta sopra.

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

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