├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .idea └── .gitignore ├── Cargo.lock ├── Cargo.toml ├── License ├── README.md └── src ├── cmdty ├── black76.rs ├── cmdty_option.rs └── mod.rs ├── core ├── data_models.rs ├── interpolation.rs ├── mod.rs ├── pde │ └── bsm.rs ├── quotes.rs ├── termstructure.rs ├── trade.rs ├── traits.rs └── utils.rs ├── equity ├── binary_option.rs ├── binomial.rs ├── blackscholes.rs ├── build_contracts.rs ├── equity_forward.rs ├── equity_future.rs ├── finite_difference.rs ├── forward_start_option.rs ├── handle_equity_contracts.rs ├── mod.rs ├── montecarlo.rs ├── utils.rs ├── vanila_option.rs └── vol_surface.rs ├── examples ├── CO │ └── cmdty_option.json ├── EQ │ ├── binary_option.json │ ├── eq2.json │ ├── equity_forward.json │ └── equity_option.json ├── IR │ └── ir1.json └── build │ └── build_ir_curve.json ├── main.rs ├── rates ├── build_contracts.rs ├── deposits.rs ├── fra.rs ├── mod.rs └── utils.rs └── utils ├── RNG.rs ├── build_cli.rs ├── mod.rs ├── parse_cli.rs ├── parse_json.rs ├── read_csv.rs └── stochastic_processes.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | - name: Install Tarpaulin. 24 | run: cargo install cargo-tarpaulin 25 | - name: Generate code coverage report. 26 | run: 27 | cargo tarpaulin 28 | --verbose 29 | --all-features 30 | --workspace 31 | --timeout 120 32 | --out Xml 33 | --exclude-files 'src/main.rs' 34 | - name: Upload coverage reports to Codecov 35 | uses: codecov/codecov-action@v3 36 | env: 37 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 38 | 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "RustyQLib" 7 | version = "0.0.1" 8 | dependencies = [ 9 | "assert_approx_eq", 10 | "bincode", 11 | "byteorder", 12 | "chrono", 13 | "clap", 14 | "csv", 15 | "libm", 16 | "ndarray", 17 | "probability", 18 | "rand", 19 | "rand_chacha", 20 | "rand_distr", 21 | "rand_pcg", 22 | "rayon", 23 | "serde", 24 | "serde_json", 25 | "strum", 26 | "strum_macros", 27 | ] 28 | 29 | [[package]] 30 | name = "android_system_properties" 31 | version = "0.1.5" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 34 | dependencies = [ 35 | "libc", 36 | ] 37 | 38 | [[package]] 39 | name = "ansi_term" 40 | version = "0.12.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 43 | dependencies = [ 44 | "winapi", 45 | ] 46 | 47 | [[package]] 48 | name = "assert_approx_eq" 49 | version = "1.1.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd" 52 | 53 | [[package]] 54 | name = "atty" 55 | version = "0.2.14" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 58 | dependencies = [ 59 | "hermit-abi", 60 | "libc", 61 | "winapi", 62 | ] 63 | 64 | [[package]] 65 | name = "autocfg" 66 | version = "1.1.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 69 | 70 | [[package]] 71 | name = "bincode" 72 | version = "1.3.3" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 75 | dependencies = [ 76 | "serde", 77 | ] 78 | 79 | [[package]] 80 | name = "bitflags" 81 | version = "1.3.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 84 | 85 | [[package]] 86 | name = "bstr" 87 | version = "0.2.17" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 90 | dependencies = [ 91 | "lazy_static", 92 | "memchr", 93 | "regex-automata", 94 | "serde", 95 | ] 96 | 97 | [[package]] 98 | name = "bumpalo" 99 | version = "3.11.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" 102 | 103 | [[package]] 104 | name = "byteorder" 105 | version = "1.4.3" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 108 | 109 | [[package]] 110 | name = "cc" 111 | version = "1.0.73" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 114 | 115 | [[package]] 116 | name = "cfg-if" 117 | version = "1.0.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 120 | 121 | [[package]] 122 | name = "chrono" 123 | version = "0.4.22" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" 126 | dependencies = [ 127 | "iana-time-zone", 128 | "js-sys", 129 | "num-integer", 130 | "num-traits", 131 | "serde", 132 | "time", 133 | "wasm-bindgen", 134 | "winapi", 135 | ] 136 | 137 | [[package]] 138 | name = "clap" 139 | version = "2.34.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 142 | dependencies = [ 143 | "ansi_term", 144 | "atty", 145 | "bitflags", 146 | "strsim", 147 | "textwrap", 148 | "unicode-width", 149 | "vec_map", 150 | ] 151 | 152 | [[package]] 153 | name = "codespan-reporting" 154 | version = "0.11.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 157 | dependencies = [ 158 | "termcolor", 159 | "unicode-width", 160 | ] 161 | 162 | [[package]] 163 | name = "core-foundation-sys" 164 | version = "0.8.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 167 | 168 | [[package]] 169 | name = "crossbeam-deque" 170 | version = "0.8.3" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 173 | dependencies = [ 174 | "cfg-if", 175 | "crossbeam-epoch", 176 | "crossbeam-utils", 177 | ] 178 | 179 | [[package]] 180 | name = "crossbeam-epoch" 181 | version = "0.9.15" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 184 | dependencies = [ 185 | "autocfg", 186 | "cfg-if", 187 | "crossbeam-utils", 188 | "memoffset", 189 | "scopeguard", 190 | ] 191 | 192 | [[package]] 193 | name = "crossbeam-utils" 194 | version = "0.8.16" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 197 | dependencies = [ 198 | "cfg-if", 199 | ] 200 | 201 | [[package]] 202 | name = "csv" 203 | version = "1.1.6" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" 206 | dependencies = [ 207 | "bstr", 208 | "csv-core", 209 | "itoa 0.4.8", 210 | "ryu", 211 | "serde", 212 | ] 213 | 214 | [[package]] 215 | name = "csv-core" 216 | version = "0.1.10" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 219 | dependencies = [ 220 | "memchr", 221 | ] 222 | 223 | [[package]] 224 | name = "cxx" 225 | version = "1.0.79" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8" 228 | dependencies = [ 229 | "cc", 230 | "cxxbridge-flags", 231 | "cxxbridge-macro", 232 | "link-cplusplus", 233 | ] 234 | 235 | [[package]] 236 | name = "cxx-build" 237 | version = "1.0.79" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86" 240 | dependencies = [ 241 | "cc", 242 | "codespan-reporting", 243 | "once_cell", 244 | "proc-macro2", 245 | "quote", 246 | "scratch", 247 | "syn 1.0.102", 248 | ] 249 | 250 | [[package]] 251 | name = "cxxbridge-flags" 252 | version = "1.0.79" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78" 255 | 256 | [[package]] 257 | name = "cxxbridge-macro" 258 | version = "1.0.79" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" 261 | dependencies = [ 262 | "proc-macro2", 263 | "quote", 264 | "syn 1.0.102", 265 | ] 266 | 267 | [[package]] 268 | name = "either" 269 | version = "1.9.0" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 272 | 273 | [[package]] 274 | name = "getrandom" 275 | version = "0.2.7" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 278 | dependencies = [ 279 | "cfg-if", 280 | "libc", 281 | "wasi 0.11.0+wasi-snapshot-preview1", 282 | ] 283 | 284 | [[package]] 285 | name = "heck" 286 | version = "0.4.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 289 | 290 | [[package]] 291 | name = "hermit-abi" 292 | version = "0.1.19" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 295 | dependencies = [ 296 | "libc", 297 | ] 298 | 299 | [[package]] 300 | name = "iana-time-zone" 301 | version = "0.1.51" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" 304 | dependencies = [ 305 | "android_system_properties", 306 | "core-foundation-sys", 307 | "iana-time-zone-haiku", 308 | "js-sys", 309 | "wasm-bindgen", 310 | "winapi", 311 | ] 312 | 313 | [[package]] 314 | name = "iana-time-zone-haiku" 315 | version = "0.1.1" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 318 | dependencies = [ 319 | "cxx", 320 | "cxx-build", 321 | ] 322 | 323 | [[package]] 324 | name = "itoa" 325 | version = "0.4.8" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 328 | 329 | [[package]] 330 | name = "itoa" 331 | version = "1.0.9" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 334 | 335 | [[package]] 336 | name = "js-sys" 337 | version = "0.3.60" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 340 | dependencies = [ 341 | "wasm-bindgen", 342 | ] 343 | 344 | [[package]] 345 | name = "lazy_static" 346 | version = "1.4.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 349 | 350 | [[package]] 351 | name = "libc" 352 | version = "0.2.132" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 355 | 356 | [[package]] 357 | name = "libm" 358 | version = "0.2.5" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" 361 | 362 | [[package]] 363 | name = "link-cplusplus" 364 | version = "1.0.7" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" 367 | dependencies = [ 368 | "cc", 369 | ] 370 | 371 | [[package]] 372 | name = "log" 373 | version = "0.4.17" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 376 | dependencies = [ 377 | "cfg-if", 378 | ] 379 | 380 | [[package]] 381 | name = "matrixmultiply" 382 | version = "0.3.8" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" 385 | dependencies = [ 386 | "autocfg", 387 | "rawpointer", 388 | ] 389 | 390 | [[package]] 391 | name = "memchr" 392 | version = "2.5.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 395 | 396 | [[package]] 397 | name = "memoffset" 398 | version = "0.9.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 401 | dependencies = [ 402 | "autocfg", 403 | ] 404 | 405 | [[package]] 406 | name = "ndarray" 407 | version = "0.15.6" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" 410 | dependencies = [ 411 | "matrixmultiply", 412 | "num-complex", 413 | "num-integer", 414 | "num-traits", 415 | "rawpointer", 416 | ] 417 | 418 | [[package]] 419 | name = "num-complex" 420 | version = "0.4.4" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" 423 | dependencies = [ 424 | "num-traits", 425 | ] 426 | 427 | [[package]] 428 | name = "num-integer" 429 | version = "0.1.45" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 432 | dependencies = [ 433 | "autocfg", 434 | "num-traits", 435 | ] 436 | 437 | [[package]] 438 | name = "num-traits" 439 | version = "0.2.15" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 442 | dependencies = [ 443 | "autocfg", 444 | "libm", 445 | ] 446 | 447 | [[package]] 448 | name = "once_cell" 449 | version = "1.15.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" 452 | 453 | [[package]] 454 | name = "ppv-lite86" 455 | version = "0.2.16" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 458 | 459 | [[package]] 460 | name = "probability" 461 | version = "0.18.0" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "d34d1ba13c5cdf590c3d5ab5f53b36da2f596e43f8a27b236ad1801b5a1695d8" 464 | dependencies = [ 465 | "random", 466 | "special", 467 | ] 468 | 469 | [[package]] 470 | name = "proc-macro2" 471 | version = "1.0.67" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 474 | dependencies = [ 475 | "unicode-ident", 476 | ] 477 | 478 | [[package]] 479 | name = "quote" 480 | version = "1.0.33" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 483 | dependencies = [ 484 | "proc-macro2", 485 | ] 486 | 487 | [[package]] 488 | name = "rand" 489 | version = "0.8.5" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 492 | dependencies = [ 493 | "libc", 494 | "rand_chacha", 495 | "rand_core", 496 | ] 497 | 498 | [[package]] 499 | name = "rand_chacha" 500 | version = "0.3.1" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 503 | dependencies = [ 504 | "ppv-lite86", 505 | "rand_core", 506 | ] 507 | 508 | [[package]] 509 | name = "rand_core" 510 | version = "0.6.3" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 513 | dependencies = [ 514 | "getrandom", 515 | ] 516 | 517 | [[package]] 518 | name = "rand_distr" 519 | version = "0.4.3" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" 522 | dependencies = [ 523 | "num-traits", 524 | "rand", 525 | ] 526 | 527 | [[package]] 528 | name = "rand_pcg" 529 | version = "0.3.1" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" 532 | dependencies = [ 533 | "rand_core", 534 | ] 535 | 536 | [[package]] 537 | name = "random" 538 | version = "0.12.2" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "97d13a3485349981c90c79112a11222c3e6e75de1d52b87a7525b3bf5361420f" 541 | 542 | [[package]] 543 | name = "rawpointer" 544 | version = "0.2.1" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" 547 | 548 | [[package]] 549 | name = "rayon" 550 | version = "1.8.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" 553 | dependencies = [ 554 | "either", 555 | "rayon-core", 556 | ] 557 | 558 | [[package]] 559 | name = "rayon-core" 560 | version = "1.12.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" 563 | dependencies = [ 564 | "crossbeam-deque", 565 | "crossbeam-utils", 566 | ] 567 | 568 | [[package]] 569 | name = "regex-automata" 570 | version = "0.1.10" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 573 | 574 | [[package]] 575 | name = "rustversion" 576 | version = "1.0.14" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 579 | 580 | [[package]] 581 | name = "ryu" 582 | version = "1.0.11" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 585 | 586 | [[package]] 587 | name = "scopeguard" 588 | version = "1.2.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 591 | 592 | [[package]] 593 | name = "scratch" 594 | version = "1.0.2" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" 597 | 598 | [[package]] 599 | name = "serde" 600 | version = "1.0.145" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" 603 | dependencies = [ 604 | "serde_derive", 605 | ] 606 | 607 | [[package]] 608 | name = "serde_derive" 609 | version = "1.0.145" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" 612 | dependencies = [ 613 | "proc-macro2", 614 | "quote", 615 | "syn 1.0.102", 616 | ] 617 | 618 | [[package]] 619 | name = "serde_json" 620 | version = "1.0.99" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" 623 | dependencies = [ 624 | "itoa 1.0.9", 625 | "ryu", 626 | "serde", 627 | ] 628 | 629 | [[package]] 630 | name = "special" 631 | version = "0.8.1" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "24a65e074159b75dcf173a4733ab2188baac24967b5c8ec9ed87ae15fcbc7636" 634 | dependencies = [ 635 | "libc", 636 | ] 637 | 638 | [[package]] 639 | name = "strsim" 640 | version = "0.8.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 643 | 644 | [[package]] 645 | name = "strum" 646 | version = "0.25.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" 649 | 650 | [[package]] 651 | name = "strum_macros" 652 | version = "0.25.2" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" 655 | dependencies = [ 656 | "heck", 657 | "proc-macro2", 658 | "quote", 659 | "rustversion", 660 | "syn 2.0.37", 661 | ] 662 | 663 | [[package]] 664 | name = "syn" 665 | version = "1.0.102" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" 668 | dependencies = [ 669 | "proc-macro2", 670 | "quote", 671 | "unicode-ident", 672 | ] 673 | 674 | [[package]] 675 | name = "syn" 676 | version = "2.0.37" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 679 | dependencies = [ 680 | "proc-macro2", 681 | "quote", 682 | "unicode-ident", 683 | ] 684 | 685 | [[package]] 686 | name = "termcolor" 687 | version = "1.1.3" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 690 | dependencies = [ 691 | "winapi-util", 692 | ] 693 | 694 | [[package]] 695 | name = "textwrap" 696 | version = "0.11.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 699 | dependencies = [ 700 | "unicode-width", 701 | ] 702 | 703 | [[package]] 704 | name = "time" 705 | version = "0.1.44" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 708 | dependencies = [ 709 | "libc", 710 | "wasi 0.10.0+wasi-snapshot-preview1", 711 | "winapi", 712 | ] 713 | 714 | [[package]] 715 | name = "unicode-ident" 716 | version = "1.0.5" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 719 | 720 | [[package]] 721 | name = "unicode-width" 722 | version = "0.1.10" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 725 | 726 | [[package]] 727 | name = "vec_map" 728 | version = "0.8.2" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 731 | 732 | [[package]] 733 | name = "wasi" 734 | version = "0.10.0+wasi-snapshot-preview1" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 737 | 738 | [[package]] 739 | name = "wasi" 740 | version = "0.11.0+wasi-snapshot-preview1" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 743 | 744 | [[package]] 745 | name = "wasm-bindgen" 746 | version = "0.2.83" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 749 | dependencies = [ 750 | "cfg-if", 751 | "wasm-bindgen-macro", 752 | ] 753 | 754 | [[package]] 755 | name = "wasm-bindgen-backend" 756 | version = "0.2.83" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 759 | dependencies = [ 760 | "bumpalo", 761 | "log", 762 | "once_cell", 763 | "proc-macro2", 764 | "quote", 765 | "syn 1.0.102", 766 | "wasm-bindgen-shared", 767 | ] 768 | 769 | [[package]] 770 | name = "wasm-bindgen-macro" 771 | version = "0.2.83" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 774 | dependencies = [ 775 | "quote", 776 | "wasm-bindgen-macro-support", 777 | ] 778 | 779 | [[package]] 780 | name = "wasm-bindgen-macro-support" 781 | version = "0.2.83" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 784 | dependencies = [ 785 | "proc-macro2", 786 | "quote", 787 | "syn 1.0.102", 788 | "wasm-bindgen-backend", 789 | "wasm-bindgen-shared", 790 | ] 791 | 792 | [[package]] 793 | name = "wasm-bindgen-shared" 794 | version = "0.2.83" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 797 | 798 | [[package]] 799 | name = "winapi" 800 | version = "0.3.9" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 803 | dependencies = [ 804 | "winapi-i686-pc-windows-gnu", 805 | "winapi-x86_64-pc-windows-gnu", 806 | ] 807 | 808 | [[package]] 809 | name = "winapi-i686-pc-windows-gnu" 810 | version = "0.4.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 813 | 814 | [[package]] 815 | name = "winapi-util" 816 | version = "0.1.5" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 819 | dependencies = [ 820 | "winapi", 821 | ] 822 | 823 | [[package]] 824 | name = "winapi-x86_64-pc-windows-gnu" 825 | version = "0.4.0" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 828 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "RustyQLib" 3 | version = "0.0.1" 4 | edition = "2021" 5 | authors = ["Siddharth Singh"] 6 | description = "RustyQLib is a lightweight yet robust quantitative finance library designed to price derivatives and perform risk analysis" 7 | license = "MIT" 8 | repository = "https://github.com/siddharthqs/RustyQLib" 9 | readme = "README.md" 10 | homepage = "https://github.com/siddharthqs/RustyQLib" 11 | keywords = [ 12 | "quantitative-finance", 13 | "derivatives", 14 | "monte-carlo", 15 | "black-scholes", 16 | "cli" 17 | ] 18 | categories = ["command-line", "quant", "monte-carlo", "finance"] 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | rand = "0.8.4" 24 | rand_distr = "0.4.3" 25 | probability = "0.18.0" 26 | libm = "0.2.1" 27 | chrono = {version = "0.4.22",features = ["serde"]} 28 | csv = "1.1" 29 | rand_chacha = "0.3.1" 30 | rand_pcg = " 0.3.1" 31 | clap = "2.33" 32 | byteorder = "1.4.3" 33 | serde = { version = "1.0.104", features = ["derive"] } 34 | serde_json = "1" 35 | bincode = "1.3.1" 36 | strum = "0.25" 37 | strum_macros = "0.25" 38 | ndarray = "0.15" 39 | assert_approx_eq = "1.1.0" 40 | rayon = "1.5.1" 41 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Siddharth Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build and Tests](https://github.com/siddharthqs/RustyQLib/actions/workflows/rust.yml/badge.svg)](https://github.com/siddharthqs/RustyQLib/actions/workflows/rust.yml) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 3 | ![Crates.io](https://img.shields.io/crates/dr/rustyqlib) 4 | ![Crates.io](https://img.shields.io/crates/v/rustyqlib) 5 | [![codecov](https://codecov.io/gh/siddharthqs/RustyQLib/graph/badge.svg?token=879K6LTTR4)](https://codecov.io/gh/siddharthqs/RustyQLib) 6 | # RUSTYQLib :Pricing Options with Confidence using JSON 7 | RustyQLib is a lightweight yet robust quantitative finance library designed for pricing options. 8 | Built entirely in Rust, it offers a unique combination of safety, performance, and expressiveness that is crucial 9 | for handling financial data and complex calculations. RustyQlib simplifies option pricing without compromising 10 | on safety, speed, or usability. It uses JSON to make distributed computing easier and integration with other systems or your websites. 11 | ## License 12 | RustyQlib is distributed under the terms of both the MIT license and the Apache License (Version 2.0). 13 | See LICENSE-APACHE and LICENSE-MIT for details. 14 | ## Running 15 | After cloning the repository and building you can run the following command: 16 | ```bash 17 | rustyqlib file --input --output 18 | ```` 19 | and for pricing all contracts in a directory 20 | ```bash 21 | rustyqlib dir --input --output 22 | ``` 23 | and for interactive mode 24 | ```bash 25 | rustyqlib interactive 26 | ``` 27 | and for build mode to build vol surface or interest rate curve 28 | ```bash 29 | rustyqlib build --input --output 30 | ``` 31 | Sample input file is provided in the repository (src\input\equity_option.json) 32 | Files are in JSON format and can be easily edited with any text editor. 33 | ## Features 34 | 35 | ### JSON Simplicity: 36 | 37 | - Ease of Use: Providing input data in JSON format is straightforward and human-readable. 38 | - Portability: JSON is a platform-independent format, so you can use it on any operating system. 39 | - Flexibility: JSON accommodates various data types and structures, enabling you to define not only the option details but also additional market data, historical information, and risk parameters as needed. 40 | - Integration-Ready: You can seamlessly connect it to data sources, trading platforms, or other financial systems, simplifying your workflow and enhancing automation. 41 | 42 | ### Stypes: 43 | - [x] European 44 | - [x] American 45 | - [ ] Bermudan 46 | - [ ] Asian 47 | 48 | ### Instruments: 49 | #### Equity 50 | - [x] Equity Forward 51 | - [x] Equity Future 52 | - [x] Equity Option 53 | - [ ] Equity Forward Start Option 54 | - [ ] Equity Basket 55 | - [ ] Equity Barrier 56 | - [ ] Equity Lookback 57 | - [ ] Equity Asian 58 | - [ ] Equity Rainbow 59 | - [ ] Equity Chooser 60 | #### Interest Rate 61 | - [x] Deposit 62 | - [ ] FRA 63 | - [ ] Interest Rate Swap 64 | #### Commodities 65 | - [x] Commodity Option 66 | - [ ] Commodity Forward Start Option 67 | - [ ] Commodity Barrier 68 | - [ ] Commodity Lookback 69 | 70 | ### Pricing engines: 71 | - [x] Black Scholes 72 | - [x] Binomial Tree 73 | - [x] Monte Carlo 74 | - [ ] Finite Difference 75 | - [ ] Longstaff-Schwartz 76 | - [ ] Heston 77 | - [ ] Local Volatility 78 | - [ ] Stochastic Volatility 79 | - [ ] Jump Diffusion 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/cmdty/black76.rs: -------------------------------------------------------------------------------- 1 | // use libm::{exp, log}; 2 | // use std::f64::consts::{PI, SQRT_2}; 3 | // use crate::core::utils::{dN, N}; 4 | // use crate::core::trade; 5 | // 6 | // use super::cmdty_option::{CmdtyOption,Engine}; 7 | // use super::super::core::termstructure::YieldTermStructure; 8 | // use super::super::core::traits::{Instrument,Greeks}; 9 | // use super::super::core::interpolation; 10 | // 11 | // pub fn npv(bsd_option: &&CmdtyOption) -> f64 { 12 | // assert!(bsd_option.volatility >= 0.0); 13 | // assert!(bsd_option.time_to_maturity >= 0.0); 14 | // assert!(bsd_option.current_price.value >= 0.0); 15 | // if bsd_option.option_type == trade::PutOrCall::Call { 16 | // let option_price = bsd_option.current_price.value() * N(bsd_option.d1()) 17 | // - bsd_option.strike_price * N(bsd_option.d2()); 18 | // return option_price; 19 | // } else { 20 | // let option_price = -bsd_option.current_price.value() 21 | // * N(-bsd_option.d1()) 22 | // + bsd_option.strike_price * N(-bsd_option.d2()); 23 | // return option_price; 24 | // } 25 | // } 26 | // 27 | // 28 | // 29 | // impl CmdtyOption { 30 | // pub fn set_risk_free_rate(&mut self){ 31 | // let model = interpolation::CubicSpline::new(&self.term_structure.date, &self.term_structure.rates); 32 | // let r = model.interpolation(self.time_to_maturity); 33 | // self.risk_free_rate = Some(r); 34 | // } 35 | // pub fn get_premium_at_risk(&self) -> f64 { 36 | // let value = self.npv(); 37 | // let mut pay_off = 0.0; 38 | // if self.option_type == trade::OptionType::Call { 39 | // pay_off = self.current_price.value() - self.strike_price; 40 | // } else if self.option_type == trade::OptionType::Put { 41 | // pay_off = self.strike_price - self.current_price.value(); 42 | // } 43 | // if pay_off > 0.0 { 44 | // return value - pay_off; 45 | // } else { 46 | // return value; 47 | // } 48 | // } 49 | // pub fn d1(&self) -> f64 { 50 | // //Black76 d1 function Parameters 51 | // let tmp1 = (self.current_price.value() / self.strike_price).ln() 52 | // + (0.5 * self.volatility.powi(2)) 53 | // * self.time_to_maturity; 54 | // 55 | // let tmp2 = self.volatility * (self.time_to_maturity.sqrt()); 56 | // return tmp1 / tmp2; 57 | // } 58 | // pub fn d2(&self) -> f64 { 59 | // let d2 = self.d1() - self.volatility * self.time_to_maturity.powf(0.5); 60 | // return d2; 61 | // } 62 | // // pub fn imp_vol(&mut self,option_price:f64) -> f64 { 63 | // // for i in 0..100{ 64 | // // let d_sigma = (self.npv()-option_price)/self.vega(); 65 | // // self.volatility -= d_sigma 66 | // // } 67 | // // self.volatility 68 | // // } 69 | // } 70 | // impl Greeks for CmdtyOption{ 71 | // fn delta(&self) -> f64 { 72 | // let mut delta = N(self.d1()); 73 | // if self.option_type == trade::OptionType::Call { 74 | // delta = delta; 75 | // } else if self.option_type == trade::OptionType::Put { 76 | // delta = delta - 1.0; 77 | // } 78 | // return delta; 79 | // } 80 | // fn gamma(&self) -> f64 { 81 | // let gamma = dN(self.d1()); 82 | // 83 | // let var_sqrt = self.volatility * (self.time_to_maturity.sqrt()); 84 | // return gamma / (self.current_price.value() * var_sqrt); 85 | // } 86 | // fn vega(&self) -> f64 { 87 | // //St * dN(d1) * math.sqrt(T - t) 88 | // let vega = self.current_price.value() * dN(self.d1()) * self.time_to_maturity.sqrt(); 89 | // return vega; 90 | // } 91 | // fn theta(&self) -> f64 { 92 | // let mut theta = 0.0; 93 | // if self.option_type == trade::OptionType::Call { 94 | // //-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) + r * K * math.exp(-r * (T - t)) * N(d2)) 95 | // let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility 96 | // / (2.0 * self.time_to_maturity.sqrt()); 97 | // 98 | // theta = t1; 99 | // } else if self.option_type == trade::OptionType::Put { 100 | // //-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) - r * K * math.exp(-r * (T - t)) * N(d2)) 101 | // let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility 102 | // / (2.0 * self.time_to_maturity.sqrt()); 103 | // 104 | // theta = t1; 105 | // } 106 | // 107 | // return theta; 108 | // } 109 | // fn rho(&self) -> f64 { 110 | // //rho K * (T - t) * math.exp(-r * (T - t)) * N(d2) 111 | // let mut rho = 0.0; 112 | // if self.option_type == trade::OptionType::Call { 113 | // rho = self.strike_price 114 | // * self.time_to_maturity 115 | // 116 | // * N(self.d2()); 117 | // } else if self.option_type == trade::OptionType::Put { 118 | // //put_rho = -K * (T - t) * math.exp(-r * (T - t)) * N(-d2) 119 | // rho = -self.strike_price 120 | // * self.time_to_maturity 121 | // * N(-self.d2()); 122 | // } 123 | // 124 | // return rho; 125 | // } 126 | // } -------------------------------------------------------------------------------- /src/cmdty/cmdty_option.rs: -------------------------------------------------------------------------------- 1 | // use super::super::core::termstructure::YieldTermStructure; 2 | // use super::super::core::quotes::Quote; 3 | // use super::super::core::traits::{Instrument,Greeks}; 4 | // use crate::core::trade; 5 | // use crate::cmdty::black76; 6 | // 7 | // pub enum Engine{ 8 | // Black76, 9 | // MonteCarlo 10 | // } 11 | // 12 | // 13 | // pub struct CmdtyOption { 14 | // pub option_type: trade::PutOrCall, 15 | // pub transection: trade::Transection, 16 | // pub current_price: Quote, 17 | // pub strike_price: f64, 18 | // pub volatility: f64, 19 | // pub time_to_maturity: f64, 20 | // pub time_to_future_maturity: Option, 21 | // pub term_structure: YieldTermStructure, 22 | // pub risk_free_rate: Option, 23 | // pub transection_price: f64, 24 | // pub engine: Engine, 25 | // pub simulation:Option 26 | // } 27 | // 28 | // impl Instrument for CmdtyOption { 29 | // fn npv(&self) -> f64 { 30 | // match self.engine{ 31 | // Engine::Black76 => { 32 | // let value = black76::npv(&self); 33 | // value 34 | // } 35 | // _ => { 36 | // 0.0 37 | // } 38 | // } 39 | // } 40 | // } -------------------------------------------------------------------------------- /src/cmdty/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cmdty_option; 2 | pub mod black76; -------------------------------------------------------------------------------- /src/core/data_models.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Clone, Debug, Deserialize, Serialize)] 4 | #[serde(tag = "product_type", rename_all = "snake_case")] 5 | pub enum ProductData { 6 | Option(EquityOptionData), 7 | Future(EquityFutureData), 8 | Forward(EquityForwardData), 9 | } 10 | 11 | #[derive(Clone, Debug, Deserialize, Serialize)] 12 | pub struct EquityInstrumentBase { 13 | pub symbol: String, 14 | pub currency: Option, 15 | pub exchange: Option, 16 | pub name: Option, 17 | pub cusip: Option, 18 | pub isin: Option, 19 | pub underlying_price: f64, 20 | pub long_short: Option, 21 | pub risk_free_rate: Option, 22 | pub settlement_type: Option, 23 | } 24 | 25 | 26 | #[derive(Clone, Debug, Deserialize, Serialize)] 27 | pub struct EquityFutureData { 28 | #[serde(flatten)] 29 | pub base: EquityInstrumentBase, 30 | pub current_price: Option, 31 | pub multiplier:Option, 32 | pub entry_price:Option, 33 | pub maturity: String, 34 | pub dividend: Option, 35 | 36 | } 37 | #[derive(Clone, Debug, Deserialize, Serialize)] 38 | pub struct EquityForwardData { 39 | #[serde(flatten)] 40 | pub base: EquityInstrumentBase, 41 | pub current_price: Option, 42 | pub notional: Option, 43 | pub entry_price:Option, 44 | pub maturity: String, 45 | pub dividend: Option, 46 | 47 | } 48 | 49 | #[derive(Clone, Debug, Deserialize, Serialize)] 50 | pub struct EquityOptionData { 51 | #[serde(flatten)] 52 | pub base: EquityInstrumentBase, 53 | pub put_or_call: String, // "Call"/"Put" 54 | pub payoff_type: String, // Vanilla/Barrier/Binary 55 | pub strike_price: f64, 56 | pub volatility: f64, 57 | pub maturity: String, 58 | pub dividend: Option, 59 | pub current_price: Option, 60 | pub multiplier:Option, 61 | pub entry_price:Option, 62 | pub simulation: Option, 63 | pub exercise_style: Option, //European, American, 64 | pub pricer:Option 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/core/interpolation.rs: -------------------------------------------------------------------------------- 1 | //Algorithm for computing natural cubic splines 2 | // https://en.wikipedia.org/w/index.php?title=Spline_%28mathematics%29&oldid=288288033#Algorithm_for_computing_natural_cubic_splines 3 | //https://stackoverflow.com/questions/1204553/are-there-any-good-libraries-for-solving-cubic-splines-in-c 4 | 5 | pub struct CubicSpline<'a>{ 6 | pub x_vec : &'a Vec, 7 | pub y_vec : &'a Vec, 8 | pub spline_set : Vec 9 | } 10 | 11 | pub struct SplineSet{ 12 | a:f64, 13 | b:f64, 14 | c:f64, 15 | d:f64, 16 | x:f64, 17 | } 18 | impl CubicSpline<'_> { 19 | pub fn new<'a>(x: &'a Vec, y: &'a Vec) -> CubicSpline<'a> { 20 | assert!(x.len() == y.len() && x.len() >= 2 && y.len() >= 2, "Must have at least 2 control points."); 21 | let n = x.len()-1; 22 | let mut a = vec![x[0]]; 23 | let mut aa = y.clone(); 24 | a.append(&mut aa); 25 | let mut c = vec![0.0; n+1]; 26 | let mut b = vec![0.0; n]; 27 | let mut d = vec![0.0; n]; 28 | let mut dx = Vec::new(); 29 | for i in 0..n { 30 | dx.push(x[i+1]-x[i]); 31 | } 32 | 33 | let mut dy = Vec::new(); 34 | for i in 0..n { 35 | dy.push(y[i+1]-y[i]); 36 | } 37 | 38 | 39 | let mut alpha = vec![0.0]; 40 | for i in 1..n { 41 | alpha.push(3.0*(dy[i]/dx[i] - dy[i-1]/dx[i-1])); 42 | } 43 | 44 | let mut l = vec![0.0; n+1]; 45 | let mut mu = vec![0.0; n+1]; 46 | let mut z = vec![0.0; n+1]; 47 | l[0] = 1.0; 48 | for i in 1..n{ 49 | l[i] = 2.0*(x[i+1]-x[i-1]) - dx[i-1]*mu[i-1]; 50 | mu[i] = dx[i]/l[i]; 51 | z[i] = (alpha[i]-dx[i-1]*z[i-1])/l[i]; 52 | } 53 | l[n] = 1.0; 54 | z[n] = 0.0; 55 | //c[n] = 0; 56 | for j in (0..n).rev(){ 57 | c[j] = z[j] - mu[j] * c[j+1]; 58 | b[j] = (a[j+1]-a[j])/dx[j]-dx[j]*(c[j+1]+2.0*c[j])/3.0; 59 | d[j] = (c[j+1]-c[j])/(3.0*dx[j]); 60 | 61 | } 62 | let mut output = Vec::new(); 63 | for i in 0..n { 64 | let spline_set = SplineSet{ 65 | a:a[i], 66 | b:b[i], 67 | c:c[i], 68 | d:d[i], 69 | x:x[i] 70 | }; 71 | output.push(spline_set); 72 | } 73 | let spline = CubicSpline{ 74 | x_vec:x, 75 | y_vec:y, 76 | spline_set:output 77 | }; 78 | spline 79 | } 80 | pub fn interpolation(&self,x:f64) -> f64 { 81 | let n = self.x_vec.len(); 82 | for i in 0..n { 83 | if x<=self.x_vec[i] { 84 | let diff = self.x_vec[i] - x; 85 | return self.spline_set[i].a + 86 | self.spline_set[i].b * diff + 87 | self.spline_set[i].c * diff * diff + 88 | self.spline_set[i].d * diff * diff * diff; 89 | } 90 | } 91 | 0.0 92 | } 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod interpolation; 2 | pub mod termstructure; 3 | pub mod quotes; 4 | pub mod traits; 5 | pub mod trade; 6 | pub mod utils; 7 | pub mod data_models; 8 | 9 | -------------------------------------------------------------------------------- /src/core/pde/bsm.rs: -------------------------------------------------------------------------------- 1 | /// This is Black Scholes Merton PDE solver using finite difference method 2 | /// $ \frac{\partial V}{\partial t} + \frac{1}{2}\sigma^2 S^2 \frac{\partial^2 V}{\partial S^2} + rS\frac{\partial V}{\partial S} - rV = 0 $ 3 | /// $ V(S,T) = max(S-K,0) $ 4 | /// $ V(0,t) = 0 $ 5 | /// $ V(S,t) \rightarrow S $ as $ S \rightarrow \infty $ 6 | ///https://de.wikipedia.org/wiki/Thomas-Algorithmus 7 | // pub fn blackscholes_pde(spot:f64,strike:f64,rate:f64,volatility:f64,time_to_maturity:f64,steps:u64,option_type:OptionType) -> f64{ 8 | // let mut grid = Grid::new(spot,strike,rate,volatility,time_to_maturity,steps,option_type); 9 | // grid.solve(); 10 | // let value = grid.get_value(); 11 | // value 12 | // } 13 | // pub struct Grid { 14 | // spot: f64, 15 | // strike: f64, 16 | // rate: f64, 17 | // dividend: f64, 18 | // //volatility:f64, 19 | // time_to_maturity: f64, 20 | // spot_steps: u64, 21 | // time_steps: u64 22 | // } 23 | // impl Grid{ 24 | // pub fn payoff(&self,spot:f64) -> f64{ 25 | // let payoff = (spot - self.strike).max(0.0); 26 | // payoff 27 | // } 28 | // pub fn build_grid(&self) -> Vec>{ 29 | // let mut grid:Array2 = Array2::zeros((self.time_steps as usize,self.spot_steps as usize)); 30 | // //let mut grid = vec![vec![0.0;self.spot_steps as usize];self.time_steps as usize]; 31 | // for i in 0..self.spot_steps as usize{ 32 | // grid[0][i] = self.payoff(i as f64); 33 | // } 34 | // grid 35 | // } 36 | // } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/core/quotes.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Quote{ 3 | pub value: f64, 4 | pub bid: f64, 5 | pub ask: f64, 6 | pub mid: f64, 7 | } 8 | impl Quote{ 9 | pub fn new(value: f64) -> Self { 10 | Quote{value: value, bid: value, ask: value, mid: value } 11 | } 12 | pub fn value(&self) -> f64 { self.value } 13 | pub fn valid_value(&self) -> bool { self.value>0.0} 14 | } -------------------------------------------------------------------------------- /src/core/termstructure.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Local, NaiveDate}; 2 | #[derive(Debug)] 3 | pub struct YieldTermStructure { 4 | pub date: Vec, 5 | pub rates: Vec 6 | } 7 | impl YieldTermStructure { 8 | pub fn new(date: Vec, rates: Vec) -> YieldTermStructure { 9 | YieldTermStructure { 10 | date, 11 | rates 12 | } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/core/trade.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq,Debug,Clone)] 2 | pub enum Transection { 3 | Buy, 4 | Sell, 5 | } 6 | 7 | #[derive(PartialEq, Debug,Clone)] 8 | pub enum PutOrCall { 9 | Call, 10 | Put, 11 | 12 | } -------------------------------------------------------------------------------- /src/core/traits.rs: -------------------------------------------------------------------------------- 1 | use chrono::NaiveDate; 2 | use crate::rates::utils::DayCountConvention; 3 | use crate::rates::utils::TermStructure; 4 | pub trait Instrument{ 5 | fn npv(&self)-> f64; 6 | } 7 | 8 | pub trait Greeks{ 9 | fn delta(&self) -> f64; 10 | fn gamma(&self) -> f64; 11 | fn vega(&self) -> f64; 12 | fn theta(&self) -> f64; 13 | fn rho(&self) -> f64; 14 | } 15 | 16 | 17 | pub trait Rates { 18 | fn get_implied_rates(&self) -> f64; 19 | fn get_maturity_date(&self) -> NaiveDate; 20 | fn get_rate(&self) -> f64; 21 | fn get_maturity_discount_factor(&self) -> f64; 22 | fn get_day_count(&self) -> &DayCountConvention; 23 | fn set_term_structure(&mut self,term_structure:TermStructure)->(); 24 | } 25 | 26 | pub trait Observer{ 27 | fn update(&mut self); 28 | fn reset(&mut self); 29 | } 30 | pub trait Observable{ 31 | fn update(&mut self); 32 | fn reset(&mut self); 33 | } -------------------------------------------------------------------------------- /src/core/utils.rs: -------------------------------------------------------------------------------- 1 | //mod dis{ 2 | use libm::{exp, log}; 3 | use probability; 4 | use probability::distribution::Distribution; 5 | use std::f64::consts::{PI, SQRT_2}; 6 | use serde::Serialize; 7 | use crate::core::data_models::ProductData; 8 | use crate::Deserialize; 9 | 10 | #[derive(PartialEq,Clone,Debug)] 11 | pub enum ContractStyle { 12 | European, 13 | American, 14 | } 15 | 16 | #[derive(strum_macros::Display)] 17 | pub enum EngineType { 18 | Analytical, 19 | MonteCarlo, 20 | Binomial, 21 | FiniteDifference, 22 | FFT, 23 | } 24 | pub trait Engine { 25 | fn npv(&self, instrument: &I) -> f64; 26 | } 27 | 28 | impl EngineType { 29 | pub fn as_str(&self) -> &'static str { 30 | match self { 31 | EngineType::Analytical => "Analytical", 32 | EngineType::MonteCarlo => "MonteCarlo", 33 | EngineType::Binomial => "Binomial", 34 | EngineType::FiniteDifference => "FiniteDifference", 35 | EngineType::FFT => "FFT", 36 | } 37 | } 38 | } 39 | 40 | 41 | 42 | // #[derive(Clone,Debug,Deserialize,Serialize)] 43 | // pub struct MarketData { 44 | // pub underlying_price:f64, 45 | // pub option_type:Option, 46 | // pub strike_price:Option, 47 | // pub volatility:Option, 48 | // pub option_price:Option, 49 | // pub risk_free_rate:Option, 50 | // pub maturity:String, 51 | // pub dividend: Option, 52 | // pub simulation:Option, 53 | // pub current_price:Option, 54 | // pub notional: Option, 55 | // pub long_short:Option, 56 | // pub multiplier:Option, 57 | // pub entry_price:Option, 58 | // } 59 | 60 | 61 | #[derive(Clone,Debug,Deserialize,Serialize)] 62 | pub struct RateData { 63 | pub instrument: String, 64 | pub currency: String, 65 | pub start_date: String, 66 | pub maturity_date: String, 67 | pub valuation_date: String, 68 | pub notional: f64, 69 | pub fix_rate: f64, 70 | pub day_count: String, 71 | pub business_day_adjustment: i8, 72 | } 73 | 74 | #[derive(Clone,Debug,Deserialize,Serialize)] 75 | pub struct Contract { 76 | pub action: String, 77 | pub asset: String, 78 | pub product_type: ProductData, 79 | pub rate_data: Option, 80 | } 81 | #[derive(Deserialize,Serialize)] 82 | pub struct CombinedContract{ 83 | pub contract: Contract, 84 | pub output: ContractOutput 85 | } 86 | 87 | #[derive(Debug, Deserialize,Serialize)] 88 | pub struct Contracts { 89 | pub asset: String, 90 | pub contracts: Vec, 91 | } 92 | #[derive(Debug, Deserialize,Serialize)] 93 | pub struct OutputJson { 94 | pub contracts: Vec, 95 | } 96 | #[derive(Deserialize,Serialize)] 97 | pub struct ContractOutput { 98 | pub pv: f64, 99 | pub delta: f64, 100 | pub gamma: f64, 101 | pub vega: f64, 102 | pub theta: f64, 103 | pub rho: f64, 104 | pub error: Option 105 | } 106 | 107 | pub fn dN(x: f64) -> f64 { 108 | /// Probability density function of standard normal random variable x. 109 | let t = -0.5 * x * x; 110 | return t.exp() / (SQRT_2 * PI.sqrt()); 111 | } 112 | 113 | pub fn N(x: f64) -> f64 { 114 | ///Cumulative density function of standard normal random variable x. 115 | let m = probability::distribution::Gaussian::new(0.0, 1.0); 116 | let cdf = m.distribution(x); 117 | return cdf; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /src/equity/binary_option.rs: -------------------------------------------------------------------------------- 1 | // use std::fmt::Binary; 2 | // use chrono::{Datelike, Local, NaiveDate}; 3 | // use crate::equity::{binomial,finite_difference,montecarlo}; 4 | // use super::super::core::termstructure::YieldTermStructure; 5 | // use super::super::core::quotes::Quote; 6 | // use super::super::core::traits::{Instrument,Greeks}; 7 | // use super::blackscholes; 8 | // use crate::equity::utils::{Engine}; 9 | // use crate::core::trade::{OptionType,Transection}; 10 | // use crate::core::utils::{Contract,ContractStyle}; 11 | // use crate::core::trade; 12 | // impl Instrument for BinaryOption { 13 | // fn npv(&self) -> f64 { 14 | // match self.engine{ 15 | // Engine::BlackScholes => { 16 | // let value = blackscholes::npv(&self); 17 | // value 18 | // } 19 | // Engine::MonteCarlo => { 20 | // 21 | // let value = montecarlo::npv(&self,false); 22 | // value 23 | // } 24 | // Engine::Binomial => { 25 | // 26 | // let value = binomial::npv(&self); 27 | // value 28 | // } 29 | // Engine::FiniteDifference => { 30 | // let value = finite_difference::npv(&self); 31 | // value 32 | // } 33 | // 34 | // } 35 | // } 36 | // } 37 | // /// This struct represents a real world equity option contract 38 | // #[derive(Debug)] 39 | // pub struct BinaryOption { 40 | // pub option_type: OptionType, 41 | // pub payoff_type: String, 42 | // pub binary_type: String, 43 | // pub transection: Transection, 44 | // pub underlying_price: Quote, 45 | // pub current_price: Quote, 46 | // pub strike_price: f64, 47 | // pub dividend_yield: f64, 48 | // pub volatility: f64, 49 | // pub maturity_date: NaiveDate, 50 | // pub valuation_date: NaiveDate, 51 | // pub term_structure: YieldTermStructure, 52 | // pub risk_free_rate: f64, 53 | // pub transection_price: f64, 54 | // pub engine: Engine, 55 | // pub simulation:Option, 56 | // pub style: ContractStyle, 57 | // } 58 | // impl BinaryOption{ 59 | // pub fn time_to_maturity(&self) -> f64{ 60 | // let time_to_maturity = (self.maturity_date - self.valuation_date).num_days() as f64/365.0; 61 | // time_to_maturity 62 | // } 63 | // } 64 | // impl BinaryOption { 65 | // pub fn from_json(data: &Contract) -> Box { 66 | // let market_data = data.market_data.as_ref().unwrap(); 67 | // let underlying_quote = Quote::new(market_data.underlying_price); 68 | // //TODO: Add term structure 69 | // let date = vec![0.01, 0.02, 0.05, 0.1, 0.5, 1.0, 2.0, 3.0]; 70 | // let rates = vec![0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05]; 71 | // let ts = YieldTermStructure::new(date, rates); 72 | // let option_type = &market_data.option_type; 73 | // let side: OptionType; 74 | // match option_type.trim() { 75 | // "C" | "c" | "Call" | "call" => side = OptionType::Call, 76 | // "P" | "p" | "Put" | "put" => side = OptionType::Put, 77 | // _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), 78 | // } 79 | // let maturity_date = &market_data.maturity; 80 | // let today = Local::today(); 81 | // let future_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); 82 | // 83 | // let risk_free_rate = Some(market_data.risk_free_rate).unwrap(); 84 | // let dividend = Some(market_data.dividend).unwrap(); 85 | // //let mut op = 0.0; 86 | // 87 | // let option_price = Quote::new(match market_data.option_price { 88 | // Some(x) => x, 89 | // None => 0.0, 90 | // }); 91 | // //let volatility = Some(market_data.volatility); 92 | // let volatility = match market_data.volatility { 93 | // Some(x) => { 94 | // x 95 | // } 96 | // None => 0.2 97 | // }; 98 | // let mut option = BinaryOption { 99 | // option_type: side, 100 | // transection: Transection::Buy, 101 | // underlying_price: underlying_quote, 102 | // current_price: option_price, 103 | // strike_price: market_data.strike_price, 104 | // volatility: volatility, 105 | // maturity_date: future_date, 106 | // risk_free_rate: risk_free_rate.unwrap_or(0.0), 107 | // dividend_yield: dividend.unwrap_or(0.0), 108 | // transection_price: 0.0, 109 | // term_structure: ts, 110 | // engine: Engine::BlackScholes, 111 | // simulation: None, 112 | // style: ContractStyle::European, 113 | // valuation_date: today.naive_utc(), 114 | // }; 115 | // match data.pricer.trim() { 116 | // "Analytical" | "analytical"|"bs" => { 117 | // option.engine = Engine::BlackScholes; 118 | // } 119 | // "MonteCarlo" | "montecarlo" | "MC"|"mc" => { 120 | // option.engine = Engine::MonteCarlo; 121 | // } 122 | // "Binomial" | "binomial"|"bino" => { 123 | // option.engine = Engine::Binomial; 124 | // } 125 | // "FiniteDifference" | "finitdifference" |"FD" |"fd" => { 126 | // option.engine = Engine::FiniteDifference; 127 | // } 128 | // _ => { 129 | // panic!("Invalid pricer"); 130 | // } 131 | // } 132 | // match data.style.as_ref().unwrap_or(&"European".to_string()).trim() { 133 | // "European" | "european" => { 134 | // option.style = ContractStyle::European; 135 | // } 136 | // "American" | "american" => { 137 | // option.style = ContractStyle::American; 138 | // } 139 | // _ => { 140 | // option.style = ContractStyle::European; 141 | // } 142 | // } 143 | // option.set_risk_free_rate(); 144 | // return Box::new(option); 145 | // } 146 | // } 147 | // 148 | // #[cfg(test)] 149 | // mod tests { 150 | // //write a unit test for from_json 151 | // use super::*; 152 | // use crate::core::utils::{Contract,MarketData}; 153 | // use crate::core::trade::OptionType; 154 | // use crate::core::trade::Transection; 155 | // use crate::core::utils::ContractStyle; 156 | // use crate::core::termstructure::YieldTermStructure; 157 | // use crate::core::quotes::Quote; 158 | // use chrono::{Datelike, Local, NaiveDate}; 159 | // #[test] 160 | // fn test_from_json() { 161 | // let data = Contract { 162 | // action: "PV".to_string(), 163 | // market_data: Some(MarketData { 164 | // underlying_price: 100.0, 165 | // strike_price: 100.0, 166 | // volatility: None, 167 | // option_price: Some(10.0), 168 | // risk_free_rate: Some(0.05), 169 | // dividend: Some(0.0), 170 | // maturity: "2024-01-01".to_string(), 171 | // option_type: "C".to_string(), 172 | // simulation: None 173 | // }), 174 | // pricer: "Analytical".to_string(), 175 | // asset: "".to_string(), 176 | // style: Some("European".to_string()), 177 | // rate_data: None 178 | // }; 179 | // let option = BinaryOption::from_json(&data); 180 | // assert_eq!(option.option_type, OptionType::Call); 181 | // assert_eq!(option.transection, Transection::Buy); 182 | // assert_eq!(option.underlying_price.value, 100.0); 183 | // assert_eq!(option.strike_price, 100.0); 184 | // assert_eq!(option.current_price.value, 10.0); 185 | // assert_eq!(option.dividend_yield, 0.0); 186 | // assert_eq!(option.volatility, 0.2); 187 | // assert_eq!(option.maturity_date, NaiveDate::from_ymd(2024, 1, 1)); 188 | // assert_eq!(option.valuation_date, Local::today().naive_utc()); 189 | // assert_eq!(option.engine, Engine::BlackScholes); 190 | // assert_eq!(option.style, ContractStyle::European); 191 | // } 192 | // } 193 | // 194 | -------------------------------------------------------------------------------- /src/equity/binomial.rs: -------------------------------------------------------------------------------- 1 | //extern crate ndarray; 2 | use super::vanila_option::{EquityOption}; 3 | use super::utils::{Engine, Payoff}; 4 | use crate::core::trade::{ PutOrCall, Transection}; 5 | use crate::core::utils::{ContractStyle}; 6 | use ndarray::Array2; 7 | 8 | /// Binomial tree model for European and American options 9 | pub fn npv(option: &EquityOption) -> f64 { 10 | assert!(option.base.volatility >= 0.0); 11 | assert!(option.time_to_maturity() >= 0.0); 12 | assert!(option.base.underlying_price.value >= 0.0); 13 | let num_steps = 1000; 14 | 15 | let dt = option.time_to_maturity() / num_steps as f64; 16 | let discount_factor = (-option.base.risk_free_rate * dt).exp(); 17 | // Calculate parameters for the binomial tree 18 | let u = (option.base.volatility*dt.sqrt()).exp(); //up movement 19 | let d = 1.0 / u; //down movement 20 | let a_factor = ((option.base.risk_free_rate-option.base.dividend_yield) * dt).exp(); 21 | let p = (a_factor - d) / (u - d); //martingale probability 22 | // Create a 2D array to represent the binomial tree 23 | let mut tree = Array2::from_elem((num_steps + 1, num_steps + 1), 0.0); 24 | //println!("{:?}",tree); 25 | // Calculate option prices at the final time step (backward induction) 26 | let multiplier = if option.payoff.put_or_call() == &PutOrCall::Call { 1.0 } else { -1.0 }; 27 | 28 | for j in 0..=num_steps { 29 | let spot_price_j = option.base.underlying_price.value * u.powi(num_steps as i32 - j as i32) * d.powi(j as i32); 30 | tree[[j,num_steps]] = (multiplier*(spot_price_j - option.base.strike_price)).max(0.0); 31 | } 32 | 33 | match option.payoff.exercise_style() { 34 | ContractStyle::European => { 35 | for i in (0..num_steps).rev() { 36 | for j in 0..=i { 37 | //let spot_price_i = option.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32); 38 | let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]); 39 | //tree[[j,i]] = (multiplier*(spot_price_i - option.strike_price)).max(discounted_option_price); 40 | tree[[j,i]] = discounted_option_price; 41 | } 42 | } 43 | 44 | } 45 | ContractStyle::American => { 46 | 47 | for i in (0..num_steps).rev() { 48 | for j in 0..=i { 49 | let spot_price_i = option.base.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32); 50 | //let intrinsic_value = (multiplier*(spot_price_i - option.strike_price)).max(0.0); 51 | let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]); 52 | tree[[j,i]] = (multiplier*(spot_price_i - option.base.strike_price)).max(discounted_option_price); 53 | } 54 | } 55 | 56 | } 57 | _ => { 58 | panic!("Invalid option style"); 59 | } 60 | } 61 | 62 | 63 | return tree[[0,0]]; 64 | } 65 | 66 | // Write a unit test for the binomial tree model 67 | 68 | // #[cfg(test)] 69 | // mod tests { 70 | // use assert_approx_eq::assert_approx_eq; 71 | // use super::*; 72 | // use crate::core::utils::{Contract,MarketData}; 73 | // use crate::core::trade::{OptionType,Transection}; 74 | // use crate::core::utils::{ContractStyle}; 75 | // use crate::equity::vanila_option::{EquityOption}; 76 | // 77 | // use chrono::{NaiveDate}; 78 | // use crate::core::traits::Instrument; 79 | // 80 | // 81 | // #[test] 82 | // fn test_binomial_tree() { 83 | // let mut data = Contract { 84 | // action: "PV".to_string(), 85 | // market_data: Some(MarketData { 86 | // underlying_price: 100.0, 87 | // strike_price: 100.0, 88 | // volatility: Some(0.3), 89 | // option_price: Some(10.0), 90 | // risk_free_rate: Some(0.05), 91 | // dividend: Some(0.0), 92 | // maturity: "2024-01-01".to_string(), 93 | // option_type: "C".to_string(), 94 | // simulation: None 95 | // }), 96 | // pricer: "Binomial".to_string(), 97 | // asset: "".to_string(), 98 | // style: Some("European".to_string()), 99 | // rate_data: None 100 | // }; 101 | // let mut option = EquityOption::from_json(&data); 102 | // option.valuation_date = NaiveDate::from_ymd(2023, 11, 06); 103 | // //Call European test 104 | // let npv = option.npv(); 105 | // assert_approx_eq!(npv, 5.058163, 1e-6); 106 | // //Call American test 107 | // option.option_type = OptionType::Call; 108 | // option.style = ContractStyle::American; 109 | // let npv = option.npv(); 110 | // assert_approx_eq!(npv, 5.058163, 1e-6); 111 | // 112 | // //Put European test 113 | // option.option_type = OptionType::Put; 114 | // option.style = ContractStyle::European; 115 | // option.valuation_date = NaiveDate::from_ymd(2023, 11, 07); 116 | // let npv = option.npv(); 117 | // assert_approx_eq!(npv, 4.259022688, 1e-6); 118 | // 119 | // //Put American test 120 | // option.option_type = OptionType::Put; 121 | // option.style = ContractStyle::American; 122 | // let npv = option.npv(); 123 | // assert_approx_eq!(npv, 4.315832381, 1e-6); 124 | // } 125 | // } 126 | -------------------------------------------------------------------------------- /src/equity/blackscholes.rs: -------------------------------------------------------------------------------- 1 | use libm::{exp, log}; 2 | use std::f64::consts::{PI, SQRT_2}; 3 | use std::{io, thread}; 4 | use crate::core::quotes::Quote; 5 | use chrono::{Datelike, Local, NaiveDate}; 6 | //use utils::{N,dN}; 7 | //use vanila_option::{EquityOption,OptionType}; 8 | use crate::core::utils::{ContractStyle, dN, N}; 9 | use crate::core::trade::{PutOrCall, Transection}; 10 | use super::vanila_option::{EquityOption, EquityOptionBase, VanillaPayoff}; 11 | use super::utils::{Engine, PayoffType, Payoff, LongShort}; 12 | use super::super::core::termstructure::YieldTermStructure; 13 | use super::super::core::traits::{Instrument,Greeks}; 14 | use super::super::core::interpolation; 15 | 16 | pub struct BlackScholesPricer; 17 | impl BlackScholesPricer { 18 | pub fn new() -> Self { 19 | BlackScholesPricer 20 | } 21 | pub fn npv(&self, bsd_option: &EquityOption) -> f64 { 22 | //assert!(bsd_option.volatility >= 0.0); 23 | assert!(bsd_option.time_to_maturity() >= 0.0, "Option is expired or negative time"); 24 | assert!(bsd_option.base.underlying_price.value >= 0.0, "Negative underlying price not allowed"); 25 | match &bsd_option.payoff.payoff_kind() { 26 | PayoffType::Vanilla => self.npv_vanilla(bsd_option), 27 | _ => {0.0} 28 | } 29 | } 30 | pub fn delta(&self, bsd_option: &EquityOption) -> f64 { 31 | //assert!(bsd_option.volatility >= 0.0); 32 | assert!(bsd_option.time_to_maturity() >= 0.0, "Option is expired or negative time"); 33 | assert!(bsd_option.base.underlying_price.value >= 0.0, "Negative underlying price not allowed"); 34 | match &bsd_option.payoff.payoff_kind() { 35 | PayoffType::Vanilla => self.delta_vanilla(bsd_option), 36 | _ => {0.0} 37 | } 38 | } 39 | pub fn gamma(&self, bsd_option: &EquityOption) -> f64 { 40 | match &bsd_option.payoff.payoff_kind() { 41 | PayoffType::Vanilla => self.gamma_vanilla(bsd_option), 42 | _ => {0.0} 43 | } 44 | } 45 | pub fn vega(&self, bsd_option: &EquityOption) -> f64 { 46 | match &bsd_option.payoff.payoff_kind() { 47 | PayoffType::Vanilla => self.vega_vanilla(bsd_option), 48 | _ => {0.0} 49 | } 50 | } 51 | pub fn theta(&self, bsd_option: &EquityOption) -> f64 { 52 | match &bsd_option.payoff.payoff_kind() { 53 | PayoffType::Vanilla => self.theta_vanilla(bsd_option), 54 | _ => {0.0} 55 | } 56 | } 57 | pub fn rho(&self, bsd_option: &EquityOption) -> f64 { 58 | match &bsd_option.payoff.payoff_kind() { 59 | PayoffType::Vanilla => self.theta_vanilla(bsd_option), 60 | _ => {0.0} 61 | } 62 | } 63 | fn npv_vanilla(&self, bsd_option: &EquityOption) -> f64 { 64 | 65 | let n_d1 = N(bsd_option.base.d1()); 66 | let n_d2 = N(bsd_option.base.d2()); 67 | let df_d = exp(-bsd_option.base.dividend_yield * bsd_option.time_to_maturity()); 68 | let df_r = exp(-bsd_option.base.risk_free_rate * bsd_option.time_to_maturity()); 69 | match bsd_option.payoff.put_or_call() { 70 | PutOrCall::Call => {bsd_option.base.underlying_price.value()*n_d1 *df_d 71 | -bsd_option.base.strike_price*n_d2*df_r 72 | } 73 | PutOrCall::Put => {bsd_option.base.strike_price*N(-bsd_option.base.d2())*df_r- 74 | bsd_option.base.underlying_price.value()*N(-bsd_option.base.d1()) *df_d 75 | } 76 | 77 | } 78 | } 79 | fn delta_vanilla(&self, bsd_option: &EquityOption) -> f64 { 80 | let n_d1 = N(bsd_option.base.d1()); 81 | 82 | let df_d = exp(-bsd_option.base.dividend_yield * bsd_option.time_to_maturity()); 83 | let df_r = exp(-bsd_option.base.risk_free_rate * bsd_option.time_to_maturity()); 84 | 85 | match bsd_option.payoff.put_or_call() { 86 | PutOrCall::Call => {n_d1 *(df_r/df_d) } 87 | PutOrCall::Put => {(n_d1-1.0) *(df_r/df_d) } 88 | } 89 | } 90 | fn gamma_vanilla(&self, bsd_option: &EquityOption) -> f64 { 91 | let dn_d1 = dN(bsd_option.base.d1()); 92 | let df_d = exp(-bsd_option.base.dividend_yield * bsd_option.time_to_maturity()); 93 | let df_r = exp(-bsd_option.base.risk_free_rate * bsd_option.time_to_maturity()); 94 | let num = dn_d1*(df_r/df_d); 95 | let var_sqrt = bsd_option.base.volatility * (bsd_option.time_to_maturity().sqrt()); 96 | num/ (bsd_option.base.underlying_price.value() * var_sqrt) 97 | } 98 | fn vega_vanilla(&self, bsd_option: &EquityOption) -> f64 { 99 | let dn_d1 = dN(bsd_option.base.d1()); 100 | let df_d = exp(-bsd_option.base.dividend_yield * bsd_option.time_to_maturity()); 101 | let df_r = exp(-bsd_option.base.risk_free_rate * bsd_option.time_to_maturity()); 102 | let df_S = bsd_option.base.underlying_price.value()*df_r/df_d; 103 | let vega = df_S * dn_d1 * bsd_option.time_to_maturity().sqrt(); 104 | vega 105 | } 106 | fn theta_vanilla(&self, bsd_option: &EquityOption) -> f64 { 107 | 108 | //let vol_time_sqrt = bsd_option.base.volatility * (bsd_option.time_to_maturity().sqrt()); 109 | let dn_d1 = dN(bsd_option.base.d1()); 110 | let n_d1 = N(bsd_option.base.d1()); 111 | let n_d2 = N(bsd_option.base.d2()); 112 | let df_d = exp(-bsd_option.base.dividend_yield * bsd_option.time_to_maturity()); 113 | let df_r = exp(-bsd_option.base.risk_free_rate * bsd_option.time_to_maturity()); 114 | let df_S = bsd_option.base.underlying_price.value()*df_r/df_d; 115 | let t1 = -df_S*dn_d1 * bsd_option.base.volatility 116 | / (2.0 * bsd_option.time_to_maturity().sqrt()); 117 | 118 | match bsd_option.payoff.put_or_call() { 119 | PutOrCall::Call => { 120 | let t2 = -df_S*n_d1*(bsd_option.base.dividend_yield-bsd_option.base.risk_free_rate); 121 | let t3 = -bsd_option.base.risk_free_rate*bsd_option.base.strike_price*df_r*n_d2; 122 | t1+t2+t3 123 | } 124 | PutOrCall::Put => { 125 | let t2 = df_S*N(-bsd_option.base.d1())*(bsd_option.base.dividend_yield-bsd_option.base.risk_free_rate); 126 | let t3 = bsd_option.base.risk_free_rate*bsd_option.base.strike_price*df_r*N(-bsd_option.base.d2()); 127 | t1+t2+t3 128 | } 129 | 130 | } 131 | } 132 | fn rho_vanilla(&self, bsd_option: &EquityOption) -> f64 { 133 | //let n_d1 = N(bsd_option.base.d1()); 134 | let n_d2 = N(bsd_option.base.d2()); 135 | //let df_d = exp(-bsd_option.base.dividend_yield * bsd_option.time_to_maturity()); 136 | let df_r = exp(-bsd_option.base.risk_free_rate * bsd_option.time_to_maturity()); 137 | let r1 = bsd_option.time_to_maturity()*bsd_option.base.strike_price; 138 | match bsd_option.payoff.put_or_call() { 139 | PutOrCall::Call => { 140 | r1*n_d2*df_r 141 | } 142 | PutOrCall::Put => {r1*N(-bsd_option.base.d2())*df_r 143 | } 144 | 145 | } 146 | } 147 | 148 | } 149 | 150 | 151 | pub fn option_pricing() { 152 | println!("Welcome to the Black-Scholes Option pricer."); 153 | print!(">>"); 154 | println!(" What is the current price of the underlying asset?"); 155 | print!(">>"); 156 | let mut curr_price = String::new(); 157 | io::stdin() 158 | .read_line(&mut curr_price) 159 | .expect("Failed to read line"); 160 | println!(" Do you want a call option ('C') or a put option ('P') ?"); 161 | print!(">>"); 162 | let mut side_input = String::new(); 163 | io::stdin() 164 | .read_line(&mut side_input) 165 | .expect("Failed to read line"); 166 | let side: PutOrCall; 167 | match side_input.trim() { 168 | "C" | "c" | "Call" | "call" => side = PutOrCall::Call, 169 | "P" | "p" | "Put" | "put" => side = PutOrCall::Put, 170 | _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), 171 | } 172 | println!("Stike price:"); 173 | print!(">>"); 174 | let mut strike = String::new(); 175 | io::stdin() 176 | .read_line(&mut strike) 177 | .expect("Failed to read line"); 178 | println!("Expected annualized volatility in %:"); 179 | println!("E.g.: Enter 50% chance as 0.50 "); 180 | print!(">>"); 181 | let mut vol = String::new(); 182 | io::stdin() 183 | .read_line(&mut vol) 184 | .expect("Failed to read line"); 185 | 186 | println!("Risk-free rate in %:"); 187 | print!(">>"); 188 | let mut rf = String::new(); 189 | io::stdin().read_line(&mut rf).expect("Failed to read line"); 190 | println!(" Maturity date in YYYY-MM-DD format:"); 191 | 192 | let mut expiry = String::new(); 193 | println!("E.g.: Enter 2020-12-31 for 31st December 2020"); 194 | print!(">>"); 195 | io::stdin() 196 | .read_line(&mut expiry) 197 | .expect("Failed to read line"); 198 | println!("{:?}", expiry.trim()); 199 | let _d = expiry.trim(); 200 | let future_date = NaiveDate::parse_from_str(&_d, "%Y-%m-%d").expect("Invalid date format"); 201 | //println!("{:?}", future_date); 202 | println!("Dividend yield on this stock:"); 203 | print!(">>"); 204 | let mut div = String::new(); 205 | io::stdin() 206 | .read_line(&mut div) 207 | .expect("Failed to read line"); 208 | 209 | //let ts = YieldTermStructure{ 210 | // date: vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0], 211 | // rates: vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12] 212 | //}; 213 | let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; 214 | let rates = vec![0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05]; 215 | let ts = YieldTermStructure::new(date,rates); 216 | let curr_quote = Quote::new( curr_price.trim().parse::().unwrap()); 217 | let mut option = EquityOptionBase { 218 | 219 | symbol:"ABC".to_string(), 220 | currency: None, 221 | exchange:None, 222 | name: None, 223 | cusip: None, 224 | isin: None, 225 | settlement_type: Some("ABC".to_string()), 226 | entry_price: 0.0, 227 | long_short: LongShort::LONG, 228 | underlying_price: curr_quote, 229 | current_price: Quote::new(0.0), 230 | strike_price: strike.trim().parse::().unwrap(), 231 | volatility: vol.trim().parse::().unwrap(), 232 | maturity_date: future_date, 233 | risk_free_rate: rf.trim().parse::().unwrap(), 234 | dividend_yield: div.trim().parse::().unwrap(), 235 | term_structure: ts, 236 | valuation_date: Local::today().naive_local(), 237 | multiplier: 1.0, 238 | }; 239 | option.set_risk_free_rate(); 240 | //println!("{:?}", option.time_to_maturity()); 241 | let payoff = Box::new(VanillaPayoff{put_or_call:side, 242 | exercise_style:ContractStyle::European}); 243 | let option = EquityOption { 244 | base: option, 245 | payoff:payoff, 246 | engine:Engine::BlackScholes, 247 | simulation: None 248 | }; 249 | println!("Theoretical Price ${}", option.npv()); 250 | println!("Premium at risk ${}", option.get_premium_at_risk()); 251 | println!("Delta {}", option.delta()); 252 | println!("Gamma {}", option.gamma()); 253 | println!("Vega {}", option.vega() * 0.01); 254 | println!("Theta {}", option.theta() * (1.0 / 365.0)); 255 | println!("Rho {}", option.rho() * 0.01); 256 | let mut wait = String::new(); 257 | io::stdin() 258 | .read_line(&mut wait) 259 | .expect("Failed to read line"); 260 | } 261 | pub fn implied_volatility(){} 262 | // pub fn implied_volatility() { 263 | // println!("Welcome to the Black-Scholes Option pricer."); 264 | // println!("(Step 1/7) What is the current price of the underlying asset?"); 265 | // let mut curr_price = String::new(); 266 | // io::stdin() 267 | // .read_line(&mut curr_price) 268 | // .expect("Failed to read line"); 269 | // 270 | // println!("(Step 2/7) Do you want a call option ('C') or a put option ('P') ?"); 271 | // let mut side_input = String::new(); 272 | // io::stdin() 273 | // .read_line(&mut side_input) 274 | // .expect("Failed to read line"); 275 | // 276 | // let side: OptionType; 277 | // match side_input.trim() { 278 | // "C" | "c" | "Call" | "call" => side = OptionType::Call, 279 | // "P" | "p" | "Put" | "put" => side = OptionType::Put, 280 | // _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), 281 | // } 282 | // 283 | // println!("Stike price:"); 284 | // let mut strike = String::new(); 285 | // io::stdin() 286 | // .read_line(&mut strike) 287 | // .expect("Failed to read line"); 288 | // 289 | // println!("What is option price:"); 290 | // let mut option_price = String::new(); 291 | // io::stdin() 292 | // .read_line(&mut option_price) 293 | // .expect("Failed to read line"); 294 | // 295 | // println!("Risk-free rate in %:"); 296 | // let mut rf = String::new(); 297 | // io::stdin().read_line(&mut rf).expect("Failed to read line"); 298 | // 299 | // println!(" Maturity date in YYYY-MM-DD format:"); 300 | // let mut expiry = String::new(); 301 | // io::stdin() 302 | // .read_line(&mut expiry) 303 | // .expect("Failed to read line"); 304 | // let future_date = NaiveDate::parse_from_str(&expiry.trim(), "%Y-%m-%d").expect("Invalid date format"); 305 | // println!("Dividend yield on this stock:"); 306 | // let mut div = String::new(); 307 | // io::stdin() 308 | // .read_line(&mut div) 309 | // .expect("Failed to read line"); 310 | // 311 | // //let ts = YieldTermStructure{ 312 | // // date: vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0], 313 | // // rates: vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12] 314 | // //}; 315 | // let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; 316 | // let rates = vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12]; 317 | // let ts = YieldTermStructure::new(date,rates); 318 | // let curr_quote = Quote::new( curr_price.trim().parse::().unwrap()); 319 | // let sim = Some(10000); 320 | // let mut option = EquityOption { 321 | // option_type: side, 322 | // transection: Transection::Buy, 323 | // underlying_price: curr_quote, 324 | // current_price: Quote::new(0.0), 325 | // strike_price: strike.trim().parse::().unwrap(), 326 | // volatility: 0.20, 327 | // maturity_date: future_date, 328 | // risk_free_rate: rf.trim().parse::().unwrap(), 329 | // dividend_yield: div.trim().parse::().unwrap(), 330 | // transection_price: 0.0, 331 | // term_structure: ts, 332 | // engine: Engine::BlackScholes, 333 | // simulation:sim, 334 | // //style:Option::from("European".to_string()), 335 | // style: ContractStyle::European, 336 | // valuation_date: Local::today().naive_utc(), 337 | // }; 338 | // option.set_risk_free_rate(); 339 | // println!("Implied Volatility {}%", 100.0*option.imp_vol(option_price.trim().parse::().unwrap())); 340 | // 341 | // let mut div1 = String::new(); 342 | // io::stdin() 343 | // .read_line(&mut div) 344 | // .expect("Failed to read line"); 345 | // } 346 | 347 | // #[cfg(test)] 348 | // mod tests { 349 | // 350 | // use assert_approx_eq::assert_approx_eq; 351 | // use super::*; 352 | // use crate::core::utils::{Contract,MarketData}; 353 | // use crate::core::trade::{OptionType,Transection}; 354 | // use crate::core::utils::{ContractStyle}; 355 | // use crate::equity::vanila_option::{EquityOption}; 356 | // 357 | // #[test] 358 | // fn test_black_scholes() { 359 | // let mut data = Contract { 360 | // action: "PV".to_string(), 361 | // market_data: Some(MarketData { 362 | // underlying_price: 100.0, 363 | // strike_price: 100.0, 364 | // volatility: Some(0.3), 365 | // option_price: Some(10.0), 366 | // risk_free_rate: Some(0.05), 367 | // dividend: Some(0.0), 368 | // maturity: "2024-01-01".to_string(), 369 | // option_type: "C".to_string(), 370 | // simulation: None 371 | // }), 372 | // pricer: "Analytical".to_string(), 373 | // asset: "".to_string(), 374 | // style: Some("European".to_string()), 375 | // rate_data: None 376 | // }; 377 | // 378 | // let mut option = EquityOption::from_json(&data); 379 | // option.valuation_date = NaiveDate::from_ymd(2023, 11, 06); 380 | // //Call European test 381 | // let npv = option.npv(); 382 | // assert_approx_eq!(npv, 5.05933313, 1e-6); 383 | // 384 | // //Put European test 385 | // option.option_type = OptionType::Put; 386 | // option.style = ContractStyle::European; 387 | // option.valuation_date = NaiveDate::from_ymd(2023, 11, 07); 388 | // let npv = option.npv(); 389 | // assert_approx_eq!(npv,4.2601813, 1e-6); 390 | // 391 | // } 392 | // } 393 | 394 | -------------------------------------------------------------------------------- /src/equity/build_contracts.rs: -------------------------------------------------------------------------------- 1 | //use crate::rates; 2 | //use crate::rates::deposits::Deposit; 3 | 4 | use chrono::{NaiveDate,Local,Weekday}; 5 | use chrono::Datelike; 6 | use crate::core::trade; 7 | use super::vanila_option::{EquityOption}; 8 | use super::super::core::termstructure::YieldTermStructure; 9 | use crate::rates::utils::TermStructure; 10 | use crate::equity::vol_surface::VolSurface; 11 | use crate::rates::utils::{DayCountConvention}; 12 | use crate::core::quotes::Quote; 13 | use crate::core::utils::{Contract,ContractStyle}; 14 | use crate::equity::utils::{Engine, Payoff}; 15 | use std::collections::BTreeMap; 16 | use crate::core::data_models::ProductData; 17 | 18 | pub fn build_eq_contracts_from_json(mut data: Vec) -> Vec> { 19 | let derivatives:Vec> = data.iter().map(|x| { 20 | let ProductData::Option(opt_data) = &x.product_type else { 21 | panic!("Not an option!"); 22 | }; 23 | EquityOption::from_json(opt_data) 24 | }).collect(); 25 | return derivatives; 26 | } 27 | -------------------------------------------------------------------------------- /src/equity/equity_forward.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Local, NaiveDate}; 2 | use crate::core::data_models::EquityForwardData; 3 | use crate::core::quotes::Quote; 4 | use crate::core::traits::Instrument; 5 | use crate::core::utils::Contract; 6 | use crate::equity::equity_future::EquityFuture; 7 | use crate::equity::utils::LongShort; 8 | ///A forward contract is an agreement between two parties to buy or sell, as the case may be, 9 | /// a commodity (or financial instrument or currency or any other underlying) 10 | /// on a pre-determined future date at a price agreed when the contract is entered into. 11 | pub struct EquityForward { 12 | 13 | pub symbol: String, 14 | pub currency: Option, 15 | pub exchange: Option, 16 | pub name: Option, 17 | pub cusip: Option, 18 | pub isin: Option, 19 | pub settlement_type: Option, 20 | 21 | pub underlying_price: Quote, 22 | pub forward_price: Quote, //Forward price you actually locked in. 23 | pub risk_free_rate: f64, 24 | pub dividend_yield: f64, 25 | pub maturity_date: NaiveDate, 26 | pub valuation_date: NaiveDate, 27 | pub long_short:LongShort, 28 | pub notional:f64 29 | } 30 | impl EquityForward { 31 | pub fn from_json(data: &EquityForwardData) -> Box { 32 | //let market_data = data.market_data.as_ref().unwrap(); 33 | //let future_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); 34 | let today = Local::today(); 35 | let maturity_date = NaiveDate::parse_from_str(&data.maturity, "%Y-%m-%d") 36 | .expect("Invalid maturity date"); 37 | 38 | let underlying_price = Quote::new(data.base.underlying_price); 39 | let quote = Some(data.entry_price).unwrap(); 40 | let entry_quote = Quote::new(quote.unwrap_or(0.0)); 41 | let risk_free_rate = data.base.risk_free_rate.unwrap_or(0.0); 42 | let dividend = data.dividend.unwrap_or(0.0); 43 | let long_short = data.base.long_short.unwrap_or(1); 44 | let position = match long_short{ 45 | 1=>LongShort::LONG, 46 | -1=>LongShort::SHORT, 47 | _=>LongShort::LONG, 48 | }; 49 | Box::new(Self { 50 | symbol:data.base.symbol.clone(), 51 | currency: data.base.currency.clone(), 52 | exchange:data.base.exchange.clone(), 53 | name: data.base.name.clone(), 54 | cusip: data.base.cusip.clone(), 55 | isin: data.base.isin.clone(), 56 | settlement_type: data.base.settlement_type.clone(), 57 | 58 | 59 | underlying_price: underlying_price, 60 | forward_price:entry_quote, 61 | risk_free_rate: risk_free_rate, 62 | dividend_yield: dividend, 63 | maturity_date: maturity_date, 64 | valuation_date: today.naive_utc(), 65 | notional:data.notional.unwrap_or(1.0), 66 | long_short:position 67 | }) 68 | } 69 | 70 | fn time_to_maturity(&self) -> f64 { 71 | let days = (self.maturity_date - self.valuation_date).num_days(); 72 | (days as f64) / 365.0 73 | } 74 | fn premiun(&self)->f64{ 75 | self.forward()-self.underlying_price.value() 76 | } 77 | fn forward(&self)->f64{ 78 | let discount_df = 1.0/(self.risk_free_rate*self.time_to_maturity()).exp(); 79 | let dividend_df = 1.0/(self.dividend_yield*self.time_to_maturity()).exp(); 80 | let forward = self.underlying_price.value()*dividend_df/discount_df; 81 | forward 82 | } 83 | } 84 | impl Instrument for EquityForward { 85 | fn npv(&self) -> f64 { 86 | // e −r(T−t) (Ft −K), 87 | let df_r = 1.0/(self.risk_free_rate*self.time_to_maturity()).exp(); 88 | let share = self.notional/self.forward_price.value(); 89 | match self.long_short{ 90 | LongShort::LONG => (self.forward()-self.forward_price.value()) * share *df_r, 91 | LongShort::SHORT => -(self.forward()-self.forward_price.value()) * share *df_r, 92 | } 93 | 94 | } 95 | } 96 | 97 | impl EquityForward{ 98 | pub fn delta(&self) -> f64 { 1.0 } 99 | pub fn gamma(&self) -> f64 { 0.0 } 100 | pub fn vega(&self) -> f64 { 0.0 } 101 | pub fn theta(&self) -> f64 { 0.0 } 102 | pub fn rho(&self) -> f64 { 0.0 } 103 | } -------------------------------------------------------------------------------- /src/equity/equity_future.rs: -------------------------------------------------------------------------------- 1 | // An equity 2 | use chrono::{Datelike, Local, NaiveDate}; 3 | use crate::core::data_models::EquityFutureData; 4 | use crate::core::quotes::Quote; 5 | use crate::core::traits::Instrument; 6 | use crate::core::utils::{Contract,ContractStyle}; 7 | use crate::equity::utils::LongShort; 8 | //use crate::equity::vanila_option::EquityOption; 9 | 10 | pub struct EquityFuture { 11 | 12 | pub symbol: String, 13 | pub currency: Option, 14 | pub exchange: Option, 15 | pub name: Option, 16 | pub cusip: Option, 17 | pub isin: Option, 18 | pub settlement_type: Option, 19 | 20 | pub underlying_price: Quote, 21 | pub current_price: Quote, 22 | pub entry_price: f64, 23 | pub multiplier: f64, 24 | pub risk_free_rate: f64, 25 | pub dividend_yield: f64, 26 | pub maturity_date: NaiveDate, 27 | pub valuation_date: NaiveDate, 28 | pub long_short:LongShort, 29 | } 30 | 31 | impl EquityFuture { 32 | pub fn from_json(data: &EquityFutureData) -> Box { 33 | //let market_data = data.market_data.as_ref().unwrap(); 34 | //let future_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); 35 | let today = Local::today(); 36 | let maturity_date = NaiveDate::parse_from_str(&data.maturity, "%Y-%m-%d") 37 | .expect("Invalid maturity date"); 38 | 39 | let underlying_quote = Quote::new(data.base.underlying_price); 40 | let quote = Some(data.current_price).unwrap(); 41 | let current_quote = Quote::new(quote.unwrap_or(0.0)); 42 | let risk_free_rate = Some(data.base.risk_free_rate).unwrap(); 43 | let dividend = Some(data.dividend).unwrap(); 44 | let long_short = data.base.long_short.unwrap_or(1); 45 | let position = match long_short{ 46 | 1=>LongShort::LONG, 47 | -1=>LongShort::SHORT, 48 | _=>LongShort::LONG, 49 | }; 50 | Box::new(Self { 51 | symbol:data.base.symbol.clone(), 52 | currency: data.base.currency.clone(), 53 | exchange:data.base.exchange.clone(), 54 | name: data.base.name.clone(), 55 | cusip: data.base.cusip.clone(), 56 | isin: data.base.isin.clone(), 57 | settlement_type: data.base.settlement_type.clone(), 58 | 59 | underlying_price: underlying_quote, 60 | current_price:current_quote, 61 | entry_price: data.entry_price.unwrap_or(0.0), 62 | multiplier: data.multiplier.unwrap_or(1.0), 63 | risk_free_rate: risk_free_rate.unwrap_or(0.0), 64 | dividend_yield: dividend.unwrap_or(0.0), 65 | maturity_date: maturity_date, 66 | valuation_date: today.naive_utc(), 67 | long_short:position 68 | }) 69 | } 70 | fn notional(&self) -> f64 { 71 | self.multiplier * self.current_price.value() 72 | } 73 | fn time_to_maturity(&self) -> f64 { 74 | let days = (self.maturity_date - self.valuation_date).num_days(); 75 | (days as f64) / 365.0 76 | } 77 | fn premiun(&self)->f64{ 78 | self.current_price.value()-self.underlying_price.value() 79 | } 80 | fn pnl(&self)->f64{ 81 | let pnl = (self.current_price.value()-self.entry_price)*self.multiplier; 82 | match self.long_short { 83 | LongShort::LONG => pnl, 84 | LongShort::SHORT => -pnl, 85 | _=>0.0, 86 | } 87 | } 88 | fn forward_price(&self)->f64{ 89 | let discount_df = 1.0/(self.risk_free_rate*self.time_to_maturity()).exp(); 90 | let dividend_df = 1.0/(self.dividend_yield*self.time_to_maturity()).exp(); 91 | let forward = self.underlying_price.value()*dividend_df/discount_df; 92 | forward 93 | } 94 | } 95 | impl Instrument for EquityFuture { 96 | fn npv(&self) -> f64 { 97 | self.pnl() 98 | } 99 | } 100 | impl EquityFuture{ 101 | pub fn delta(&self) -> f64 { 1.0 } 102 | pub fn gamma(&self) -> f64 { 0.0 } 103 | pub fn vega(&self) -> f64 { 0.0 } 104 | pub fn theta(&self) -> f64 { 0.0 } 105 | pub fn rho(&self) -> f64 { 0.0 } 106 | } -------------------------------------------------------------------------------- /src/equity/finite_difference.rs: -------------------------------------------------------------------------------- 1 | use super::vanila_option::{EquityOption}; 2 | use super::utils::{Engine, Payoff}; 3 | 4 | use crate::core::trade::{Transection}; 5 | use crate::core::utils::{ContractStyle}; 6 | use ndarray::{Array, Array2,Array1, ArrayBase, Ix1, OwnedRepr, s}; 7 | //use num_integer::Integer; 8 | /// finite difference model for European and American options 9 | pub fn npv(option: &EquityOption) -> f64 { 10 | 0.0 11 | // assert!(option.volatility >= 0.0); 12 | // assert!(option.time_to_maturity() >= 0.0); 13 | // assert!(option.underlying_price.value >= 0.0); 14 | // let strike_price = option.strike_price; 15 | // let time_to_maturity = option.time_to_maturity(); 16 | // let underlying_price = option.underlying_price.value; 17 | // let volatility = option.volatility; 18 | // let risk_free_rate = option.risk_free_rate; 19 | // let dividend_yield = option.dividend_yield; 20 | // let time_steps:f64 = 1000.0; 21 | // //let time_steps:f64 = time_to_maturity/0.001 as f64; 22 | // 23 | // let mut spot_steps = (time_steps / 50.0) as usize; //should be even number 24 | // //let spot_steps:usize = 20; 25 | // if spot_steps % 2 != 0{ 26 | // spot_steps = spot_steps + 1; 27 | // }// should be even number 28 | // 29 | // if option.option_type == OptionType::Call { 30 | // return fd(underlying_price,strike_price,risk_free_rate,dividend_yield,volatility, 31 | // time_to_maturity,spot_steps,time_steps); 32 | // } else { 33 | // //TODO implement for put option 34 | // //println!("Not implemented""); 35 | // return 0.0; 36 | // } 37 | 38 | } 39 | fn fd(s0:f64,k:f64,risk_free_rate:f64,dividend_yield:f64,sigma:f64,time_to_mat:f64,spot_steps:usize,time_steps:f64)->f64{ 40 | let ds = 2.0*s0 / spot_steps as f64; 41 | 42 | let M:i32 = (spot_steps as f64+(spot_steps as f64)/2.0) as i32; // 40 +20 = 60-20 = 40 43 | //let ds = 2.0*s0 / spot_steps as f64; 44 | //let M:i32 = spot_steps as i32; 45 | let dt = time_to_mat/(time_steps as f64); 46 | let time_steps = time_steps as i32; 47 | // convert float to nearest integer 48 | 49 | //println!(" h {:?}",dt / (ds*ds)); 50 | //let sigma:f64 = 0.3; 51 | //let r = 0.05; 52 | //let q = 0.01; 53 | let r = risk_free_rate-dividend_yield; 54 | let mut price_grid:Array2 = Array2::zeros((M as usize +1,time_steps as usize+1)); 55 | // Underlying price Grid 56 | for j in 0..time_steps+1 { 57 | for i in 0..M+1{ 58 | price_grid[[(M-i) as usize,j as usize]] = (i as f64)*ds as f64; 59 | } 60 | } 61 | 62 | 63 | let mut v_grid:Array2 = Array2::zeros((M as usize +1,time_steps as usize+1)); 64 | // Boundary condition 65 | // for j in 0..time_steps+1 { 66 | // for i in 0..M+1{ 67 | // v_grid[[(M-i) as usize,j as usize]] = (price_grid[[(M-i) as usize,j as usize]]-k).max(0.0); 68 | // } 69 | // } 70 | // Boundary condition 71 | for i in 0..M+1{ 72 | v_grid[[(M-i) as usize,time_steps as usize]] = (price_grid[[(M-i) as usize,time_steps as usize]]-k).max(0.0); 73 | } 74 | 75 | let mut pd:Vec = Vec::with_capacity((M + 1) as usize); 76 | let mut b:Vec = Vec::with_capacity((M + 1) as usize); 77 | let mut x_:Vec = Vec::with_capacity((M + 1) as usize); 78 | let mut y_:Vec = Vec::with_capacity((M + 1) as usize); 79 | let mut pu:Vec = Vec::with_capacity((M + 1) as usize); 80 | //let ss = price_grid.slice(s![0..M+1,time_steps]).to_vec(); 81 | //let mut xx_:Vec = Vec::with_capacity((M + 1) as usize); 82 | for j in 0..M + 1 { 83 | // ssj = (j as f64)*ds; 84 | //let x = r * ss[j as usize] / ds; 85 | let x = r*((M-j) as f64); 86 | //let y = sigma.powi(2) * (ss[j as usize].powi(2)) / ds.powi(2); 87 | let y = sigma.powi(2) * ((M-j) as f64).powi(2); 88 | x_.push(x); 89 | y_.push(y); 90 | //xx_.push(ss[j as usize] / ds); 91 | b.push(1.0 + dt * r + y * dt*0.5); //0.5 * dt * (j as f64)*((r-q) - sigma.powi(2)*(j as f64)); 92 | pu.push(-0.25 * dt * (x + y)); //0.5 * dt * (j as f64)*((r-q) + sigma.powi(2)*(j as f64)) 93 | pd.push(0.25 * dt * (x - y)); //-0.5*dt*(j as f64)*((r-q) + sigma.powi(2)*(j as f64)) 94 | } 95 | 96 | for i in (1..time_steps+1).rev(){ 97 | let mut d = v_grid.slice(s![0..M as usize+1,i]).to_vec(); 98 | d[0] = d[0]*(1.0-pu[0]); 99 | for j in 1..M{ 100 | d[j as usize] = d[j as usize] +0.25*x_[j as usize]*dt*(d[(j-1) as usize]-d[(j+1) as usize]) + 101 | 0.25*y_[j as usize]*dt*(d[(j-1) as usize]+d[(j+1) as usize] - 2.0*d[j as usize] ); 102 | } 103 | let x = thomas_algorithm(&pu[0..M as usize], &b, &pd[1..(M+1) as usize], &d); 104 | for j in 0..M+1{ 105 | v_grid[[j as usize,(i-1) as usize]] = x[j as usize]; 106 | } 107 | } 108 | 109 | return v_grid[[spot_steps as usize,0]]; 110 | 111 | } 112 | pub fn thomas_algorithm(a: &[f64], b: &[f64], c: &[f64], d: &[f64]) -> Vec { 113 | ///https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm 114 | /// Solves Ax = d where A is a tridiagonal matrix consisting of vectors a, b, c 115 | let n = d.len(); 116 | let mut c_ = c.to_vec(); 117 | let mut d_ = d.to_vec(); 118 | let mut x: Vec = vec![0.0; n]; 119 | 120 | // Adjust for the upper boundary condition 121 | //d_[0] = d_[0]*(1.0-a[0]); 122 | 123 | c_[0] = c_[0] / b[0]; 124 | d_[0] = d_[0] / b[0]; 125 | for i in 1..n-1 { 126 | let id = 1.0 / (b[i] - a[i-1] * c_[i - 1]); 127 | c_[i] = c_[i] * id; 128 | d_[i] = (d_[i] - a[i-1] * d_[i - 1]) * id; 129 | } 130 | d_[n - 1] = (d_[n - 1] - a[n - 2] * d_[n - 2]) / (b[n - 1] - a[n - 2] * c_[n - 2]); 131 | 132 | x[n - 1] = d_[n - 1]; 133 | for i in (0..n - 1).rev() { 134 | x[i] = d_[i] - c_[i] * x[i + 1]; 135 | } 136 | x 137 | } 138 | -------------------------------------------------------------------------------- /src/equity/forward_start_option.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthqs/RustyQLib/8da29ed630fe6bdc646a5a1dec5f8658b575e2c6/src/equity/forward_start_option.rs -------------------------------------------------------------------------------- /src/equity/handle_equity_contracts.rs: -------------------------------------------------------------------------------- 1 | use crate::core::traits::Instrument; 2 | use crate::core::utils::{Contract,CombinedContract, ContractOutput}; 3 | use crate::core::data_models::ProductData; 4 | use crate::equity::equity_forward::EquityForward; 5 | use crate::equity::vanila_option::EquityOption; 6 | use crate::equity::equity_future::EquityFuture; 7 | pub fn handle_equity_contract(data: &Contract) -> String { 8 | match &data.product_type { 9 | ProductData::Option(opt) => { 10 | let option = EquityOption::from_json(opt); 11 | let contract_output = ContractOutput { 12 | pv: option.npv(), 13 | delta: option.delta(), 14 | gamma: option.gamma(), 15 | vega: option.vega(), 16 | theta: option.theta(), 17 | rho: option.rho(), 18 | error: None 19 | }; 20 | println!("Theoretical Price ${}", contract_output.pv); 21 | println!("Delta ${}", contract_output.delta); 22 | let combined_ = CombinedContract{ 23 | contract: data.clone(), 24 | output:contract_output 25 | }; 26 | serde_json::to_string(&combined_).expect("Failed to generate output") 27 | } 28 | ProductData::Future(fut) => { 29 | let future = EquityFuture::from_json(fut); 30 | let contract_output = ContractOutput { 31 | pv: future.npv(), 32 | delta: future.delta(), 33 | gamma: future.gamma(), 34 | vega: future.vega(), 35 | theta: future.theta(), 36 | rho: future.rho(), 37 | error: None 38 | }; 39 | println!("Equity Future Price: {}", contract_output.pv); 40 | let combined_ = CombinedContract { 41 | contract: data.clone(), 42 | output: contract_output 43 | }; 44 | serde_json::to_string(&combined_).expect("Failed to generate output") 45 | } 46 | ProductData::Forward(forward) => { 47 | let future = EquityForward::from_json(forward); 48 | let contract_output = ContractOutput { 49 | pv: future.npv(), 50 | delta: future.delta(), 51 | gamma: future.gamma(), 52 | vega: future.vega(), 53 | theta: future.theta(), 54 | rho: future.rho(), 55 | error: None 56 | }; 57 | println!("Equity Forward Price: {}", contract_output.pv); 58 | let combined_ = CombinedContract { 59 | contract: data.clone(), 60 | output: contract_output 61 | }; 62 | serde_json::to_string(&combined_).expect("Failed to generate output") 63 | } 64 | _ => { 65 | panic!("Unsupported or missing product_type for asset EQ"); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/equity/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blackscholes; 2 | pub mod vanila_option; 3 | pub mod montecarlo; 4 | pub mod forward_start_option; 5 | pub mod utils; 6 | pub mod binomial; 7 | pub mod build_contracts; 8 | pub mod vol_surface; 9 | pub mod finite_difference; 10 | pub mod binary_option; 11 | mod equity_future; 12 | pub mod handle_equity_contracts; 13 | mod equity_forward; 14 | -------------------------------------------------------------------------------- /src/equity/montecarlo.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::io; 3 | use chrono::{Datelike, Local, NaiveDate}; 4 | use libm::exp; 5 | 6 | //use crate::equity::vanila_option::{Engine, EquityOption, OptionType, Transection}; 7 | use crate::core::utils::{ContractStyle,dN, N}; 8 | 9 | use super::vanila_option::{EquityOption, EquityOptionBase, VanillaPayoff}; 10 | use super::utils::{Engine, LongShort, Payoff}; 11 | use crate::core::trade::{PutOrCall, Transection}; 12 | use super::super::utils::RNG; 13 | use crate::core::quotes::Quote; 14 | use crate::core::termstructure::YieldTermStructure; 15 | use crate::core::traits::Instrument; 16 | use serde::de::Unexpected::Option; 17 | use crate::core::trade::PutOrCall::Put; 18 | 19 | pub fn simulate_market(option: &EquityOption) -> Vec{ 20 | let mut monte_carlo = RNG::MonteCarloSimulation{ 21 | antithetic: true, 22 | moment_matching: true, 23 | dimentation: 1, 24 | size: option.simulation.unwrap(), 25 | standard_normal_vector: vec![] as Vec, 26 | standard_normal_matrix: vec![] as Vec> 27 | }; 28 | monte_carlo.set_standard_normal_vector(); 29 | let path = monte_carlo.get_standard_normal_vector(); 30 | 31 | let mut market_at_maturity:Vec = Vec::new(); 32 | for z in path{ 33 | let sim_value = option.base.underlying_price.value() 34 | *exp(((option.base.risk_free_rate - option.base.dividend_yield - 0.5 * option.base.volatility.powi(2)) 35 | * option.time_to_maturity())+option.base.volatility * option.time_to_maturity().sqrt()*z); 36 | market_at_maturity.push(sim_value); 37 | } 38 | market_at_maturity 39 | } 40 | 41 | pub fn simulate_market_path_wise(option: &EquityOption) -> Vec{ 42 | let M = 1000; 43 | let N = 10000; 44 | let dt = option.time_to_maturity()/1000.0; 45 | let path = RNG::get_matrix_standard_normal(N,M); 46 | let mut market_at_maturity:Vec = Vec::new(); 47 | for ipath in &path{ 48 | let mut st = option.base.current_price.value(); 49 | for z in ipath{ 50 | st = st 51 | *exp(((option.base.risk_free_rate - option.base.dividend_yield - 0.5 * option.base.volatility.powi(2)) 52 | * dt)+option.base.volatility * dt.sqrt()*z); 53 | } 54 | market_at_maturity.push(st); 55 | } 56 | market_at_maturity 57 | } 58 | 59 | pub fn payoff(market: &Vec, 60 | strike: &f64, 61 | option_type: &PutOrCall) -> Vec{ 62 | let mut payoff_vec = Vec::new(); 63 | match option_type{ 64 | PutOrCall::Call=>{ 65 | for st in market{ 66 | let pay = (st - strike).max(0.0); 67 | payoff_vec.push(pay); 68 | } 69 | } 70 | PutOrCall::Put=>{ 71 | for st in market{ 72 | let pay = (strike-st).max(0.0); 73 | payoff_vec.push(pay); 74 | } 75 | } 76 | _ => {} 77 | } 78 | payoff_vec 79 | } 80 | 81 | 82 | pub fn npv(option: &EquityOption,path_size: bool) -> f64 { 83 | assert!(option.base.volatility >= 0.0); 84 | assert!(option.base.time_to_maturity() >= 0.0); 85 | assert!(option.base.underlying_price.value >= 0.0); 86 | let mut st = vec![]; 87 | if path_size { 88 | st = simulate_market_path_wise(&option); 89 | } 90 | else { 91 | //let sim_size = 10000; 92 | //println!("simulating{}",option.simulation.unwrap()); 93 | st = simulate_market(&option); 94 | } 95 | 96 | let payoff = payoff(&st,&option.base.strike_price,&option.payoff.put_or_call()); 97 | let sum_pay:f64 = payoff.iter().sum(); 98 | let num_of_simulations = st.len() as f64; 99 | let c0:f64 = (sum_pay / num_of_simulations)*exp(-(option.base.risk_free_rate)*option.time_to_maturity()); 100 | c0 101 | } 102 | 103 | 104 | pub fn option_pricing() { 105 | println!("Welcome to the Black-Scholes Option pricer."); 106 | println!("(Step 1/7) What is the current price of the underlying asset?"); 107 | let mut curr_price = String::new(); 108 | io::stdin() 109 | .read_line(&mut curr_price) 110 | .expect("Failed to read line"); 111 | 112 | println!("(Step 2/7) Do you want a call option ('C') or a put option ('P') ?"); 113 | let mut side_input = String::new(); 114 | io::stdin() 115 | .read_line(&mut side_input) 116 | .expect("Failed to read line"); 117 | 118 | let side: PutOrCall; 119 | match side_input.trim() { 120 | "C" | "c" | "Call" | "call" => side = PutOrCall::Call, 121 | "P" | "p" | "Put" | "put" => side = PutOrCall::Put, 122 | _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), 123 | } 124 | 125 | println!("Stike price:"); 126 | let mut strike = String::new(); 127 | io::stdin() 128 | .read_line(&mut strike) 129 | .expect("Failed to read line"); 130 | 131 | println!("Expected annualized volatility in %:"); 132 | println!("E.g.: Enter 50% chance as 0.50 "); 133 | let mut vol = String::new(); 134 | io::stdin() 135 | .read_line(&mut vol) 136 | .expect("Failed to read line"); 137 | 138 | println!("Risk-free rate in %:"); 139 | let mut rf = String::new(); 140 | io::stdin().read_line(&mut rf).expect("Failed to read line"); 141 | 142 | println!("Maturity date in YYYY-MM-DD format:"); 143 | let mut expiry = String::new(); 144 | io::stdin() 145 | .read_line(&mut expiry) 146 | .expect("Failed to read line"); 147 | let future_date = NaiveDate::parse_from_str(&expiry.trim(), "%Y-%m-%d").expect("Invalid date format"); 148 | println!("Dividend yield on this stock:"); 149 | let mut div = String::new(); 150 | io::stdin() 151 | .read_line(&mut div) 152 | .expect("Failed to read line"); 153 | 154 | //let ts = YieldTermStructure{ 155 | // date: vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0], 156 | // rates: vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12] 157 | //}; 158 | let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; 159 | let rates = vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12]; 160 | let ts = YieldTermStructure::new(date,rates); 161 | let curr_quote = Quote::new( curr_price.trim().parse::().unwrap()); 162 | let mut option = EquityOptionBase { 163 | 164 | symbol:"ABC".to_string(), 165 | currency: None, 166 | exchange:None, 167 | name: None, 168 | cusip: None, 169 | isin: None, 170 | settlement_type: Some("ABC".to_string()), 171 | entry_price: 0.0, 172 | long_short: LongShort::LONG, 173 | underlying_price: curr_quote, 174 | current_price: Quote::new(0.0), 175 | strike_price: strike.trim().parse::().unwrap(), 176 | volatility: vol.trim().parse::().unwrap(), 177 | maturity_date: future_date, 178 | risk_free_rate: rf.trim().parse::().unwrap(), 179 | dividend_yield: div.trim().parse::().unwrap(), 180 | 181 | term_structure: ts, 182 | 183 | valuation_date: Local::today().naive_local(), 184 | multiplier: 1.0, 185 | }; 186 | option.set_risk_free_rate(); 187 | println!("{:?}", option.time_to_maturity()); 188 | let payoff = Box::new(VanillaPayoff{put_or_call:side,exercise_style:ContractStyle::European}); 189 | let equityoption = EquityOption { 190 | base: option, 191 | payoff:payoff, 192 | engine:Engine::BlackScholes, 193 | simulation: None 194 | }; 195 | 196 | println!("Theoretical Price ${}", equityoption.npv()); 197 | // println!("Premium at risk ${}", option.get_premium_at_risk()); 198 | // println!("Delata {}", option.delta()); 199 | // println!("Gamma {}", option.gamma()); 200 | // println!("Vega {}", option.vega() * 0.01); 201 | // println!("Theta {}", option.theta() * (1.0 / 365.0)); 202 | // println!("Rho {}", option.rho() * 0.01); 203 | let mut div1 = String::new(); 204 | io::stdin() 205 | .read_line(&mut div) 206 | .expect("Failed to read line"); 207 | } -------------------------------------------------------------------------------- /src/equity/utils.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use crate::equity::vanila_option::EquityOptionBase; 3 | use std::str::FromStr; 4 | use std::error::Error; 5 | use crate::core::trade::{PutOrCall}; 6 | use std::fmt::Debug; 7 | use crate::core::utils::ContractStyle; 8 | 9 | ///Enum for different engines to price options 10 | #[derive(PartialEq,Clone,Debug)] 11 | pub enum Engine{ 12 | BlackScholes, 13 | MonteCarlo, 14 | Binomial, 15 | FiniteDifference 16 | } 17 | #[derive(Debug)] 18 | pub enum LongShort{ 19 | LONG, 20 | SHORT 21 | } 22 | #[derive(Deserialize, Debug)] 23 | #[serde(rename_all = "snake_case")] 24 | pub enum PayoffType { 25 | Vanilla, 26 | Binary, 27 | Barrier, 28 | Asian, 29 | // etc. 30 | } 31 | impl FromStr for PayoffType { 32 | type Err = Box; 33 | fn from_str(s: &str) -> Result { 34 | match s.to_lowercase().as_str() { 35 | "vanilla" => Ok(PayoffType::Vanilla), 36 | "binary" => Ok(PayoffType::Binary), 37 | "barrier" => Ok(PayoffType::Barrier), 38 | "asian" => Ok(PayoffType::Asian), 39 | _ => Err("Invalid payoff type".into()), 40 | } 41 | } 42 | } 43 | 44 | 45 | pub trait Payoff: Debug { 46 | fn payoff_amount(&self, base: &EquityOptionBase) -> f64; 47 | fn payoff_kind(&self) -> PayoffType; 48 | fn put_or_call(&self) -> &PutOrCall; 49 | fn exercise_style(&self)->&ContractStyle; 50 | 51 | // possibly other methods for payoff logic 52 | } -------------------------------------------------------------------------------- /src/equity/vanila_option.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use chrono::{Datelike, Local, NaiveDate}; 3 | use crate::equity::{binomial,finite_difference,montecarlo}; 4 | use super::super::core::termstructure::YieldTermStructure; 5 | use super::super::core::quotes::Quote; 6 | use super::super::core::traits::{Instrument,Greeks}; 7 | use super::blackscholes; 8 | use crate::equity::utils::{Engine, PayoffType, Payoff, LongShort}; 9 | use crate::core::trade::{PutOrCall,Transection}; 10 | use crate::core::utils::{Contract,ContractStyle}; 11 | use crate::core::{interpolation, trade}; 12 | use serde::Deserialize; 13 | use blackscholes::BlackScholesPricer; 14 | use crate::core::data_models::EquityOptionData; 15 | 16 | #[derive(Debug)] 17 | pub struct VanillaPayoff { 18 | pub put_or_call: PutOrCall, 19 | pub exercise_style: ContractStyle, 20 | } 21 | #[derive(Debug)] 22 | pub struct BinaryPayoff { 23 | pub put_or_call: PutOrCall, 24 | pub exercise_style: ContractStyle, 25 | } 26 | #[derive(Debug)] 27 | pub struct BarrierPayoff { 28 | pub put_or_call: PutOrCall, 29 | pub exercise_style: ContractStyle, 30 | } 31 | #[derive(Debug)] 32 | pub struct AsianPayoff { 33 | pub put_or_call: PutOrCall, 34 | pub exercise_style: ContractStyle, 35 | } 36 | impl Payoff for VanillaPayoff { 37 | fn payoff_amount(&self, base: &EquityOptionBase) -> f64 { 38 | // implement vanilla payoff 39 | let intrinsic_value = base.underlying_price.value() - base.strike_price; 40 | match &self.put_or_call { 41 | PutOrCall::Call=> intrinsic_value.max(0.0), 42 | PutOrCall::Put=> (-intrinsic_value).max(0.0), 43 | _=>0.0 44 | } 45 | } 46 | fn payoff_kind(&self) -> PayoffType { 47 | PayoffType::Vanilla 48 | } 49 | fn put_or_call(&self) -> &PutOrCall { 50 | &self.put_or_call 51 | } 52 | fn exercise_style(&self) -> &ContractStyle { 53 | &self.exercise_style 54 | } 55 | 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct EquityOptionBase { 60 | pub symbol: String, 61 | pub currency: Option, 62 | pub exchange: Option, 63 | pub name: Option, 64 | pub cusip: Option, 65 | pub isin: Option, 66 | pub settlement_type: Option, 67 | 68 | //pub payoff_type: String, // Vanilla/Barrier/Binary 69 | pub underlying_price: Quote, 70 | pub current_price: Quote, 71 | pub strike_price: f64, 72 | pub dividend_yield: f64, 73 | pub volatility: f64, 74 | pub maturity_date: NaiveDate, 75 | pub valuation_date: NaiveDate, 76 | pub term_structure: YieldTermStructure, 77 | pub risk_free_rate: f64, 78 | pub entry_price: f64, 79 | pub long_short: LongShort, 80 | pub multiplier: f64, 81 | 82 | } 83 | #[derive(Debug)] 84 | pub struct EquityOption { 85 | pub base: EquityOptionBase, 86 | pub payoff: Box, 87 | pub engine: Engine, 88 | pub simulation: Option, 89 | 90 | } 91 | impl EquityOption{ 92 | pub fn time_to_maturity(&self) -> f64{ 93 | let time_to_maturity = (self.base.maturity_date - self.base.valuation_date).num_days() as f64/365.0; 94 | time_to_maturity 95 | } 96 | 97 | 98 | } 99 | impl EquityOption { 100 | 101 | pub fn from_json(data: &EquityOptionData) -> Box { 102 | let date = vec![0.01, 0.02, 0.05, 0.1, 0.5, 1.0, 2.0, 3.0]; 103 | let rates = vec![0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05]; 104 | let ts = YieldTermStructure::new(date, rates); 105 | let maturity_date = NaiveDate::parse_from_str(&data.maturity, "%Y-%m-%d").expect("Invalid date format"); 106 | let mut base_option = EquityOptionBase { 107 | symbol:data.base.symbol.clone(), 108 | currency: data.base.currency.clone(), 109 | exchange:data.base.exchange.clone(), 110 | name: data.base.name.clone(), 111 | cusip: data.base.cusip.clone(), 112 | isin: data.base.isin.clone(), 113 | settlement_type: data.base.settlement_type.clone(), 114 | 115 | underlying_price: Quote::new(data.base.underlying_price), 116 | current_price: Quote::new(data.current_price.unwrap_or(0.0)), 117 | strike_price: data.strike_price, 118 | volatility: data.volatility, 119 | maturity_date, 120 | risk_free_rate: data.base.risk_free_rate.unwrap_or(0.0), 121 | entry_price: data.entry_price.unwrap_or(0.0), 122 | long_short: LongShort::LONG, 123 | dividend_yield: data.dividend.unwrap_or(0.0), 124 | term_structure: ts, 125 | valuation_date: Local::today().naive_utc(), 126 | multiplier: data.multiplier.unwrap_or(1.0), 127 | }; 128 | let payoff_type = &data.payoff_type.parse::().unwrap(); 129 | let side: PutOrCall; 130 | let put_or_call = data.put_or_call.clone(); 131 | match put_or_call.trim() { 132 | "C" | "c" | "Call" | "call" => side = PutOrCall::Call, 133 | "P" | "p" | "Put" | "put" => side = PutOrCall::Put, 134 | _ => panic!("Invalid side argument! Side has to be either 'C' or 'P'."), 135 | } 136 | 137 | let style = match data.exercise_style.as_ref().unwrap_or(&"European".to_string()).trim() { 138 | "European" | "european" => { 139 | ContractStyle::European 140 | } 141 | "American" | "american" => { 142 | ContractStyle::American 143 | } 144 | _ => { 145 | ContractStyle::European 146 | } 147 | }; 148 | 149 | let payoff:Box = match &payoff_type { 150 | PayoffType::Vanilla => Box::new(VanillaPayoff{ 151 | put_or_call:side, 152 | exercise_style:style}), 153 | //Ok(PayoffType::Binary) => Box::new(BinaryPayoff{option_type:side}), 154 | //Ok(PayoffType::Barrier) => Box::new(BarrierPayoff{option_type:side}), 155 | //Ok(PayoffType::Asian) => Box::new(AsianPayoff{option_type:side}), 156 | _ => {Box::new(VanillaPayoff{ 157 | put_or_call:side, 158 | exercise_style:style})} 159 | }; 160 | 161 | base_option.set_risk_free_rate(); 162 | let equityoption = EquityOption { 163 | base: base_option, 164 | payoff, 165 | engine: match data.pricer.as_ref().map_or("Analytical",|v| v).trim() { 166 | "Analytical" | "analytical" | "bs" => Engine::BlackScholes, 167 | "MonteCarlo" | "montecarlo" | "MC" | "mc" => Engine::MonteCarlo, 168 | "Binomial" | "binomial" | "bino" => Engine::Binomial, 169 | "FiniteDifference" | "finitdifference" | "FD" | "fd" => Engine::FiniteDifference, 170 | _ => { 171 | panic!("Invalid pricer"); 172 | } 173 | }, 174 | simulation: None 175 | }; 176 | Box::new(equityoption) 177 | } 178 | } 179 | 180 | impl EquityOptionBase { 181 | pub fn time_to_maturity(&self) -> f64{ 182 | let time_to_maturity = (self.maturity_date - self.valuation_date).num_days() as f64/365.0; 183 | time_to_maturity 184 | } 185 | pub fn set_risk_free_rate(&mut self) { 186 | let model = interpolation::CubicSpline::new(&self.term_structure.date, &self.term_structure.rates); 187 | let r = model.interpolation(self.time_to_maturity()); 188 | self.risk_free_rate = r; 189 | } 190 | pub fn d1(&self) -> f64 { 191 | //Black-Scholes-Merton d1 function Parameters 192 | let d1_numerator = (self.underlying_price.value() / self.strike_price).ln() 193 | + (self.risk_free_rate - self.dividend_yield + 0.5 * self.volatility.powi(2)) 194 | * self.time_to_maturity(); 195 | 196 | let d1_denominator = self.volatility * (self.time_to_maturity().sqrt()); 197 | return d1_numerator / d1_denominator; 198 | } 199 | pub fn d2(&self) -> f64 { 200 | let d2 = self.d1() - self.volatility * self.time_to_maturity().powf(0.5); 201 | return d2; 202 | } 203 | } 204 | impl EquityOption { 205 | pub fn get_premium_at_risk(&self) -> f64 { 206 | let value = self.npv(); 207 | let mut pay_off = self.payoff.payoff_amount(&self.base); 208 | if pay_off > 0.0 { 209 | return value - pay_off; 210 | } else { 211 | return value; 212 | } 213 | } 214 | 215 | pub fn imp_vol(&mut self,option_price:f64) -> f64 { 216 | for i in 0..100{ 217 | let d_sigma = (self.npv()-option_price)/self.vega(); 218 | self.base.volatility -= d_sigma 219 | } 220 | self.base.volatility 221 | } 222 | pub fn get_imp_vol(&mut self) -> f64 { 223 | for i in 0..100{ 224 | let d_sigma = (self.npv()-self.base.current_price.value)/self.vega(); 225 | self.base.volatility -= d_sigma 226 | } 227 | self.base.volatility 228 | } 229 | } 230 | 231 | 232 | impl Instrument for EquityOption { 233 | fn npv(&self) -> f64 { 234 | match self.engine { 235 | Engine::BlackScholes => { 236 | let pricer = BlackScholesPricer::new(); 237 | let value = pricer.npv(&self); 238 | value 239 | } 240 | _ => { 0.0 } 241 | } 242 | // Engine::MonteCarlo => { 243 | // 244 | // let value = montecarlo::npv(&self,false); 245 | // value 246 | // } 247 | // Engine::Binomial => { 248 | // 249 | // let value = binomial::npv(&self); 250 | // value 251 | // } 252 | // Engine::FiniteDifference => { 253 | // let value = finite_difference::npv(&self); 254 | // value 255 | // } 256 | 257 | } 258 | } 259 | 260 | impl EquityOption { 261 | pub fn delta(&self) -> f64 { 262 | match self.engine { 263 | Engine::BlackScholes => { 264 | let pricer = BlackScholesPricer::new(); 265 | let value = pricer.delta(&self); 266 | value 267 | } 268 | _ => { 0.0 } 269 | } 270 | } 271 | pub fn gamma(&self) -> f64 { 272 | match self.engine { 273 | Engine::BlackScholes => { 274 | let pricer = BlackScholesPricer::new(); 275 | let value = pricer.gamma(&self); 276 | value 277 | } 278 | _ => { 0.0 } 279 | } 280 | } 281 | pub fn vega(&self) -> f64 { 282 | match self.engine { 283 | Engine::BlackScholes => { 284 | let pricer = BlackScholesPricer::new(); 285 | let value = pricer.vega(&self); 286 | value 287 | } 288 | _ => { 0.0 } 289 | } 290 | } 291 | pub fn theta(&self) -> f64 { 292 | match self.engine { 293 | Engine::BlackScholes => { 294 | let pricer = BlackScholesPricer::new(); 295 | let value = pricer.theta(&self); 296 | value 297 | } 298 | _ => { 0.0 } 299 | } 300 | } 301 | pub fn rho(&self) -> f64 { 302 | match self.engine { 303 | Engine::BlackScholes => { 304 | let pricer = BlackScholesPricer::new(); 305 | let value = pricer.rho(&self); 306 | value 307 | } 308 | _ => { 0.0 } 309 | } 310 | } 311 | } 312 | // #[cfg(test)] 313 | // mod tests { 314 | // //write a unit test for from_json 315 | // use super::*; 316 | // use crate::core::utils::{Contract,MarketData}; 317 | // use crate::core::trade::OptionType; 318 | // use crate::core::trade::Transection; 319 | // use crate::core::utils::ContractStyle; 320 | // use crate::core::termstructure::YieldTermStructure; 321 | // use crate::core::quotes::Quote; 322 | // use chrono::{Datelike, Local, NaiveDate}; 323 | // #[test] 324 | // fn test_from_json() { 325 | // let data = Contract { 326 | // action: "PV".to_string(), 327 | // market_data: Some(MarketData { 328 | // underlying_price: 100.0, 329 | // strike_price: 100.0, 330 | // volatility: None, 331 | // option_price: Some(10.0), 332 | // risk_free_rate: Some(0.05), 333 | // dividend: Some(0.0), 334 | // maturity: "2024-01-01".to_string(), 335 | // option_type: "C".to_string(), 336 | // simulation: None 337 | // }), 338 | // pricer: "Analytical".to_string(), 339 | // asset: "".to_string(), 340 | // style: Some("European".to_string()), 341 | // rate_data: None 342 | // }; 343 | // let option = EquityOption::from_json(&data); 344 | // assert_eq!(option.option_type, OptionType::Call); 345 | // assert_eq!(option.transection, Transection::Buy); 346 | // assert_eq!(option.underlying_price.value, 100.0); 347 | // assert_eq!(option.strike_price, 100.0); 348 | // assert_eq!(option.current_price.value, 10.0); 349 | // assert_eq!(option.dividend_yield, 0.0); 350 | // assert_eq!(option.volatility, 0.2); 351 | // assert_eq!(option.maturity_date, NaiveDate::from_ymd(2024, 1, 1)); 352 | // assert_eq!(option.valuation_date, Local::today().naive_utc()); 353 | // assert_eq!(option.engine, Engine::BlackScholes); 354 | // assert_eq!(option.style, ContractStyle::European); 355 | // } 356 | // } 357 | 358 | -------------------------------------------------------------------------------- /src/equity/vol_surface.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::{BTreeMap, HashMap}; 3 | use crate::rates::utils::{DayCountConvention}; 4 | use chrono::{NaiveDate}; 5 | use crate::core::trade::PutOrCall; 6 | use crate::equity::utils::Payoff; 7 | use super::vanila_option::{EquityOption}; 8 | 9 | /// Vol Surface is a collection of volatilities for different maturities and strikes 10 | #[derive(Clone,Debug,Serialize,Deserialize)] 11 | pub struct VolSurface{ 12 | pub term_structure: BTreeMap>, 13 | pub spot: f64, 14 | pub spot_date: NaiveDate, 15 | pub day_count: DayCountConvention, 16 | } 17 | 18 | impl VolSurface { 19 | pub fn new(term_structure: BTreeMap>,spot:f64,spot_date:NaiveDate,day_count:DayCountConvention) -> VolSurface { 20 | VolSurface { 21 | term_structure, 22 | spot, 23 | spot_date, 24 | day_count 25 | } 26 | } 27 | pub fn get_vol(&self,val_date:NaiveDate,maturity_date:NaiveDate,strike:f64)-> f64{ 28 | //TODO: Interpolate Vol Surface 29 | 0.0 30 | } 31 | pub fn get_year_fraction(&self,val_date:NaiveDate,maturity_date:NaiveDate) -> f64 { 32 | self.day_count.get_year_fraction(val_date,maturity_date) 33 | } 34 | pub fn build_eq_vol(mut contracts:Vec>) -> VolSurface { 35 | let mut vol_tree:BTreeMap> = BTreeMap::new(); 36 | let spot_date = contracts[0].base.valuation_date; 37 | let spot_price = contracts[0].base.underlying_price.value; 38 | for i in 0..contracts.len(){ 39 | let mut moneyness=1.0; 40 | let mut contract = contracts[i].as_mut(); 41 | match contract.payoff.put_or_call(){ 42 | PutOrCall::Call => { 43 | moneyness = contract.base.underlying_price.value / contract.base.strike_price as f64; 44 | 45 | }, 46 | PutOrCall::Put => { 47 | moneyness = contract.base.strike_price / contract.base.underlying_price.value as f64; 48 | } 49 | _ => { 50 | panic!("Option type not supported"); 51 | } 52 | } 53 | let volatility = contract.get_imp_vol(); 54 | let maturity = contract.base.maturity_date; 55 | vol_tree.entry(maturity).or_insert(Vec::new()).push((moneyness,volatility)); 56 | } 57 | let vol_surface:VolSurface = VolSurface::new(vol_tree, spot_price, spot_date, 58 | DayCountConvention::Act365); 59 | return vol_surface; 60 | } 61 | 62 | } 63 | 64 | // #[cfg(test)] 65 | // mod tests{ 66 | // use super::*; 67 | // use crate::core::quotes::Quote; 68 | // use crate::core::utils::{Contract,ContractStyle}; 69 | // use crate::equity::utils::{Engine}; 70 | // use crate::core::trade::OptionType; 71 | // use crate::core::trade::OptionStyle; 72 | // use crate::core::trade::OptionStyle::European; 73 | // 74 | // #[test] 75 | // fn test_build_eq_vol(){ 76 | // // write a unit test for this function 77 | // let mut contract = C 78 | // let mut contracts:Vec> = Vec::new(); 79 | // 80 | // 81 | // } -------------------------------------------------------------------------------- /src/examples/CO/cmdty_option.json: -------------------------------------------------------------------------------- 1 | {"contracts" : [{ 2 | "action":"PV", 3 | "pricer":"Analytical", 4 | "asset":"CO", 5 | "market_data":{ 6 | "underlying_price":2.846, 7 | "option_type":"C", 8 | "strike_price":2.75, 9 | "volatility":0.587, 10 | "maturity":"2023-10-26", 11 | "simulation":100000 12 | } 13 | 14 | }, 15 | { 16 | "action":"PV", 17 | "pricer":"Analytical", 18 | "asset":"CO", 19 | "market_data":{ 20 | "underlying_price":2.846, 21 | "option_type":"C", 22 | "strike_price":2.85, 23 | "volatility":0.587, 24 | "maturity":"2023-10-26", 25 | "simulation":100000 26 | } 27 | 28 | }] 29 | } -------------------------------------------------------------------------------- /src/examples/EQ/binary_option.json: -------------------------------------------------------------------------------- 1 | {"contracts" : [ 2 | { 3 | "action":"PV", 4 | "pricer":"Analytical", 5 | "asset":"EQ", 6 | "payoff_type": "Binary", 7 | "binary_type":"Cash-or-Nothing", 8 | "market_data":{ 9 | "underlying_price":100, 10 | "option_type":"C", 11 | "strike_price":100, 12 | "volatility":0.4, 13 | "risk_free_rate":0.06, 14 | "maturity":"2023-12-31", 15 | "dividend": 0.01 16 | } 17 | }, 18 | { 19 | "action":"PV", 20 | "pricer":"MonteCarlo", 21 | "asset":"EQ", 22 | "payoff_type": "Binary", 23 | "binary_type":"Cash-or-Nothing", 24 | "market_data":{ 25 | "underlying_price":100, 26 | "option_type":"C", 27 | "strike_price":105, 28 | "volatility":0.45, 29 | "risk_free_rate":0.06, 30 | "maturity":"2023-12-31", 31 | "dividend": 0.01, 32 | "simulation":100000 33 | }} 34 | ] 35 | } -------------------------------------------------------------------------------- /src/examples/EQ/eq2.json: -------------------------------------------------------------------------------- 1 | {"asset":"EQ", 2 | "contracts" : [ 3 | { 4 | "action":"PV", 5 | "style":"American", 6 | "pricer":"Binomial", 7 | "asset":"EQ", 8 | "market_data":{ 9 | "underlying_price":100, 10 | "option_type":"P", 11 | "strike_price":100, 12 | "volatility":0.3, 13 | "risk_free_rate":0.05, 14 | "maturity":"2024-01-01", 15 | "dividend": 0.0 16 | } 17 | }, 18 | { 19 | "action":"PV", 20 | "pricer":"Binomial", 21 | "asset":"EQ", 22 | "style":"American", 23 | "market_data":{ 24 | "underlying_price":100, 25 | "option_type":"P", 26 | "strike_price":105, 27 | "volatility":0.3, 28 | "risk_free_rate":0.05, 29 | "maturity":"2024-01-01", 30 | "dividend": 0.0 31 | } 32 | }, 33 | { 34 | "action":"PV", 35 | "pricer":"Binomial", 36 | "asset":"EQ", 37 | "style":"American", 38 | "market_data":{ 39 | "underlying_price":100, 40 | "option_type":"P", 41 | "strike_price":110, 42 | "volatility":0.3, 43 | "risk_free_rate":0.05, 44 | "maturity":"2024-01-01", 45 | "dividend": 0.0 46 | } 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /src/examples/EQ/equity_forward.json: -------------------------------------------------------------------------------- 1 | {"asset":"EQ", 2 | "contracts" : [ 3 | { 4 | "action": "PV", 5 | "pricer": "Analytical", 6 | "asset": "EQ", 7 | "product_type": "Forward", 8 | "payoff_type": "Vanilla", 9 | "market_data": { 10 | "underlying_price": 96.0, 11 | "entry_price": 103.0, 12 | "risk_free_rate": 0.06, 13 | "maturity": "2025-03-31", 14 | "dividend": 0.01, 15 | "notional": 10000, 16 | "long_short": 1 17 | } 18 | }, 19 | { 20 | "action":"PV", 21 | "pricer":"Analytical", 22 | "asset":"EQ", 23 | "product_type": "Future", 24 | "payoff_type": "Vanilla", 25 | "market_data":{ 26 | "underlying_price":96.0, 27 | "current_price": 99.0, 28 | "entry_price": 103.0, 29 | "risk_free_rate":0.06, 30 | "maturity":"2025-03-31", 31 | "dividend": 0.01, 32 | "multiplier": 1000, 33 | "long_short": -1 34 | } 35 | }] 36 | } -------------------------------------------------------------------------------- /src/examples/EQ/equity_option.json: -------------------------------------------------------------------------------- 1 | {"asset":"EQ", 2 | "contracts" : [ 3 | { 4 | "action":"PV", 5 | "pricer":"Analytical", 6 | "asset":"EQ", 7 | "product_type": "Option", 8 | "payoff_type": "Vanilla", 9 | "market_data":{ 10 | "underlying_price":100, 11 | "option_type":"C", 12 | "strike_price":100, 13 | "volatility":0.4, 14 | "risk_free_rate":0.06, 15 | "maturity":"2025-03-31", 16 | "dividend": 0.01 17 | } 18 | }, 19 | { 20 | "action":"PV", 21 | "pricer":"Analytical", 22 | "asset":"EQ", 23 | "product_type": "Future", 24 | "market_data":{ 25 | "underlying_price":100, 26 | "current_price":104, 27 | "risk_free_rate":0.06, 28 | "maturity":"2025-03-31", 29 | "dividend": 0.01 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /src/examples/IR/ir1.json: -------------------------------------------------------------------------------- 1 | {"contracts" : [ 2 | { 3 | "action":"PV", 4 | "pricer":"Analytical", 5 | "asset":"IR", 6 | "rate_data":{ 7 | "instrument": "Deposit", 8 | "currency": "USD", 9 | "start_date": "0M", 10 | "maturity_date":"1M", 11 | "valuation_date": "0M", 12 | "notional": 1000000, 13 | "fix_rate": 0.055, 14 | "day_count": "A360", 15 | "business_day_adjustment": 0 16 | } 17 | }, 18 | { 19 | "action":"PV", 20 | "pricer":"Analytical", 21 | "asset":"IR", 22 | "rate_data":{ 23 | "instrument": "Deposit", 24 | "currency": "USD", 25 | "start_date": "0M", 26 | "maturity_date":"3M", 27 | "valuation_date": "0M", 28 | "notional": 1000000, 29 | "fix_rate": 0.05, 30 | "day_count": "A360", 31 | "business_day_adjustment": 0 32 | } 33 | }, 34 | { 35 | "action":"PV", 36 | "pricer":"Analytical", 37 | "asset":"IR", 38 | "rate_data":{ 39 | "instrument": "FRA", 40 | "currency": "USD", 41 | "start_date": "3M", 42 | "maturity_date":"6M", 43 | "valuation_date": "0M", 44 | "notional": 1000000, 45 | "fix_rate": 0.06, 46 | "day_count": "A360", 47 | "business_day_adjustment": 0 48 | } 49 | }, 50 | { 51 | "action":"PV", 52 | "pricer":"Analytical", 53 | "asset":"IR", 54 | "rate_data":{ 55 | "instrument": "FRA", 56 | "currency": "USD", 57 | "start_date": "6M", 58 | "maturity_date":"9M", 59 | "valuation_date": "0M", 60 | "notional": 1000000, 61 | "fix_rate": 0.065, 62 | "day_count": "A360", 63 | "business_day_adjustment": 0 64 | } 65 | } 66 | ] 67 | } -------------------------------------------------------------------------------- /src/examples/build/build_ir_curve.json: -------------------------------------------------------------------------------- 1 | {"asset":"IR", 2 | "contracts" : [ 3 | { 4 | "action":"PV", 5 | "pricer":"Analytical", 6 | "asset":"IR", 7 | "rate_data":{ 8 | "instrument": "Deposit", 9 | "currency": "USD", 10 | "start_date": "0M", 11 | "maturity_date":"1M", 12 | "valuation_date": "0M", 13 | "notional": 1000000, 14 | "fix_rate": 0.055, 15 | "day_count": "A360", 16 | "business_day_adjustment": 0 17 | } 18 | }, 19 | { 20 | "action":"PV", 21 | "pricer":"Analytical", 22 | "asset":"IR", 23 | "rate_data":{ 24 | "instrument": "Deposit", 25 | "currency": "USD", 26 | "start_date": "0M", 27 | "maturity_date":"3M", 28 | "valuation_date": "0M", 29 | "notional": 1000000, 30 | "fix_rate": 0.05, 31 | "day_count": "A360", 32 | "business_day_adjustment": 0 33 | } 34 | }, 35 | { 36 | "action":"PV", 37 | "pricer":"Analytical", 38 | "asset":"IR", 39 | "rate_data":{ 40 | "instrument": "FRA", 41 | "currency": "USD", 42 | "start_date": "3M", 43 | "maturity_date":"6M", 44 | "valuation_date": "0M", 45 | "notional": 1000000, 46 | "fix_rate": 0.06, 47 | "day_count": "A360", 48 | "business_day_adjustment": 0 49 | } 50 | }, 51 | { 52 | "action":"PV", 53 | "pricer":"Analytical", 54 | "asset":"IR", 55 | "rate_data":{ 56 | "instrument": "FRA", 57 | "currency": "USD", 58 | "start_date": "6M", 59 | "maturity_date":"9M", 60 | "valuation_date": "0M", 61 | "notional": 1000000, 62 | "fix_rate": 0.065, 63 | "day_count": "A360", 64 | "business_day_adjustment": 0 65 | } 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // extern crate probability; 2 | // extern crate rand_chacha; 3 | // extern crate rand_pcg; 4 | 5 | use rand; 6 | //use rand::{SeedableRng}; 7 | //use chrono::{Local,DateTime,NaiveDate,NaiveTime,Datelike, Duration}; 8 | //use rand::distributions::{Standard,Uniform}; 9 | //use rand::distributions::Distribution; 10 | //use rand_distr::StandardNormal; 11 | mod equity; 12 | mod core; 13 | mod utils; 14 | mod cmdty; 15 | mod rates; 16 | 17 | use std::fs; 18 | use std::path::Path; 19 | 20 | use rand::prelude::*; 21 | use serde::Deserialize; 22 | 23 | 24 | use std::io::Read; 25 | use std::collections::HashMap; 26 | use std::error::Error; 27 | //use csv; 28 | //use std::env::{args,Args}; 29 | //use utils::read_csv; 30 | //use utils::RNG; 31 | 32 | //use std::env::{args, temp_dir}; 33 | //use rand::Rng; 34 | 35 | use clap::{App, Arg, ArgMatches, SubCommand}; 36 | //use std::env; 37 | use utils::{parse_json,build_cli}; 38 | use std::time::{Instant}; 39 | #[allow(dead_code)] 40 | #[allow(unused_variables)] 41 | fn main() { 42 | let matches = build_cli::build_cli().get_matches(); 43 | // Build the CLI 44 | 45 | // Match and dispatch subcommand 46 | match matches.subcommand() { 47 | ("build", Some(build_matches)) => build_cli::handle_build(build_matches), 48 | ("file", Some(file_matches)) => build_cli::handle_file(file_matches), 49 | ("dir", Some(dir_matches)) => build_cli::handle_dir(dir_matches), 50 | ("interactive", Some(_)) => build_cli::handle_interactive(), 51 | _ => { 52 | // No mode specified or unknown mode 53 | println!("No valid subcommand specified. Use --help to see available options."); 54 | } 55 | } 56 | 57 | 58 | 59 | } 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/rates/build_contracts.rs: -------------------------------------------------------------------------------- 1 | use crate::rates; 2 | use crate::rates::deposits::Deposit; 3 | use chrono::{NaiveDate,Local,Weekday}; 4 | use chrono::Datelike; 5 | use crate::rates::fra::FRA; 6 | use crate::core::traits::{Instrument,Rates}; 7 | use crate::core::utils::{Contract, Contracts}; 8 | use crate::rates::utils::TermStructure; 9 | 10 | pub fn build_ir_contracts(data: Contract) -> Box { 11 | let rate_data = data.rate_data.clone().unwrap(); 12 | let mut start_date_str = rate_data.start_date; // Only for 0M case 13 | let mut maturity_date_str = rate_data.maturity_date; 14 | let current_date = Local::today(); 15 | let maturity_date = rates::utils::convert_mm_to_date(maturity_date_str); 16 | let start_date = rates::utils::convert_mm_to_date(start_date_str); 17 | if rate_data.instrument.as_str() == "Deposit" { 18 | let mut deposit = Deposit { 19 | start_date: start_date, 20 | maturity_date: maturity_date, 21 | valuation_date: current_date.naive_utc(), 22 | notional: rate_data.notional, 23 | fix_rate: rate_data.fix_rate, 24 | day_count: rates::utils::DayCountConvention::Act360, 25 | business_day_adjustment: 0, 26 | term_structure: None 27 | }; 28 | match rate_data.day_count.as_str() { 29 | "Act360" |"A360" => { 30 | deposit.day_count = rates::utils::DayCountConvention::Act360; 31 | } 32 | "Act365" |"A365" => { 33 | deposit.day_count = rates::utils::DayCountConvention::Act365; 34 | } 35 | "Thirty360" |"30/360" => { 36 | deposit.day_count = rates::utils::DayCountConvention::Thirty360; 37 | } 38 | _ => {} 39 | } 40 | let mut ird:Box = Box::new(deposit); 41 | return ird; 42 | } 43 | else if rate_data.instrument.as_str()=="FRA" { 44 | //let mut start_date_str = rate_data.start_date; 45 | //let mut maturity_date_str = rate_data.maturity_date; 46 | //let current_date = Local::today(); 47 | //let maturity_date = rates::utils::convert_mm_to_date(maturity_date_str); 48 | //let start_date = rates::utils::convert_mm_to_date(start_date_str); 49 | let mut fra = FRA { 50 | start_date: start_date, 51 | maturity_date: maturity_date, 52 | valuation_date: current_date.naive_utc(), 53 | notional: rate_data.notional, 54 | currency: rate_data.currency, 55 | fix_rate: rate_data.fix_rate, 56 | day_count: rates::utils::DayCountConvention::Act360, 57 | business_day_adjustment: 0, 58 | term_structure: None 59 | }; 60 | match rate_data.day_count.as_str() { 61 | "Act360" |"A360" => { 62 | fra.day_count = rates::utils::DayCountConvention::Act360; 63 | } 64 | "Act365" |"A365" => { 65 | fra.day_count = rates::utils::DayCountConvention::Act365; 66 | } 67 | "Thirty360" |"30/360" => { 68 | fra.day_count = rates::utils::DayCountConvention::Thirty360; 69 | } 70 | _ => {} 71 | } 72 | let ird:Box = Box::new(fra); 73 | return ird; 74 | } 75 | else { 76 | panic!("Invalid asset"); 77 | } 78 | } 79 | 80 | pub fn build_ir_contracts_from_json(data: Vec) -> Vec> { 81 | let mut irds:Vec> = Vec::new(); 82 | for contract in data { 83 | let ird = build_ir_contracts(contract); 84 | irds.push(ird); 85 | } 86 | return irds; 87 | } 88 | pub fn build_term_structure(mut contracts:Vec>) -> TermStructure { 89 | let mut ts:rates::utils::TermStructure = rates::utils::TermStructure::new(vec![],vec![],vec![], 90 | rates::utils::DayCountConvention::Act360); 91 | let mut contract = contracts[0].as_mut(); 92 | ts.discount_factor.push(contract.get_maturity_discount_factor()); 93 | ts.date.push(contract.get_maturity_date()); 94 | ts.rate.push(contract.get_rate()); 95 | for i in 1..contracts.len(){ 96 | let mut contract = contracts[i].as_mut(); 97 | contract.set_term_structure(ts.clone()); 98 | ts.discount_factor.push(contract.get_maturity_discount_factor()); 99 | ts.date.push(contract.get_maturity_date()); 100 | ts.rate.push(contract.get_rate()); 101 | } 102 | return ts 103 | } -------------------------------------------------------------------------------- /src/rates/deposits.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Local, NaiveDate}; 2 | use crate::core::traits::{Instrument,Rates}; 3 | use crate::rates::utils::{DayCountConvention,TermStructure}; 4 | /* 5 | "" An deposit is an agreement to borrow money interbank at the Ibor fixing rate starting on the start 6 | date and repaid on the maturity date with the interest amount calculated according to a day 7 | count convention and dates calculated according to a calendar and business day adjustment rule. 8 | */ 9 | pub struct Deposit { 10 | pub start_date: NaiveDate, 11 | pub maturity_date: NaiveDate, 12 | pub valuation_date: NaiveDate, 13 | pub notional: f64, 14 | pub fix_rate: f64, 15 | pub day_count: DayCountConvention, 16 | pub business_day_adjustment: i8, 17 | pub term_structure: Option, 18 | } 19 | impl Deposit { 20 | pub fn new(start_date: NaiveDate, maturity_date: NaiveDate, valuation_date: NaiveDate, 21 | notional: f64, fix_rate: f64, day_count: DayCountConvention, 22 | business_day_adjustment: i8) -> Deposit { 23 | Deposit { 24 | start_date, 25 | maturity_date, 26 | valuation_date, 27 | notional, 28 | fix_rate, 29 | day_count, 30 | business_day_adjustment, 31 | term_structure: None, 32 | } 33 | } 34 | pub fn builder(start_date: String,maturity_date:String,notional: f64, fix_rate: f64,day_count: String) ->Deposit{ 35 | 36 | let today = Local::today(); 37 | let start_date = NaiveDate::parse_from_str(&start_date, "%Y-%m-%d").expect("Invalid date format"); 38 | let maturity_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); 39 | let mut deposit = Deposit { 40 | start_date: start_date, 41 | maturity_date: maturity_date, 42 | valuation_date: today.naive_utc(), 43 | notional: 1000000.0, 44 | fix_rate: 0.05, 45 | day_count: DayCountConvention::Act360, 46 | business_day_adjustment: 0, 47 | term_structure: None, 48 | }; 49 | match day_count.as_str() { 50 | "Act360" |"A360" => { 51 | deposit.day_count = DayCountConvention::Act360; 52 | } 53 | "Act365" |"A365" => { 54 | deposit.day_count = DayCountConvention::Act365; 55 | } 56 | "Thirty360" |"30/360" => { 57 | deposit.day_count = DayCountConvention::Thirty360; 58 | } 59 | _ => {} 60 | } 61 | return deposit; 62 | } 63 | pub fn get_start_date(&self) -> NaiveDate { 64 | self.start_date 65 | } 66 | 67 | pub fn get_notional(&self) -> f64 { 68 | self.notional 69 | } 70 | pub fn get_rate(&self) -> f64 { 71 | let df = self.get_discount_factor(); 72 | let year_fraction = self.get_year_fraction(self.start_date); 73 | -df.ln() / year_fraction 74 | } 75 | pub fn get_business_day_adjustment(&self) -> i8 { 76 | self.business_day_adjustment 77 | } 78 | pub fn get_year_fraction(&self,date:NaiveDate) -> f64 { 79 | let duration = self.maturity_date.signed_duration_since(date); 80 | let year_fraction = duration.num_days() as f64 / self.day_count.num_of_days() as f64; 81 | year_fraction 82 | } 83 | pub fn get_discount_factor(&self) -> f64 { 84 | let year_fraction = self.get_year_fraction(self.start_date); 85 | let discount_factor = 1.0 / (1.0 + self.fix_rate * year_fraction); 86 | discount_factor 87 | } 88 | pub fn get_remaining_interest_amount(&self) -> f64 { 89 | let year_fraction = self.get_year_fraction(self.valuation_date); 90 | let interest_amount = self.notional * self.fix_rate * year_fraction; 91 | interest_amount 92 | } 93 | pub fn get_value(&self) -> f64 { 94 | //let discount_factor = self.get_discount_factor(); 95 | // 96 | //let pv = self.notional * discount_factor + interest_amount; 97 | let value = (1.0 + self.fix_rate * self.get_year_fraction(self.start_date)) * self.notional; 98 | value 99 | } 100 | 101 | pub fn get_pv(&self,curve:&TermStructure) -> f64 { 102 | let df = curve.interpolate_log_linear(self.valuation_date,self.maturity_date); 103 | let value = self.get_value() * df; 104 | return value; 105 | } 106 | } 107 | impl Rates for Deposit{ 108 | fn get_implied_rates(&self) -> f64 { 109 | let curve = self.term_structure.as_ref().expect("Term structure is not set"); 110 | let df = curve.interpolate_log_linear(self.valuation_date,self.maturity_date); 111 | let implied_rate = (1.0/df - 1.0)/self.get_year_fraction(self.valuation_date); 112 | return implied_rate; 113 | } 114 | fn get_maturity_date(&self) -> NaiveDate { 115 | self.maturity_date 116 | } 117 | fn get_rate(&self) -> f64 { 118 | self.fix_rate 119 | } 120 | fn get_maturity_discount_factor(&self) -> f64 { 121 | self.get_discount_factor() 122 | } 123 | fn get_day_count(&self) -> &DayCountConvention { 124 | &self.day_count 125 | } 126 | fn set_term_structure(&mut self,term_structure:TermStructure) { 127 | self.term_structure = Some(term_structure); 128 | } 129 | } 130 | // impl Instrument for Deposit { 131 | // fn npv(&self) -> f64 { 132 | // self.get_pv() 133 | // } 134 | // } 135 | -------------------------------------------------------------------------------- /src/rates/fra.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Local, NaiveDate}; 2 | use crate::core::traits::{Instrument,Rates}; 3 | use crate::rates::utils::{DayCountConvention,TermStructure}; 4 | 5 | /* 6 | A forward rate agreement or simply "forward contract" is an agreement to exchange a fixed pre-agreed rate for a 7 | floating rate is not known until some specified 8 | future fixing date. The FRA payment occurs on or soon after this date 9 | on the FRA settlement date. Typically the timing gap is two days. 10 | 11 | */ 12 | 13 | pub struct FRA { 14 | pub start_date: NaiveDate, //The date the FRA starts to accrue interest 15 | pub maturity_date: NaiveDate, 16 | pub valuation_date: NaiveDate, 17 | pub notional: f64, 18 | pub currency: String, 19 | pub fix_rate: f64, 20 | pub day_count: DayCountConvention, 21 | pub business_day_adjustment: i8, 22 | pub term_structure: Option, 23 | } 24 | impl FRA { 25 | pub fn new(start_date: NaiveDate, maturity_date: NaiveDate, valuation_date: NaiveDate, 26 | notional: f64, fix_rate: f64, day_count: DayCountConvention, 27 | business_day_adjustment: i8) -> FRA { 28 | FRA { 29 | start_date, 30 | maturity_date, 31 | valuation_date, 32 | notional, 33 | currency: String::from("USD"), 34 | fix_rate, 35 | day_count, 36 | business_day_adjustment, 37 | term_structure: None, 38 | } 39 | } 40 | pub fn builder(start_date: String,maturity_date:String,notional: f64, fix_rate: f64,day_count: String) ->FRA{ 41 | 42 | let today = Local::today(); 43 | let start_date = NaiveDate::parse_from_str(&start_date, "%Y-%m-%d").expect("Invalid date format"); 44 | let maturity_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); 45 | let mut fra = FRA { 46 | start_date: start_date, 47 | maturity_date: maturity_date, 48 | valuation_date: today.naive_utc(), 49 | notional: notional, 50 | currency: String::from("USD"), 51 | fix_rate: fix_rate, 52 | day_count: DayCountConvention::Act360, 53 | business_day_adjustment: 0, 54 | term_structure: None, 55 | }; 56 | match day_count.as_str() { 57 | "Act360" |"A360" => { 58 | fra.day_count = DayCountConvention::Act360; 59 | } 60 | "Act365" |"A365" => { 61 | fra.day_count = DayCountConvention::Act365; 62 | } 63 | "Thirty360" |"30/360" => { 64 | fra.day_count = DayCountConvention::Thirty360; 65 | } 66 | _ => {} 67 | } 68 | fra 69 | } 70 | pub fn get_year_fraction(&self,date:NaiveDate) -> f64 { 71 | let duration = self.maturity_date.signed_duration_since(date); 72 | let year_fraction = duration.num_days() as f64 / self.day_count.num_of_days() as f64; 73 | year_fraction 74 | } 75 | pub fn get_discount_factor(&self,df_start_date:f64) -> f64 { 76 | let year_fraction = self.get_year_fraction(self.start_date); 77 | let discount_factor = df_start_date / (1.0 + self.fix_rate * year_fraction); 78 | discount_factor 79 | } 80 | } 81 | 82 | impl Rates for FRA{ 83 | fn get_implied_rates(&self) -> f64 { 84 | let curve = self.term_structure.as_ref().expect("Term structure is not set"); 85 | let df = curve.interpolate_log_linear(self.valuation_date,self.maturity_date); 86 | let implied_rate = (1.0/df - 1.0)/self.get_year_fraction(self.valuation_date); 87 | return implied_rate; 88 | } 89 | fn get_maturity_date(&self) -> NaiveDate { 90 | self.maturity_date 91 | } 92 | fn get_rate(&self) -> f64 { 93 | let df = self.get_maturity_discount_factor(); 94 | let time = self.get_year_fraction(self.valuation_date); 95 | -df.ln() / time 96 | } 97 | fn get_maturity_discount_factor(&self) -> f64 { 98 | let curve = self.term_structure.as_ref().expect("Term structure is not set"); 99 | let df = curve.interpolate_log_linear(self.valuation_date,self.start_date); 100 | self.get_discount_factor(df) 101 | } 102 | fn get_day_count(&self) -> &DayCountConvention { 103 | &self.day_count 104 | } 105 | fn set_term_structure(&mut self,term_structure:TermStructure) { 106 | self.term_structure = Some(term_structure); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/rates/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod deposits; 2 | pub(crate) mod utils; 3 | pub mod fra; 4 | pub mod build_contracts; 5 | -------------------------------------------------------------------------------- /src/rates/utils.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Local, NaiveDate, Weekday}; 2 | use chrono::Datelike; 3 | use crate::rates::deposits::Deposit; 4 | use crate::core::traits::Rates; 5 | use serde::{Serialize,Deserialize}; 6 | fn is_weekend(date: NaiveDate) -> bool { 7 | // Check if the day of the week is Saturday (6) or Sunday (7) 8 | let day_of_week = date.weekday(); 9 | day_of_week == Weekday::Sat || day_of_week == Weekday::Sun 10 | } 11 | 12 | fn is_holiday(date: NaiveDate) -> bool { 13 | // Check if the date is Christmas 14 | if date.month() == 12 && date.day() == 25{ 15 | return true; 16 | } 17 | else if date.month() == 1 && date.day() == 1{ // Check if the date is New Year's Day 18 | return true; 19 | } 20 | else if date.month() == 1 && date.weekday() == Weekday::Mon 21 | && date.day() > 14 && date.day() <= 21 { 22 | // Check if the date is Martin Luther King Jr. Day 23 | return true; 24 | } 25 | else if date.month() == 11 && 26 | date.weekday() == Weekday::Thu && date.day() > 21 && date.day() <= 28{ 27 | // Check if the date is in November and falls on the fourth Thursday Thanksgiving Day 28 | return true; 29 | } 30 | else if date.month() == 9 && date.weekday() == Weekday::Mon && date.day() <= 7{ 31 | // Check if the date is in September and falls on the first Monday Labor Day 32 | return true; 33 | } 34 | return false; 35 | } 36 | 37 | fn adjust_for_weekend(mut date: NaiveDate) -> NaiveDate { 38 | // Increment the date until it's not a weekend 39 | while is_holiday(date) || is_holiday(date) { 40 | date = date.succ(); 41 | 42 | } 43 | date 44 | } 45 | #[derive(Clone,Debug,Serialize,Deserialize)] 46 | pub enum DayCountConvention{ 47 | Act365, 48 | Act360, 49 | Thirty360, 50 | } 51 | impl DayCountConvention{ 52 | pub fn num_of_days(&self) -> usize 53 | { 54 | match self { 55 | DayCountConvention::Act365 => 365, 56 | DayCountConvention::Act360 => 360, 57 | DayCountConvention::Thirty360 => 360, 58 | } 59 | } 60 | pub fn get_year_fraction(&self,start_date:NaiveDate,maturity_date:NaiveDate) -> f64 { 61 | let duration = maturity_date.signed_duration_since(start_date); 62 | let year_fraction = duration.num_days() as f64 / self.num_of_days() as f64; 63 | year_fraction 64 | } 65 | } 66 | 67 | #[derive(Clone,Debug)] 68 | pub struct TermStructure { 69 | pub date: Vec, 70 | pub discount_factor: Vec, 71 | pub rate: Vec, 72 | pub day_count: DayCountConvention, 73 | } 74 | 75 | impl TermStructure { 76 | pub fn new(date: Vec, discount_factor: Vec,rate:Vec,day_count:DayCountConvention) -> TermStructure { 77 | TermStructure { 78 | date, 79 | discount_factor, 80 | rate, 81 | day_count 82 | } 83 | } 84 | 85 | pub fn interpolate_log_linear(&self,val_date:NaiveDate,maturity_date:NaiveDate)-> f64{ 86 | let year_fraction = self.get_year_fraction(val_date); 87 | let target_yf = maturity_date.signed_duration_since(val_date).num_days() as f64 88 | / self.day_count.num_of_days() as f64; 89 | let mut df1 = 1.0; 90 | let mut df2 = 1.0; 91 | let mut t1 = 0.0; 92 | let mut t2 = 0.0; 93 | for (i, time) in year_fraction.iter().enumerate() { 94 | if time==&target_yf{ 95 | return self.discount_factor[i]; 96 | } 97 | else if time< &target_yf { 98 | t1 = *time; 99 | df1 = self.discount_factor[i]; 100 | } 101 | else if time> &target_yf { 102 | t2 = *time; 103 | df2 = self.discount_factor[i]; 104 | break; 105 | } 106 | 107 | } 108 | let log_df1 = f64::ln(df1); 109 | let log_df2 = f64::ln(df2); 110 | let w = (target_yf - t1) / (t2 - t1); 111 | let log_df = log_df1 + w * (log_df2 - log_df1); 112 | let df = f64::exp(log_df); 113 | return df; 114 | //let dfs = self.discount_factor; 115 | 116 | } 117 | pub fn get_year_fraction(&self,val_date:NaiveDate) -> Vec { 118 | let mut year_fraction_vec:Vec = Vec::new(); 119 | for time in self.date.iter() { 120 | let duration = time.signed_duration_since(val_date); 121 | let year_fraction = duration.num_days() as f64 / self.day_count.num_of_days() as f64; 122 | year_fraction_vec.push(year_fraction); 123 | } 124 | year_fraction_vec 125 | } 126 | pub fn rates(&self,val_date:NaiveDate) -> Vec { 127 | let mut rates:Vec = Vec::new(); 128 | for i in 0..self.discount_factor.len() { 129 | let rate = (1.0 / self.discount_factor[i] - 1.0) / self.day_count.get_year_fraction(val_date,self.date[i]); 130 | rates.push(rate); 131 | } 132 | return rates; 133 | } 134 | pub fn build_term_structure(&self,valuation_date:NaiveDate,deposits:Vec) -> TermStructure { 135 | let mut discount_factor:Vec = Vec::new(); 136 | let mut rate:Vec = Vec::new(); 137 | let mut dates:Vec = Vec::new(); 138 | for deposit in deposits.iter() { 139 | discount_factor.push(deposit.get_discount_factor()); 140 | dates.push(deposit.get_maturity_date()); 141 | rate.push(deposit.get_rate()); 142 | } 143 | let day_count = deposits[0].day_count.clone(); 144 | let mut term_structure = TermStructure::new(dates,discount_factor,rate,day_count); 145 | return term_structure; 146 | } 147 | } 148 | 149 | pub fn convert_mm_to_date(mut date: String) -> NaiveDate { 150 | let current_date = Local::today(); 151 | date.pop(); 152 | let month = date.parse::().unwrap(); 153 | 154 | let (new_year, new_month) = if current_date.month() + month > 12 { 155 | let year = ((current_date.month() + month) / 12) as i32; 156 | let m:u32 = (year * 12) as u32; 157 | let new_month = current_date.month() + month; 158 | (current_date.year() + year, new_month-m) 159 | } else { 160 | (current_date.year(), current_date.month() + month) 161 | }; 162 | let date_in_months = current_date.with_year(new_year).unwrap_or(current_date) 163 | .with_month(new_month).unwrap_or(current_date); 164 | let mut maturity_date = date_in_months.naive_utc(); 165 | maturity_date = adjust_for_weekend(maturity_date); 166 | return maturity_date; 167 | } 168 | 169 | -------------------------------------------------------------------------------- /src/utils/RNG.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | use rand::distributions::{Standard,Uniform}; 3 | use rand::distributions::Distribution; 4 | use rand_distr::StandardNormal; 5 | use std::f64::consts::PI; 6 | use libm::cos; 7 | use libm::sin; 8 | use std::io::Write; // bring trait into scope 9 | use byteorder::{ByteOrder, LittleEndian,BigEndian}; 10 | use byteorder::WriteBytesExt; 11 | use byteorder::ReadBytesExt; 12 | use std::fs::File; 13 | use std::io::prelude::*; 14 | use std::fs; 15 | use std::path::Path; 16 | use std::env::temp_dir; 17 | /// Random Number Generator using marsaglia polar method 18 | fn generate_standard_normal_marsaglia_polar() -> (f64, f64) { 19 | let mut rng = rand::thread_rng(); 20 | let mut x = 0.0; 21 | let mut y = 0.0; 22 | let mut s = 0.0f64; 23 | 24 | while true { 25 | x = Uniform::new(0.0,1.0).sample(&mut rng)*2.0 -1.0; 26 | y = Uniform::new(0.0,1.0).sample(&mut rng)*2.0 -1.0; 27 | s = x*x + y*y; 28 | if s<1.0f64 && s != 0.0f64 { 29 | break; 30 | } 31 | } 32 | let i = ((-2.0 * s.ln()) / s).sqrt(); 33 | (i*x,i*y) 34 | 35 | } 36 | /// Random Number Generator using Box Muller method 37 | fn generate_standard_normal_box() -> (f64, f64) { 38 | let mut rng = rand::thread_rng(); 39 | 40 | let r:f64 = Uniform::new(0.0,1.0).sample(&mut rng); 41 | let p:f64 = Uniform::new(0.0,1.0).sample(&mut rng); 42 | 43 | let tmp:f64 = (-2.0*r.ln()).sqrt(); 44 | (tmp*cos(p*2.0*PI),tmp*sin(p*2.0*PI)) 45 | 46 | } 47 | 48 | pub struct MonteCarloSimulation{ 49 | pub antithetic:bool, 50 | pub moment_matching:bool, 51 | pub dimentation: u64, 52 | pub size: u64, 53 | pub standard_normal_vector: Vec, 54 | pub standard_normal_matrix: Vec> 55 | } 56 | impl MonteCarloSimulation{ 57 | pub fn set_standard_normal_vector(&mut self) { 58 | let mut dir = temp_dir(); 59 | dir.push("rng1d"); 60 | let rng_dir = dir.as_path(); 61 | if !rng_dir.exists() { 62 | let _ = fs::create_dir(rng_dir); 63 | } 64 | dir.push("1dt.bin"); 65 | let path = dir.as_path(); 66 | let mut rn_vec:Vec = Vec::new(); 67 | if path.exists() { 68 | rn_vec = read_from_file_byteorder(path).unwrap(); 69 | self.standard_normal_vector = rn_vec; 70 | } 71 | else{ 72 | let mut rng = rand::thread_rng(); 73 | let mut rn_vec:Vec = Vec::new(); 74 | 75 | for i in 0..self.size{ 76 | let rn = rng.sample(StandardNormal); 77 | rn_vec.push(rn); 78 | if self.antithetic{ 79 | rn_vec.push(-rn) 80 | } 81 | } 82 | if self.moment_matching{ 83 | let sum = rn_vec.iter().sum::() as f64; 84 | let mean = sum / rn_vec.len() as f64; 85 | let variance = rn_vec.iter().map(|x| { 86 | let diff = mean -(*x as f64); 87 | diff*diff 88 | }).sum::()/rn_vec.len() as f64; 89 | let std_dev = variance.sqrt(); 90 | let mut mo_rn_vec = vec![]; 91 | for i in 0..rn_vec.len() { 92 | let mo_rn = (rn_vec[i]-mean)/std_dev as f64; 93 | mo_rn_vec.push(mo_rn) 94 | } 95 | rn_vec = mo_rn_vec; 96 | } 97 | self.standard_normal_vector = rn_vec.clone(); 98 | write_to_file_byteorder(&rn_vec, path).unwrap(); 99 | } 100 | } 101 | pub fn get_standard_normal_vector(&self) ->&Vec{ 102 | let ptr = &self.standard_normal_vector; 103 | ptr 104 | } 105 | 106 | } 107 | 108 | pub fn get_matrix_standard_normal(size_n:u64,size_m:u64)-> Vec> { 109 | // let mut dir = temp_dir(); 110 | // dir.push("rng2d"); 111 | // dir.push("1dt.bin"); 112 | // let path = dir.as_path(); 113 | 114 | let mut rng = rand::thread_rng(); 115 | let mut rn_vec_n:Vec> = Vec::new(); 116 | for i in 0..size_n{ 117 | let mut rn_vec_m:Vec = Vec::new(); 118 | for j in 0..size_m{ 119 | rn_vec_m.push(rng.sample(StandardNormal)); 120 | } 121 | rn_vec_n.push(rn_vec_m); 122 | } 123 | rn_vec_n 124 | } 125 | /// Write a vector of f64 to a file using BigEndian byte order 126 | fn write_to_file_byteorder>(data: &[f64], path: P) -> std::io::Result<()> { 127 | let mut file = File::create(path)?; 128 | for f in data { 129 | file.write_f64::(*f)?; 130 | } 131 | Ok(()) 132 | } 133 | /// Read a vector of f64 from a file using BigEndian byte order 134 | fn read_from_file_byteorder>(path: P) -> std::io::Result> { 135 | let mut file = File::open(path)?; 136 | let buf_len = file.metadata()?.len() / 8; // 8 bytes for one f64 137 | let mut buf: Vec = vec![0.0; buf_len.try_into().unwrap()]; 138 | file.read_f64_into::(&mut buf)?; 139 | Ok(buf) 140 | } -------------------------------------------------------------------------------- /src/utils/build_cli.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | use clap::{App, Arg, SubCommand}; 3 | use std::fs::File; 4 | use utils::parse_json; 5 | use crate::utils; 6 | use std::fs; 7 | use std::path::Path; 8 | use std::{io, thread}; 9 | use std::io::Write; 10 | use crate::equity::blackscholes; 11 | use crate::equity::montecarlo; 12 | pub fn build_cli() -> App<'static, 'static> { 13 | App::new("RustyQLib Quant Library for Option Pricing") 14 | .version("0.0.2") 15 | .author("Siddharth Singh ") 16 | .about("Pricing and risk management of financial derivatives") 17 | .subcommand( 18 | SubCommand::with_name("build") 19 | .about("Building the curve / Vol surface") 20 | .arg( 21 | Arg::with_name("input") 22 | .short("i") 23 | .long("input") 24 | .value_name("FILE") 25 | .help("Input financial contracts to use in construction") 26 | .required(true) 27 | .takes_value(true), 28 | ) 29 | .arg( 30 | Arg::with_name("output") 31 | .short("o") 32 | .long("output") 33 | .value_name("FILE") 34 | .help("Output file name") 35 | .required(true) 36 | .takes_value(true), 37 | ), 38 | ) 39 | .subcommand( 40 | SubCommand::with_name("file") 41 | .about("Pricing a single contract") 42 | .arg( 43 | Arg::with_name("input") 44 | .short("i") 45 | .long("input") 46 | .value_name("FILE") 47 | .help("Pricing a single contract") 48 | .required(true) 49 | .takes_value(true), 50 | ) 51 | .arg( 52 | Arg::with_name("output") 53 | .short("o") 54 | .long("output") 55 | .value_name("FILE") 56 | .help("Output file name") 57 | .required(true) 58 | .takes_value(true), 59 | ), 60 | ) 61 | .subcommand( 62 | SubCommand::with_name("dir") 63 | .about("Pricing all contracts in a directory") 64 | .arg( 65 | Arg::with_name("input") 66 | .short("i") 67 | .long("input") 68 | .value_name("DIR") 69 | .help("Pricing all contracts in a directory") 70 | .required(true) 71 | .takes_value(true), 72 | ) 73 | .arg( 74 | Arg::with_name("output") 75 | .short("o") 76 | .long("output") 77 | .value_name("DIR") 78 | .help("Output priced contracts to a directory") 79 | .required(true) 80 | .takes_value(true), 81 | ), 82 | ) 83 | .subcommand( 84 | SubCommand::with_name("interactive").about("Interactive mode"), 85 | ) 86 | } 87 | 88 | /// Handle the "build" subcommand. 89 | pub fn handle_build(matches: &clap::ArgMatches<'_>) { 90 | let input_file = matches.value_of("input").unwrap(); 91 | let output_file = matches.value_of("output").unwrap(); 92 | 93 | // We measure the time of the operation 94 | measure_time("build_curve", || { 95 | let mut file = File::open(input_file).expect("Failed to open JSON file"); 96 | parse_json::build_curve(&mut file, output_file); 97 | println!("(Stub) build_curve from {}", input_file); 98 | }); 99 | 100 | // Save or do something with output_file if needed 101 | } 102 | 103 | /// Handle the "file" subcommand. 104 | pub fn handle_file(matches: &clap::ArgMatches<'_>) { 105 | let input_file = matches.value_of("input").unwrap(); 106 | let output_file = matches.value_of("output").unwrap(); 107 | 108 | measure_time("parse_contract (single file)", || { 109 | let mut file = File::open(input_file).expect("Failed to open JSON file"); 110 | parse_json::parse_contract(&mut file, output_file); 111 | println!("(Stub) parse_contract from {}", input_file); 112 | }); 113 | } 114 | 115 | /// Handle the "dir" subcommand. 116 | pub fn handle_dir(matches: &clap::ArgMatches<'_>) { 117 | let input_dir = matches.value_of("input").unwrap(); 118 | let output_dir = matches.value_of("output").unwrap(); 119 | 120 | let input_path = Path::new(input_dir); 121 | let output_path = Path::new(output_dir); 122 | 123 | measure_time("parse_contract (directory)", || { 124 | // Read the directory 125 | let files = fs::read_dir(input_path).expect("Failed to read input directory"); 126 | 127 | for file_result in files { 128 | let dir_entry = file_result.expect("Failed to read entry"); 129 | let path = dir_entry.path(); 130 | 131 | if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") { 132 | let mut file = File::open(&path).expect("Failed to open JSON file"); 133 | 134 | // Construct the corresponding output file path 135 | let output_file_path = output_path.join( 136 | path.file_name().expect("Failed to get file name"), 137 | ); 138 | 139 | parse_json::parse_contract(&mut file, output_file_path.to_str().unwrap()); 140 | println!( 141 | "(Stub) parse_contract from {:?} -> {:?}", 142 | path, output_file_path 143 | ); 144 | } 145 | } 146 | }); 147 | } 148 | 149 | /// Handle the "interactive" subcommand. 150 | pub fn handle_interactive() { 151 | println!("Welcome to Option pricing CLI"); 152 | loop { 153 | println!("Do you want to price an option (1), calculate implied volatility (2), or exit (3)?"); 154 | 155 | // Prompt user 156 | print!("> "); 157 | io::stdout().flush().expect("Failed to flush stdout"); 158 | 159 | // Read user input 160 | let mut input = String::new(); 161 | io::stdin() 162 | .read_line(&mut input) 163 | .expect("Failed to read line"); 164 | 165 | let selection: u8 = match input.trim().parse() { 166 | Ok(num) => num, 167 | Err(_) => { 168 | eprintln!("Please enter a valid number!"); 169 | continue; 170 | } 171 | }; 172 | 173 | match selection { 174 | 1 => { 175 | println!("Do you want to use the Black-Scholes (1) or Monte-Carlo (2) model?"); 176 | print!("> "); 177 | io::stdout().flush().expect("Failed to flush stdout"); 178 | 179 | let mut model_input = String::new(); 180 | io::stdin() 181 | .read_line(&mut model_input) 182 | .expect("Failed to read line"); 183 | 184 | let model_num: u8 = match model_input.trim().parse() { 185 | Ok(num) => num, 186 | Err(_) => { 187 | eprintln!("Please enter a valid number!"); 188 | continue; 189 | } 190 | }; 191 | 192 | match model_num { 193 | 1 => { 194 | blackscholes::option_pricing(); 195 | println!("(Stub) blackscholes::option_pricing()"); 196 | } 197 | 2 => { 198 | montecarlo::option_pricing(); 199 | println!("(Stub) montecarlo::option_pricing()"); 200 | } 201 | _ => println!("You gave a wrong number! Accepted arguments are 1 and 2."), 202 | } 203 | } 204 | 2 => { 205 | blackscholes::implied_volatility(); 206 | println!("(Stub) blackscholes::implied_volatility()"); 207 | } 208 | 3 => { 209 | println!("Exiting interactive mode..."); 210 | break; 211 | } 212 | _ => println!("You gave a wrong number! Accepted arguments are 1, 2, or 3."), 213 | } 214 | } 215 | } 216 | 217 | /// Helper function to measure the time taken by a closure. 218 | fn measure_time(label: &str, f: F) { 219 | let start_time = Instant::now(); 220 | f(); 221 | let elapsed_time = start_time.elapsed(); 222 | println!("Time taken for {}: {:?}", label, elapsed_time); 223 | } 224 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod read_csv; 2 | pub mod RNG; 3 | pub mod stochastic_processes; 4 | pub mod parse_json; 5 | pub mod build_cli; 6 | -------------------------------------------------------------------------------- /src/utils/parse_cli.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthqs/RustyQLib/8da29ed630fe6bdc646a5a1dec5f8658b575e2c6/src/utils/parse_cli.rs -------------------------------------------------------------------------------- /src/utils/parse_json.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fs::File; 3 | use std::fs; 4 | use byteorder::{ByteOrder, LittleEndian,BigEndian}; 5 | use std::io::Read; 6 | use chrono::{Datelike, Local, NaiveDate}; 7 | use crate::core::quotes::Quote; 8 | use crate::core::termstructure::YieldTermStructure; 9 | use crate::equity::vanila_option::{EquityOption}; 10 | //use crate::core::utils::{dN, N}; 11 | //use super::vanila_option::{EquityOption}; 12 | use crate::equity::utils::{Engine}; 13 | //use crate::cmdty::cmdty_option::{CmdtyOption}; 14 | use crate::core::trade; 15 | use crate::cmdty::cmdty_option; 16 | use crate::core::traits::{Instrument, Rates}; 17 | use crate::core::utils::{Contract,CombinedContract, ContractOutput, Contracts, OutputJson,EngineType}; 18 | use crate::core::utils::ContractStyle; 19 | use crate::core::traits::Greeks; 20 | use std::io::Write; 21 | use std::env::temp_dir; 22 | //use crate::read_csv::read_ts; 23 | use crate::rates; 24 | use crate::rates::deposits::Deposit; 25 | use crate::rates::build_contracts::{build_ir_contracts, build_ir_contracts_from_json, build_term_structure}; 26 | use crate::equity::build_contracts::{build_eq_contracts_from_json}; 27 | use crate::equity::vol_surface::VolSurface; 28 | use crate::equity::handle_equity_contracts::handle_equity_contract; 29 | 30 | use rayon::prelude::*; 31 | /// This function saves the output to a file and returns the path to the file. 32 | pub fn save_to_file<'a>(output_folder: &'a str, subfolder: &'a str, filename: &'a str, output: &'a str) -> String { 33 | let mut dir = std::path::PathBuf::from(output_folder); 34 | if subfolder.len() > 0 { 35 | dir.push(subfolder); 36 | } 37 | let _dir = dir.as_path(); 38 | if !_dir.exists() { 39 | let _ = fs::create_dir(_dir); 40 | } 41 | dir.push(filename); 42 | let mut file = File::create(&dir).expect("Failed to create file"); 43 | file.write_all(output.as_bytes()).expect("Failed to write to file"); 44 | return dir.as_path().to_str().unwrap().to_string(); 45 | } 46 | 47 | /// This function different types of curves such as term structure, volatility surface, etc. 48 | pub fn build_curve(mut file: &mut File,output_filename: &str)->() { 49 | let mut contents = String::new(); 50 | file.read_to_string(&mut contents) 51 | .expect("Failed to read JSON file"); 52 | let list_contracts: Contracts = serde_json::from_str(&contents).expect("Failed to deserialize JSON"); 53 | if list_contracts.contracts.len() == 0 { 54 | panic!("No contracts found in JSON file"); 55 | } 56 | else if list_contracts.asset=="EQ"{ 57 | println!("Building volatility surface"); 58 | let mut contracts:Vec> = build_eq_contracts_from_json(list_contracts.contracts); 59 | let vol_surface = VolSurface::build_eq_vol(contracts); 60 | let serialized_vol_surface = serde_json::to_string(&vol_surface).unwrap(); 61 | let out_dir = save_to_file(output_filename, "vol_surface", "vol_surface.json", &serialized_vol_surface); 62 | println!("Volatility surface saved to {}", out_dir); 63 | } 64 | else if list_contracts.asset=="CO"{ 65 | //Todo -build commodity vol surface 66 | panic!("Commodity contracts not supported"); 67 | } 68 | else if list_contracts.asset=="IR"{ 69 | let mut contracts:Vec> = build_ir_contracts_from_json(list_contracts.contracts); 70 | let ts = build_term_structure(contracts); 71 | let mut output: String = String::new(); 72 | for i in 0..ts.date.len(){ 73 | output.push_str(&format!("{},{},{}\n",ts.date[i],ts.discount_factor[i],ts.rate[i])); 74 | } 75 | 76 | let out_dir = save_to_file(output_filename, "term_structure", "term_structure.csv", &output); 77 | println!("Term structure saved to {}", out_dir); 78 | 79 | } 80 | else{ 81 | panic!("Asset class not supported"); 82 | } 83 | } 84 | 85 | pub fn parse_contract(mut file: &mut File,output_filename: &str) { 86 | let mut contents = String::new(); 87 | file.read_to_string(&mut contents) 88 | .expect("Failed to read JSON file"); 89 | 90 | let list_contracts: Contracts = serde_json::from_str(&contents).expect("Failed to deserialize JSON"); 91 | 92 | if list_contracts.contracts.len() == 0 { 93 | println!("No contracts found in JSON file"); 94 | return; 95 | } 96 | // parallel processing of each contract using rayon 97 | let mut output_vec: Vec<_> = list_contracts.contracts.par_iter().enumerate() 98 | .map(|(index,data)| (index,process_contract(data))) 99 | .collect(); 100 | output_vec.sort_by_key(|k| k.0); 101 | 102 | let output_vec: Vec = output_vec.into_iter().map(|(_,v)| v).collect(); 103 | let output_str = output_vec.join(","); 104 | //Write to file 105 | let mut file = File::create(output_filename).expect("Failed to create file"); 106 | file.write_all(output_str.as_bytes()).expect("Failed to write to file"); 107 | } 108 | pub fn process_contract(data: &Contract) -> String { 109 | 110 | let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; 111 | let rates = vec![0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05]; 112 | let ts = YieldTermStructure::new(date,rates); 113 | 114 | if data.action=="PV" && data.asset=="EQ"{ 115 | return handle_equity_contract(data); 116 | 117 | } 118 | // else if data.action=="PV" && data.asset=="CO"{ 119 | // let market_data = data.market_data.clone().unwrap(); 120 | // let curr_quote = Quote::new( market_data.underlying_price); 121 | // let option_type = &market_data.option_type; 122 | // let side: trade::OptionType; 123 | // let option_type = match &market_data.option_type { 124 | // Some(x) => x.clone(), 125 | // None => "".to_string(), 126 | // }; 127 | // match option_type.trim() { 128 | // "C" | "c" | "Call" | "call" => side = trade::OptionType::Call, 129 | // "P" | "p" | "Put" | "put" => side = trade:: OptionType::Put, 130 | // _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), 131 | // } 132 | // let maturity_date = &market_data.maturity; 133 | // let today = Local::today(); 134 | // let future_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); 135 | // let duration = future_date.signed_duration_since(today.naive_utc()); 136 | // let year_fraction = duration.num_days() as f64 / 365.0; 137 | // let vol = Some(market_data.volatility).unwrap(); 138 | // 139 | // let sim = market_data.simulation; 140 | // if data.pricer=="Analytical"{ 141 | // let mut option: CmdtyOption = CmdtyOption { 142 | // option_type: side, 143 | // transection: trade::Transection::Buy, 144 | // current_price: curr_quote, 145 | // strike_price: market_data.strike_price.unwrap_or(0.0), 146 | // volatility: vol.unwrap(), 147 | // time_to_maturity: year_fraction, 148 | // transection_price: 0.0, 149 | // term_structure: ts, 150 | // engine: cmdty_option::Engine::Black76, 151 | // simulation: Option::from(sim.unwrap_or(10000)), 152 | // time_to_future_maturity: None, 153 | // risk_free_rate: None 154 | // }; 155 | // let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None }; 156 | // println!("Theoretical Price ${}", contract_output.pv); 157 | // println!("Delta ${}", contract_output.delta); 158 | // let combined_ = CombinedContract{ 159 | // contract: data.clone(), 160 | // output:contract_output 161 | // }; 162 | // let output_json = serde_json::to_string(&combined_).expect("Failed to generate output"); 163 | // return output_json; 164 | // 165 | // 166 | // } 167 | // 168 | // } 169 | else if data.action=="PV" && data.asset=="IR"{ 170 | //println!("Processing {:?}",data); 171 | let rate_data = data.rate_data.clone().unwrap(); 172 | let mut start_date_str = rate_data.start_date; // Only for 0M case 173 | let mut maturity_date_str = rate_data.maturity_date; 174 | let current_date = Local::today(); 175 | let maturity_date = rates::utils::convert_mm_to_date(maturity_date_str); 176 | let start_date = rates::utils::convert_mm_to_date(start_date_str); 177 | println!("Maturity Date {:?}",maturity_date); 178 | let mut deposit = Deposit { 179 | start_date: start_date, 180 | maturity_date: maturity_date, 181 | valuation_date: current_date.naive_utc(), 182 | notional: rate_data.notional, 183 | fix_rate: rate_data.fix_rate, 184 | day_count: rates::utils::DayCountConvention::Act360, 185 | business_day_adjustment: 0, 186 | term_structure: None 187 | }; 188 | match rate_data.day_count.as_str() { 189 | "Act360" |"A360" => { 190 | deposit.day_count = rates::utils::DayCountConvention::Act360; 191 | } 192 | "Act365" |"A365" => { 193 | deposit.day_count = rates::utils::DayCountConvention::Act365; 194 | } 195 | "Thirty360" |"30/360" => { 196 | deposit.day_count = rates::utils::DayCountConvention::Thirty360; 197 | } 198 | _ => {} 199 | } 200 | let df = deposit.get_discount_factor(); 201 | println!("Discount Factor {:?}",df); 202 | return "Work in progress".to_string(); 203 | } 204 | else{ 205 | panic!("Invalid action"); 206 | } 207 | return "Invalid Action".to_string(); 208 | 209 | } -------------------------------------------------------------------------------- /src/utils/read_csv.rs: -------------------------------------------------------------------------------- 1 | use csv; 2 | pub fn read_ts(path: &str){ 3 | let mut reader = csv::Reader::from_path(path).unwrap(); 4 | for record in reader.records() { 5 | let r = record.unwrap(); 6 | println!("{:?}", &r[0]); 7 | println!("{:?}", &r[1]); 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/stochastic_processes.rs: -------------------------------------------------------------------------------- 1 | pub trait StochasticProcess{ 2 | fn drift(&self)-> f64; 3 | fn diffusion(&self)-> f64; 4 | } 5 | --------------------------------------------------------------------------------