26 août 2016

Fabrique - Abstraite

Design & Code

Dr. Ruijing

HU

Image équipe dev.

26 août 2016

Fabrique - Abstraite

Design & Code

Dr. Ruijing

HU

Image équipe dev.

26 août 2016

Fabrique - Abstraite

Design & Code

Dr. Ruijing

HU

Image équipe dev.

Parmi les titres financiers on a de nombreux produits dérivés qui sont très difficiles à construire tout d’un coup. Car un contrat dérivé pourrait comprendre plusieurs composants fondamentaux dont chacun est crée d’une manière indépendante et complexe. Par exemple, il y a un instrument financier : l’option, dont l’acheteur a le droit mais non pas obligation d’acheter (call) ou de vendre (put) un actif sous-jacent à un prix d’exercice fixé à l'avance (strike), pendant un temps donné ou à une date fixée [1]. Sa construction consiste inévitablement en deux principaux composants : l’instrument sous-jacent et le modèle de valorisation. Au fait, les instruments sous-jacent pourraient être les matières premières, les actions, les bonds, les futures, les forwards, les indices, les devises, les swaps, etc., alors que l’on choisirait les modèles comme Monte Carlo, Black-Scholes ou CRR (le modèle binomial) [2] au grès du besoin pour évaluer une option dérivative d’un instrument. D’ailleurs, les instruments et les modèles peuvent être indépendamment combinés au cours de la création des options européennes ou américaines. En plus, les options américaines sont exerçables à tout moment entre la date d’achat et la date d’échéance définie, lorsque les options européennes ne sont exercées qu’à l’échéance définie [3]. Du coup, le développement de toutes les options de ces deux styles une à une devrait devenir très lourd, si l’on les crée tout simplement par la fabrique [4] en vérifiant les conditions de fabrication dans le code. Sinon, une introduction d’un nouveau titre sous-jacent pourrait demander un enrichissement à tous les modèles pour une suite d’options dans le programme parfois.

Par conséquent, on cherche une nouvelle façon pour s’adapter à produire les objets de composants sophistiqués comme l’option. A l’instar du patron de conception : la fabrique [4], la fabrique abstraite (abstract factory en anglais) fait partie de la famille créatrice [5], lorsqu’elle est considérée comme l’usine des usines. En d’autres termes, elle est censée fabriquer et assembler à la chaîne les composants majeurs pour les objets en question. A cet égard, les composants complexes des options héritant des différentes classes abstraites sont implémentés individuellement par leurs sous-classes ou les usines fabricatrices concrètes, tandis que les mises ensemble des composants spécifiques et nécessaires pour l’option européenne et l’option américaine ont lieu séparément dans l’usine européenne et l’usine américaine dérivant d’une classe abstraite commune abstract factory.

Dans cet article, on va concevoir et réaliser le patron de conception de fabrique abstraite qui fait face au défi de la complexité importante pendant les montages des différentes options avec les composants complexes, alors que le programme doit être rendu maintenable et extensible sous le principe SOLID [6].


Conception

Le diagramme ULM démontre en général deux parties importantes dans le patron de conception Fabrique Abstraite en Python 3. La 1e partie comprend plusieurs usines concrètes de la classe (usine) abstraite Abstract Factory, qui assemblent les composants difficiles à fabriquer tout d’un coup mais recherchés pour les objets désirés à la fin. Ces composants sont d’ailleurs instanciés indépendamment par les classes dérivées des diverses interfaces dans la 2e partie.

Dans notre contexte, on conçoit dans un premier temps deux usines Factory A et Factory B pour fabriquer deux familles d’option : européenne et américaine, qui héritent de la même classe abstraite Abstract Factory ayant les caractéristiques partagées. Pour ces deux styles d’option, on peut créer par exemple, une option dont l’actif sous-jacent peut être la matière première, la futures ou le bond ; et le modèle est choisi parmi les méthodes Monte Carlo, Black-Scholes, ou CRR. La fabrication des composants dans une option est tellement compliquée à manipuler tout d’un coup que l’on la décompose en deux interfaces à réaliser avant de les assembler dans les deux usines concrètement développées au début. De fait, l’interface 1 est chargée du développement des titres sous-jacents, lorsque les modèles sont mis en place par les classes dérivées de l’interface 2.

