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[] = 77 "This example demonstrates use of the TAO package to\n\ 78 solve a linear complementarity problem for pricing American put options.\n\ 79 The code uses backward differences in time and central differences in\n\ 80 space. The command line options are:\n\ 81 -rate <r>, where <r> = interest rate\n\ 82 -sigma <s>, where <s> = volatility of the underlying\n\ 83 -alpha <a>, where <a> = elasticity of the underlying\n\ 84 -delta <d>, where <d> = dividend rate\n\ 85 -strike <e>, where <e> = strike price\n\ 86 -expiry <t>, where <t> = the expiration date\n\ 87 -mt <tg>, where <tg> = number of grid points in time\n\ 88 -ms <sg>, where <sg> = number of grid points in space\n\ 89 -es <se>, where <se> = ending point of the space discretization\n\n"; 90 91 /* 92 User-defined application context - contains data needed by the 93 application-provided call-back routines, FormFunction(), and FormJacobian(). 94 */ 95 96 typedef struct { 97 PetscReal *Vt1; /* Value of the option at time T + dt */ 98 PetscReal *c; /* Constant -- (r - D)S */ 99 PetscReal *d; /* Constant -- -0.5(sigma**2)(S**alpha) */ 100 101 PetscReal rate; /* Interest rate */ 102 PetscReal sigma, alpha, delta; /* Underlying asset properties */ 103 PetscReal strike, expiry; /* Option contract properties */ 104 105 PetscReal es; /* Finite value used for maximum asset value */ 106 PetscReal ds, dt; /* Discretization properties */ 107 PetscInt ms, mt; /* Number of elements */ 108 109 DM dm; 110 } AppCtx; 111 112 /* -------- User-defined Routines --------- */ 113 114 PetscErrorCode FormConstraints(Tao, Vec, Vec, void *); 115 PetscErrorCode FormJacobian(Tao, Vec, Mat, Mat, void *); 116 PetscErrorCode ComputeVariableBounds(Tao, Vec, Vec, void*); 117 118 int main(int argc, char **argv) 119 { 120 Vec x; /* solution vector */ 121 Vec c; /* Constraints function vector */ 122 Mat J; /* jacobian matrix */ 123 PetscBool flg; /* A return variable when checking for user options */ 124 Tao tao; /* Tao solver context */ 125 AppCtx user; /* user-defined work context */ 126 PetscInt i, j; 127 PetscInt xs,xm,gxs,gxm; 128 PetscReal sval = 0; 129 PetscReal *x_array; 130 Vec localX; 131 132 /* Initialize PETSc, TAO */ 133 PetscCall(PetscInitialize(&argc, &argv, (char *)0, help)); 134 135 /* 136 Initialize the user-defined application context with reasonable 137 values for the American option to price 138 */ 139 user.rate = 0.04; 140 user.sigma = 0.40; 141 user.alpha = 2.00; 142 user.delta = 0.01; 143 user.strike = 10.0; 144 user.expiry = 1.0; 145 user.mt = 10; 146 user.ms = 150; 147 user.es = 100.0; 148 149 /* Read in alternative values for the American option to price */ 150 PetscCall(PetscOptionsGetReal(NULL,NULL, "-alpha", &user.alpha, &flg)); 151 PetscCall(PetscOptionsGetReal(NULL,NULL, "-delta", &user.delta, &flg)); 152 PetscCall(PetscOptionsGetReal(NULL,NULL, "-es", &user.es, &flg)); 153 PetscCall(PetscOptionsGetReal(NULL,NULL, "-expiry", &user.expiry, &flg)); 154 PetscCall(PetscOptionsGetInt(NULL,NULL, "-ms", &user.ms, &flg)); 155 PetscCall(PetscOptionsGetInt(NULL,NULL, "-mt", &user.mt, &flg)); 156 PetscCall(PetscOptionsGetReal(NULL,NULL, "-rate", &user.rate, &flg)); 157 PetscCall(PetscOptionsGetReal(NULL,NULL, "-sigma", &user.sigma, &flg)); 158 PetscCall(PetscOptionsGetReal(NULL,NULL, "-strike", &user.strike, &flg)); 159 160 /* Check that the options set are allowable (needs to be done) */ 161 162 user.ms++; 163 PetscCall(DMDACreate1d(PETSC_COMM_WORLD,DM_BOUNDARY_NONE,user.ms,1,1,NULL,&user.dm)); 164 PetscCall(DMSetFromOptions(user.dm)); 165 PetscCall(DMSetUp(user.dm)); 166 /* Create appropriate vectors and matrices */ 167 168 PetscCall(DMDAGetCorners(user.dm,&xs,NULL,NULL,&xm,NULL,NULL)); 169 PetscCall(DMDAGetGhostCorners(user.dm,&gxs,NULL,NULL,&gxm,NULL,NULL)); 170 171 PetscCall(DMCreateGlobalVector(user.dm,&x)); 172 /* 173 Finish filling in the user-defined context with the values for 174 dS, dt, and allocating space for the constants 175 */ 176 user.ds = user.es / (user.ms-1); 177 user.dt = user.expiry / user.mt; 178 179 PetscCall(PetscMalloc1(gxm,&(user.Vt1))); 180 PetscCall(PetscMalloc1(gxm,&(user.c))); 181 PetscCall(PetscMalloc1(gxm,&(user.d))); 182 183 /* 184 Calculate the values for the constant. Vt1 begins with the ending 185 boundary condition. 186 */ 187 for (i=0; i<gxm; i++) { 188 sval = (gxs+i)*user.ds; 189 user.Vt1[i] = PetscMax(user.strike - sval, 0); 190 user.c[i] = (user.delta - user.rate)*sval; 191 user.d[i] = -0.5*user.sigma*user.sigma*PetscPowReal(sval, user.alpha); 192 } 193 if (gxs+gxm==user.ms) { 194 user.Vt1[gxm-1] = 0; 195 } 196 PetscCall(VecDuplicate(x, &c)); 197 198 /* 199 Allocate the matrix used by TAO for the Jacobian. Each row of 200 the Jacobian matrix will have at most three elements. 201 */ 202 PetscCall(DMCreateMatrix(user.dm,&J)); 203 204 /* The TAO code begins here */ 205 206 /* Create TAO solver and set desired solution method */ 207 PetscCall(TaoCreate(PETSC_COMM_WORLD, &tao)); 208 PetscCall(TaoSetType(tao,TAOSSILS)); 209 210 /* Set routines for constraints function and Jacobian evaluation */ 211 PetscCall(TaoSetConstraintsRoutine(tao, c, FormConstraints, (void *)&user)); 212 PetscCall(TaoSetJacobianRoutine(tao, J, J, FormJacobian, (void *)&user)); 213 214 /* Set the variable bounds */ 215 PetscCall(TaoSetVariableBoundsRoutine(tao,ComputeVariableBounds,(void*)&user)); 216 217 /* Set initial solution guess */ 218 PetscCall(VecGetArray(x,&x_array)); 219 for (i=0; i< xm; i++) 220 x_array[i] = user.Vt1[i-gxs+xs]; 221 PetscCall(VecRestoreArray(x,&x_array)); 222 /* Set data structure */ 223 PetscCall(TaoSetSolution(tao, x)); 224 225 /* Set routines for function and Jacobian evaluation */ 226 PetscCall(TaoSetFromOptions(tao)); 227 228 /* Iteratively solve the linear complementarity problems */ 229 for (i = 1; i < user.mt; i++) { 230 231 /* Solve the current version */ 232 PetscCall(TaoSolve(tao)); 233 234 /* Update Vt1 with the solution */ 235 PetscCall(DMGetLocalVector(user.dm,&localX)); 236 PetscCall(DMGlobalToLocalBegin(user.dm,x,INSERT_VALUES,localX)); 237 PetscCall(DMGlobalToLocalEnd(user.dm,x,INSERT_VALUES,localX)); 238 PetscCall(VecGetArray(localX,&x_array)); 239 for (j = 0; j < gxm; j++) { 240 user.Vt1[j] = x_array[j]; 241 } 242 PetscCall(VecRestoreArray(x,&x_array)); 243 PetscCall(DMRestoreLocalVector(user.dm,&localX)); 244 } 245 246 /* Free TAO data structures */ 247 PetscCall(TaoDestroy(&tao)); 248 249 /* Free PETSc data structures */ 250 PetscCall(VecDestroy(&x)); 251 PetscCall(VecDestroy(&c)); 252 PetscCall(MatDestroy(&J)); 253 PetscCall(DMDestroy(&user.dm)); 254 /* Free user-defined workspace */ 255 PetscCall(PetscFree(user.Vt1)); 256 PetscCall(PetscFree(user.c)); 257 PetscCall(PetscFree(user.d)); 258 259 PetscCall(PetscFinalize()); 260 return 0; 261 } 262 263 /* -------------------------------------------------------------------- */ 264 PetscErrorCode ComputeVariableBounds(Tao tao, Vec xl, Vec xu, void*ctx) 265 { 266 AppCtx *user = (AppCtx *) ctx; 267 PetscInt i; 268 PetscInt xs,xm; 269 PetscInt ms = user->ms; 270 PetscReal sval=0.0,*xl_array,ub= PETSC_INFINITY; 271 272 /* Set the variable bounds */ 273 PetscCall(VecSet(xu, ub)); 274 PetscCall(DMDAGetCorners(user->dm,&xs,NULL,NULL,&xm,NULL,NULL)); 275 276 PetscCall(VecGetArray(xl,&xl_array)); 277 for (i = 0; i < xm; i++) { 278 sval = (xs+i)*user->ds; 279 xl_array[i] = PetscMax(user->strike - sval, 0); 280 } 281 PetscCall(VecRestoreArray(xl,&xl_array)); 282 283 if (xs==0) { 284 PetscCall(VecGetArray(xu,&xl_array)); 285 xl_array[0] = PetscMax(user->strike, 0); 286 PetscCall(VecRestoreArray(xu,&xl_array)); 287 } 288 if (xs+xm==ms) { 289 PetscCall(VecGetArray(xu,&xl_array)); 290 xl_array[xm-1] = 0; 291 PetscCall(VecRestoreArray(xu,&xl_array)); 292 } 293 294 return 0; 295 } 296 /* -------------------------------------------------------------------- */ 297 298 /* 299 FormFunction - Evaluates gradient of f. 300 301 Input Parameters: 302 . tao - the Tao context 303 . X - input vector 304 . ptr - optional user-defined context, as set by TaoAppSetConstraintRoutine() 305 306 Output Parameters: 307 . F - vector containing the newly evaluated gradient 308 */ 309 PetscErrorCode FormConstraints(Tao tao, Vec X, Vec F, void *ptr) 310 { 311 AppCtx *user = (AppCtx *) ptr; 312 PetscReal *x, *f; 313 PetscReal *Vt1 = user->Vt1, *c = user->c, *d = user->d; 314 PetscReal rate = user->rate; 315 PetscReal dt = user->dt, ds = user->ds; 316 PetscInt ms = user->ms; 317 PetscInt i, xs,xm,gxs,gxm; 318 Vec localX,localF; 319 PetscReal zero=0.0; 320 321 PetscCall(DMGetLocalVector(user->dm,&localX)); 322 PetscCall(DMGetLocalVector(user->dm,&localF)); 323 PetscCall(DMGlobalToLocalBegin(user->dm,X,INSERT_VALUES,localX)); 324 PetscCall(DMGlobalToLocalEnd(user->dm,X,INSERT_VALUES,localX)); 325 PetscCall(DMDAGetCorners(user->dm,&xs,NULL,NULL,&xm,NULL,NULL)); 326 PetscCall(DMDAGetGhostCorners(user->dm,&gxs,NULL,NULL,&gxm,NULL,NULL)); 327 PetscCall(VecSet(F, zero)); 328 /* 329 The problem size is smaller than the discretization because of the 330 two fixed elements (V(0,T) = E and V(Send,T) = 0. 331 */ 332 333 /* Get pointers to the vector data */ 334 PetscCall(VecGetArray(localX, &x)); 335 PetscCall(VecGetArray(localF, &f)); 336 337 /* Left Boundary */ 338 if (gxs==0) { 339 f[0] = x[0]-user->strike; 340 } else { 341 f[0] = 0; 342 } 343 344 /* All points in the interior */ 345 /* for (i=gxs+1;i<gxm-1;i++) { */ 346 for (i=1;i<gxm-1;i++) { 347 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]) + 348 (d[i]/(2*ds*ds))*(x[i+1] -2*x[i] + x[i-1] + Vt1[i+1] - 2*Vt1[i] + Vt1[i-1]); 349 } 350 351 /* Right boundary */ 352 if (gxs+gxm==ms) { 353 f[xm-1]=x[gxm-1]; 354 } else { 355 f[xm-1]=0; 356 } 357 358 /* Restore vectors */ 359 PetscCall(VecRestoreArray(localX, &x)); 360 PetscCall(VecRestoreArray(localF, &f)); 361 PetscCall(DMLocalToGlobalBegin(user->dm,localF,INSERT_VALUES,F)); 362 PetscCall(DMLocalToGlobalEnd(user->dm,localF,INSERT_VALUES,F)); 363 PetscCall(DMRestoreLocalVector(user->dm,&localX)); 364 PetscCall(DMRestoreLocalVector(user->dm,&localF)); 365 PetscCall(PetscLogFlops(24.0*(gxm-2))); 366 /* 367 info=VecView(F,PETSC_VIEWER_STDOUT_WORLD); 368 */ 369 return 0; 370 } 371 372 /* ------------------------------------------------------------------- */ 373 /* 374 FormJacobian - Evaluates Jacobian matrix. 375 376 Input Parameters: 377 . tao - the Tao context 378 . X - input vector 379 . ptr - optional user-defined context, as set by TaoSetJacobian() 380 381 Output Parameters: 382 . J - Jacobian matrix 383 */ 384 PetscErrorCode FormJacobian(Tao tao, Vec X, Mat J, Mat tJPre, void *ptr) 385 { 386 AppCtx *user = (AppCtx *) ptr; 387 PetscReal *c = user->c, *d = user->d; 388 PetscReal rate = user->rate; 389 PetscReal dt = user->dt, ds = user->ds; 390 PetscInt ms = user->ms; 391 PetscReal val[3]; 392 PetscInt col[3]; 393 PetscInt i; 394 PetscInt gxs,gxm; 395 PetscBool assembled; 396 397 /* Set various matrix options */ 398 PetscCall(MatSetOption(J,MAT_IGNORE_OFF_PROC_ENTRIES,PETSC_TRUE)); 399 PetscCall(MatAssembled(J,&assembled)); 400 if (assembled) PetscCall(MatZeroEntries(J)); 401 402 PetscCall(DMDAGetGhostCorners(user->dm,&gxs,NULL,NULL,&gxm,NULL,NULL)); 403 404 if (gxs==0) { 405 i = 0; 406 col[0] = 0; 407 val[0]=1.0; 408 PetscCall(MatSetValues(J,1,&i,1,col,val,INSERT_VALUES)); 409 } 410 for (i=1; i < gxm-1; i++) { 411 col[0] = gxs + i - 1; 412 col[1] = gxs + i; 413 col[2] = gxs + i + 1; 414 val[0] = -c[i]/(4*ds) + d[i]/(2*ds*ds); 415 val[1] = 1.0/dt + rate - d[i]/(ds*ds); 416 val[2] = c[i]/(4*ds) + d[i]/(2*ds*ds); 417 PetscCall(MatSetValues(J,1,&col[1],3,col,val,INSERT_VALUES)); 418 } 419 if (gxs+gxm==ms) { 420 i = ms-1; 421 col[0] = i; 422 val[0]=1.0; 423 PetscCall(MatSetValues(J,1,&i,1,col,val,INSERT_VALUES)); 424 } 425 426 /* Assemble the Jacobian matrix */ 427 PetscCall(MatAssemblyBegin(J,MAT_FINAL_ASSEMBLY)); 428 PetscCall(MatAssemblyEnd(J,MAT_FINAL_ASSEMBLY)); 429 PetscCall(PetscLogFlops(18.0*(gxm)+5)); 430 return 0; 431 } 432 433 /*TEST 434 435 build: 436 requires: !complex 437 438 test: 439 args: -tao_monitor -tao_type ssils -tao_gttol 1.e-5 440 requires: !single 441 442 test: 443 suffix: 2 444 args: -tao_monitor -tao_type ssfls -tao_max_it 10 -tao_gttol 1.e-5 445 requires: !single 446 447 test: 448 suffix: 3 449 args: -tao_monitor -tao_type asils -tao_subset_type subvec -tao_gttol 1.e-5 450 requires: !single 451 452 test: 453 suffix: 4 454 args: -tao_monitor -tao_type asils -tao_subset_type mask -tao_gttol 1.e-5 455 requires: !single 456 457 test: 458 suffix: 5 459 args: -tao_monitor -tao_type asils -tao_subset_type matrixfree -pc_type jacobi -tao_max_it 6 -tao_gttol 1.e-5 460 requires: !single 461 462 test: 463 suffix: 6 464 args: -tao_monitor -tao_type asfls -tao_subset_type subvec -tao_max_it 10 -tao_gttol 1.e-5 465 requires: !single 466 467 test: 468 suffix: 7 469 args: -tao_monitor -tao_type asfls -tao_subset_type mask -tao_max_it 10 -tao_gttol 1.e-5 470 requires: !single 471 472 TEST*/ 473