Metode virtuale in MQL5 – aplicatie asupra optiunilor

[English version] [MQLmagazine.com in english] [Editia romaneasca]

Acesta este o continuare a articolului nostru Introducere in optiuni. Partea a II-a : Evaluarea optiunilor si coeficientii lor . Totusi, poate fi vazut si inteles separat, pentru se axeaza pe partea de programare.

Modelele de evaluare a optiunilor sunt extrem de complicate din punct de vedere matematic. Scopul acestui articol este de a prezenta o implementare a modelului de evaluare Black-Scholes, introducand totodata metodele virtuale.

Una dintre functiile care apar mereu si mereu in matematica financiara, inclusiv in modelul Back-Scholes, este
functia distributiei cumulate (cummulative distribution function).

Functia care descrie forma de clopot a distributiei se numeste functia densitatii de probabilitate (probability density function). Zona care este sub ea, adica integrala ei definita, este functia distributiei cumulate. Si pentru cazul cu medie 0 si deviatie standard 1 (fara balans, The area that is below it, meaningly the defined integral , is the cummulative distribution function. And for the case with 0 mean and 1 standard deviation, (de asemenea fara balans, mezokurtica), aceasta distributie este numita si normala.

Integrala definita, cunoscuta ca si suma Riemann, calculeaza zona de sub graficul unei functii adaugand arii infinitezimale care se intind intre functie si axa x. Evident, procedura Riemann este intotdeauna aceeasi, pe cand functia este definita de utilizator. Atat Pascal cat si C++ permit definirea tipurilor functie. De exemplu, o declaratie C++ ar fi aratat asa:
A C++ declaration would have looked like this:

typedef  double (*SingleVarFunction) (double) ;
double Riemann(SingleVarFunction pFunc,double a, double b, int divisions)

In acest caz in C++, Riemann ar fi primit ca parametru orice functie care ar fi acceptat ca parametru un double si ar fi returnat un double ca rezultat. Totusi, acest mecanism nu exista in MQL5.

Aici intra in joc “metodele virtuale”. Obiectele au proprietati. Proprietati care sunt proiectate sa fie publice pentru a fi accesate din afara obiectelor, si apoi metodele apelate din nou. De exemplu, utilizatorul poate schimba raza unui cerc si rechema metoda de de redesenare. Dar in cazul sumei Riemann, ceea ce trebuie trimis ca si valoare este chiar o metoda. Metodele definite de utilizator care sunt apelate de metode mostenite se numesc metode virtuale.

Aceasta este implementarea MQL5 pentru suma Riemann:

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
class RiemannSumCalculus
    {
     public:
     virtual double IntegratableFunction(double x)
       {
        return (0.0);
       }     
     double Riemann(double a,double b,uint divisions)
       {
        double sum,swap,stepsize,x;
        long istep;
        if (a>b) 
          {
           swap=a;
           a=b;
           b=swap;
          }
        sum=0.0;
        if (divisions==0)
          divisions=1;
        stepsize=(b-a)/divisions;
        for (istep=1;istep<=divisions;istep++)
           {
            x=a+(istep-0.5)*stepsize;
            sum=sum+IntegratableFunction(x)*stepsize;
           }
        return(sum);
       }
     };

Dupa cum se vede, metoda IntegratableFunction este specificata ca virtual. Functia in sine nu face nimic, intoarce intotdeauna un zero. Ea joaca rolul unui loc liber pentru functii IntegratableFunction definite de utilizator care sa fie apelate de metoda Riemann.

Urmatorul pas este sa avem distributia cumulata Gauss.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GaussCummulativeFunction : public RiemannSumCalculus          
  { 
    private: double sigma;  
    public:                                 
    virtual double IntegratableFunction(double x)
       { 
        return( exp((-x*x)/(2*sigma*sigma)) );
       }
    double CummulativeDistribution(double stddev,double l1,double l2,uint divisions)
       {
        sigma=stddev;
        return( (1/(stddev*sqrt(2*M_PI))) * Riemann(l1,l2,divisions) );
       }
  };

Dupa cum se poate vedea, clasa este mostenita din RiemannSumCalculus. Functia IntegratableFunction este redefinita, si functia CummulativeDistribuition din interior apeleaza metoda Riemann mostenita, care, la randul ei, apeleaza functia locala IntegratableFunction in loc de cea mostenita care intoarce numai zerouri. Si pentru ca aceasta este o clasa, putem crea un mod mai facil de a accesa aceasta functie, apeland metoda dintr-o functie:

1
2
3
4
5
double cum_norm(double x)
  {
   GaussCummulativeFunction normaldistribution;      
   return(normaldistribution.CummulativeDistribution(1,-7.0,x,1000000));   
  }

Poate ca 1.000.000 de diviziuni e cam mult, dar e necesar pentru a pastra corectitudinea rezultatelor.

Ceea ce urmeaza este versiunea MQL5 a procedurii Black-Scholes scrisa de George Levy in cartea sa , “Computational finance using C and C++”, pagina 76 (cu mici modificari):

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
struct Greeks
  {
   double delta;
   double gamma;
   double vega;
   double theta;
   double rho;
  };  
 
void black_scholes(double& value, Greeks &greeks, double s0, double x, double sigma, double t, double r, double q, bool iscall, long &iflag)
    {
     /* 
     Input parameters:
     =================
     s0 - the current price of the underlying asset
     x - the strike price
     sigma - the volatility
     t - the time to maturity
     r - the interest rate
     q - the continuous dividend yield
     iscall - true if option is a call
 
     Output parameters:
     ==================
     value - the value of the option
     greeks[] - the hedge statistics output;
     iflag - an error indicator
     */
 
     greeks.delta=0.0;
     greeks.gamma=0.0;
     greeks.vega=0.0;
     greeks.theta=0.0;
     greeks.rho=0.0;
     double one=1.0,two=2.0,zero=0.0;
     double eps,d1,d2,temp,temp1,temp2,np;
     /* Check if any of the the input arguments are too small */
     eps=1.0e-16;
     if ( (x < eps) || (sigma < eps) || (t < eps) ) 
       { 
        iflag = 2;
        return;
       }
     temp = log(s0/x);
     d1 = temp+(r-q+(sigma*sigma/two))*t;
     d1 = d1/(sigma*sqrt(t));
     d2 = d1-sigma*sqrt(t);
     /* evaluate the option price */
     if (iscall==true)
       value = (s0*exp(-q*t)*cum_norm(d1)- x*exp(-r*t)*cum_norm(d2));
     else
       value = (-s0*exp(-q*t)*cum_norm(-d1) + x*exp(-r*t)*cum_norm(-d2));
     /* then calculate the Greeks */
     temp1 = -d1*d1/two;
     d2 = d1-sigma*sqrt(t);
     np = (one/sqrt(two*M_PI)) * exp(temp1);
     if (iscall==true) 
       { 
        /* a call option */
        greeks.delta = (cum_norm(d1))*exp(-q*t); 
        greeks.theta = -s0*exp(-q*t)*np*sigma/(two*sqrt(t)) + q*s0*cum_norm(d1)*exp(-q*t)- r*x*exp(-r*t)*cum_norm(d2); 
        greeks.rho = x*t*exp(-r*t)*cum_norm(d2);
       }
     else 
       { 
        /* a put option */
        greeks.delta = (cum_norm(d1) - one)*exp(-q*t);
        greeks.theta = -s0*exp(-q*t)*np*sigma/(two*sqrt(t)) - q*s0*cum_norm(-d1)*exp(-q*t) + r*x*exp(-r*t)*cum_norm(-d2); 
        greeks.rho = -x*t*exp(-r*t)*cum_norm(-d2);
       }
     greeks.gamma = np*exp(-q*t)/(s0*sigma*sqrt(t)); 
     greeks.vega = s0*sqrt(t)*np*exp(-q*t);
    return;
    }

Si urmatoarea, din aceeasi carte, pentru a obtine volatilitatea implicita:

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
void implied_volatility(double value, double s0, double x, double& sigma[],double t, double r, double q, bool iscall, long& iflag)
   {
    /* Input parameters:
    =================
    value - the current value of the option
    s0 - the current price of the underlying asset
    x - the strike price
    sigma[] - the input bounds on the volatility: sigma[0], the lower bound and, sigma[1],the upper bound
    t - the time to maturity
    r - the interest rate
    q - the continuous dividend yield
    iscall - true if option is a call;
 
    Output parameters:
    ==================
    sigma[] - the element sigma[0] contains the estimated implied volatility
    iflag - an error indicator
    */
    double zero=0.0;
    double fx, sig1, sig2;
    double val,tolx;
    double temp,eps,epsqrt,temp1,v1;
    long max_iters, i, ind, ir;
    double c[20];
    double sig,vega;
    Greeks greeks;
    long done;
    tolx = eps;
    eps=1.0e-16;
    epsqrt = sqrt(eps);
    if (iscall == true) /* a call option */
      temp1 = MathMax(s0*exp(-q*t)-x*exp(-r*t),zero);
    else /* a put option */
      temp1 = MathMax(x*exp(-r*t)-s0*exp(-q*t),zero);
    v1 = fabs(value-temp1);
    if (v1 <= epsqrt) 
      { /* the volatility is too small */
       iflag = 3;
       return;
      }
   iflag = 0;
   i = 0;
   max_iters = 50;
   done = 0;
   sig = sigma[0]; /* initial estimate */
   val = value;
   while ((i < max_iters) && (!done)) 
        { /* Newton iteration */
         black_scholes(val,greeks,s0,x,sig,t,r,q,iscall,iflag); /* compute the Black-Scholes option value, val */
         vega = greeks.vega; /* and vega. */
         sig1 = sig - ((val - value)/vega); /* compute the new estimate of sigma using Newton’s method */
         if (tolx > fabs((sig1 - sig)/sig1)) 
           { /* check whether the specified accuracy has been reached */
             done = 1;
           }
         sig = sig1; /* up date sigma */
         ++i;
        }
   sigma[0] = sig1; /* return the estimate for sigma */
   return;
  }

Acum, pastrand acestea ca pe inceputul unui script, vom completa scriptul pentru a testa modelul Black-Scholes.

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
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   double StockPrice=100.00;
   double SelectedStrike=100.00;
   double r=0.1;
   double sigma=0.3;
   double q=0.06;
   ObjectsDeleteAll(ChartID(),-1,-1);
   int s0;
   Greeks greeks;
   double value;
   double t;
   int T;   
   long iflag;
 
 
   for (T=10;T>=1;T--)
      {
       t=0.1*T;
       black_scholes(value, greeks, StockPrice, SelectedStrike, sigma, t, r, q, true, iflag);       
       Print(DoubleToString(t,1)," :: ",DoubleToString(value,3)," ",DoubleToString(greeks.delta,3)," ",DoubleToString(greeks.gamma,3)," ",DoubleToString(greeks.vega,3)," ",DoubleToString(greeks.theta,3)," ",DoubleToString(greeks.rho,3));
      }
   Print("Time    Value     Delta    Gamma    Vega    Theta      Rho");
   Print("Calls");
   Print("");
   for (T=10;T>=1;T--)
      {
       t=0.1*T;
       black_scholes(value, greeks, StockPrice, SelectedStrike, sigma, t, r, q, false, iflag);       
       Print(DoubleToString(t,1)," :: ",DoubleToString(value,3)," ",DoubleToString(greeks.delta,3)," ",DoubleToString(greeks.gamma,3)," ",DoubleToString(greeks.vega,3)," ",DoubleToString(greeks.theta,3)," ",DoubleToString(greeks.rho,3));
      }      
   Print("Time    Value     Delta    Gamma    Vega    Theta      Rho");   
   Print("Puts");  
   Print("");
   Print("Continuous dividend yield: ",q*100,"%");  
   Print("Implied volatility: ",sigma*100,"%");
   Print("Riskless rate: ",r*100,"%");
   Print("Selected strike: ",SelectedStrike);
   Print("Stock price: ",StockPrice);
   Print("ASSUMPTIONS: ");         
  }