Du coup, le client qui cherche à créer une option européenne de la matière première sous la méthode de valorisation Mont Carlo accède à la classe Factory A, dans laquelle la méthode statique make_product_1 est appelée avec les deux composants : la matière première et la méthode Mont Carlo. Au fait, ces derniers sont retournés respectivement par les méthodes de classe create_1 et create_2 dans la 2e partie du schéma, qui est implémentée par les sous-classes concrètes des interfaces. D’ailleurs, une option américaine serait montée avec les deux composants identiques, si la méthode statique make_product_1 était appelée en Factory B.

De la même façon, on instancie le bond et le modèle CRR pour monter une option américaine du bond sous la méthode de valorisation CRR en appelant la méthode statique make_product_2 dans la classe Factory B.

Par conséquent, la complexité de la création d’une option est justement réduite par la décomposition de la fabrication complexe en les composants élémentaires et leur assemblage dans l’usine correspondante, tandis que l’enrichissement d’un nouveau composant pour une nouvelle option n’influence pas le reste du programme conçu par la fabrique abstraite.

Développement

Pour que notre démonstration soit claire et simple, on instancie les options avec les attributs basiques :

  • Le style : européen ou américain

  • Le type : la doit à acheter l’actif sous-jacent (call) ou à le vendre (put).

  • Le prix d’exercice : le prix préfixé à exécuter l’option si besoin chez l’acheteur de l’option.

  • La maturité : une date précise où l’option européenne est exécutable, ou un intervalle du temps possible pendant lequel l’option américaine est exerçable, si l’acheteur de l’option souhaite.

  • L’actif sous-jacent : l’instrument financier qui est couvert par l’option.

  • Le modèle : la métrologie à utiliser pour évaluer l’option

Sinon, les deux dernières sont les deux composants complexes à mettre en œuvre chez les sous-classes des deux interfaces : IUnderlying et IModel. Au fait, dans notre exemple on s’intéresse à fabriquer trois combinaisons entre les deux composants :

  • La matière première (commodity) avec le modèle Mont Carlo.

  • Le bond avec le modèle CRR

  • L’action avec le modèle Black-Scholes

Dans l’intention de réaliser facilement les deux interfaces : IUnderlying pour l’actif sous-jacent et IModel pour le modèle de valorisation, nous profitons des classes de bases abstraites (Abstract Base Class ou ABC en anglais) en Python 3, qui fournit la métaclasse ABCMeta pour définir les ABC ainsi que la classe d'aide ABC, cette dernière permettant de définir des ABC en utilisant l'héritage [7]. D’ailleurs, on marque le décorateur [8] @abstractmethod pour les méthodes comme abstraites.

En effet, une classe concrétise le contrat d'une instance de la classe abstraite (par exemple, la classe Bond concrétise le comportement de la fabrication du sous-jacent de l’actif en interface). D'autre part, une métaclasse définit le comportement d'une classe (à savoir que la classe ABCMeta décrit le comportement de chaque classe ABC). Une classe est une instance d'une métaclasse. Le module abc est livré avec une métaclasse ABCMeta. à l'époque, nous devions l'utiliser pour définir des métaclasses avec class ainsi :

De nos jours, le simple héritage d'ABC irait faire la même chose, au lieu passer par métaclasses. Dans notre exemple, on réalise la classe abstraite par 

Alors, on serait prêt d’y définir une méthode abstraite create_underlying(cls) qui est une méthode de la classe (à savoir : une méthode qui est liée à une classe plutôt qu'à son objet. Elle ne nécessite pas la création d'une instance de classe mais l’état de la classe). Pour ce faire, nous devons utiliser un décorateur : @abstractmethod, entre @abstractmethod et la signature de la méthode abstraite, afin de vérifier dans un premier temps si toutes les abstractions méthodologiques sont bien implémentées, lors de l’instanciation. Tout essai d’instancier une classe dérivant de l’interface IUnderlying sans implémenter la méthode create_underlying obtiendra une erreur :

Sinon, pour assurer son implémentation, on introduit NotImplementedError [9] dans les méthodes abstraites chez les interfaces. Cette exception serait levée en indiquant que l'implémentation concrète doit encore être ajoutée, lorsque nous nécessitons des classes dérivées pour remplacer la méthode, ou lorsque la classe est en cours de développement.

