Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/scaddins/source/pricing/black_scholes.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * Copyright (C) 2012 Tino Kluge <tino.kluge@hrz.tu-chemnitz.de>
10
 *
11
 */
12
13
#include <cstdio>
14
#include <cstdlib>
15
#include <cmath>
16
#include <cassert>
17
#include <algorithm>
18
#include <rtl/math.hxx>
19
#include "black_scholes.hxx"
20
21
// options prices and greeks in the Black-Scholes model
22
// also known as TV (theoretical value)
23
24
// the code is structured as follows:
25
26
// (1) basic assets
27
//   - cash-or-nothing option:  bincash()
28
//   - asset-or-nothing option: binasset()
29
30
// (2) derived basic assets, can all be priced based on (1)
31
//   - vanilla put/call:  putcall() = +/- ( binasset() - K*bincash() )
32
//   - truncated put/call (barriers active at maturity only)
33
34
// (3) write a wrapper function to include all vanilla prices
35
//   - this is so we don't duplicate code when pricing barriers
36
//     as this is derived from vanillas
37
38
// (4) single barrier options (knock-out), priced based on truncated vanillas
39
//   - it follows from the reflection principle that the price W(S) of a
40
//     single barrier option is given by
41
//        W(S) = V(S) - (B/S)^a V(B^2/S), a = 2(rd-rf)/vol^2 - 1
42
//     where V(S) is the price of the corresponding truncated vanilla
43
//     option
44
//   - to reduce code duplication and in anticipation of double barrier
45
//     options we write the following function
46
//        barrier_term(S,c) = V(c*S) - (B/S)^a V(c*B^2/S)
47
48
//  (5) double barrier options (knock-out)
49
//   - value is an infinite sum over option prices of the corresponding
50
//     truncated vanillas (truncated at both barriers):
51
52
//   W(S)=sum (B2/B1)^(i*a) (V(S(B2/B1)^(2i)) - (B1/S)^a V(B1^2/S (B2/B1)^(2i))
53
54
//  (6) write routines for put/call barriers and touch options which
55
//     mainly call the general double barrier pricer
56
//     the main routines are touch() and barrier()
57
//     both can price in/out barriers, double/single barriers as well as
58
//     vanillas
59
60
61
// the framework allows any barriers to be priced as long as we define
62
// the value/greek functions for the corresponding truncated vanilla
63
// and wrap them into internal::vanilla() and internal::vanilla_trunc()
64
65
// disadvantage of that approach is that due to the rules of
66
// differentiations the formulas for greeks become long and possible
67
// simplifications in the formulas won't be made
68
69
// other code inefficiency due to multiplication with pm (+/- 1)
70
//   cvtsi2sd: int-->double, 6/3 cycles
71
//   mulsd: double-double multiplication, 5/1 cycles
72
//   with -O3, however, it compiles 2 versions with pm=1, and pm=-1
73
//   which are efficient
74
//   note this is tiny anyway as compared to exp/log (100 cycles),
75
//   pow (200 cycles), erf (70 cycles)
76
77
// this code is not tested for numerical instability, ie overruns,
78
// underruns, accuracy, etc
79
80
81
namespace sca::pricing::bs {
82
83
84
// helper functions
85
86
0
static double sqr(double x) {
87
0
    return x*x;
88
0
}
89
// normal density (see also ScInterpreter::phi)
90
0
static double dnorm(double x) {
91
    //return (1.0/sqrt(2.0*M_PI))*exp(-0.5*x*x); // windows may not have M_PI
92
0
    return 0.39894228040143268*exp(-0.5*x*x);
93
0
}
94
// cumulative normal distribution (see also ScInterpreter::integralPhi)
95
0
static double pnorm(double x) {
96
0
    return 0.5 * std::erfc(-x * M_SQRT1_2);
97
0
}
98
99
// binary option cash (domestic)
100
//   call - pays 1 if S_T is above strike K
101
//   put  - pays 1 if S_T is below strike K
102
double bincash(double S, double vol, double rd, double rf,
103
               double tau, double K,
104
0
               types::PutCall pc, types::Greeks greeks) {
105
0
    assert(tau>=0.0);
106
0
    assert(S>0.0);
107
0
    assert(vol>0.0);
108
0
    assert(K>=0.0);
109
110
0
    double   val=0.0;
111
112
0
    if(tau<=0.0) {
113
        // special case tau=0 (expiry)
114
0
        switch(greeks) {
115
0
        case types::Value:
116
0
            if( (pc==types::Call && S>=K) || (pc==types::Put && S<=K) ) {
117
0
                val = 1.0;
118
0
            } else {
119
0
                val = 0.0;
120
0
            }
121
0
            break;
122
0
        default:
123
0
            val = 0.0;
124
0
        }
125
0
    } else if(K==0.0) {
126
        // special case with zero strike
127
0
        if(pc==types::Put) {
128
            // up-and-out (put) with K=0
129
0
            val=0.0;
130
0
        } else {
131
            // down-and-out (call) with K=0 (zero coupon bond)
132
0
            switch(greeks) {
133
0
            case types::Value:
134
0
                val = 1.0;
135
0
                break;
136
0
            case types::Theta:
137
0
                val = rd;
138
0
                break;
139
0
            case types::Rho_d:
140
0
                val = -tau;
141
0
                break;
142
0
            default:
143
0
                val = 0.0;
144
0
            }
145
0
        }
146
0
    } else {
147
        // standard case with K>0, tau>0
148
0
        double   d1 = ( log(S/K)+(rd-rf+0.5*vol*vol)*tau ) / (vol*sqrt(tau));
149
0
        double   d2 = d1 - vol*sqrt(tau);
150
0
        int      pm    = (pc==types::Call) ? 1 : -1;
151
152
0
        switch(greeks) {
153
0
        case types::Value:
154
0
            val = pnorm(pm*d2);
155
0
            break;
156
0
        case types::Delta:
157
0
            val = pm*dnorm(d2)/(S*vol*sqrt(tau));
158
0
            break;
159
0
        case types::Gamma:
160
0
            val = -pm*dnorm(d2)*d1/(sqr(S*vol)*tau);
161
0
            break;
162
0
        case types::Theta:
163
0
            val = rd*pnorm(pm*d2)
164
0
                  + pm*dnorm(d2)*(log(S/K)/(vol*sqrt(tau))-0.5*d2)/tau;
165
0
            break;
166
0
        case types::Vega:
167
0
            val = -pm*dnorm(d2)*d1/vol;
168
0
            break;
169
0
        case types::Volga:
170
0
            val = pm*dnorm(d2)/(vol*vol)*(-d1*d1*d2+d1+d2);
171
0
            break;
172
0
        case types::Vanna:
173
0
            val = pm*dnorm(d2)/(S*vol*vol*sqrt(tau))*(d1*d2-1.0);
174
0
            break;
175
0
        case types::Rho_d:
176
0
            val = -tau*pnorm(pm*d2) + pm*dnorm(d2)*sqrt(tau)/vol;
177
0
            break;
178
0
        case types::Rho_f:
179
0
            val = -pm*dnorm(d2)*sqrt(tau)/vol;
180
0
            break;
181
0
        default:
182
0
            printf("bincash: greek %d not implemented\n", greeks );
183
0
            abort();
184
0
        }
185
0
    }
186
0
    return exp(-rd*tau)*val;
187
0
}
188
189
// binary option asset (foreign)
190
//   call - pays S_T if S_T is above strike K
191
//   put  - pays S_T if S_T is below strike K
192
double binasset(double S, double vol, double rd, double rf,
193
                double tau, double K,
194
0
                types::PutCall pc, types::Greeks greeks) {
195
0
    assert(tau>=0.0);
196
0
    assert(S>0.0);
197
0
    assert(vol>0.0);
198
0
    assert(K>=0.0);
199
200
0
    double   val=0.0;
201
0
    if(tau<=0.0) {
202
        // special case tau=0 (expiry)
203
0
        switch(greeks) {
204
0
        case types::Value:
205
0
            if( (pc==types::Call && S>=K) || (pc==types::Put && S<=K) ) {
206
0
                val = S;
207
0
            } else {
208
0
                val = 0.0;
209
0
            }
210
0
            break;
211
0
        case types::Delta:
212
0
            if( (pc==types::Call && S>=K) || (pc==types::Put && S<=K) ) {
213
0
                val = 1.0;
214
0
            } else {
215
0
                val = 0.0;
216
0
            }
217
0
            break;
218
0
        default:
219
0
            val = 0.0;
220
0
        }
221
0
    } else if(K==0.0) {
222
        // special case with zero strike (forward with zero strike)
223
0
        if(pc==types::Put) {
224
            // up-and-out (put) with K=0
225
0
            val = 0.0;
226
0
        } else {
227
            // down-and-out (call) with K=0 (type of forward)
228
0
            switch(greeks) {
229
0
            case types::Value:
230
0
                val = S;
231
0
                break;
232
0
            case types::Delta:
233
0
                val = 1.0;
234
0
                break;
235
0
            case types::Theta:
236
0
                val = rf*S;
237
0
                break;
238
0
            case types::Rho_f:
239
0
                val = -tau*S;
240
0
                break;
241
0
            default:
242
0
                val = 0.0;
243
0
            }
244
0
        }
245
0
    } else {
246
        // normal case
247
0
        double   d1 = ( log(S/K)+(rd-rf+0.5*vol*vol)*tau ) / (vol*sqrt(tau));
248
0
        double   d2 = d1 - vol*sqrt(tau);
249
0
        int      pm    = (pc==types::Call) ? 1 : -1;
250
251
0
        switch(greeks) {
252
0
        case types::Value:
253
0
            val = S*pnorm(pm*d1);
254
0
            break;
255
0
        case types::Delta:
256
0
            val = pnorm(pm*d1) + pm*dnorm(d1)/(vol*sqrt(tau));
257
0
            break;
258
0
        case types::Gamma:
259
0
            val = -pm*dnorm(d1)*d2/(S*sqr(vol)*tau);
260
0
            break;
261
0
        case types::Theta:
262
0
            val = rf*S*pnorm(pm*d1)
263
0
                  + pm*S*dnorm(d1)*(log(S/K)/(vol*sqrt(tau))-0.5*d1)/tau;
264
0
            break;
265
0
        case types::Vega:
266
0
            val = -pm*S*dnorm(d1)*d2/vol;
267
0
            break;
268
0
        case types::Volga:
269
0
            val = pm*S*dnorm(d1)/(vol*vol)*(-d1*d2*d2+d1+d2);
270
0
            break;
271
0
        case types::Vanna:
272
0
            val = pm*dnorm(d1)/(vol*vol*sqrt(tau))*(d2*d2-1.0);
273
0
            break;
274
0
        case types::Rho_d:
275
0
            val = pm*S*dnorm(d1)*sqrt(tau)/vol;
276
0
            break;
277
0
        case types::Rho_f:
278
0
            val = -tau*S*pnorm(pm*d1) - pm*S*dnorm(d1)*sqrt(tau)/vol;
279
0
            break;
280
0
        default:
281
0
            printf("binasset: greek %d not implemented\n", greeks );
282
0
            abort();
283
0
        }
284
0
    }
285
0
    return exp(-rf*tau)*val;
286
0
}
287
288
// just for convenience we can combine bincash and binasset into
289
// one function binary
290
// using bincash()  if fd==types::Domestic
291
// using binasset() if fd==types::Foreign
292
static double binary(double S, double vol, double rd, double rf,
293
              double tau, double K,
294
              types::PutCall pc, types::ForDom fd,
295
0
              types::Greeks greek) {
296
0
    double val=0.0;
297
0
    switch(fd) {
298
0
    case types::Domestic:
299
0
        val = bincash(S,vol,rd,rf,tau,K,pc,greek);
300
0
        break;
301
0
    case types::Foreign:
302
0
        val = binasset(S,vol,rd,rf,tau,K,pc,greek);
303
0
        break;
304
0
    default:
305
        // never get here
306
0
        assert(false);
307
0
    }
308
0
    return val;
309
0
}
310
311
// further wrapper to combine single/double barrier binary options
312
// into one function
313
// B1<=0 - it is assumed lower barrier not set
314
// B2<=0 - it is assumed upper barrier not set
315
static double binary(double S, double vol, double rd, double rf,
316
              double tau, double B1, double B2,
317
0
              types::ForDom fd, types::Greeks greek) {
318
0
    assert(tau>=0.0);
319
0
    assert(S>0.0);
320
0
    assert(vol>0.0);
321
322
0
    double val=0.0;
323
324
0
    if(B1<=0.0 && B2<=0.0) {
325
        // no barriers set, payoff 1.0 (domestic) or S_T (foreign)
326
0
        val = binary(S,vol,rd,rf,tau,0.0,types::Call,fd,greek);
327
0
    } else if(B1<=0.0 && B2>0.0) {
328
        // upper barrier (put)
329
0
        val = binary(S,vol,rd,rf,tau,B2,types::Put,fd,greek);
330
0
    } else if(B1>0.0 && B2<=0.0) {
331
        // lower barrier (call)
332
0
        val = binary(S,vol,rd,rf,tau,B1,types::Call,fd,greek);
333
0
    } else if(B1>0.0 && B2>0.0) {
334
        // double barrier
335
0
        if(B2<=B1) {
336
0
            val = 0.0;
337
0
        } else {
338
0
            val = binary(S,vol,rd,rf,tau,B2,types::Put,fd,greek)
339
0
                  - binary(S,vol,rd,rf,tau,B1,types::Put,fd,greek);
340
0
        }
341
0
    } else {
342
        // never get here
343
0
        assert(false);
344
0
    }
345
346
0
    return val;
347
0
}
348
349
// vanilla put/call option
350
//   call pays (S_T-K)^+
351
//   put  pays (K-S_T)^+
352
// this is the same as: +/- (binasset - K*bincash)
353
double putcall(double S, double vol, double rd, double rf,
354
               double tau, double K,
355
0
               types::PutCall putcall, types::Greeks greeks) {
356
357
0
    assert(tau>=0.0);
358
0
    assert(S>0.0);
359
0
    assert(vol>0.0);
360
0
    assert(K>=0.0);
361
362
0
    double   val = 0.0;
363
0
    int      pm  = (putcall==types::Call) ? 1 : -1;
364
365
0
    if(K==0 || tau==0.0) {
366
        // special cases, simply refer to binasset() and bincash()
367
0
        val = pm * ( binasset(S,vol,rd,rf,tau,K,putcall,greeks)
368
0
                     - K*bincash(S,vol,rd,rf,tau,K,putcall,greeks) );
369
0
    } else {
370
        // general case
371
        // we could just use pm*(binasset-K*bincash), however
372
        // since the formula for delta and gamma simplify we write them
373
        // down here
374
0
        double   d1 = ( log(S/K)+(rd-rf+0.5*vol*vol)*tau ) / (vol*sqrt(tau));
375
0
        double   d2 = d1 - vol*sqrt(tau);
376
377
0
        switch(greeks) {
378
0
        case types::Value:
379
0
            val = pm * ( exp(-rf*tau)*S*pnorm(pm*d1)-exp(-rd*tau)*K*pnorm(pm*d2) );
380
0
            break;
381
0
        case types::Delta:
382
0
            val = pm*exp(-rf*tau)*pnorm(pm*d1);
383
0
            break;
384
0
        case types::Gamma:
385
0
            val = exp(-rf*tau)*dnorm(d1)/(S*vol*sqrt(tau));
386
0
            break;
387
0
        default:
388
            // too lazy for the other greeks, so simply refer to binasset/bincash
389
0
            val = pm * ( binasset(S,vol,rd,rf,tau,K,putcall,greeks)
390
0
                         - K*bincash(S,vol,rd,rf,tau,K,putcall,greeks) );
391
0
        }
392
0
    }
393
0
    return val;
394
0
}
395
396
// truncated put/call option, single barrier
397
// need to specify whether it's down-and-out or up-and-out
398
// regular (keeps monotonicity): down-and-out for call, up-and-out for put
399
// reverse (destroys monoton):   up-and-out for call, down-and-out for put
400
//   call pays (S_T-K)^+
401
//   put  pays (K-S_T)^+
402
double putcalltrunc(double S, double vol, double rd, double rf,
403
                    double tau, double K, double B,
404
                    types::PutCall pc, types::KOType kotype,
405
0
                    types::Greeks greeks) {
406
407
0
    assert(tau>=0.0);
408
0
    assert(S>0.0);
409
0
    assert(vol>0.0);
410
0
    assert(K>=0.0);
411
0
    assert(B>=0.0);
412
413
0
    int      pm  = (pc==types::Call) ? 1 : -1;
414
0
    double   val = 0.0;
415
416
0
    switch(kotype) {
417
0
    case types::Regular:
418
0
        if( (pc==types::Call && B<=K) || (pc==types::Put && B>=K) ) {
419
            // option degenerates to standard plain vanilla call/put
420
0
            val = putcall(S,vol,rd,rf,tau,K,pc,greeks);
421
0
        } else {
422
            // normal case with truncation
423
0
            val = pm * ( binasset(S,vol,rd,rf,tau,B,pc,greeks)
424
0
                         - K*bincash(S,vol,rd,rf,tau,B,pc,greeks) );
425
0
        }
426
0
        break;
427
0
    case types::Reverse:
428
0
        if( (pc==types::Call && B<=K) || (pc==types::Put && B>=K) ) {
429
            // option degenerates to zero payoff
430
0
            val = 0.0;
431
0
        } else {
432
            // normal case with truncation
433
0
            val = binasset(S,vol,rd,rf,tau,K,types::Call,greeks)
434
0
                  - binasset(S,vol,rd,rf,tau,B,types::Call,greeks)
435
0
                  - K * ( bincash(S,vol,rd,rf,tau,K,types::Call,greeks)
436
0
                          - bincash(S,vol,rd,rf,tau,B,types::Call,greeks) );
437
0
        }
438
0
        break;
439
0
    default:
440
0
        assert(false);
441
0
    }
442
0
    return val;
443
0
}
444
445
// wrapper function for put/call option which combines
446
// double/single/no truncation barrier
447
// B1<=0 - assume no lower barrier
448
// B2<=0 - assume no upper barrier
449
double putcalltrunc(double S, double vol, double rd, double rf,
450
                    double tau, double K, double B1, double B2,
451
0
                    types::PutCall pc, types::Greeks greek) {
452
453
0
    assert(tau>=0.0);
454
0
    assert(S>0.0);
455
0
    assert(vol>0.0);
456
0
    assert(K>=0.0);
457
458
0
    double val=0.0;
459
460
0
    if(B1<=0.0 && B2<=0.0) {
461
        // no barriers set, plain vanilla
462
0
        val = putcall(S,vol,rd,rf,tau,K,pc,greek);
463
0
    } else if(B1<=0.0 && B2>0.0) {
464
        // upper barrier: reverse barrier for call, regular barrier for put
465
0
        if(pc==types::Call) {
466
0
            val = putcalltrunc(S,vol,rd,rf,tau,K,B2,pc,types::Reverse,greek);
467
0
        } else {
468
0
            val = putcalltrunc(S,vol,rd,rf,tau,K,B2,pc,types::Regular,greek);
469
0
        }
470
0
    } else if(B1>0.0 && B2<=0.0) {
471
        // lower barrier: regular barrier for call, reverse barrier for put
472
0
        if(pc==types::Call) {
473
0
            val = putcalltrunc(S,vol,rd,rf,tau,K,B1,pc,types::Regular,greek);
474
0
        } else {
475
0
            val = putcalltrunc(S,vol,rd,rf,tau,K,B1,pc,types::Reverse,greek);
476
0
        }
477
0
    } else if(B1>0.0 && B2>0.0) {
478
        // double barrier
479
0
        if(B2<=B1) {
480
0
            val = 0.0;
481
0
        } else {
482
0
            int   pm  = (pc==types::Call) ? 1 : -1;
483
0
            val = pm * (
484
0
                      putcalltrunc(S,vol,rd,rf,tau,K,B1,pc,types::Regular,greek)
485
0
                      - putcalltrunc(S,vol,rd,rf,tau,K,B2,pc,types::Regular,greek)
486
0
                  );
487
0
        }
488
0
    } else {
489
        // never get here
490
0
        assert(false);
491
0
    }
492
0
    return val;
493
0
}
494
495
namespace internal {
496
497
// wrapper function for all non-path dependent options
498
// this is only an internal function, used to avoid code duplication when
499
// going to path-dependent barrier options,
500
// K<0  - assume binary option
501
// K>=0 - assume put/call option
502
static double vanilla(double S, double vol, double rd, double rf,
503
               double tau, double K, double B1, double B2,
504
               types::PutCall pc, types::ForDom fd,
505
0
               types::Greeks greek) {
506
0
    double val = 0.0;
507
0
    if(K<0.0) {
508
        // binary option if K<0
509
0
        val = binary(S,vol,rd,rf,tau,B1,B2,fd,greek);
510
0
    } else {
511
0
        val = putcall(S,vol,rd,rf,tau,K,pc,greek);
512
0
    }
513
0
    return val;
514
0
}
515
static double vanilla_trunc(double S, double vol, double rd, double rf,
516
                     double tau, double K, double B1, double B2,
517
                     types::PutCall pc, types::ForDom fd,
518
0
                     types::Greeks greek) {
519
0
    double val = 0.0;
520
0
    if(K<0.0) {
521
        // binary option if K<0
522
        // truncated is actually the same as the vanilla binary
523
0
        val = binary(S,vol,rd,rf,tau,B1,B2,fd,greek);
524
0
    } else {
525
0
        val = putcalltrunc(S,vol,rd,rf,tau,K,B1,B2,pc,greek);
526
0
    }
527
0
    return val;
528
0
}
529
530
} // namespace internal
531
532
// path dependent options
533
534
535
namespace internal {
536
537
// helper term for any type of options with continuously monitored barriers,
538
// internal, should not be called from outside
539
// calculates value and greeks based on
540
//   V(S) = V1(sc*S) - (B/S)^a V1(sc*B^2/S)
541
//   (a=2 mu/vol^2, mu drift in logspace, ie. mu=(rd-rf-1/2vol^2))
542
// with sc=1 and V1() being the price of the respective truncated
543
// vanilla option, V() would be the price of the respective barrier
544
// option if only one barrier is present
545
static double barrier_term(double S, double vol, double rd, double rf,
546
                    double tau, double K, double B1, double B2, double sc,
547
                    types::PutCall pc, types::ForDom fd,
548
0
                    types::Greeks greek) {
549
550
0
    assert(tau>=0.0);
551
0
    assert(S>0.0);
552
0
    assert(vol>0.0);
553
554
    // V(S) = V1(sc*S) - (B/S)^a V1(sc*B^2/S)
555
0
    double   val = 0.0;
556
0
    double   B   = (B1>0.0) ? B1 : B2;
557
0
    double   a   = 2.0*(rd-rf)/(vol*vol)-1.0;    // helper variable
558
0
    double   b   = 4.0*(rd-rf)/(vol*vol*vol);    // helper variable -da/dvol
559
0
    double   c   = 12.0*(rd-rf)/(vol*vol*vol*vol); // helper -db/dvol
560
0
    switch(greek) {
561
0
    case types::Value:
562
0
    case types::Theta:
563
0
        val = vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
564
0
              - pow(B/S,a)*
565
0
              vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
566
0
        break;
567
0
    case types::Delta:
568
0
        val = sc*vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
569
0
              + pow(B/S,a) * (
570
0
                  a/S*
571
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Value)
572
0
                  + sqr(B/S)*sc*
573
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
574
0
              );
575
0
        break;
576
0
    case types::Gamma:
577
0
        val = sc*sc*vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
578
0
              - pow(B/S,a) * (
579
0
                  a*(a+1.0)/(S*S)*
580
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Value)
581
0
                  + (2.0*a+2.0)*B*B/(S*S*S)*sc*
582
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Delta)
583
0
                  + sqr(sqr(B/S))*sc*sc*
584
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Gamma)
585
0
              );