//+------------------------------------------------------------------+

Si iesirea va arata in felul urmator:

Iesire la testul Black-Scholes

Acum sterge liniile adaugate mai inainte care au completat scriptul, si adauga-le pe urmatoarele, pentru a testa functia volatilitatii implicite:

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
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
  double S = 10.0;
  double X = 10.5;
  double r = 0.1;
  double sigmat = 0.5;
  double q = 0.04;  
  Greeks greeks;
  double sigma[2];
  double value, T;
  long i, ifail, put;
  double origsigma=sigmat;
  long flag;
  ifail = 0;
  for (i = 5;i >= 1; i--) 
    {
     T = (double)i*0.5;
     black_scholes(value,greeks,S,X,sigmat,T,r,q,true,flag);
     sigma[0] = 0.05;
     sigma[1] = 1.0;
     implied_volatility(value,S,X,sigma,T,r,q,true,flag);
     printf("%8.4f %15.4f    %15.4f     (%8.4e) ",T,value,sigma[0],fabs(sigmat-sigma[0]));  
     sigmat = sigmat - 0.1;
    }
  printf (" Time(years)  Option value  True sigma   Error");
  Print("");
  Print("Underlying= ",DoubleToString(S,2));
  Print("Strike= ",DoubleToString(X,2));
  Print("Continuous dividend yield= ",DoubleToString(q*100,2),"%");
  Print("Implied volatility= ",DoubleToString(origsigma*100,2),"%");
  Print("Riskless rate= ",DoubleToString(r*100,2),"%");
  Print("x= ",DoubleToString(X,2));
  Print("S= ",DoubleToString(S,2));
  Print("ASSUMPTIONS");  
  return;
 }

Scriptul pune niste valori in modelul Black-Scholes, obtine valoarea optiunilor, seteaza un iunterval de estimare si apeleaza functia volatilitatii implicite, care da rezultate destul de corecte:

Iesire la testul volatilitatii implicite

Tags: , ,

Leave a Reply