Au fait, la classe Option va prendre les objets des classes concrètes qui implémentent les deux interfaces : les classes de Commodity, Bond et Equity chez les actifs, ainsi que celles de MontCarloModel, BlackScholesModel et CrrModel chez les modèles dans notre exemple.

Comme nous avons deux styles d’option : européen et américain à développer pour ces combinaisons, une usine abstraite commune AbstractOptionFactory est mise en œuvre par le biais des classes de bases abstraites mentionnées au-dessus, tandis que deux usines concrètes : EuropeanOptionFactory et AmericainOptionFactory sont construites en tant que les points d’accès pour le client.

En plus, pour tester la justesse de l’instanciation des options par la fabrique abstraite, on enrichit la classe Option par une méthode qui calcule son profit (payoff). Néanmoins, faute d’espace, le code simplifie les implémentations des algorithmes pour les modèles et les calculs du profit par les impressions de processus.

Maintenant, nous pouvons procéder le développement en deux phases globalement.

Dans un premier temps, on cherche à créer les objets des titres sous-jacents et des modèles de valorisation définis dans la 2e partie du diagramme ULM. En général, deux interfaces fournissent les points d’appel create_underlying et create_model pour retourner les deux composants, lorsque leurs sous-classes mettent au point indépendamment et individuellement leurs objets concrets. Dès leurs validités, on pourrait leur faire entrer dans les usines que l’on va construire toute à l’heure.

Ensuite, on développe les usines de la 1e partie dans le schéma de la conception, en profitant des titres sous-jacents et les modèles de valorisation qui sont réalisés dans la 1e phase pour se faire entrer dans les usines. Les classes EuropeanOptionFactory et AmericainOptionFactory dérivent de la classe abstraite AbstractOptionFactory en définissant trois méthodes abstraites statiques, ce qui implique les trois éventuelles combinaisons des composants. Comme l’option américaine fournit un intervalle du temps possible à exercer le droit, la classe AmericainOptionFactory transforme la fin de la maturité par une période en tuple en Python 3.

Grace à ces mises en œuvres, le client pourrait désormais demander les options désirées.

# abstract_factory.py
from abc import ABC, abstractmethod
from typing import Tuple, Union
class IUnderlying(ABC):
    "An Abstract Component Class Interface (Underlying)"
    @classmethod
    @abstractmethod
    def create_underlying(cls):
        "An abstract interface method to create underlying"
        raise NotImplementedError 
class IModel(ABC):
    "An Abstract Component Class Interface (Model)"
    @classmethod
    @abstractmethod
    def create_model(cls):
        "An abstract interface method to create model"
        raise NotImplementedError
class Bond(IUnderlying):
    "A Concrete Bond Class that implements the IUnderlying interface"
    def __init__(self):
        self._name = 'Bond'
    @classmethod
    def create_underlying(cls) -> IUnderlying:
        "A concrete method to create bond"
        print('Bond will be ready...')
        return cls()
class Commodity(IUnderlying):
    "A Concrete Commodity Class that implements the IUnderlying interface"
    def __init__(self):
        self._name = 'Commodity'
    @classmethod
    def create_underlying(cls) -> IUnderlying:
        "A concrete method to create commodity"
        print('Commodity will be ready...')
        return cls()
class Equity(IUnderlying):
    "A Concrete Equity Class that implements the IUnderlying interface"
    def __init__(self):
        self._name = 'Equity'
    @classmethod
    def create_underlying(cls) -> IUnderlying:
        "A concrete method to create equity"
        print('Equity will be ready...')
        return cls()
class MontCarloModel(IModel):
    "Mont Carlo Model"
    def __init__(self):
        self._name = 'Mont Carlo'
    @classmethod
    def create_model(cls) -> IUnderlying:
        "A concrete method to create Mont Carlo Model"
        print('Mont Carlo Model will be ready...')
        return cls()
class BlackScholesModel(IModel):
    "Black Scholes Model"
    def __init__(self):
        self._name = 'Black-Scholes'
    @classmethod
    def create_model(cls) -> IUnderlying:
        "A concrete method to create Black Scholes Model"
        print('Black-Scholes Model will be ready...')
        return cls()
class CrrModel(IModel):
    "CRR (Binominal) Model"
    def __init__(self):
        self._name = 'CRR'
    @classmethod
    def create_model(cls) -> IUnderlying:
        "A concrete method to create CRR Model"
        print('CRR Model will be ready...')
        return cls()
