xref: /petsc/src/tao/complementarity/tutorials/blackscholes.c (revision 4dfa11a44d5adf2389f1d3acbc8f3c1116dc6c3a)
1 /**********************************************************************
2     American Put Options Pricing using the Black-Scholes Equation
3 
4    Background (European Options):
5      The standard European option is a contract where the holder has the right
6      to either buy (call option) or sell (put option) an underlying asset at
7      a designated future time and price.
8 
9      The classic Black-Scholes model begins with an assumption that the
10      price of the underlying asset behaves as a lognormal random walk.
11      Using this assumption and a no-arbitrage argument, the following
12      linear parabolic partial differential equation for the value of the
13      option results:
14 
15        dV/dt + 0.5(sigma**2)(S**alpha)(d2V/dS2) + (r - D)S(dV/dS) - rV = 0.
16 
17      Here, sigma is the volatility of the underling asset, alpha is a
18      measure of elasticity (typically two), D measures the dividend payments
19      on the underling asset, and r is the interest rate.
20 
21      To completely specify the problem, we need to impose some boundary
22      conditions.  These are as follows:
23 
24        V(S, T) = max(E - S, 0)
25        V(0, t) = E for all 0 <= t <= T
26        V(s, t) = 0 for all 0 <= t <= T and s->infinity
27 
28      where T is the exercise time time and E the strike price (price paid
29      for the contract).
30 
31      An explicit formula for the value of an European option can be
32      found.  See the references for examples.
33 
34    Background (American Options):
35      The American option is similar to its European counterpart.  The
36      difference is that the holder of the American option can exercise
37      their right to buy or sell the asset at any time prior to the
38      expiration.  This additional ability introduce a free boundary into
39      the Black-Scholes equation which can be modeled as a linear
40      complementarity problem.
41 
42        0 <= -(dV/dt + 0.5(sigma**2)(S**alpha)(d2V/dS2) + (r - D)S(dV/dS) - rV)
43          complements
44        V(S,T) >= max(E-S,0)
45 
46      where the variables are the same as before and we have the same boundary
47      conditions.
48 
49      There is not explicit formula for calculating the value of an American
50      option.  Therefore, we discretize the above problem and solve the
51      resulting linear complementarity problem.
52 
53      We will use backward differences for the time variables and central
54      differences for the space variables.  Crank-Nicholson averaging will
55      also be used in the discretization.  The algorithm used by the code
56      solves for V(S,t) for a fixed t and then uses this value in the
57      calculation of V(S,t - dt).  The method stops when V(S,0) has been
58      found.
59 
60    References:
61 + * - Huang and Pang, "Options Pricing and Linear Complementarity,"
62        Journal of Computational Finance, volume 2, number 3, 1998.
63 - * - Wilmott, "Derivatives: The Theory and Practice of Financial Engineering,"
64        John Wiley and Sons, New York, 1998.
65 ***************************************************************************/
66 
67 /*
68   Include "petsctao.h" so we can use TAO solvers.
69   Include "petscdmda.h" so that we can use distributed meshes (DMs) for managing
70   the parallel mesh.
71 */
72 
73 #include <petscdmda.h>
74 #include <petsctao.h>
75 
76 static char help[] = "This example demonstrates use of the TAO package to\n\
77 solve a linear complementarity problem for pricing American put options.\n\
78 The code uses backward differences in time and central differences in\n\
79 space.  The command line options are:\n\
80   -rate <r>, where <r> = interest rate\n\
81   -sigma <s>, where <s> = volatility of the underlying\n\
82   -alpha <a>, where <a> = elasticity of the underlying\n\
83   -delta <d>, where <d> = dividend rate\n\
84   -strike <e>, where <e> = strike price\n\
85   -expiry <t>, where <t> = the expiration date\n\
86   -mt <tg>, where <tg> = number of grid points in time\n\
87   -ms <sg>, where <sg> = number of grid points in space\n\
88   -es <se>, where <se> = ending point of the space discretization\n\n";
89 
90 /*
91   User-defined application context - contains data needed by the
92   application-provided call-back routines, FormFunction(), and FormJacobian().
93 */
94 
95 typedef struct {
96   PetscReal *Vt1; /* Value of the option at time T + dt */
97   PetscReal *c;   /* Constant -- (r - D)S */
98   PetscReal *d;   /* Constant -- -0.5(sigma**2)(S**alpha) */
99 
100   PetscReal rate;                /* Interest rate */
101   PetscReal sigma, alpha, delta; /* Underlying asset properties */
102   PetscReal strike, expiry;      /* Option contract properties */
103 
104   PetscReal es;     /* Finite value used for maximum asset value */
105   PetscReal ds, dt; /* Discretization properties */
106   PetscInt  ms, mt; /* Number of elements */
107 
108   DM dm;
109 } AppCtx;
110 
111 /* -------- User-defined Routines --------- */
112 
113 PetscErrorCode FormConstraints(Tao, Vec, Vec, void *);
114 PetscErrorCode FormJacobian(Tao, Vec, Mat, Mat, void *);
115 PetscErrorCode ComputeVariableBounds(Tao, Vec, Vec, void *);
116 
117 int main(int argc, char **argv) {
118   Vec        x;    /* solution vector */
119   Vec        c;    /* Constraints function vector */
120   Mat        J;    /* jacobian matrix */
121   PetscBool  flg;  /* A return variable when checking for user options */
122   Tao        tao;  /* Tao solver context */
123   AppCtx     user; /* user-defined work context */
124   PetscInt   i, j;
125   PetscInt   xs, xm, gxs, gxm;
126   PetscReal  sval = 0;
127   PetscReal *x_array;
128   Vec        localX;
129 
130   /* Initialize PETSc, TAO */
131   PetscFunctionBeginUser;
132   PetscCall(PetscInitialize(&argc, &argv, (char *)0, help));
133 
134   /*
135      Initialize the user-defined application context with reasonable
136      values for the American option to price
137   */
138   user.rate   = 0.04;
139   user.sigma  = 0.40;
140   user.alpha  = 2.00;
141   user.delta  = 0.01;
142   user.strike = 10.0;
143   user.expiry = 1.0;
144   user.mt     = 10;
145   user.ms     = 150;
146   user.es     = 100.0;
147 
148   /* Read in alternative values for the American option to price */
149   PetscCall(PetscOptionsGetReal(NULL, NULL, "-alpha", &user.alpha, &flg));
150   PetscCall(PetscOptionsGetReal(NULL, NULL, "-delta", &user.delta, &flg));
151   PetscCall(PetscOptionsGetReal(NULL, NULL, "-es", &user.es, &flg));
152   PetscCall(PetscOptionsGetReal(NULL, NULL, "-expiry", &user.expiry, &flg));
153   PetscCall(PetscOptionsGetInt(NULL, NULL, "-ms", &user.ms, &flg));
154   PetscCall(PetscOptionsGetInt(NULL, NULL, "-mt", &user.mt, &flg));
155   PetscCall(PetscOptionsGetReal(NULL, NULL, "-rate", &user.rate, &flg));
156   PetscCall(PetscOptionsGetReal(NULL, NULL, "-sigma", &user.sigma, &flg));
157   PetscCall(PetscOptionsGetReal(NULL, NULL, "-strike", &user.strike, &flg));
158 
159   /* Check that the options set are allowable (needs to be done) */
160 
161   user.ms++;
162   PetscCall(DMDACreate1d(PETSC_COMM_WORLD, DM_BOUNDARY_NONE, user.ms, 1, 1, NULL, &user.dm));
163   PetscCall(DMSetFromOptions(user.dm));
164   PetscCall(DMSetUp(user.dm));
165   /* Create appropriate vectors and matrices */
166 
167   PetscCall(DMDAGetCorners(user.dm, &xs, NULL, NULL, &xm, NULL, NULL));
168   PetscCall(DMDAGetGhostCorners(user.dm, &gxs, NULL, NULL, &gxm, NULL, NULL));
169 
170   PetscCall(DMCreateGlobalVector(user.dm, &x));
171   /*
172      Finish filling in the user-defined context with the values for
173      dS, dt, and allocating space for the constants
174   */
175   user.ds = user.es / (user.ms - 1);
176   user.dt = user.expiry / user.mt;
177 
178   PetscCall(PetscMalloc1(gxm, &(user.Vt1)));
179   PetscCall(PetscMalloc1(gxm, &(user.c)));
180   PetscCall(PetscMalloc1(gxm, &(user.d)));
181 
182   /*
183      Calculate the values for the constant.  Vt1 begins with the ending
184      boundary condition.
185   */
186   for (i = 0; i < gxm; i++) {
187     sval        = (gxs + i) * user.ds;
188     user.Vt1[i] = PetscMax(user.strike - sval, 0);
189     user.c[i]   = (user.delta - user.rate) * sval;
190     user.d[i]   = -0.5 * user.sigma * user.sigma * PetscPowReal(sval, user.alpha);
191   }
192   if (gxs + gxm == user.ms) user.Vt1[gxm - 1] = 0;
193   PetscCall(VecDuplicate(x, &c));
194 
195   /*
196      Allocate the matrix used by TAO for the Jacobian.  Each row of
197      the Jacobian matrix will have at most three elements.
198   */
199   PetscCall(DMCreateMatrix(user.dm, &J));
200 
201   /* The TAO code begins here */
202 
203   /* Create TAO solver and set desired solution method  */
204   PetscCall(TaoCreate(PETSC_COMM_WORLD, &tao));
205   PetscCall(TaoSetType(tao, TAOSSILS));
206 
207   /* Set routines for constraints function and Jacobian evaluation */
208   PetscCall(TaoSetConstraintsRoutine(tao, c, FormConstraints, (void *)&user));
209   PetscCall(TaoSetJacobianRoutine(tao, J, J, FormJacobian, (void *)&user));
210 
211   /* Set the variable bounds */
212   PetscCall(TaoSetVariableBoundsRoutine(tao, ComputeVariableBounds, (void *)&user));
213 
214   /* Set initial solution guess */
215   PetscCall(VecGetArray(x, &x_array));
216   for (i = 0; i < xm; i++) x_array[i] = user.Vt1[i - gxs + xs];
217   PetscCall(VecRestoreArray(x, &x_array));
218   /* Set data structure */
219   PetscCall(TaoSetSolution(tao, x));
220 
221   /* Set routines for function and Jacobian evaluation */
222   PetscCall(TaoSetFromOptions(tao));
223 
224   /* Iteratively solve the linear complementarity problems  */
225   for (i = 1; i < user.mt; i++) {
226     /* Solve the current version */
227     PetscCall(TaoSolve(tao));
228 
229     /* Update Vt1 with the solution */
230     PetscCall(DMGetLocalVector(user.dm, &localX));
231     PetscCall(DMGlobalToLocalBegin(user.dm, x, INSERT_VALUES, localX));
232     PetscCall(DMGlobalToLocalEnd(user.dm, x, INSERT_VALUES, localX));
233     PetscCall(VecGetArray(localX, &x_array));
234     for (j = 0; j < gxm; j++) user.Vt1[j] = x_array[j];
235     PetscCall(VecRestoreArray(x, &x_array));
236     PetscCall(DMRestoreLocalVector(user.dm, &localX));
237   }
238 
239   /* Free TAO data structures */
240   PetscCall(TaoDestroy(&tao));
241 
242   /* Free PETSc data structures */
243   PetscCall(VecDestroy(&x));
244   PetscCall(VecDestroy(&c));
245   PetscCall(MatDestroy(&J));
246   PetscCall(DMDestroy(&user.dm));
247   /* Free user-defined workspace */
248   PetscCall(PetscFree(user.Vt1));
249   PetscCall(PetscFree(user.c));
250   PetscCall(PetscFree(user.d));
251 
252   PetscCall(PetscFinalize());
253   return 0;
254 }
255 
256 /* -------------------------------------------------------------------- */
257 PetscErrorCode ComputeVariableBounds(Tao tao, Vec xl, Vec xu, void *ctx) {
258   AppCtx   *user = (AppCtx *)ctx;
259   PetscInt  i;
260   PetscInt  xs, xm;
261   PetscInt  ms   = user->ms;
262   PetscReal sval = 0.0, *xl_array, ub = PETSC_INFINITY;
263 
264   /* Set the variable bounds */
265   PetscCall(VecSet(xu, ub));
266   PetscCall(DMDAGetCorners(user->dm, &xs, NULL, NULL, &xm, NULL, NULL));
267 
268   PetscCall(VecGetArray(xl, &xl_array));
269   for (i = 0; i < xm; i++) {
270     sval        = (xs + i) * user->ds;
271     xl_array[i] = PetscMax(user->strike - sval, 0);
272   }
273   PetscCall(VecRestoreArray(xl, &xl_array));
274 
275   if (xs == 0) {
276     PetscCall(VecGetArray(xu, &xl_array));
277     xl_array[0] = PetscMax(user->strike, 0);
278     PetscCall(VecRestoreArray(xu, &xl_array));
279   }
280   if (xs + xm == ms) {
281     PetscCall(VecGetArray(xu, &xl_array));
282     xl_array[xm - 1] = 0;
283     PetscCall(VecRestoreArray(xu, &xl_array));
284   }
285 
286   return 0;
287 }
288 /* -------------------------------------------------------------------- */
289 
290 /*
291     FormFunction - Evaluates gradient of f.
292 
293     Input Parameters:
294 .   tao  - the Tao context
295 .   X    - input vector
296 .   ptr  - optional user-defined context, as set by TaoAppSetConstraintRoutine()
297 
298     Output Parameters:
299 .   F - vector containing the newly evaluated gradient
300 */
301 PetscErrorCode FormConstraints(Tao tao, Vec X, Vec F, void *ptr) {
302   AppCtx    *user = (AppCtx *)ptr;
303   PetscReal *x, *f;
304   PetscReal *Vt1 = user->Vt1, *c = user->c, *d = user->d;
305   PetscReal  rate = user->rate;
306   PetscReal  dt = user->dt, ds = user->ds;
307   PetscInt   ms = user->ms;
308   PetscInt   i, xs, xm, gxs, gxm;
309   Vec        localX, localF;
310   PetscReal  zero = 0.0;
311 
312   PetscCall(DMGetLocalVector(user->dm, &localX));
313   PetscCall(DMGetLocalVector(user->dm, &localF));
314   PetscCall(DMGlobalToLocalBegin(user->dm, X, INSERT_VALUES, localX));
315   PetscCall(DMGlobalToLocalEnd(user->dm, X, INSERT_VALUES, localX));
316   PetscCall(DMDAGetCorners(user->dm, &xs, NULL, NULL, &xm, NULL, NULL));
317   PetscCall(DMDAGetGhostCorners(user->dm, &gxs, NULL, NULL, &gxm, NULL, NULL));
318   PetscCall(VecSet(F, zero));
319   /*
320      The problem size is smaller than the discretization because of the
321      two fixed elements (V(0,T) = E and V(Send,T) = 0.
322   */
323 
324   /* Get pointers to the vector data */
325   PetscCall(VecGetArray(localX, &x));
326   PetscCall(VecGetArray(localF, &f));
327 
328   /* Left Boundary */
329   if (gxs == 0) {
330     f[0] = x[0] - user->strike;
331   } else {
332     f[0] = 0;
333   }
334 
335   /* All points in the interior */
336   /*  for (i=gxs+1;i<gxm-1;i++) { */
337   for (i = 1; i < gxm - 1; i++) {
338     f[i] = (1.0 / dt + rate) * x[i] - Vt1[i] / dt + (c[i] / (4 * ds)) * (x[i + 1] - x[i - 1] + Vt1[i + 1] - Vt1[i - 1]) + (d[i] / (2 * ds * ds)) * (x[i + 1] - 2 * x[i] + x[i - 1] + Vt1[i + 1] - 2 * Vt1[i] + Vt1[i - 1]);
339   }
340 
341   /* Right boundary */
342   if (gxs + gxm == ms) {
343     f[xm - 1] = x[gxm - 1];
344   } else {
345     f[xm - 1] = 0;
346   }
347 
348   /* Restore vectors */
349   PetscCall(VecRestoreArray(localX, &x));
350   PetscCall(VecRestoreArray(localF, &f));
351   PetscCall(DMLocalToGlobalBegin(user->dm, localF, INSERT_VALUES, F));
352   PetscCall(DMLocalToGlobalEnd(user->dm, localF, INSERT_VALUES, F));
353   PetscCall(DMRestoreLocalVector(user->dm, &localX));
354   PetscCall(DMRestoreLocalVector(user->dm, &localF));
355   PetscCall(PetscLogFlops(24.0 * (gxm - 2)));
356   /*
357   info=VecView(F,PETSC_VIEWER_STDOUT_WORLD);
358   */
359   return 0;
360 }
361 
362 /* ------------------------------------------------------------------- */
363 /*
364    FormJacobian - Evaluates Jacobian matrix.
365 
366    Input Parameters:
367 .  tao  - the Tao context
368 .  X    - input vector
369 .  ptr  - optional user-defined context, as set by TaoSetJacobian()
370 
371    Output Parameters:
372 .  J    - Jacobian matrix
373 */
374 PetscErrorCode FormJacobian(Tao tao, Vec X, Mat J, Mat tJPre, void *ptr) {
375   AppCtx    *user = (AppCtx *)ptr;
376   PetscReal *c = user->c, *d = user->d;
377   PetscReal  rate = user->rate;
378   PetscReal  dt = user->dt, ds = user->ds;
379   PetscInt   ms = user->ms;
380   PetscReal  val[3];
381   PetscInt   col[3];
382   PetscInt   i;
383   PetscInt   gxs, gxm;
384   PetscBool  assembled;
385 
386   /* Set various matrix options */
387   PetscCall(MatSetOption(J, MAT_IGNORE_OFF_PROC_ENTRIES, PETSC_TRUE));
388   PetscCall(MatAssembled(J, &assembled));
389   if (assembled) PetscCall(MatZeroEntries(J));
390 
391   PetscCall(DMDAGetGhostCorners(user->dm, &gxs, NULL, NULL, &gxm, NULL, NULL));
392 
393   if (gxs == 0) {
394     i      = 0;
395     col[0] = 0;
396     val[0] = 1.0;
397     PetscCall(MatSetValues(J, 1, &i, 1, col, val, INSERT_VALUES));
398   }
399   for (i = 1; i < gxm - 1; i++) {
400     col[0] = gxs + i - 1;
401     col[1] = gxs + i;
402     col[2] = gxs + i + 1;
403     val[0] = -c[i] / (4 * ds) + d[i] / (2 * ds * ds);
404     val[1] = 1.0 / dt + rate - d[i] / (ds * ds);
405     val[2] = c[i] / (4 * ds) + d[i] / (2 * ds * ds);
406     PetscCall(MatSetValues(J, 1, &col[1], 3, col, val, INSERT_VALUES));
407   }
408   if (gxs + gxm == ms) {
409     i      = ms - 1;
410     col[0] = i;
411     val[0] = 1.0;
412     PetscCall(MatSetValues(J, 1, &i, 1, col, val, INSERT_VALUES));
413   }
414 
415   /* Assemble the Jacobian matrix */
416   PetscCall(MatAssemblyBegin(J, MAT_FINAL_ASSEMBLY));
417   PetscCall(MatAssemblyEnd(J, MAT_FINAL_ASSEMBLY));
418   PetscCall(PetscLogFlops(18.0 * (gxm) + 5));
419   return 0;
420 }
421 
422 /*TEST
423 
424    build:
425       requires: !complex
426 
427    test:
428       args: -tao_monitor -tao_type ssils -tao_gttol 1.e-5
429       requires: !single
430 
431    test:
432       suffix: 2
433       args: -tao_monitor -tao_type ssfls -tao_max_it 10 -tao_gttol 1.e-5
434       requires: !single
435 
436    test:
437       suffix: 3
438       args: -tao_monitor -tao_type asils -tao_subset_type subvec -tao_gttol 1.e-5
439       requires: !single
440 
441    test:
442       suffix: 4
443       args: -tao_monitor -tao_type asils -tao_subset_type mask -tao_gttol 1.e-5
444       requires: !single
445 
446    test:
447       suffix: 5
448       args: -tao_monitor -tao_type asils -tao_subset_type matrixfree -pc_type jacobi -tao_max_it 6 -tao_gttol 1.e-5
449       requires: !single
450 
451    test:
452       suffix: 6
453       args: -tao_monitor -tao_type asfls -tao_subset_type subvec -tao_max_it 10 -tao_gttol 1.e-5
454       requires: !single
455 
456    test:
457       suffix: 7
458       args: -tao_monitor -tao_type asfls -tao_subset_type mask -tao_max_it 10 -tao_gttol 1.e-5
459       requires: !single
460 
461 TEST*/
462