586
0
        break;
587
0
    case types::Vega:
588
0
        val = vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
589
0
              - pow(B/S,a) * (
590
0
                  - b*log(B/S)*
591
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Value)
592
0
                  + 1.0*
593
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
594
0
              );
595
0
        break;
596
0
    case types::Volga:
597
0
        val = vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
598
0
              - pow(B/S,a) * (
599
0
                  log(B/S)*(b*b*log(B/S)+c)*
600
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Value)
601
0
                  - 2.0*b*log(B/S)*
602
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Vega)
603
0
                  + 1.0*
604
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Volga)
605
0
              );
606
0
        break;
607
0
    case types::Vanna:
608
0
        val = sc*vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
609
0
              - pow(B/S,a) * (
610
0
                  b/S*(log(B/S)*a+1.0)*
611
0
                  vanilla_trunc(B*B/S*sc,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Value)
612
0
                  + b*log(B/S)*sqr(B/S)*sc*
613
0
                  vanilla_trunc(B*B/S*sc,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Delta)
614
0
                  - a/S*
615
0
                  vanilla_trunc(B*B/S*sc,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Vega)
616
0
                  - sqr(B/S)*sc*
617
0
                  vanilla_trunc(B*B/S*sc,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Vanna)
618
0
              );
619
0
        break;
