Parmi les modèles utilisés pour le pricing des options, on a le célébrissime modèle de Black-Scholes-Merton(1973) mais aussi le non-moins célèbre modèle de Cox-Ross-Rubinstein(1979) qui est un modèle discret contrairement au premier et qui a la cote parmi les modèles discrets. Il a la particularité d’être simple et intuitif, il vous épargne toute l’armada mathématique (les processus et équations différentielles stochastiques) sous bassement du modèle de Black-Scholes-Merton et ne démérite en terme de résultats. En effet, avec un peu de gymnastique mathématique, on peut aboutir au modèle de Black-Scholes-Merton.
Notre article ne porte pas sur la dérivation du modèle de Black-Scholes-Merton mais celle du Modèle de Cox-Ross-Rubinstein et de son implémentation en C++ dans un paradigme Orienté Objet.

Exemple d’arbre binomial après 3 itérations (Extrait du livre : The Theory and Practice of Investment Management)
Modèle Binomial de Cox-Ross-Rubinstein :
Description du modèle dans le cadre d’une option européenne
En l’Absence d’Opportunité d’Arbitrage et en supposant notamment que le prix du sous-jacent , ne puisse prendre à une itération i de
que la valeur
quand elle évolue à la hausse et
lors d’une évolution à la baisse, telle que :
Nous pouvons alors définir la valeur par rapport à la valeur
( le prix de l’actif sous-jacent à la date du contrat) comme un arbre binomial dont la valeur à l’issue de
itération et pour
hausse vaut :
(1)
S’il est possible de déterminer les valeurs de à chaque nœud d’un arbre, il est donc aussi possible de calculer la valeur de l’option
à chacun de ces nœuds comme ceci :
(2)
Prenons un exemple de Call après une itération la valeur à chaque nœud vaut :
Donc d’après ce développement, il est possible de déterminer la valeur de selon que
ou
se réalise. Mais ce que nous nous voulons c’est la valeur
de l’option Call.
C’est là qu’il faut faire appel à un ingrédient incontournable l’hypothèse de Risque Neutre qui veut que :
- – L’espérance de rendement de l’actif sous-jacent est égale au taux de rendement de l’actif sans risque
- –
, le taux de l’actif sans risque soit le taux d’actualisation.
Et dans ce cas :
ou de manière générale, on peut calculer la valeur d’une option call comme put de type européenne(dont l’exercice ne peut advenir qu’à la date d’expiration):
(3)
ou si l’on préfère cette écriture
(4)
Avec où
est la maturité de l’option et
le nombre d’itération ou de niveau de notre arbre binomial. Et
la probabilité risque neutre qui vaut
(5)
par ce qu’en effet d’après la première conséquence de l’hypothèse risque neutre :
Par ailleurs, ils nous restent encore deux paramètres dont nous n’avons pas encore déterminés les valeurs, il s’agit de et
. De combien évolue le sous-jacent- il à la hausse
et à la baisse
?
Pour trouver ces valeurs, Cox, Ross et Rubinstein vont tout simplement les extraire de l’expression de la variance et en remplaçant le
par l’équation
et après simplification des membres avec des dégrés supérieur ou égal à
, on obtient que :
(6)
Cas particulier de l’option américaine
Contrairement à une option européenne qui attend d’être exercée à la date d’expiration, l’option américaine, peut être exercée en tout point optimal entre la date de signature du contrat et la date d’expiration. Ainsi, à chaque nœud au lieu de considérer seulement on comparera cette dernière à
dans le cas d’un call et
dans le cas d’un put :
(7)
dans le cas d’un call et
(8)
si c’est un call.
Nombre d’itération et convergence du modèle CRR vers le Modèle de BSM
Prenons une option de type call européen ayant les caractéristiques suivantes : Prix spot 35, prix d’exercice 36, taux d’intérêt sans risque 5%(annuel), la volatilité 30%(annuelle), maturité 1(annuelle). Normalement la valeur d’un tel call européen pour Black-Scholes-Merton vaut 4.5169.
Voyons maintenant avec l’arbre binomial de Cox-Ross-Rubinstein. Considérons d’abord que le nombre d’itération est de N = 1, nous pouvons calculer : , donc
et
et après bifurcation on aura :
.
Puis pou N=2 : , donc
et
et après bifurcation on aura ce que le
:
Maintenant on peut observer comme le montre la figure ci-dessous(en abscisse le nombre d’itération et en ordonnée la valeur de l’option) et celle ci-dessus que lorsque le nombre d’itération est grande et par conséquence
, la valeur d’une option calculée à la manière de CRR (put ou call) donnera avec les mêmes paramètres la valeur obtenue avec le modèle de BSM.
Implémentation du modèle Binomial de CRR en C++
Design et Description des classes : la classe de base CRRBinomialTree
Pour les besoins de l’implémentation nous allons adopter l’architecture du diagramme ci-dessous. En effet, nous définirons une classe mère CRRBinomialTree pour fournir les différents paramètres de pricing selon le modèle de CRR aux classes filles nommément EuropeanOption et AmericanOption.
L’idée est de tirer profit du polymorphisme de ces objets pour plus de souplesses en redéfinissant certaines méthodes membres de la classe de base à savoir price() et display() dans les classes filles.
Par ailleurs, nous ne voulons pas que l’utilisateur final puisse avoir accès aux fonctions getStock(),payOff(), et Up()/Down(), qui ne sont dans notre cas précis que des fonctions intermédiaires.D’où l’intérêt de les déclarer comme protégées, ainsi seule les classes filles peuvent en faire usage. Elles permettent de déterminer respectivement, le prix du sous-jacent à un nœud de l’arbre Cf. équation , le pay Off Cf.equation
et les deux relations de l’équation
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/* * File: CRRBinomialTree.h * Author: daname * Created on 16 décembre 2016, 10:08 */ #ifndef CRRBINOMIALTREE_H #define CRRBINOMIALTREE_H #include <string> class CRRBinomialTree { private: double So; // prix actuel du sous -jacent double K; // prix d'exercice double r; // taux d'interet double T; // maturité double sigma; // volatilité int N; // Nombre d'itération protected: double payOff(double S,double K, const std::string type = "call")const; double getStock(int j,int n); double Up(int j)const; double Down(int j)const; public: CRRBinomialTree(); CRRBinomialTree(double _So,double _K,double _T,double _r, double _sigma,int _n = 1000); CRRBinomialTree & operator=(const CRRBinomialTree & opt); virtual double price(); virtual void display(); double riskNeutralProb()const; void setSigma(double sigma); double getSigma() const; void setR(double r); double getR() const; void setT(double T); double getT() const; void setK(double K); double getK() const; void setSo(double So); double getSo() const; int getN() const; void setN(int N); virtual ~CRRBinomialTree(); }; #endif /* CRRBINOMIALTREE_H */ |
Aussi pour finir nous avons les méthodes usuelles d’une classe digne de ce nom à savoir les constructeurs, les accesseurs/mutateurs et le destructeur.
Héritage : les classes filles AmericanOption et EuropeanOption
Comme énoncé précédemment, les classes filles vont redéfinir les méthodes price() et display(). Cette dernière n’affichera que les différents caractéristiques du modèles et la valeur de l’options. Les constructeurs des classes filles vont permettre comme on peut le voir ci-dessous, d’ajouter un paramètre additionnel à savoir le type “call” ou “put”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#ifndef AMERICANOPTION_H #define AMERICANOPTION_H #include "CRRBinomialTree.h" #include <string> class AmericanOption: public CRRBinomialTree { private : std::string type; public: AmericanOption(); AmericanOption(double So,double K,double T,double r, double sigma,const std::string type = "call",int n = 1000); AmericanOption(const CRRBinomialTree &_opt, const std::string _type = "call"); double price(); void display(); void setType(std::string type); std::string getType() const; virtual ~AmericanOption(); }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#ifndef EUROPEANOPTION_H #define EUROPEANOPTION_H #include "CRRBinomialTree.h" #include <string> class EuropeanOption: public CRRBinomialTree { private : std::string type; public: EuropeanOption(); EuropeanOption(double So,double K,double T,double r, double sigma,const std::string type = "call",int n = 1000); EuropeanOption(const CRRBinomialTree& opt,const std::string type = "call"); double price(); void display(); void setType(std::string type); std::string getType() const; virtual ~EuropeanOption(); }; #endif /* EUROPEANOPTION_H */ |
Maintenant si l’on s’intéresse de près à la fonction price() dont vous verrez le corps dans les blocs de codes plus bas (American.cpp, European.cpp). On remarque d’abord l’utilisation de la routine for pour remplir un tableau permettant l’allocation dynamique de mémoire pour N+1 éléments,( d’où l’usage de pointeur si vous n’êtes fans des pointeurs utiliser la librairie vector ) avec les valeurs de l’options c’est les lignes suivantes :
1 2 3 |
for(int j=0;j<=CRRBinomialTree::getN();++j) *(v+j)= payOff(getStock(j,CRRBinomialTree::getN()), CRRBinomialTree::getK(),type); |
avec getStock() on récupéré la valeur du titre ensuite avec payOff() on détermine le pay-off selon le type call ou put au niveau des feuilles de notre arbre. Dans la suite de la fonction, nous implémentons la relation décrite dans l’équation :
1 2 3 4 5 6 7 |
for(int i=CRRBinomialTree::getN()-1;i>=0;--i){ for(int j=0;j<=i;++j){ *(v+j)=(p*(*(v+j+1))+(1-p)*(*(v+j)))*exp(-CRRBinomialTree::getR() *CRRBinomialTree::getT()/CRRBinomialTree::getN()); } } return *v; |
Qui va calculer la valeur actuelle des pay-off et ceci de façon récursive en commençant du dernier élément du tableau au premier. Et comme c’est un tableau pour retourner le premier l’élément qui contiendrait à la fin du processus la valeur de l’option on renvoie *v. La version Américaine de la fonction diffère seulement au niveau des 2 lignes suivantes :
1 2 |
Sij = payOff(getStock(j,i),CRRBinomialTree::getK(),type); *(v+j)=(*(v+j)> Sij)?(*(v+j)): Sij; |
Ces deux lignes sont la traduction de l’équation . Aussi, autres points importants c’est l’utilisation
CRRBinomialTree:: ,l’opérateur de résolution de portée permet d’accéder aux méthodes membres public de la classe mère. Vous constaterez donc que les méthodes membres protected, n’en ont pas besoin.
Par ailleurs, nous allons invoquer le constructeur de la classe mère dans la définition des constructeur des classes filles. Pour finir voici la définition des trois classes :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
/* * File: CRRBinomialTree.cpp * Author: danam * * Created on 16 décembre 2016, 10:08 */ #include "CRRBinomialTree.h" #include <iostream> #include <string> #include <cmath> using namespace std; CRRBinomialTree::CRRBinomialTree(){ } CRRBinomialTree::CRRBinomialTree(double _So,double _K,double _T,double _r,double _sigma,int n){ So =_So; K = _K; T =_T; r = _r; sigma = _sigma; N = n; } CRRBinomialTree & CRRBinomialTree::operator=(const CRRBinomialTree & opt){ So = opt.So; K = opt.K; T = opt.T; r = opt.r; sigma = opt.sigma; N = opt.N; return *this; } double CRRBinomialTree::price(){ cout<< "Option type is not defined" <<endl; return(0); } double CRRBinomialTree::getStock(int j, int n){ return So*Up(j)*Down(n-j); } double CRRBinomialTree::payOff(double S, double K, const string type)const{ if(type == "call") return((S-K)>0)?(S-K):0; else return ((K-S)>0)?(K-S):0; } void CRRBinomialTree::display(){ cout<<"----------------------------------"<<endl; cout<<"Current price (So) : "<< So << endl; cout<<"Strike(K) : "<< K << endl; cout<<"Maturity(T) : "<< T << endl; cout<<"Risk-free rate(r) : "<< r << endl; cout<<"Volatility(sigma) : "<< sigma << endl; cout<<"Number of steps : "<<N <<endl; cout<<"=================================="<<endl; } double CRRBinomialTree::riskNeutralProb()const{ return (exp(r*T/N)-Down(1))/(Up(1)-Down(1)) ; } double CRRBinomialTree::Up(int j) const { return pow(exp(sigma*sqrt(T/N)),j); } double CRRBinomialTree::Down(int j) const { return pow(exp(-sigma*sqrt(T/N)),j); } void CRRBinomialTree::setSigma(double sigma) { this->sigma = sigma; } double CRRBinomialTree::getSigma() const { return sigma; } void CRRBinomialTree::setR(double r) { this->r = r; } double CRRBinomialTree::getR() const { return r; } void CRRBinomialTree::setT(double T) { this->T = T; } double CRRBinomialTree::getT() const { return T; } void CRRBinomialTree::setK(double K) { this->K = K; } double CRRBinomialTree::getK() const { return K; } void CRRBinomialTree::setSo(double So) { this->So = So; } double CRRBinomialTree::getSo() const { return So; } void CRRBinomialTree::setN(int N){ this->N = N; } int CRRBinomialTree::getN() const{ return N; } CRRBinomialTree::~CRRBinomialTree() { } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/* * File: EuropeanOption.cpp * Author: danam * * Created on 16 décembre 2016, 10:25 */ #include "EuropeanOption.h" #include "CRRBinomialTree.h" #include <iostream> #include <cmath> #include <string> using namespace std; EuropeanOption::EuropeanOption(){} EuropeanOption::EuropeanOption(const CRRBinomialTree &opt, const string _type):CRRBinomialTree(opt.getSo(),opt.getK(),opt.getT(), opt.getR(),opt.getSigma(),opt.getN()),type(_type){ } EuropeanOption::EuropeanOption(double So,double K,double T,double r, double sigma,const string _type, int n):CRRBinomialTree(So,K,T,r,sigma,n),type(_type){ } void EuropeanOption::display(){ CRRBinomialTree::display(); cout<<" Value of European "<<type <<" : "<<price()<<endl; cout<<"----------------------------------"<<endl; } double EuropeanOption::price(){ double *v = new double[CRRBinomialTree::getN()+1]; double p = CRRBinomialTree::riskNeutralProb(); for(int j=0;j<=CRRBinomialTree::getN();++j) *(v+j)= CRRBinomialTree::payOff(CRRBinomialTree::getStock(j, CRRBinomialTree::getN()),CRRBinomialTree::getK(),type); for(int i=CRRBinomialTree::getN()-1;i>=0;--i){ for(int j=0;j<=i;++j){ *(v+j)=(p*(*(v+j+1))+(1-p)*(*(v+j)))*exp(-CRRBinomialTree::getR() *CRRBinomialTree::getT()/CRRBinomialTree::getN()); } } return *v; } void EuropeanOption::setType(std::string type) { this->type = type; } std::string EuropeanOption::getType() const { return type; } EuropeanOption::~EuropeanOption(){ } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/* * File: AmericanOption.cpp * Author: danam * * Created on 16 décembre 2016, 10:24 */ #include "AmericanOption.h" #include "CRRBinomialTree.h" #include <iostream> #include <cmath> #include <string> using namespace std; AmericanOption::AmericanOption(){} AmericanOption::AmericanOption(const CRRBinomialTree &opt, const string _type):CRRBinomialTree(opt.getSo(),opt.getK(),opt.getT(), opt.getR(),opt.getSigma(),opt.getN()),type(_type){ } AmericanOption::AmericanOption(double So,double K,double T,double r, double sigma,const string _type, int n):CRRBinomialTree(So,K,T,r,sigma,n),type(_type){ } double AmericanOption::price(){ double *v= new double[CRRBinomialTree::getN()+1]; double Sij; double p = CRRBinomialTree::riskNeutralProb(); for(int j=0;j<=CRRBinomialTree::getN();++j) *(v+j)= payOff(getStock(j,CRRBinomialTree::getN()), CRRBinomialTree::getK(),type); for(int i= CRRBinomialTree::getN()-1;i>=0;--i){ for(int j=0;j<=i;++j){ *(v+j)=(p*(*(v+j+1))+(1-p)*(*(v+j)))*exp(-CRRBinomialTree::getR() *CRRBinomialTree::getT()/CRRBinomialTree::getN()); Sij = payOff(getStock(j,i),CRRBinomialTree::getK(),type); *(v+j)=(*(v+j)> Sij)?(*(v+j)): Sij; } } return *v; } void AmericanOption::display(){ CRRBinomialTree::display(); cout<<"----------------------------------"<<endl; cout<<" Value of American "<<type <<" : "<<price()<<endl; } void AmericanOption::setType(std::string type) { this->type = type; } std::string AmericanOption::getType() const { return type; } AmericanOption::~AmericanOption(){ } |
Application numérique :
Nous allons montrer quelques utilisations des objets issus des classes précédemment décrites. Faites attention à l’utilisation des pointeurs n’oubliez pas les symboles * et -> là où il faut. Et à la fin utiliser delete pour les détruire enfin de libérer de la mémoire.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <iostream> #include "CRRBinomialTree.h" #include "EuropeanOption.h" #include "AmericanOption.h" using namespace std; int main() { double So = 35, K = 36, T = 1, r = 0.05,sigma = 0.3; CRRBinomialTree *tree = new CRRBinomialTree(So,K,T,r,sigma); CRRBinomialTree *optree = new AmericanOption(So,K,T,r,sigma,"put"); EuropeanOption optE(*tree); AmericanOption optA(*tree); EuropeanOption optE1(*optree,"call"); AmericanOption * callopt = (AmericanOption*)tree; // les valeurs call d'abord optE.display(); optA.display(); optE1.display(); callopt->display(); // les valeurs put ensuite optE.setType("put"); optA.setType("put"); optE1.setType("put"); optE.display(); optA.display(); optE1.display(); optE.setN(10000); // augmentons le nombre d'iteration histoire de voir le resultat optE.setType("call"); optE.display(); // une valeur plus proche de celle de Black-Scholes-Merton 4.5169 delete tree ,optree,callopt; return 0; } |
Résultat :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 1000 ================================== Value of European call : 4.51786 ---------------------------------- ---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 1000 ================================== ---------------------------------- Value of American call : 4.51786 ---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 1000 ================================== Value of European call : 4.51786 ---------------------------------- ---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 1000 ================================== ---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 1000 ================================== Value of European put : 3.76212 ---------------------------------- ---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 1000 ================================== ---------------------------------- Value of American put : 3.98032 ---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 1000 ================================== Value of European put : 3.76212 ---------------------------------- ---------------------------------- Current price (So) : 35 Strike(K) : 36 Maturity(T) : 1 Risk-free rate(r) : 0.05 Volatility(sigma) : 0.3 Number of steps : 10000 ================================== Value of European call : 4.51696 ---------------------------------- RUN SUCCESSFUL (total time: 3s) |
Comme on peut le voir la rapidité d’exécution malgré le grand nombre de routine utiliser, fait du C++ un langage de choix pour un Quant !
Nous obtenons au finish la même valeur qu’avec le modèle de Black-Scholes and Merton 4.5169 précédement fourni dans l’exemple et que vous pouvez vérifier chez vous avec Excel.