class Option(object):
    "An Option Class"
    def __init__(self, style: str, is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]], underlying: IUnderlying, model: IModel):
        """
        Attributes
        ----------
        style: is European or Americain style
        is_call: is call option ?
        strike: strike price
        maturity : maturity date or interval to excercise option
        underlying : underlying security objetct
        model : evaluation model objetct
        """
        self._style = style
        self._is_call = is_call
        self._strike = strike
        self._maturity = maturity
        self._underlying = underlying
        self._model = model
        print(f"creating {style} {'call' if is_call else 'put'} option "
              f"for strike {strike} able to excercise in {maturity}...")
        print(f"its model: {model._name}")
        print(f"its underlying: {underlying._name}")
    def calculate_payoff(self):
        """
        A method to calculate payoff of option
        """
        print(f"calculating option payoff with underlying "
              f"{self._underlying._name} by {self._model._name} model "
              f"for strike {self._strike} able to excercise in "
              f"{self._maturity}...")
        pass
class AbstractOptionFactory(ABC):
    "An Abstract Factory Class (Option Factory)"
    @staticmethod 
    @abstractmethod
    def make_option_of_commodity_with_monte_carlo(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        An abstract interface method to make option of commodity 
        as underlying and Mont Carlo as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        pass
    @staticmethod 
    @abstractmethod
    def make_option_of_bond_with_crr(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        An abstract interface method to make option of bond 
        as underlying and CRR as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        pass 
    @staticmethod 
    @abstractmethod
    def make_option_of_equity_with_black_scholes(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        An abstract interface method to make option of equity 
        as underlying and Black-Scholes as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        pass
class EuropeanOptionFactory(AbstractOptionFactory):
    "A Concrete Factory Class for European Option"
    style = 'European'
    @staticmethod    
    def make_option_of_commodity_with_monte_carlo(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        An concrete method to make option of commodity 
        as underlying and Mont Carlo as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        print("European Option Factory streamlines...")
        return Option(__class__.style,
                      is_call, strike, maturity, 
                      Commodity.create_underlying(),
                      MontCarloModel.create_model())
    @staticmethod 
    def make_option_of_bond_with_crr(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        A concrete method to make option of bond 
        as underlying and CRR as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        print("European Option Factory streamlines...")
        return Option(__class__.style,
                      is_call, strike, maturity, 
                      Bond.create_underlying(),
                      CrrModel.create_model())
    @staticmethod    
    def make_option_of_equity_with_black_scholes(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        A concrete method to make option of equity 
        as underlying and Black Scholes as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        print("European Option Factory streamlines...")
        return Option(__class__.style,
                      is_call, strike, maturity, 
                      Equity.create_underlying(),  
                      BlackScholesModel.create_model())
class AmericainOptionFactory(AbstractOptionFactory):
    "A Concrete Factory Class for Americain Option"    
    style = 'Americain'
    @staticmethod    
    def make_option_of_commodity_with_monte_carlo(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        A concrete method to make option of commodity 
        as underlying and Mont Carlo as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        print("Americain Option Factory streamlines...")
        return Option(__class__.style,
                      is_call, strike, (0, maturity),
                      Commodity.create_underlying(),
                      MontCarloModel.create_model())
    @staticmethod 
    def make_option_of_bond_with_crr(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        A concrete method to make option of bond 
        as underlying and CRR as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        print("Americain Option Factory streamlines...")
        return Option(__class__.style,
                      is_call, strike, (0, maturity), 
                      Bond.create_underlying(),
                      CrrModel.create_model())
    @staticmethod    
    def make_option_of_equity_with_black_scholes(
            is_call: bool, strike: str, maturity: Union[str, Tuple[str, str]]):
        """
        A concrete method to make option of equity 
        as underlying and Black Scholes as model
        Attributes
        ----------
        is_call: is call option ?
        strike: strike price
        maturity : maturity
        """
        print("Americain Option Factory streamlines...")
        return Option(__class__.style,
                      is_call, strike, (0, maturity), 
                      Equity.create_underlying(),  
                      BlackScholesModel.create_model())
if __name__ == '__main__

Chez les tests, on crée deux boucles for pour générer les options ayant le droit d’achat (call) dans notre cas. A la boucle externe on itère les usines des deux styles optionnels, lorsqu’on appelle la méthode generate_factory_options pour fabriquer les options intéressantes, tout en offrant les objets correspondants des actifs sous-jacents et des modèles d’évaluation qui sont instanciés et assemblés dans les usines.

Au fait, toutes les trois combinaisons pour les options européennes et américaines valident les tests unitaires sur les types de maturité liées aux deux styles d’option, tandis que les affichages des processus avec le calcul du profit (payoff) sont mis en lumière en détail en-dessous :

European Option Factory streamlines…
Commodity will be ready…
Mont Carlo Model will be ready…
creating European call option for strike 120 euros able to excercise in 12 months…
its model: Mont Carlo
its underlying: Commodity
calculating option payoff with underlying Commodity by Mont Carlo model for strike 120 euros able to excercise in 12 months…

European Option Factory streamlines…
Bond will be ready…
CRR Model will be ready…
creating European call option for strike 120 euros able to excercise in 9 months…
its model: CRR
its underlying: Bond
calculating option payoff with underlying Bond by CRR model for strike 120 euros able to excercise in 9 months…

European Option Factory streamlines…
Equity will be ready…
Black-Scholes Model will be ready…
creating European call option for strike 120 euros able to excercise in 3 months…
its model: Black-Scholes
its underlying: Equity
calculating option payoff with underlying Equity by Black-Scholes model for strike 120 euros able to excercise in 3 months…

Americain Option Factory streamlines…
Commodity will be ready…
Mont Carlo Model will be ready…
creating Americain call option for strike 120 euros able to excercise in (0, '12 months')…
its model: Mont Carlo
its underlying: Commodity
calculating option payoff with underlying Commodity by Mont Carlo model for strike 120 euros able to excercise in (0, '12 months')…

Americain Option Factory streamlines…
Bond will be ready…
CRR Model will be ready…
creating Americain call option for strike 120 euros able to excercise in (0, '9 months')…
its model: CRR
its underlying: Bond
calculating option payoff with underlying Bond by CRR model for strike 120 euros able to excercise in (0, '9 months')…

Americain Option Factory streamlines…
Equity will be ready…
Black-Scholes Model will be ready…
creating Americain call option for strike 120 euros able to excercise in (0, '3 months')…
its model: Black-Scholes
its underlying: Equity
calculating option payoff with underlying Equity by Black-Scholes model for strike 120 euros able to excercise in (0, '3 months')…

Évidement, chaque usine fabrique le titre sous-jacent avec son modèle chez une option, avant qu’on ne calcule son profit par l’appel de la méthode calculate_payoff. Comme toutes les options sont testées par les deux boucles for dans le programme, les objets des options sont correctement retournés avec le profit juste l’un après l’autre par l’usine d’option européenne et celle d’option américaine.

De cette façon, on profite du patron de conception Fabrique Abstraite pour réduire la complexité de la fabrication des objets ayant composants complexes en Python 3. D’ores et déjà, on pourrait développer à l’aise autant de styles d’option, par exemple Bermuda [1], ou autant de composants sur les instruments sous-jacents et les nouveaux modèles dont on a besoin, tout en ne rajoutant les sous-classes des interfaces respectives dans le schéma de conception, lorsque la réalisation serait décomposée et répartie en efficacité en différentes classes (usines) concrètes 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 Fabrique Abstraite contre l’issue des instanciations des objets avec les composants très complexes à fabriquer tout d’un coup. A l’instar de la Fabrique [4], il respecte le principe de responsabilité unique. En tant que l’usine des usines, il s’appuie sur les productions décomposées en composants fondamentaux qui sont assemblés à la chaîne en fonction du besoin à la fin. Ce patron de conception créateur est implémenté 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://corporatefinanceinstitute.com/resources/knowledge/valuation/option-pricing-models/
[3] https://www.investopedia.com/articles/optioninvestor/08/american-european-options.asp
[4] https://www.invivoo.com/blog/fabrique-design-patterns/
[5] https://www.invivoo.com/blog/design-patterns-patrons-conception/
[6] https://www.invivoo.com/blog/lart-clean-code-environnement-java/
[7] https://docs.python.org/fr/3/library/abc.html
[8] https://www.invivoo.com/blog/decorateurs-python-demystifies/
[9] https://docs.python.org/3/library/exceptions.html