620
0
    case types::Rho_d:
621
0
        val = vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
622
0
              - pow(B/S,a) * (
623
0
                  2.0*log(B/S)/(vol*vol)*
624
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Value)
625
0
                  + 1.0*
626
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
627
0
              );
628
0
        break;
629
0
    case types::Rho_f:
630
0
        val = vanilla_trunc(sc*S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
631
0
              - pow(B/S,a) * (
632
0
                  - 2.0*log(B/S)/(vol*vol)*
633
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,types::Value)
634
0
                  + 1.0*
635
0
                  vanilla_trunc(sc*B*B/S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
636
0
              );
637
0
        break;
638
0
    default:
639
0
        printf("barrier_term: greek %d not implemented\n", greek );
640
0
        abort();
641
0
    }
642
0
    return val;
643
0
}
644
645
// one term of the infinite sum for the valuation of double barriers
646
static double barrier_double_term( double S, double vol, double rd, double rf,
647
                            double tau, double K, double B1, double B2,
648
                            double fac, double sc, int i,
649
0
                            types::PutCall pc, types::ForDom fd, types::Greeks greek) {
650
651
0
    double val = 0.0;
652
0
    double   b   = 4.0*i*(rd-rf)/(vol*vol*vol);    // helper variable -da/dvol
653
0
    double   c   = 12.0*i*(rd-rf)/(vol*vol*vol*vol); // helper -db/dvol
654
0
    switch(greek) {
655
0
    case types::Value:
656
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek);
657
0
        break;
658
0
    case types::Delta:
659
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek);
660
0
        break;
