logo le blog invivoo blanc

Moteur – Design Patterns

16 novembre 2022 | Python | 0 comments

Afin de réaliser différentes stratégies pour la spéculation ou la couverture de risque, les gestionnaires de fonds cherchent de divers instruments financiers basiques pour construire les produits dérivés composés possédant les caractéristiques favorables pour un profit élevé ou contre le risque de marché. Par exemple, pour bien couvrir les titres sous-jacents, les multiples stratégies simples ou composées des options [1] se produisent en suivant les règles élémentaires en commun, qui indépendamment récupèrent les actifs et options nécessaires à acheter ou à vendre dans le marché avant de générer leurs P&L en total. Les positions acheteur et vendeur qui représentent une stratégie pourraient être très nombreuses, lorsque les recherches des contrats financiers seraient simplement effectuées individuellement à la chaîne. De plus, une stratégie ne peut s’appliquer que sur un seul type d’actif couvert par des options du style identique (par exemple, européen ou américain). Si l’on ne met en œuvre toutes les stratégies que par la fabrique [2], les branches de vérifications seront très importantes dans le code. D’ailleurs, différente que le problème résolu par la fabrique abstraite [3] faisant face au défi des composants complexes dans un objet, notre issue actuelle consiste à créer une interface de fabrication élastique, qui pourrait éventuellement réduire ou augmenter à l’aise les positions présentées dans la procédure de création d’une stratégie au gré des demandes.

En effet, le patron de conception : le moteur (builder en anglais) saura répondre à cette question comment construire un objet complexe lors de sa représentation. À l’instar des autres patrons de conception créateurs[4], il s’occupe de fabriquer les objets d‘un même processus, lorsque les éléments des différentes représentations sont créés par un moteur spécifique et composés chez un directeur moteur. À cet égard, dans notre contexte les moteurs héritant d’une même interface de moteur offrent les positions simples et représentées en option des différents styles avec les actifs sous-jacents très variés. Ils implémentent indépendamment les positions dans leurs sous-classes, alors que le directeur prenant un de ces moteurs met ensemble ses positions concrètes et nécessaires, avant que l’on n’obtienne une stratégie d’un style des options en composition qui courent sur un titre à la demande.

Dans cet article, on va apprendre le patron de conception de moteur en guise d’une mise en place des stratégies optionnelles. Le moteur prépare les positions acheteur et vendeurs des contrats élémentaires en commun dont les stratégies peuvent disposer, avant qu’il ne les commande facilement à la chaîne, tandis que le programme respecte le principe SOLID [5].

Conception

Le diagramme ULM démontre en général deux rôles importants joués dans le patron de conception : Moteur en Python 3. D’abord c’est l’interface motrice, dans laquelle les signatures des procédures élémentaires impliquées à la fabrication complexe sont proposées et définies dans ses sous-classes. La sélection d’un des moteurs concrets a lieu chez le rôle de directeur, qui commande les étapes nécessaires à la chaîne avant de retourner l’objet à la demande du client.

Dans notre contexte, les positions basiques sont organisées de la même manière pour une stratégie complexe, lorsque les styles d’option et les titres sous-jacents se varient dans la stratégie au gré du besoin chez les produits. Du fait, on conçoit l’interface motrice pour les différentes positions éventuellement présentées dans une stratégie. On l’implémente dans les moteurs concrets pour les divers styles d’option avec les actifs sous-jacents variés. Le directeur prend un moteur dont on a besoin comme un attribut, ainsi que les méthodes qui organisent les positions étape à étape avant de fournir une stratégie instanciée.

Développement

Dans notre exemple, on s’appuie sur les 5 positions élémentaires pour réaliser une stratégie en question :

  • Achète des positions sous-jacentes
  • Achète des options call
  • Achète des options put
  • Vendre des options call
  • Vendre des options put

Afin de simplifier la démonstration, on définit simplement les prix d’exercice : les prix préfixés à exécuter l’option si besoin chez l’acheteur de l’option, ainsi que la maturité d’option de la stratégie et les nombres d’option nécessaires comme les arguments d’entrée, grâce auxquels les contrats correspondants pourraient être obtenus dans les marchés financiers. Les recherches ont lieu chez le moteur par les méthodes dont chacune présente une étape de construction créant une position élémentaire. Par ailleurs, faute d’espace, le code simplifie les implémentations des algorithmes pour telles recherches par les impressions de processus, en supposant que les contrats satisfaisants sont toujours disponibles dans le marché.

La classe Strategy fournit un protocole basique pour les instances stratégiques. Le directeur doit la réaliser à la demande d’un client, en organisant les appels des méthodes de production chez les moteurs. En plus, pour tester la justesse de l’instanciation des stratégies par le moteur, on enrichit la classe Strategy par la méthode __str__ qui retourne toutes les positions comprises dedans sous une chaîne de caractères.

/code

# builder.py

from __future__ import annotations
from abc import ABC, abstractmethod


class Strategy(object):
    """
    Strategy class to buy and sell the positions
    """

    def __init__(self):
        """
        initialize strategy with empty positions list
        """
        self._positions = []

    def add_position(self, position: str):
        """
        add the position to buy or sell
        :param position: the information of positions
        :return:
        """
        self._positions.append(position)

    @property
    def positions(self) -> list:
        """
        get all the positions in the strategy
        :return: the positions in the strategy
        """
        return self._positions

    def __str__(self) -> str:
        """
        show the strategy composition
        :return: the strategy composition in str
        """
        return f"The strategy is composed by {', '.join(self._positions)}."

Dans un premier temps, on conçoit en Python 3 l’interface motrice IStrategyBuilder avec les méthodes de fabrication abstraites, qui sont mises en œuvre aux classes dérivées pour trouver les actions sous-jacentes avec ses options américaines, ainsi que les bonds avec ses options européennes respectivement. Toutes les méthodes retournent l’instance de son moteur, ce qui favorise les appels en chaîne sans limite chez le directeur, avant que la méthode get_strategy ne retourne l’objet stratégique en question.  

class IStrategyBuilder(ABC):
    @abstractmethod
    def buy_underlying(self, number: int) -> IStrategyBuilder:
        """
        buy underlying instrument positions
        :param number: unit number
        :return: strategy builder
        """
        raise NotImplemented

    @abstractmethod
    def buy_call(self, strike_price: str,
                 maturity: str,
                 number: int) -> IStrategyBuilder:
        """
        buy call option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        raise NotImplemented

    @abstractmethod
    def buy_put(self, strike_price: str,
                maturity: str,
                number: int) -> IStrategyBuilder:
        """
        buy put option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        raise NotImplemented

    @abstractmethod
    def sell_call(self, strike_price: str,
                  maturity: str,
                  number: int) -> IStrategyBuilder:
        """
        sell call option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        raise NotImplemented

    @abstractmethod
    def sell_put(self, strike_price: str,
                 maturity: str,
                 number: int) -> IStrategyBuilder:
        """
        sell put option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        raise NotImplemented

    @abstractmethod
    def get_strategy(self) -> Strategy:
        """
        get strategy
        :return: final built strategy
        """
        raise NotImplemented


class AmericanOptionOfStockStrategyBuilder(IStrategyBuilder):
    """
    Construct Strategy by American Stock Options and Bond
    """

    def __init__(self):
        """
        initialize builder with empty strategy
        """
        self._strategy = Strategy()

    def buy_underlying(self,
                       number: int) -> AmericanOptionOfStockStrategyBuilder:
        """
        buy underlying instrument positions
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} stock contracts for sale in the market...")
        self._strategy.add_position(f"buying {number} stock units")
        return self

    def buy_call(self, strike_price: str,
                 maturity: str,
                 number: int) -> AmericanOptionOfStockStrategyBuilder:
        """
        buy call option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} written contracts "
              f"having American call of stock at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(
            f"buying {number} American calls of stock at price of "
            f"{strike_price} with maturity of {maturity}")
        return self

    def buy_put(self, strike_price: str,
                maturity: str,
                number: int) -> AmericanOptionOfStockStrategyBuilder:
        """
        buy put option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} written contracts "
              f"having American put of stock at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(
            f"buying {number} American puts of stock at price of "
            f"{strike_price} with maturity of {maturity}")
        return self

    def sell_call(self, strike_price: str,
                  maturity: str,
                  number: int) -> AmericanOptionOfStockStrategyBuilder:
        """
        sell call option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} buyer contracts willing purchase "
              f"American call of stock at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(
            f"selling {number} American calls of stock "
            f"at price of {strike_price} "
            f"with maturity of {maturity}")
        return self

    def sell_put(self, strike_price: str,
                 maturity: str,
                 number: int) -> AmericanOptionOfStockStrategyBuilder:
        """
        sell put option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} buyer contracts willing purchase "
              f"American put of stock at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(
            f"selling {number} American puts of stock "
            f"at price of {strike_price} "
            f"with maturity of {maturity}")
        return self

    def get_strategy(self) -> Strategy:
        """
        get strategy
        :return: final built strategy
        """
        return self._strategy


class EuropeanOptionOfBondStrategyBuilder(IStrategyBuilder):
    """
    Construct Strategy by European Bond Options and Bond
    """

    def __init__(self):
        """
        initialize builder with empty strategy
        """
        self._strategy = Strategy()

    def buy_underlying(self,
                       number: int) -> EuropeanOptionOfBondStrategyBuilder:
        """
        buy underlying instrument positions
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} bond contracts for sale in the market...")
        self._strategy.add_position("buying {number} bond units")
        return self

    def buy_call(self, strike_price: str,
                 maturity: str,
                 number: int) -> EuropeanOptionOfBondStrategyBuilder:
        """
        buy call option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} written contracts "
              f"having European call of bond at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(
            f"buying {number} European calls of bond at price of "
            f"{strike_price} with maturity of {maturity}")
        return self

    def buy_put(self, strike_price: str,
                maturity: str,
                number: int) -> EuropeanOptionOfBondStrategyBuilder:
        """
        buy put option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} written contracts "
              f"having European put of bond at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(
            f"buying {number} European puts of bond at price of "
            f"{strike_price} with maturity of {maturity}")
        return self

    def sell_call(self, strike_price: str,
                  maturity: str,
                  number: int) -> EuropeanOptionOfBondStrategyBuilder:
        """
        sell call option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} buyer contracts willing purchase "
              f"European call of stock at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(
            f"selling {number} European calls of stock "
            f"at price of {strike_price} "
            f"with maturity of {maturity}")
        return self

    def sell_put(self, strike_price: str,
                 maturity: str,
                 number: int) -> EuropeanOptionOfBondStrategyBuilder:
        """
        sell put option
        :param strike_price: strike price
        :param maturity: maturity
        :param number: unit number
        :return: strategy builder
        """
        print(f"Finding {number} buyer contracts willing purchase "
              f"European put of bond at nearest price of "
              f"{strike_price} with maturity of {maturity} in the market...")
        self._strategy.add_position(f"selling {number} European puts of bond "
                                     f"at price of {strike_price} "
                                     f"with maturity of {maturity}")
        return self

    def get_strategy(self) -> Strategy:
        """
        get strategy
        :return: final built strategy
        """
        return self._strategy

Ensuite, le directeur cherche à comprendre les compositions dans chaque stratégie. Dans cet exemple, on implémente les trois stratégies parmi [6] : l’option de vente de protection (protective put en anglais) [7], le tunnel (protective collar en anglais) [8] et l’achat de papillon en call (long butterfly by calls en anglais) [9], qui sont populairement pratiquées dans la gestion des risques, lorsque les autres stratégies seraient facilement étendues grâce notamment au moteur.

class Director(object):
    """
    Director controls the chain of building elements.
    """

    def __init__(self, stat_builder: IStrategyBuilder):
        """
        initialize director with strategy builder
        """
        self._builder = stat_builder

    def make_protective_put(self, number: int,
                            maturity: str,
                            strike_price: str) -> Strategy:
        """
        make protective put
        :param number: unit number
        :param maturity:  maturity
        :param strike_price: strike price of put
        :return: strategy
        """
        print("------------------Protective Put Strategy---------------------")
        return (self._builder.buy_underlying(number).
                buy_put(strike_price, maturity, number).
                get_strategy())

    def make_protective_collar(self, number: int,
                               maturity: str,
                               strike_price_call: str,
                               strike_price_put: str) -> Strategy:
        """
        make protective collar
        :param number: unit number
        :param maturity: maturity
        :param strike_price_call: strike price of call
        :param strike_price_put: strike price of put
        :return: strategy
        """
        print("------------------Protective Collar Strategy------------------")
        return (self._builder.buy_underlying(number).
                buy_put(strike_price_put, maturity, number).
                sell_call(strike_price_call, maturity, number).
                get_strategy())

    def make_long_butterfly_by_calls(self, number: int,
                                     maturity: str,
                                     low_strike_price_call: str,
                                     high_strike_price_call: str) -> Strategy:
        """
        make long butterfly by 2 calls
        :param number: unit number
        :param maturity: maturity
        :param low_strike_price_call: low strike price of call
        :param high_strike_price_call: high strike price of call
        :return: strategy
        """
        low_price, currency = low_strike_price_call.split(' ')
        high_price, currency = high_strike_price_call.split(' ')
        mid_strike_price_call = (
            f"{(float(low_price) + float(high_price)) / 2} "
            f"{currency}")
        print("-----------------Long Butterfly by Calls Strategy-------------")
        return (self._builder.
                buy_call(low_strike_price_call, maturity, number).
                buy_call(high_strike_price_call, maturity, number).
                sell_call(mid_strike_price_call, maturity, number).
                sell_call(mid_strike_price_call, maturity, number).
                get_strategy())

A la fin, le client peut instancier un moteur intéressant à passer chez un directeur, qui prépare toutes les stratégies en combinant les représentations de position complexes, avant de retourner l’objet stratégique au gré de la demande clientèle.

if __name__ == '__main__':
    # client build strategies over american stock and european bond options
    for strategy_builder in (AmericanOptionOfStockStrategyBuilder,
                             EuropeanOptionOfBondStrategyBuilder):
        print(strategy_builder.__doc__)
        builder = strategy_builder()
        director = Director(builder)

        # 1. protective put
        protective_put_strategy = director.make_protective_put(
            10, '3 Months', '100 EUR'
        )
        assert len(protective_put_strategy.positions) == 2
        print(protective_put_strategy)

        # 2. protective collar
        builder = strategy_builder()
        director = Director(builder)
        protective_collar_strategy = director.make_protective_collar(
            10, '3 Months', '100 EUR', '90 EUR'
        )
        assert len(protective_collar_strategy.positions) == 3
        print(protective_collar_strategy)

        # 3. long butterfly
        builder = strategy_builder()
        director = Director(builder)
        butterfly_strategy = director.make_long_butterfly_by_calls(
            10, '3 Months', '90 EUR', '110 EUR'
        )
        assert len(butterfly_strategy.positions) == 4
        print(butterfly_strategy)
        print("==============================================================")

Toutes les trois stratégies se valident les tests unitaires pour les actions avec les options américaines, mais aussi pour les bonds avec les options européennes, tandis que les affichages des processus détaillés sont mis en lumière ci-dessous :

Construct Strategy by American Stock Options and Bond
    
------------------Protective Put Strategy---------------------
Finding 10 stock contracts for sale in the market...
Finding 10 written contracts having American put of stock at nearest price of 100 EUR with maturity of 3 Months in the market...
The strategy is composed by buying 10 stock units, buying 10 American puts of stock at price of 100 EUR with maturity of 3 Months.
------------------Protective Collar Strategy------------------
Finding 10 stock contracts for sale in the market...
Finding 10 written contracts having American put of stock at nearest price of 90 EUR with maturity of 3 Months in the market...
Finding 10 buyer contracts willing purchase American call of stock at nearest price of 100 EUR with maturity of 3 Months in the market...
The strategy is composed by buying 10 stock units, buying 10 American puts of stock at price of 90 EUR with maturity of 3 Months, selling 10 American calls of stock at price of 100 EUR with maturity of 3 Months.
-----------------Long Butterfly by Calls Strategy-------------
Finding 10 written contracts having American call of stock at nearest price of 90 EUR with maturity of 3 Months in the market...
Finding 10 written contracts having American call of stock at nearest price of 110 EUR with maturity of 3 Months in the market...
Finding 10 buyer contracts willing purchase American call of stock at nearest price of 100.0 EUR with maturity of 3 Months in the market...
Finding 10 buyer contracts willing purchase American call of stock at nearest price of 100.0 EUR with maturity of 3 Months in the market...
The strategy is composed by buying 10 American calls of stock at price of 90 EUR with maturity of 3 Months, buying 10 American calls of stock at price of 110 EUR with maturity of 3 Months, selling 10 American calls of stock at price of 100.0 EUR with maturity of 3 Months, selling 10 American calls of stock at price of 100.0 EUR with maturity of 3 Months.
==============================================================
Construct Strategy by European Bond Options and Bond
------------------Protective Put Strategy---------------------
Finding 10 bond contracts for sale in the market...
Finding 10 written contracts having European put of bond at nearest price of 100 EUR with maturity of 3 Months in the market...
The strategy is composed by buying {number} bond units, buying 10 European puts of bond at price of 100 EUR with maturity of 3 Months.
------------------Protective Collar Strategy------------------
Finding 10 bond contracts for sale in the market...
Finding 10 written contracts having European put of bond at nearest price of 90 EUR with maturity of 3 Months in the market...
Finding 10 buyer contracts willing purchase European call of stock at nearest price of 100 EUR with maturity of 3 Months in the market...
The strategy is composed by buying {number} bond units, buying 10 European puts of bond at price of 90 EUR with maturity of 3 Months, selling 10 European calls of stock at price of 100 EUR with maturity of 3 Months.
-----------------Long Butterfly by Calls Strategy-------------
Finding 10 written contracts having European call of bond at nearest price of 90 EUR with maturity of 3 Months in the market...
Finding 10 written contracts having European call of bond at nearest price of 110 EUR with maturity of 3 Months in the market...
Finding 10 buyer contracts willing purchase European call of stock at nearest price of 100.0 EUR with maturity of 3 Months in the market...
Finding 10 buyer contracts willing purchase European call of stock at nearest price of 100.0 EUR with maturity of 3 Months in the market...
The strategy is composed by buying 10 European calls of bond at price of 90 EUR with maturity of 3 Months, buying 10 European calls of bond at price of 110 EUR with maturity of 3 Months, selling 10 European calls of stock at price of 100.0 EUR with maturity of 3 Months, selling 10 European calls of stock at price of 100.0 EUR with maturity of 3 Months.
==============================================================

Comme toutes les trois stratégies sont testées pour les deux moteurs en boucle for, les stratégies sont correctement retournées l’une après l’autre.

De cette façon, le patron de conception Moteur réduit la complexité de la fabrication des objets ayant représentations complexes à la chaîne sans limite. En effet, il nous ouvre deux dimensions d’extension possibles en code de Python 3. On pourrait développer à l’aise autant de stratégies, par exemple Box Spread [5], en rajoutant autant de positions élémentaires nécessaires chez le directeur. En parallèle, on pourrait également enrichir autant de moteurs concrets pour les nouveaux produits auxquels on mettrait les stratégies sans déranger le reste du programme, ce qui fait passer à l’échelle le développement.

Conclusion

Dans cet article, on met en évidence la nécessité du patron de conception Moteur contre l’issue des instanciations des objets avec les représentations très complexes à fabriquer à la chaîne. À l’instar des autres patrons de conceptions créateurs[4], il respecte le principe de responsabilité unique. Il s’appuie sur les représentations décomposées en composants élémentaires chez les moteurs qui sont assemblés à la chaîne sans limite en fonction du besoin chez le directeur. Le patron de conception moteur est mis en place et testé en Python 3 pour monter les options, lorsque l’enrichissement des nouvelles caractéristiques pour les nouvelles options pourrait efficacement passer à l’échelle dans le programme.

Références

[1] https://fr.wikipedia.org/wiki/Option

[2] https://www.invivoo.com/fabrique-design-patterns/

[3] https://www.invivoo.com/fabrique-abstraite/

[4] https://www.invivoo.com/design-patterns/

[5] https://www.invivoo.com/lart-clean-code-environnement-java/

[6] https://www.investopedia.com/trading/options-strategies/

[7] https://www.investopedia.com/terms/p/protective-put.asp

[8]https://www.investopedia.com/articles/active-trading/011515/how-protective-collar-works.asp

[9] https://www.investopedia.com/terms/b/butterflyspread.asp