661
0
    case types::Gamma:
662
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek);
663
0
        break;
664
0
    case types::Theta:
665
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek);
666
0
        break;
667
0
    case types::Vega:
668
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek)
669
0
              - b*log(B2/B1)*fac *
670
0
              barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,types::Value);
671
0
        break;
672
0
    case types::Volga:
673
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek)
674
0
              - 2.0*b*log(B2/B1)*fac *
675
0
              barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,types::Vega)
676
0
              + log(B2/B1)*fac*(c+b*b*log(B2/B1)) *
677
0
              barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,types::Value);
678
0
        break;
679
0
    case types::Vanna:
680
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek)
681
0
              - b*log(B2/B1)*fac *
682
0
              barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,types::Delta);
683
0
        break;
684
0
    case types::Rho_d:
685
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek)
686
0
              + 2.0*i/(vol*vol)*log(B2/B1)*fac *
687
0
              barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,types::Value);
688
0
        break;
689
0
    case types::Rho_f:
690
0
        val = fac*barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,greek)
691
0
              - 2.0*i/(vol*vol)*log(B2/B1)*fac *
692
0
              barrier_term(S,vol,rd,rf,tau,K,B1,B2,sc,pc,fd,types::Value);
693
0
        break;
694
0
    default:
695
0
        printf("barrier_double_term: greek %d not implemented\n", greek );
696
0
        abort();
697
0
    }
698
0
    return val;
699
0
}
700
701
// general pricer for any type of options with continuously monitored barriers
702
// allows two, one or zero barriers, only knock-out style
703
// payoff profiles allowed based on vanilla_trunc()
704
static double barrier_ko(double S, double vol, double rd, double rf,
705
                  double tau, double K, double B1, double B2,
706
                  types::PutCall pc, types::ForDom fd,
707
0
                  types::Greeks greek) {
708
709
0
    assert(tau>=0.0);
710
0
    assert(S>0.0);
711
0
    assert(vol>0.0);
712
713
0
    double val = 0.0;
714
715
0
    if(B1<=0.0 && B2<=0.0) {
716
        // no barriers --> vanilla case
717
0
        val = vanilla(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
718
0
    } else if(B1>0.0 && B2<=0.0) {
719
        // lower barrier
720
0
        if(S<=B1) {
721
0
            val = 0.0;     // knocked out
722
0
        } else {
723
0
            val = barrier_term(S,vol,rd,rf,tau,K,B1,B2,1.0,pc,fd,greek);
724
0
        }
725
0
    } else if(B1<=0.0 && B2>0.0) {
726
        // upper barrier
727
0
        if(S>=B2) {
728
0
            val = 0.0;     // knocked out
729
0
        } else {
730
0
            val = barrier_term(S,vol,rd,rf,tau,K,B1,B2,1.0,pc,fd,greek);
731
0
        }
732
0
    } else if(B1>0.0 && B2>0.0) {
733
        // double barrier
734
0
        if(S<=B1 || S>=B2) {
735
0
            val = 0.0;     // knocked out (always true if wrong input B1>B2)
736
0
        } else {
737
            // more complex calculation as we have to evaluate an infinite
738
            // sum
739
            // to reduce very costly pow() calls we define some variables
740
0
            double a = 2.0*(rd-rf)/(vol*vol)-1.0;    // 2 (mu-1/2vol^2)/sigma^2
741
0
            double BB2=sqr(B2/B1);
742
0
            double BBa=pow(B2/B1,a);
743
0
            double BB2inv=1.0/BB2;
744
0
            double BBainv=1.0/BBa;
745
0
            double fac=1.0;
746
0
            double facinv=1.0;
747
0
            double sc=1.0;
748
0
            double scinv=1.0;
749
750
            // initial term i=0
751
0
            val=barrier_double_term(S,vol,rd,rf,tau,K,B1,B2,fac,sc,0,pc,fd,greek);
752
            // infinite loop, 10 should be plenty, normal would be 2
753
0
            for(int i=1; i<10; i++) {
754
0
                fac*=BBa;
755
0
                facinv*=BBainv;
756
0
                sc*=BB2;
757
0
                scinv*=BB2inv;
758
0
                double add =
759
0
                    barrier_double_term(S,vol,rd,rf,tau,K,B1,B2,fac,sc,i,pc,fd,greek) +
760
0
                    barrier_double_term(S,vol,rd,rf,tau,K,B1,B2,facinv,scinv,-i,pc,fd,greek);
761
0
                val += add;
762
                //printf("%i: val=%e (add=%e)\n",i,val,add);
763
0
                if(fabs(add) <= 1e-12*fabs(val)) {
764
0
                    break;
765
0
                }
766
0
            }
767
            // not knocked-out double barrier end
768
0
        }
769
        // double barrier end
770
0
    } else {
771
        // no such barrier combination exists
772
0
        assert(false);
773
0
    }
774
775
0
    return val;
776
0
}
777
778
// knock-in style barrier
779
static double barrier_ki(double S, double vol, double rd, double rf,
780
                  double tau, double K, double B1, double B2,
781
                  types::PutCall pc, types::ForDom fd,
782
0
                  types::Greeks greek) {
783
0
    return vanilla(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
784
0
           -barrier_ko(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
785
0
}
786
787
// general barrier
788
static double barrier(double S, double vol, double rd, double rf,
789
               double tau, double K, double B1, double B2,
790
               types::PutCall pc, types::ForDom fd,
791
               types::BarrierKIO kio, types::BarrierActive bcont,
792
0
               types::Greeks greek) {
793
794
0
    double val = 0.0;
795
0
    if( kio==types::KnockOut && bcont==types::Maturity ) {
796
        // truncated vanilla option
797
0
        val = vanilla_trunc(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
798
0
    } else if ( kio==types::KnockOut && bcont==types::Continuous ) {
799
        // standard knock-out barrier
800
0
        val = barrier_ko(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
801
0
    } else if ( kio==types::KnockIn && bcont==types::Maturity ) {
802
        // inverse truncated vanilla
803
0
        val = vanilla(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek)
804
0
              - vanilla_trunc(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
805
0
    } else if ( kio==types::KnockIn && bcont==types::Continuous ) {
806
        // standard knock-in barrier
807
0
        val = barrier_ki(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
808
0
    } else {
809
        // never get here
810
0
        assert(false);
811
0
    }
812
0
    return val;
813
0
}
814
815
} // namespace internal
816
817
818
// touch/no-touch options (cash/asset or nothing payoff profile)
819
double touch(double S, double vol, double rd, double rf,
820
             double tau, double B1, double B2, types::ForDom fd,
821
             types::BarrierKIO kio, types::BarrierActive bcont,
822
0
             types::Greeks greek) {
823
824
0
    double K=-1.0;                      // dummy
825
0
    types::PutCall pc = types::Call;    // dummy
826
0
    double val = 0.0;
827
0
    if( kio==types::KnockOut && bcont==types::Maturity ) {
828
        // truncated vanilla option
829
0
        val = internal::vanilla_trunc(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
830
0
    } else if ( kio==types::KnockOut && bcont==types::Continuous ) {
831
        // standard knock-out barrier
832
0
        val = internal::barrier_ko(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
833
0
    } else if ( kio==types::KnockIn && bcont==types::Maturity ) {
834
        // inverse truncated vanilla
835
0
        val = internal::vanilla(S,vol,rd,rf,tau,K,-1.0,-1.0,pc,fd,greek)
836
0
              - internal::vanilla_trunc(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
837
0
    } else if ( kio==types::KnockIn && bcont==types::Continuous ) {
838
        // standard knock-in barrier
839
0
        val = internal::vanilla(S,vol,rd,rf,tau,K,-1.0,-1.0,pc,fd,greek)
840
0
              - internal::barrier_ko(S,vol,rd,rf,tau,K,B1,B2,pc,fd,greek);
841
0
    } else {
842
        // never get here
843
0
        assert(false);
844
0
    }
845
0
    return val;
846
0
}
847
848
// barrier option  (put/call payoff profile)
849
double barrier(double S, double vol, double rd, double rf,
850
               double tau, double K, double B1, double B2,
851
               double rebate,
852
               types::PutCall pc, types::BarrierKIO kio,
853
               types::BarrierActive bcont,
854
0
               types::Greeks greek) {
855
0
    assert(tau>=0.0);
856
0
    assert(S>0.0);
857
0
    assert(vol>0.0);
858
0
    assert(K>=0.0);
859
0
    types::ForDom fd = types::Domestic;
860
0
    double val=internal::barrier(S,vol,rd,rf,tau,K,B1,B2,pc,fd,kio,bcont,greek);
861
0
    if(rebate!=0.0) {
862
        // opposite of barrier knock-in/out type
863
0
        types::BarrierKIO kio2 = (kio==types::KnockIn) ? types::KnockOut
864
0
                                 : types::KnockIn;
865
0
        val += rebate*touch(S,vol,rd,rf,tau,B1,B2,fd,kio2,bcont,greek);
866
0
    }
867
0
    return val;
868
0
}
869
870
// probability of hitting a barrier
871
// this is almost the same as the price of a touch option (domestic)
872
// as it pays one if a barrier is hit; we only have to offset the
873
// discounting and we get the probability
874
double prob_hit(double S, double vol, double mu,
875
0
                double tau, double B1, double B2) {
876
0
    double const rd=0.0;
877
0
    double rf=-mu;
878
0
    return 1.0 - touch(S,vol,rd,rf,tau,B1,B2,types::Domestic,types::KnockOut,
879
0
                       types::Continuous, types::Value);
880
0
}
881
882
// probability of being in-the-money, ie payoff is greater zero,
883
// assuming payoff(S_T) > 0 iff S_T in [B1, B2]
884
// this the same as the price of a cash or nothing option
885
// with no discounting
886
double prob_in_money(double S, double vol, double mu,
887
0
                     double tau, double B1, double B2) {
888
0
    assert(S>0.0);
889
0
    assert(vol>0.0);
890
0
    assert(tau>=0.0);
891
0
    double val = 0.0;
892
0
    if( B1<B2 || B1<=0.0 || B2<=0.0 ) {
893
0
        val = binary(S,vol,0.0,-mu,tau,B1,B2,types::Domestic,types::Value);
894
0
    }
895
0
    return val;
896
0
}
897
double prob_in_money(double S, double vol, double mu,
898
                     double tau, double K, double B1, double B2,
899
0
                     types::PutCall pc) {
900
0
    assert(S>0.0);
901
0
    assert(vol>0.0);
902
0
    assert(tau>=0.0);
903
904
    // if K<0 we assume a binary option is given
905
0
    if(K<0.0) {
906
0
        return prob_in_money(S,vol,mu,tau,B1,B2);
907
0
    }
908
909
0
    double val = 0.0;
910
0
    double BM1, BM2;     // range of in the money [BM1, BM2]
911
    // non-sense parameters with no positive payoff
912
0
    if( (B1>B2 && B1>0.0 && B2>0.0) ||
913
0
            (K>=B2 && B2>0.0 && pc==types::Call) ||
914
0
            (K<=B1 && pc==types::Put) ) {
915
0
        val = 0.0;
916
        // need to figure out between what barriers payoff is greater 0
917
0
    } else if(pc==types::Call) {
918
0
        BM1=std::max(B1, K);
919
0
        BM2=B2;
920
0
        val = prob_in_money(S,vol,mu,tau,BM1,BM2);
921
0
    } else if (pc==types::Put) {
922
0
        BM1=B1;
923
0
        BM2= (B2>0.0) ? std::min(B2,K) : K;
924
0
        val = prob_in_money(S,vol,mu,tau,BM1,BM2);
925
0
    } else {
926
        // don't get here
927
        assert(false);
928
0
    }
929
0
    return val;
930
0
}
931
932
} // namespace sca
933
934
935
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */