diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f4cce23 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,745 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "RustyQLib" +version = "0.0.1" +dependencies = [ + "bincode", + "byteorder", + "chrono", + "clap", + "csv", + "libm", + "ndarray", + "probability", + "rand", + "rand_chacha", + "rand_distr", + "rand_pcg", + "serde", + "serde_json", + "strum", + "strum_macros", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "cxx" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 1.0.102", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.102", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "libm" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "probability" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d1ba13c5cdf590c3d5ab5f53b36da2f596e43f8a27b236ad1801b5a1695d8" +dependencies = [ + "random", + "special", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "random" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d13a3485349981c90c79112a11222c3e6e75de1d52b87a7525b3bf5361420f" + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "serde" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.102", +] + +[[package]] +name = "serde_json" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +dependencies = [ + "itoa 1.0.9", + "ryu", + "serde", +] + +[[package]] +name = "special" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a65e074159b75dcf173a4733ab2188baac24967b5c8ec9ed87ae15fcbc7636" +dependencies = [ + "libc", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.37", +] + +[[package]] +name = "syn" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.102", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.102", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cac1451 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "RustyQLib" +version = "0.0.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.4" +rand_distr = "0.4.3" +probability = "0.18.0" +libm = "0.2.1" +chrono = {version = "0.4.22",features = ["serde"]} +csv = "1.1" +rand_chacha = "0.3.1" +rand_pcg = " 0.3.1" +clap = "2.33" +byteorder = "1.4.3" +serde = { version = "1.0.104", features = ["derive"] } +serde_json = "1" +bincode = "1.3.1" +strum = "0.25" +strum_macros = "0.25" +ndarray = "0.15" diff --git a/src/cmdty/black76.rs b/src/cmdty/black76.rs new file mode 100644 index 0000000..5a8a381 --- /dev/null +++ b/src/cmdty/black76.rs @@ -0,0 +1,126 @@ +use libm::{exp, log}; +use std::f64::consts::{PI, SQRT_2}; +use crate::core::utils::{dN, N}; +use crate::core::trade; + +use super::cmdty_option::{CmdtyOption,Engine}; +use super::super::core::termstructure::YieldTermStructure; +use super::super::core::traits::{Instrument,Greeks}; +use super::super::core::interpolation; + +pub fn npv(bsd_option: &&CmdtyOption) -> f64 { + assert!(bsd_option.volatility >= 0.0); + assert!(bsd_option.time_to_maturity >= 0.0); + assert!(bsd_option.current_price.value >= 0.0); + if bsd_option.option_type == trade::OptionType::Call { + let option_price = bsd_option.current_price.value() * N(bsd_option.d1()) + - bsd_option.strike_price * N(bsd_option.d2()); + return option_price; + } else { + let option_price = -bsd_option.current_price.value() + * N(-bsd_option.d1()) + + bsd_option.strike_price * N(-bsd_option.d2()); + return option_price; + } +} + + + +impl CmdtyOption { + pub fn set_risk_free_rate(&mut self){ + let model = interpolation::CubicSpline::new(&self.term_structure.date, &self.term_structure.rates); + let r = model.interpolation(self.time_to_maturity); + self.risk_free_rate = Some(r); + } + pub fn get_premium_at_risk(&self) -> f64 { + let value = self.npv(); + let mut pay_off = 0.0; + if self.option_type == trade::OptionType::Call { + pay_off = self.current_price.value() - self.strike_price; + } else if self.option_type == trade::OptionType::Put { + pay_off = self.strike_price - self.current_price.value(); + } + if pay_off > 0.0 { + return value - pay_off; + } else { + return value; + } + } + pub fn d1(&self) -> f64 { + //Black76 d1 function Parameters + let tmp1 = (self.current_price.value() / self.strike_price).ln() + + (0.5 * self.volatility.powi(2)) + * self.time_to_maturity; + + let tmp2 = self.volatility * (self.time_to_maturity.sqrt()); + return tmp1 / tmp2; + } + pub fn d2(&self) -> f64 { + let d2 = self.d1() - self.volatility * self.time_to_maturity.powf(0.5); + return d2; + } + // pub fn imp_vol(&mut self,option_price:f64) -> f64 { + // for i in 0..100{ + // let d_sigma = (self.npv()-option_price)/self.vega(); + // self.volatility -= d_sigma + // } + // self.volatility + // } +} +impl Greeks for CmdtyOption{ + fn delta(&self) -> f64 { + let mut delta = N(self.d1()); + if self.option_type == trade::OptionType::Call { + delta = delta; + } else if self.option_type == trade::OptionType::Put { + delta = delta - 1.0; + } + return delta; + } + fn gamma(&self) -> f64 { + let gamma = dN(self.d1()); + + let var_sqrt = self.volatility * (self.time_to_maturity.sqrt()); + return gamma / (self.current_price.value() * var_sqrt); + } + fn vega(&self) -> f64 { + //St * dN(d1) * math.sqrt(T - t) + let vega = self.current_price.value() * dN(self.d1()) * self.time_to_maturity.sqrt(); + return vega; + } + fn theta(&self) -> f64 { + let mut theta = 0.0; + if self.option_type == trade::OptionType::Call { + //-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) + r * K * math.exp(-r * (T - t)) * N(d2)) + let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility + / (2.0 * self.time_to_maturity.sqrt()); + + theta = t1; + } else if self.option_type == trade::OptionType::Put { + //-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) - r * K * math.exp(-r * (T - t)) * N(d2)) + let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility + / (2.0 * self.time_to_maturity.sqrt()); + + theta = t1; + } + + return theta; + } + fn rho(&self) -> f64 { + //rho K * (T - t) * math.exp(-r * (T - t)) * N(d2) + let mut rho = 0.0; + if self.option_type == trade::OptionType::Call { + rho = self.strike_price + * self.time_to_maturity + + * N(self.d2()); + } else if self.option_type == trade::OptionType::Put { + //put_rho = -K * (T - t) * math.exp(-r * (T - t)) * N(-d2) + rho = -self.strike_price + * self.time_to_maturity + * N(-self.d2()); + } + + return rho; + } +} \ No newline at end of file diff --git a/src/cmdty/cmdty_option.rs b/src/cmdty/cmdty_option.rs new file mode 100644 index 0000000..a68dda0 --- /dev/null +++ b/src/cmdty/cmdty_option.rs @@ -0,0 +1,40 @@ +use super::super::core::termstructure::YieldTermStructure; +use super::super::core::quotes::Quote; +use super::super::core::traits::{Instrument,Greeks}; +use crate::core::trade; +use crate::cmdty::black76; + +pub enum Engine{ + Black76, + MonteCarlo +} + + +pub struct CmdtyOption { + pub option_type: trade::OptionType, + pub transection: trade::Transection, + pub current_price: Quote, + pub strike_price: f64, + pub volatility: f64, + pub time_to_maturity: f64, + pub time_to_future_maturity: Option, + pub term_structure: YieldTermStructure, + pub risk_free_rate: Option, + pub transection_price: f64, + pub engine: Engine, + pub simulation:Option +} + +impl Instrument for CmdtyOption { + fn npv(&self) -> f64 { + match self.engine{ + Engine::Black76 => { + let value = black76::npv(&self); + value + } + _ => { + 0.0 + } + } + } +} \ No newline at end of file diff --git a/src/cmdty/mod.rs b/src/cmdty/mod.rs new file mode 100644 index 0000000..7a30e8c --- /dev/null +++ b/src/cmdty/mod.rs @@ -0,0 +1,2 @@ +pub mod cmdty_option; +pub mod black76; \ No newline at end of file diff --git a/src/core/interpolation.rs b/src/core/interpolation.rs new file mode 100644 index 0000000..cefac63 --- /dev/null +++ b/src/core/interpolation.rs @@ -0,0 +1,95 @@ +//Algorithm for computing natural cubic splines +// https://en.wikipedia.org/w/index.php?title=Spline_%28mathematics%29&oldid=288288033#Algorithm_for_computing_natural_cubic_splines +//https://stackoverflow.com/questions/1204553/are-there-any-good-libraries-for-solving-cubic-splines-in-c + +pub struct CubicSpline<'a>{ + pub x_vec : &'a Vec, + pub y_vec : &'a Vec, + pub spline_set : Vec +} + +pub struct SplineSet{ + a:f64, + b:f64, + c:f64, + d:f64, + x:f64, +} +impl CubicSpline<'_> { + pub fn new<'a>(x: &'a Vec, y: &'a Vec) -> CubicSpline<'a> { + assert!(x.len() == y.len() && x.len() >= 2 && y.len() >= 2, "Must have at least 2 control points."); + let n = x.len()-1; + let mut a = vec![x[0]]; + let mut aa = y.clone(); + a.append(&mut aa); + let mut c = vec![0.0; n+1]; + let mut b = vec![0.0; n]; + let mut d = vec![0.0; n]; + let mut dx = Vec::new(); + for i in 0..n { + dx.push(x[i+1]-x[i]); + } + + let mut dy = Vec::new(); + for i in 0..n { + dy.push(y[i+1]-y[i]); + } + + + let mut alpha = vec![0.0]; + for i in 1..n { + alpha.push(3.0*(dy[i]/dx[i] - dy[i-1]/dx[i-1])); + } + + let mut l = vec![0.0; n+1]; + let mut mu = vec![0.0; n+1]; + let mut z = vec![0.0; n+1]; + l[0] = 1.0; + for i in 1..n{ + l[i] = 2.0*(x[i+1]-x[i-1]) - dx[i-1]*mu[i-1]; + mu[i] = dx[i]/l[i]; + z[i] = (alpha[i]-dx[i-1]*z[i-1])/l[i]; + } + l[n] = 1.0; + z[n] = 0.0; + //c[n] = 0; + for j in (0..n).rev(){ + c[j] = z[j] - mu[j] * c[j+1]; + b[j] = (a[j+1]-a[j])/dx[j]-dx[j]*(c[j+1]+2.0*c[j])/3.0; + d[j] = (c[j+1]-c[j])/(3.0*dx[j]); + + } + let mut output = Vec::new(); + for i in 0..n { + let spline_set = SplineSet{ + a:a[i], + b:b[i], + c:c[i], + d:d[i], + x:x[i] + }; + output.push(spline_set); + } + let spline = CubicSpline{ + x_vec:x, + y_vec:y, + spline_set:output + }; + spline + } + pub fn interpolation(&self,x:f64) -> f64 { + let n = self.x_vec.len(); + for i in 0..n { + if x<=self.x_vec[i] { + let diff = self.x_vec[i] - x; + return self.spline_set[i].a + + self.spline_set[i].b * diff + + self.spline_set[i].c * diff * diff + + self.spline_set[i].d * diff * diff * diff; + } + } + 0.0 + } +} + + diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..4db048c --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,7 @@ +pub mod interpolation; +pub mod termstructure; +pub mod quotes; +pub mod traits; +pub mod trade; +pub mod utils; + diff --git a/src/core/quotes.rs b/src/core/quotes.rs new file mode 100644 index 0000000..1ddc346 --- /dev/null +++ b/src/core/quotes.rs @@ -0,0 +1,14 @@ +#[derive(Debug)] +pub struct Quote{ + pub value: f64, + pub bid: f64, + pub ask: f64, + pub mid: f64, +} +impl Quote{ + pub fn new(value: f64) -> Self { + Quote{value: value, bid: value, ask: value, mid: value } + } + pub fn value(&self) -> f64 { self.value } + pub fn valid_value(&self) -> bool { self.value>0.0} +} \ No newline at end of file diff --git a/src/core/termstructure.rs b/src/core/termstructure.rs new file mode 100644 index 0000000..d03c982 --- /dev/null +++ b/src/core/termstructure.rs @@ -0,0 +1,15 @@ +use chrono::{DateTime, Local, NaiveDate}; +#[derive(Debug)] +pub struct YieldTermStructure { + pub date: Vec, + pub rates: Vec +} +impl YieldTermStructure { + pub fn new(date: Vec, rates: Vec) -> YieldTermStructure { + YieldTermStructure { + date, + rates + } + } +} + diff --git a/src/core/trade.rs b/src/core/trade.rs new file mode 100644 index 0000000..bfe3b3d --- /dev/null +++ b/src/core/trade.rs @@ -0,0 +1,12 @@ +#[derive(PartialEq,Debug,Clone)] +pub enum Transection { + Buy, + Sell, +} + +#[derive(PartialEq, Debug,Clone)] +pub enum OptionType { + Call, + Put, + Straddle, +} \ No newline at end of file diff --git a/src/core/traits.rs b/src/core/traits.rs new file mode 100644 index 0000000..3917855 --- /dev/null +++ b/src/core/traits.rs @@ -0,0 +1,33 @@ +use chrono::NaiveDate; +use crate::rates::utils::DayCountConvention; +use crate::rates::utils::TermStructure; +pub trait Instrument{ + fn npv(&self)-> f64; +} + +pub trait Greeks{ + fn delta(&self) -> f64; + fn gamma(&self) -> f64; + fn vega(&self) -> f64; + fn theta(&self) -> f64; + fn rho(&self) -> f64; +} + + +pub trait Rates { + fn get_implied_rates(&self) -> f64; + fn get_maturity_date(&self) -> NaiveDate; + fn get_rate(&self) -> f64; + fn get_maturity_discount_factor(&self) -> f64; + fn get_day_count(&self) -> &DayCountConvention; + fn set_term_structure(&mut self,term_structure:TermStructure)->(); +} + +pub trait Observer{ + fn update(&mut self); + fn reset(&mut self); +} +pub trait Observable{ + fn update(&mut self); + fn reset(&mut self); +} \ No newline at end of file diff --git a/src/core/utils.rs b/src/core/utils.rs new file mode 100644 index 0000000..1834c95 --- /dev/null +++ b/src/core/utils.rs @@ -0,0 +1,107 @@ +//mod dis{ +use libm::{exp, log}; +use probability; +use probability::distribution::Distribution; +use std::f64::consts::{PI, SQRT_2}; +use serde::Serialize; +use crate::Deserialize; + +#[derive(PartialEq,Clone,Debug)] +pub enum ContractStyle { + European, + American, +} + +#[derive(strum_macros::Display)] +pub enum EngineType { + Analytical, + MonteCarlo, + Binomial, + FiniteDifference, + FFT, +} +impl EngineType { + pub fn as_str(&self) -> &'static str { + match self { + EngineType::Analytical => "Analytical", + EngineType::MonteCarlo => "MonteCarlo", + EngineType::Binomial => "Binomial", + EngineType::FiniteDifference => "FiniteDifference", + EngineType::FFT => "FFT", + } + } +} + +#[derive(Clone,Debug,Deserialize,Serialize)] +pub struct MarketData { + pub underlying_price:f64, + pub option_type:String, + pub strike_price:f64, + pub volatility:Option, + pub option_price:Option, + pub risk_free_rate:Option, + pub maturity:String, + pub dividend: Option, + pub simulation:Option, +} +#[derive(Clone,Debug,Deserialize,Serialize)] +pub struct RateData { + pub instrument: String, + pub currency: String, + pub start_date: String, + pub maturity_date: String, + pub valuation_date: String, + pub notional: f64, + pub fix_rate: f64, + pub day_count: String, + pub business_day_adjustment: i8, +} + +#[derive(Clone,Debug,Deserialize,Serialize)] +pub struct Contract { + pub action: String, + pub pricer: String, + pub asset: String, + pub style: Option, + pub market_data: Option, + pub rate_data: Option, +} +#[derive(Deserialize,Serialize)] +pub struct CombinedContract{ + pub contract: Contract, + pub output: ContractOutput +} + +#[derive(Debug, Deserialize,Serialize)] +pub struct Contracts { + pub asset: String, + pub contracts: Vec, +} +#[derive(Debug, Deserialize,Serialize)] +pub struct OutputJson { + pub contracts: Vec, +} +#[derive(Deserialize,Serialize)] +pub struct ContractOutput { + pub pv: f64, + pub delta: f64, + pub gamma: f64, + pub vega: f64, + pub theta: f64, + pub rho: f64, + pub error: Option +} + +pub fn dN(x: f64) -> f64 { + // Probability density function of standard normal random variable x. + let t = -0.5 * x * x; + return t.exp() / (SQRT_2 * PI.sqrt()); +} + +pub fn N(x: f64) -> f64 { + //umulative density function of standard normal random variable x. + let m = probability::distribution::Gaussian::new(0.0, 1.0); + let cdf = m.distribution(x); + return cdf; +} +//} diff --git a/src/equity/binomial.rs b/src/equity/binomial.rs new file mode 100644 index 0000000..a645b7f --- /dev/null +++ b/src/equity/binomial.rs @@ -0,0 +1,59 @@ +//extern crate ndarray; +use super::vanila_option::{EquityOption}; +use super::utils::{Engine}; +use crate::core::trade::{OptionType,Transection}; +use crate::core::utils::{ContractStyle}; +use ndarray::Array2; +pub fn npv(option: &&EquityOption) -> f64 { + assert!(option.volatility >= 0.0); + assert!(option.time_to_maturity() >= 0.0); + assert!(option.current_price.value >= 0.0); + let num_steps = 1000; + + let dt = option.time_to_maturity() / num_steps as f64; + let discount_factor = (-option.risk_free_rate * dt).exp(); + // Calculate parameters for the binomial tree + let u = (option.volatility*dt.sqrt()).exp(); //up movement + let d = 1.0 / u; //down movement + let a_factor = ((option.risk_free_rate-option.dividend_yield) * dt).exp(); + let p = (a_factor - d) / (u - d); //martingale probability + // Create a 2D array to represent the binomial tree + let mut tree = Array2::from_elem((num_steps + 1, num_steps + 1), 0.0); + //println!("{:?}",tree); + // Calculate option prices at the final time step (backward induction) + let multiplier = if option.option_type == OptionType::Call { 1.0 } else { -1.0 }; + for j in 0..=num_steps { + let spot_price_j = option.current_price.value * u.powi(num_steps as i32 - j as i32) * d.powi(j as i32); + tree[[j,num_steps]] = (multiplier*(spot_price_j - option.strike_price)).max(0.0); + } + + match option.style { + ContractStyle::European => { + for i in (0..num_steps).rev() { + for j in 0..=i { + let spot_price_i = option.current_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32); + let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]); + //tree[[j,i]] = (multiplier*(spot_price_i - option.strike_price)).max(discounted_option_price); + tree[[j,i]] = discounted_option_price; + } + } + } + ContractStyle::American => { + println!("American"); + for i in (0..num_steps).rev() { + for j in 0..=i { + let spot_price_i = option.current_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32); + //let intrinsic_value = (multiplier*(spot_price_i - option.strike_price)).max(0.0); + let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]); + tree[[j,i]] = (multiplier*(spot_price_i - option.strike_price)).max(discounted_option_price); + } + } + } + _ => { + panic!("Invalid option style"); + } + } + + + return tree[[0,0]]; +} \ No newline at end of file diff --git a/src/equity/blackscholes.rs b/src/equity/blackscholes.rs new file mode 100644 index 0000000..f61d629 --- /dev/null +++ b/src/equity/blackscholes.rs @@ -0,0 +1,320 @@ +use libm::{exp, log}; +use std::f64::consts::{PI, SQRT_2}; +use std::{io, thread}; +use crate::core::quotes::Quote; +use chrono::{Datelike, Local, NaiveDate}; +//use utils::{N,dN}; +//use vanila_option::{EquityOption,OptionType}; +use crate::core::utils::{ContractStyle, dN, N}; +use crate::core::trade::{OptionType,Transection}; +use super::vanila_option::{EquityOption}; +use super::utils::{Engine}; +use super::super::core::termstructure::YieldTermStructure; +use super::super::core::traits::{Instrument,Greeks}; +use super::super::core::interpolation; + +pub fn npv(bsd_option: &&EquityOption) -> f64 { + //assert!(bsd_option.volatility >= 0.0); + assert!(bsd_option.time_to_maturity() >= 0.0); + assert!(bsd_option.underlying_price.value >= 0.0); + if bsd_option.option_type == OptionType::Call { + let option_price = bsd_option.underlying_price.value() + * N(bsd_option.d1()) + * exp(-bsd_option.dividend_yield * bsd_option.time_to_maturity()) + - bsd_option.strike_price + * exp(-bsd_option.risk_free_rate * bsd_option.time_to_maturity()) + * N(bsd_option.d2()); + return option_price; + } else { + let option_price = -bsd_option.underlying_price.value() + * N(-bsd_option.d1()) + * exp(-bsd_option.dividend_yield * bsd_option.time_to_maturity()) + + bsd_option.strike_price + * exp(-bsd_option.risk_free_rate * bsd_option.time_to_maturity()) + * N(-bsd_option.d2()); + return option_price; + } +} + +impl Greeks for EquityOption{ + fn delta(&self) -> f64 { + let mut delta = N(self.d1()); + if self.option_type == OptionType::Call { + delta = delta * exp(-self.dividend_yield * self.time_to_maturity()); + } else if self.option_type == OptionType::Put { + delta = delta - 1.0; + } + return delta; + } + fn gamma(&self) -> f64 { + let gamma = dN(self.d1()); + //(St * sigma * math.sqrt(T - t)) + let var_sqrt = self.volatility * (self.time_to_maturity().sqrt()); + return gamma / (self.current_price.value() * var_sqrt); + } + fn vega(&self) -> f64 { + //St * dN(d1) * math.sqrt(T - t) + let vega = self.underlying_price.value * dN(self.d1()) * self.time_to_maturity().sqrt(); + return vega; + } + fn theta(&self) -> f64 { + let mut theta = 0.0; + if self.option_type == OptionType::Call { + //-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) + r * K * math.exp(-r * (T - t)) * N(d2)) + let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility + / (2.0 * self.time_to_maturity().sqrt()); + let t2 = (self.risk_free_rate - self.dividend_yield) + * self.strike_price + * exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity()) + * N(self.d2()); + theta = t1 + t2; + } else if self.option_type == OptionType::Put { + //-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) - r * K * math.exp(-r * (T - t)) * N(d2)) + let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility + / (2.0 * self.time_to_maturity().sqrt()); + let t2 = (self.risk_free_rate - self.dividend_yield) + * self.strike_price + * exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity()) + * N(self.d2()); + theta = t1 - t2; + } + + return theta; + } + fn rho(&self) -> f64 { + //rho K * (T - t) * math.exp(-r * (T - t)) * N(d2) + let mut rho = 0.0; + if self.option_type == OptionType::Call { + rho = self.strike_price + * self.time_to_maturity() + * exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity()) + * N(self.d2()); + } else if self.option_type == OptionType::Put { + //put_rho = -K * (T - t) * math.exp(-r * (T - t)) * N(-d2) + rho = -self.strike_price + * self.time_to_maturity() + * exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity()) + * N(-self.d2()); + } + + return rho; + } +} + +impl EquityOption { + pub fn set_risk_free_rate(&mut self){ + let model = interpolation::CubicSpline::new(&self.term_structure.date, &self.term_structure.rates); + let r = model.interpolation(self.time_to_maturity()); + self.risk_free_rate = r; + } + pub fn get_premium_at_risk(&self) -> f64 { + let value = self.npv(); + let mut pay_off = 0.0; + if self.option_type == OptionType::Call { + pay_off = self.current_price.value() - self.strike_price; + } else if self.option_type == OptionType::Put { + pay_off = self.strike_price - self.current_price.value(); + } + if pay_off > 0.0 { + return value - pay_off; + } else { + return value; + } + } + pub fn d1(&self) -> f64 { + //Black-Scholes-Merton d1 function Parameters + let tmp1 = (self.underlying_price.value() / self.strike_price).ln() + + (self.risk_free_rate - self.dividend_yield + 0.5 * self.volatility.powi(2)) + * self.time_to_maturity(); + + let tmp2 = self.volatility * (self.time_to_maturity().sqrt()); + return tmp1 / tmp2; + } + pub fn d2(&self) -> f64 { + let d2 = self.d1() - self.volatility * self.time_to_maturity().powf(0.5); + return d2; + } + pub fn imp_vol(&mut self,option_price:f64) -> f64 { + for i in 0..100{ + let d_sigma = (self.npv()-option_price)/self.vega(); + self.volatility -= d_sigma + } + self.volatility + } + pub fn get_imp_vol(&mut self) -> f64 { + for i in 0..100{ + let d_sigma = (self.npv()-self.current_price.value)/self.vega(); + self.volatility -= d_sigma + } + self.volatility + } +} +pub fn option_pricing() { + println!("Welcome to the Black-Scholes Option pricer."); + println!("(Step 1/7) What is the current price of the underlying asset?"); + print!(">>"); + let mut curr_price = String::new(); + io::stdin() + .read_line(&mut curr_price) + .expect("Failed to read line"); + println!("(Step 2/7) Do you want a call option ('C') or a put option ('P') ?"); + let mut side_input = String::new(); + io::stdin() + .read_line(&mut side_input) + .expect("Failed to read line"); + let side: OptionType; + match side_input.trim() { + "C" | "c" | "Call" | "call" => side = OptionType::Call, + "P" | "p" | "Put" | "put" => side = OptionType::Put, + _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), + } + println!("Stike price:"); + let mut strike = String::new(); + io::stdin() + .read_line(&mut strike) + .expect("Failed to read line"); + println!("Expected annualized volatility in %:"); + println!("E.g.: Enter 50% chance as 0.50 "); + let mut vol = String::new(); + io::stdin() + .read_line(&mut vol) + .expect("Failed to read line"); + + println!("Risk-free rate in %:"); + let mut rf = String::new(); + io::stdin().read_line(&mut rf).expect("Failed to read line"); + println!(" Maturity date in YYYY-MM-DD format:"); + + let mut expiry = String::new(); + io::stdin() + .read_line(&mut expiry) + .expect("Failed to read line"); + let future_date = NaiveDate::parse_from_str(&expiry, "%Y-%m-%d").expect("Invalid date format"); + println!("Dividend yield on this stock:"); + let mut div = String::new(); + io::stdin() + .read_line(&mut div) + .expect("Failed to read line"); + + //let ts = YieldTermStructure{ + // date: vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0], + // rates: vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12] + //}; + let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; + let rates = vec![0.05,0.05,0.06,0.07,0.08,0.9,0.9,0.10]; + let ts = YieldTermStructure::new(date,rates); + let curr_quote = Quote::new( curr_price.trim().parse::().unwrap()); + let mut option = EquityOption { + option_type: side, + transection: Transection::Buy, + underlying_price: curr_quote, + current_price: Quote::new(0.0), + strike_price: strike.trim().parse::().unwrap(), + volatility: vol.trim().parse::().unwrap(), + maturity_date: future_date, + risk_free_rate: rf.trim().parse::().unwrap(), + dividend_yield: div.trim().parse::().unwrap(), + transection_price: 0.0, + term_structure: ts, + engine: Engine::BlackScholes, + simulation: None, + style: ContractStyle::European, + valuation_date: Local::today().naive_local(), + }; + option.set_risk_free_rate(); + println!("Theoretical Price ${}", option.npv()); + println!("Premium at risk ${}", option.get_premium_at_risk()); + println!("Delata {}", option.delta()); + println!("Gamma {}", option.gamma()); + println!("Vega {}", option.vega() * 0.01); + println!("Theta {}", option.theta() * (1.0 / 365.0)); + println!("Rho {}", option.rho() * 0.01); + let mut div1 = String::new(); + io::stdin() + .read_line(&mut div) + .expect("Failed to read line"); +} +pub fn implied_volatility() { + println!("Welcome to the Black-Scholes Option pricer."); + println!("(Step 1/7) What is the current price of the underlying asset?"); + let mut curr_price = String::new(); + io::stdin() + .read_line(&mut curr_price) + .expect("Failed to read line"); + + println!("(Step 2/7) Do you want a call option ('C') or a put option ('P') ?"); + let mut side_input = String::new(); + io::stdin() + .read_line(&mut side_input) + .expect("Failed to read line"); + + let side: OptionType; + match side_input.trim() { + "C" | "c" | "Call" | "call" => side = OptionType::Call, + "P" | "p" | "Put" | "put" => side = OptionType::Put, + _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), + } + + println!("Stike price:"); + let mut strike = String::new(); + io::stdin() + .read_line(&mut strike) + .expect("Failed to read line"); + + println!("What is option price:"); + let mut option_price = String::new(); + io::stdin() + .read_line(&mut option_price) + .expect("Failed to read line"); + + println!("Risk-free rate in %:"); + let mut rf = String::new(); + io::stdin().read_line(&mut rf).expect("Failed to read line"); + + println!(" Maturity date in YYYY-MM-DD format:"); + let mut expiry = String::new(); + io::stdin() + .read_line(&mut expiry) + .expect("Failed to read line"); + let future_date = NaiveDate::parse_from_str(&expiry.trim(), "%Y-%m-%d").expect("Invalid date format"); + println!("Dividend yield on this stock:"); + let mut div = String::new(); + io::stdin() + .read_line(&mut div) + .expect("Failed to read line"); + + //let ts = YieldTermStructure{ + // date: vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0], + // rates: vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12] + //}; + let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; + let rates = vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12]; + let ts = YieldTermStructure::new(date,rates); + let curr_quote = Quote::new( curr_price.trim().parse::().unwrap()); + let sim = Some(10000); + let mut option = EquityOption { + option_type: side, + transection: Transection::Buy, + underlying_price: curr_quote, + current_price: Quote::new(0.0), + strike_price: strike.trim().parse::().unwrap(), + volatility: 0.20, + maturity_date: future_date, + risk_free_rate: rf.trim().parse::().unwrap(), + dividend_yield: div.trim().parse::().unwrap(), + transection_price: 0.0, + term_structure: ts, + engine: Engine::BlackScholes, + simulation:sim, + //style:Option::from("European".to_string()), + style: ContractStyle::European, + valuation_date: Local::today().naive_utc(), + }; + option.set_risk_free_rate(); + println!("Implied Volatility {}%", 100.0*option.imp_vol(option_price.trim().parse::().unwrap())); + + let mut div1 = String::new(); + io::stdin() + .read_line(&mut div) + .expect("Failed to read line"); +} \ No newline at end of file diff --git a/src/equity/build_contracts.rs b/src/equity/build_contracts.rs new file mode 100644 index 0000000..9bfd172 --- /dev/null +++ b/src/equity/build_contracts.rs @@ -0,0 +1,36 @@ +//use crate::rates; +//use crate::rates::deposits::Deposit; + +use chrono::{NaiveDate,Local,Weekday}; +use chrono::Datelike; +use crate::core::trade; +use super::vanila_option::{EquityOption}; +use super::super::core::termstructure::YieldTermStructure; +use crate::rates::utils::TermStructure; +use crate::equity::vol_surface::VolSurface; +use crate::rates::utils::{DayCountConvention}; +use crate::core::quotes::Quote; +use crate::core::utils::{Contract,ContractStyle}; +use crate::equity::utils::{Engine}; +use std::collections::BTreeMap; + +pub fn build_eq_contracts_from_json(data: Vec) -> Vec> { + let derivatives:Vec> = data.iter().map(|x| EquityOption::from_json(x.clone())).collect(); + return derivatives; +} +pub fn build_volatility_surface(mut contracts:Vec>) -> VolSurface { + + let mut vol_tree:BTreeMap> = BTreeMap::new(); + let spot_date = contracts[0].valuation_date; + let spot_price = contracts[0].underlying_price.value; + for i in 0..contracts.len(){ + let mut contract = contracts[i].as_mut(); + let moneyness = contract.underlying_price.value / contract.strike_price as f64; + let volatility = contract.get_imp_vol(); + let maturity = contract.maturity_date; + vol_tree.entry(maturity).or_insert(Vec::new()).push((moneyness,volatility)); + } + let vol_surface:VolSurface = VolSurface::new(vol_tree, spot_price, spot_date, + DayCountConvention::Act365); + return vol_surface; +} \ No newline at end of file diff --git a/src/equity/forward_start_option.rs b/src/equity/forward_start_option.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/equity/mod.rs b/src/equity/mod.rs new file mode 100644 index 0000000..0ec5813 --- /dev/null +++ b/src/equity/mod.rs @@ -0,0 +1,8 @@ +pub mod blackscholes; +pub mod vanila_option; +pub mod montecarlo; +pub mod forward_start_option; +pub mod utils; +pub mod binomial; +pub mod build_contracts; +pub mod vol_surface; diff --git a/src/equity/montecarlo.rs b/src/equity/montecarlo.rs new file mode 100644 index 0000000..d62797b --- /dev/null +++ b/src/equity/montecarlo.rs @@ -0,0 +1,200 @@ + + + +// let val: f64 = thread_rng().sample(StandardNormal); +// println!("normal vector: {}", val); +// let t = RNG::get_vector_standard_normal(10000); +// for i in &t{ +// println!("{}", i); +// } +//println!(":?:{}",t); + +use std::io; +use chrono::{Datelike, Local, NaiveDate}; +use libm::exp; + +//use crate::equity::vanila_option::{Engine, EquityOption, OptionType, Transection}; +use crate::core::utils::{ContractStyle,dN, N}; + +use super::vanila_option::{EquityOption}; +use super::utils::{Engine}; +use crate::core::trade::{OptionType,Transection}; +use super::super::utils::RNG; +use crate::core::quotes::Quote; +use crate::core::termstructure::YieldTermStructure; +use crate::core::traits::Instrument; +use serde::de::Unexpected::Option; + +pub fn simulate_market(option: &&EquityOption) -> Vec{ + let mut monte_carlo = RNG::MonteCarloSimulation{ + antithetic: true, + moment_matching: true, + dimentation: 1, + size: option.simulation.unwrap(), + standard_normal_vector: vec![] as Vec, + standard_normal_matrix: vec![] as Vec> + }; + monte_carlo.set_standard_normal_vector(); + let path = monte_carlo.get_standard_normal_vector(); + + let mut market_at_maturity:Vec = Vec::new(); + for z in path{ + let sim_value = option.underlying_price.value() + *exp(((option.risk_free_rate - option.dividend_yield - 0.5 * option.volatility.powi(2)) + * option.time_to_maturity())+option.volatility * option.time_to_maturity().sqrt()*z); + market_at_maturity.push(sim_value); + } + market_at_maturity +} + +pub fn simulate_market_path_wise(option: &&EquityOption) -> Vec{ + let M = 1000; + let N = 10000; + let dt = option.time_to_maturity()/1000.0; + let path = RNG::get_matrix_standard_normal(N,M); + let mut market_at_maturity:Vec = Vec::new(); + for ipath in &path{ + let mut st = option.current_price.value(); + for z in ipath{ + st = st + *exp(((option.risk_free_rate - option.dividend_yield - 0.5 * option.volatility.powi(2)) + * dt)+option.volatility * dt.sqrt()*z); + } + market_at_maturity.push(st); + } + market_at_maturity +} + +pub fn payoff(market: &Vec, + strike: &f64, + option_type: &OptionType) -> Vec{ + let mut payoff_vec = Vec::new(); + match option_type{ + OptionType::Call=>{ + for st in market{ + let pay = (st - strike).max(0.0); + payoff_vec.push(pay); + } + } + OptionType::Put=>{ + for st in market{ + let pay = (strike-st).max(0.0); + payoff_vec.push(pay); + } + } + _ => {} + } + payoff_vec +} + + +pub fn npv(option: &&EquityOption,path_size: bool) -> f64 { + assert!(option.volatility >= 0.0); + assert!(option.time_to_maturity() >= 0.0); + assert!(option.underlying_price.value >= 0.0); + let mut st = vec![]; + if path_size { + st = simulate_market_path_wise(&option); + } + else { + //let sim_size = 10000; + //println!("simulating{}",option.simulation.unwrap()); + st = simulate_market(&option); + } + + let payoff = payoff(&st,&option.strike_price,&option.option_type); + let sum_pay:f64 = payoff.iter().sum(); + let num_of_simulations = st.len() as f64; + let c0:f64 = (sum_pay / num_of_simulations)*exp(-(option.risk_free_rate)*option.time_to_maturity()); + c0 + } + + +pub fn option_pricing() { + println!("Welcome to the Black-Scholes Option pricer."); + println!("(Step 1/7) What is the current price of the underlying asset?"); + let mut curr_price = String::new(); + io::stdin() + .read_line(&mut curr_price) + .expect("Failed to read line"); + + println!("(Step 2/7) Do you want a call option ('C') or a put option ('P') ?"); + let mut side_input = String::new(); + io::stdin() + .read_line(&mut side_input) + .expect("Failed to read line"); + + let side: OptionType; + match side_input.trim() { + "C" | "c" | "Call" | "call" => side = OptionType::Call, + "P" | "p" | "Put" | "put" => side = OptionType::Put, + _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), + } + + println!("Stike price:"); + let mut strike = String::new(); + io::stdin() + .read_line(&mut strike) + .expect("Failed to read line"); + + println!("Expected annualized volatility in %:"); + println!("E.g.: Enter 50% chance as 0.50 "); + let mut vol = String::new(); + io::stdin() + .read_line(&mut vol) + .expect("Failed to read line"); + + println!("Risk-free rate in %:"); + let mut rf = String::new(); + io::stdin().read_line(&mut rf).expect("Failed to read line"); + + println!("Maturity date in YYYY-MM-DD format:"); + let mut expiry = String::new(); + io::stdin() + .read_line(&mut expiry) + .expect("Failed to read line"); + let future_date = NaiveDate::parse_from_str(&expiry.trim(), "%Y-%m-%d").expect("Invalid date format"); + println!("Dividend yield on this stock:"); + let mut div = String::new(); + io::stdin() + .read_line(&mut div) + .expect("Failed to read line"); + + //let ts = YieldTermStructure{ + // date: vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0], + // rates: vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12] + //}; + let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; + let rates = vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12]; + let ts = YieldTermStructure::new(date,rates); + let curr_quote = Quote::new( curr_price.trim().parse::().unwrap()); + let mut option = EquityOption { + option_type: side, + transection: Transection::Buy, + underlying_price: curr_quote, + current_price: Quote::new(0.01), + strike_price: strike.trim().parse::().unwrap(), + volatility: vol.trim().parse::().unwrap(), + maturity_date: future_date, + risk_free_rate: rf.trim().parse::().unwrap(), + dividend_yield: div.trim().parse::().unwrap(), + transection_price: 0.0, + term_structure: ts, + engine: Engine::MonteCarlo, + simulation: std::option::Option::Some(10000), + style: ContractStyle::European, + valuation_date: Local::today().naive_utc(), + }; + option.set_risk_free_rate(); + println!("Theoretical Price ${}", option.npv()); + // println!("Premium at risk ${}", option.get_premium_at_risk()); + // println!("Delata {}", option.delta()); + // println!("Gamma {}", option.gamma()); + // println!("Vega {}", option.vega() * 0.01); + // println!("Theta {}", option.theta() * (1.0 / 365.0)); + // println!("Rho {}", option.rho() * 0.01); + let mut div1 = String::new(); + io::stdin() + .read_line(&mut div) + .expect("Failed to read line"); +} \ No newline at end of file diff --git a/src/equity/utils.rs b/src/equity/utils.rs new file mode 100644 index 0000000..1e01c59 --- /dev/null +++ b/src/equity/utils.rs @@ -0,0 +1,6 @@ +#[derive(PartialEq,Clone,Debug)] +pub enum Engine{ + BlackScholes, + MonteCarlo, + Binomial, +} \ No newline at end of file diff --git a/src/equity/vanila_option.rs b/src/equity/vanila_option.rs new file mode 100644 index 0000000..600bea4 --- /dev/null +++ b/src/equity/vanila_option.rs @@ -0,0 +1,185 @@ +use chrono::{Datelike, Local, NaiveDate}; +use crate::equity::montecarlo; +use crate::equity::binomial; +use super::super::core::termstructure::YieldTermStructure; +use super::super::core::quotes::Quote; +use super::super::core::traits::{Instrument,Greeks}; +use super::blackscholes; +use crate::equity::utils::{Engine}; +use crate::core::trade::{OptionType,Transection}; +use crate::core::utils::{Contract,ContractStyle}; +use crate::core::trade; + +impl Instrument for EquityOption { + fn npv(&self) -> f64 { + match self.engine{ + Engine::BlackScholes => { + let value = blackscholes::npv(&self); + value + } + Engine::MonteCarlo => { + println!("Using MonteCarlo Engine "); + let value = montecarlo::npv(&self,false); + value + } + Engine::Binomial => { + println!("Using Binomial Engine "); + let value = binomial::npv(&self); + value + } + _ => { + 0.0 + } + } + } +} + +#[derive(Debug)] +pub struct EquityOption { + pub option_type: OptionType, + pub transection: Transection, + pub underlying_price: Quote, + pub current_price: Quote, + pub strike_price: f64, + pub dividend_yield: f64, + pub volatility: f64, + pub maturity_date: NaiveDate, + pub valuation_date: NaiveDate, + pub term_structure: YieldTermStructure, + pub risk_free_rate: f64, + pub transection_price: f64, + pub engine: Engine, + pub simulation:Option, + pub style: ContractStyle, +} +impl EquityOption{ + pub fn time_to_maturity(&self) -> f64{ + let time_to_maturity = (self.maturity_date - self.valuation_date).num_days() as f64/365.0; + time_to_maturity + } +} +impl EquityOption { + pub fn from_json(data: Contract) -> Box { + let market_data = data.market_data.unwrap(); + let underlying_quote = Quote::new(market_data.underlying_price); + //TODO: Add term structure + let date = vec![0.01, 0.02, 0.05, 0.1, 0.5, 1.0, 2.0, 3.0]; + let rates = vec![0.05, 0.055, 0.06, 0.07, 0.07, 0.08, 0.1, 0.1]; + let ts = YieldTermStructure::new(date, rates); + let option_type = &market_data.option_type; + let side: trade::OptionType; + match option_type.trim() { + "C" | "c" | "Call" | "call" => side = trade::OptionType::Call, + "P" | "p" | "Put" | "put" => side = trade::OptionType::Put, + _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), + } + let maturity_date = &market_data.maturity; + let today = Local::today(); + let future_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); + + let risk_free_rate = Some(market_data.risk_free_rate).unwrap(); + let dividend = Some(market_data.dividend).unwrap(); + let option_price = Quote::new(match Some(market_data.option_price) { + Some(x) => x.unwrap(), + None => 0.0, + }); + //let volatility = Some(market_data.volatility); + let volatility = match market_data.volatility { + Some(x) => { + x + } + None => 0.2 + }; + let mut option = EquityOption { + option_type: side, + transection: Transection::Buy, + underlying_price: underlying_quote, + current_price: option_price, + strike_price: market_data.strike_price, + volatility: volatility, + maturity_date: future_date, + risk_free_rate: risk_free_rate.unwrap_or(0.0), + dividend_yield: dividend.unwrap_or(0.0), + transection_price: 0.0, + term_structure: ts, + engine: Engine::BlackScholes, + simulation: None, + style: ContractStyle::European, + valuation_date: today.naive_utc(), + }; + match data.pricer.trim() { + "Analytical" | "analytical" => { + option.engine = Engine::BlackScholes; + } + "MonteCarlo" | "montecarlo" | "MC" => { + option.engine = Engine::MonteCarlo; + } + "Binomial" | "binomial" => { + option.engine = Engine::Binomial; + } + _ => { + panic!("Invalid pricer"); + } + } + match data.style.as_ref().unwrap_or(&"European".to_string()).trim() { + "European" | "european" => { + option.style = ContractStyle::European; + } + "American" | "american" => { + option.style = ContractStyle::American; + } + _ => { + option.style = ContractStyle::European; + } + } + option.set_risk_free_rate(); + return Box::new(option); + } +} + +#[cfg(test)] +mod tests { + //write a unit test for from_json + use super::*; + use crate::core::utils::{Contract,MarketData}; + use crate::core::trade::OptionType; + use crate::core::trade::Transection; + use crate::core::utils::ContractStyle; + use crate::core::termstructure::YieldTermStructure; + use crate::core::quotes::Quote; + use chrono::{Datelike, Local, NaiveDate}; + #[test] + fn test_from_json() { + let data = Contract { + action: "PV".to_string(), + market_data: Some(MarketData { + underlying_price: 100.0, + strike_price: 100.0, + volatility: None, + option_price: Some(10.0), + risk_free_rate: Some(0.05), + dividend: Some(0.0), + maturity: "2024-01-01".to_string(), + option_type: "C".to_string(), + simulation: None + }), + pricer: "Analytical".to_string(), + asset: "".to_string(), + style: Some("European".to_string()), + rate_data: None + }; + let option = EquityOption::from_json(data); + assert_eq!(option.option_type, OptionType::Call); + assert_eq!(option.transection, Transection::Buy); + assert_eq!(option.underlying_price.value, 100.0); + assert_eq!(option.strike_price, 100.0); + assert_eq!(option.current_price.value, 10.0); + assert_eq!(option.dividend_yield, 0.0); + assert_eq!(option.volatility, 0.2); + assert_eq!(option.maturity_date, NaiveDate::from_ymd(2024, 1, 1)); + assert_eq!(option.valuation_date, Local::today().naive_utc()); + assert_eq!(option.engine, Engine::BlackScholes); + assert_eq!(option.style, ContractStyle::European); + } +} + diff --git a/src/equity/vol_surface.rs b/src/equity/vol_surface.rs new file mode 100644 index 0000000..c6b8025 --- /dev/null +++ b/src/equity/vol_surface.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; +use crate::rates::utils::{DayCountConvention}; +use chrono::{NaiveDate}; +#[derive(Clone,Debug,Serialize,Deserialize)] +pub struct VolSurface{ + pub term_structure: BTreeMap>, + pub spot: f64, + pub spot_date: NaiveDate, + pub day_count: DayCountConvention, +} + +impl VolSurface { + pub fn new(term_structure: BTreeMap>,spot:f64,spot_date:NaiveDate,day_count:DayCountConvention) -> VolSurface { + VolSurface { + term_structure, + spot, + spot_date, + day_count + } + } + pub fn get_vol(&self,val_date:NaiveDate,maturity_date:NaiveDate,strike:f64)-> f64{ + //TODO: Interpolate Vol Surface + 0.0 + } + pub fn get_year_fraction(&self,val_date:NaiveDate,maturity_date:NaiveDate) -> f64 { + self.day_count.get_year_fraction(val_date,maturity_date) + } + +} \ No newline at end of file diff --git a/src/input/IR/ir1.json b/src/input/IR/ir1.json new file mode 100644 index 0000000..f383443 --- /dev/null +++ b/src/input/IR/ir1.json @@ -0,0 +1,67 @@ +{"contracts" : [ +{ +"action":"PV", +"pricer":"Analytical", +"asset":"IR", +"rate_data":{ + "instrument": "Deposit", + "currency": "USD", + "start_date": "0M", + "maturity_date":"1M", + "valuation_date": "0M", + "notional": 1000000, + "fix_rate": 0.055, + "day_count": "A360", + "business_day_adjustment": 0 +} +}, +{ +"action":"PV", +"pricer":"Analytical", +"asset":"IR", +"rate_data":{ + "instrument": "Deposit", + "currency": "USD", + "start_date": "0M", + "maturity_date":"3M", + "valuation_date": "0M", + "notional": 1000000, + "fix_rate": 0.05, + "day_count": "A360", + "business_day_adjustment": 0 +} +}, +{ + "action":"PV", + "pricer":"Analytical", + "asset":"IR", + "rate_data":{ + "instrument": "FRA", + "currency": "USD", + "start_date": "3M", + "maturity_date":"6M", + "valuation_date": "0M", + "notional": 1000000, + "fix_rate": 0.06, + "day_count": "A360", + "business_day_adjustment": 0 + } +}, +{ + "action":"PV", + "pricer":"Analytical", + "asset":"IR", + "rate_data":{ + "instrument": "FRA", + "currency": "USD", + "start_date": "6M", + "maturity_date":"9M", + "valuation_date": "0M", + "notional": 1000000, + "fix_rate": 0.065, + "day_count": "A360", + "business_day_adjustment": 0 + } +} +] +} \ No newline at end of file diff --git a/src/input/cmdty_option.json b/src/input/cmdty_option.json new file mode 100644 index 0000000..ae3795c --- /dev/null +++ b/src/input/cmdty_option.json @@ -0,0 +1,29 @@ +{"contracts" : [{ +"action":"PV", +"pricer":"Analytical", +"asset":"CO", +"market_data":{ + "underlying_price":2.846, + "option_type":"C", + "strike_price":2.75, + "volatility":0.587, + "maturity":"2023-10-26", + "simulation":100000 + } + +}, +{ +"action":"PV", +"pricer":"Analytical", +"asset":"CO", +"market_data":{ + "underlying_price":2.846, + "option_type":"C", + "strike_price":2.85, + "volatility":0.587, + "maturity":"2023-10-26", + "simulation":100000 + } + +}] +} \ No newline at end of file diff --git a/src/input/equity_option.json b/src/input/equity_option.json new file mode 100644 index 0000000..d53abbe --- /dev/null +++ b/src/input/equity_option.json @@ -0,0 +1,31 @@ +{"contracts" : [ +{ +"action":"PV", +"pricer":"Analytical", +"asset":"EQ", +"market_data":{ +"underlying_price":100, +"option_type":"C", +"strike_price":100, +"volatility":0.4, +"risk_free_rate":0.06, +"maturity":"2023-12-31", +"dividend": 0.01 +} +}, +{ +"action":"PV", +"pricer":"MonteCarlo", +"asset":"EQ", +"market_data":{ +"underlying_price":100, +"option_type":"C", +"strike_price":105, +"volatility":0.45, +"risk_free_rate":0.06, +"maturity":"2023-12-31", +"dividend": 0.01, +"simulation":100000 +}} +] +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8f5f45c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,220 @@ +// extern crate probability; +// extern crate rand_chacha; +// extern crate rand_pcg; + +use rand; +use rand::{SeedableRng}; +use chrono::{Local,DateTime,NaiveDate,NaiveTime,Datelike, Duration}; +use rand::distributions::{Standard,Uniform}; +use rand::distributions::Distribution; +use rand_distr::StandardNormal; +mod equity; +mod core; +mod utils; +mod cmdty; +mod rates; + +use std::fs; +use std::path::Path; + +use rand::prelude::*; +use serde::Deserialize; + +use std::fs::File; +use std::io::Read; +use std::{io, thread}; +use std::collections::HashMap; +use std::error::Error; +use csv; +//use std::env::{args,Args}; +use utils::read_csv; +use utils::RNG; + +use std::env::{args, temp_dir}; +use rand::Rng; +use equity::blackscholes; +use crate::equity::montecarlo; +use clap::{App, Arg, ArgMatches, SubCommand}; +use std::env; +use utils::parse_json; +use std::time::{Instant}; + +#[allow(dead_code)] +#[allow(unused_variables)] +fn main() { + let matches = App::new("Rust Quant Option Pricing CLI") + .version("0.1.0") + .author("Siddharth Singh ") + .about("Pricing and risk management of financial derivatives") + .subcommand( + SubCommand::with_name("build") + .about("Building the curve / Vol surface") + .arg( + Arg::with_name("input") + .short("i") + .long("input") + .value_name("FILE") + .help("input financial contracts to use in constuction ") + .required(true) + .takes_value(true) + ) + .arg( + Arg::with_name("output") + .short("o") + .long("output") + .value_name("FILE") + .help("Output file name") + .required(true) + .takes_value(true) + ) + ) + .subcommand( + SubCommand::with_name("file") + .about("Pricing a single contract") + .arg( + Arg::with_name("input") + .short("i") + .long("input") + .value_name("FILE") + .help("Pricing a single contract") + .required(true) + .takes_value(true) + ) + .arg( + Arg::with_name("output") + .short("o") + .long("output") + .value_name("FILE") + .help("Output file name") + .required(true) + .takes_value(true) + ) + ) + .subcommand( + SubCommand::with_name("dir") + .about("Pricing all contracts in a directory") + .arg( + Arg::with_name("input") + .short("i") + .long("input") + .value_name("DIR") + .help("Pricing all contracts in a directory") + .required(true) + .takes_value(true) + ) + .arg( + Arg::with_name("output") + .short("o") + .long("output") + .value_name("DIR") + .help("Output priced contracts to a directory") + .required(true) + .takes_value(true) + ) + ) + .subcommand( + SubCommand::with_name("interactive") + .about("Interactive mode") + ) + .get_matches(); + let build_matches = matches.subcommand_matches("build"); + let input_matches = matches.subcommand_matches("file"); + let dir_matches = matches.subcommand_matches("dir"); + let interactive_matches = matches.subcommand_matches("interactive"); + match matches.subcommand(){ + ("build",Some(build_matches)) => { + let input_file = build_matches.value_of("input").unwrap(); + let output_file = build_matches.value_of("output").unwrap(); + let mut file = File::open(input_file).expect("Failed to open JSON file"); + let start_time = Instant::now(); + parse_json::build_curve(&mut file,output_file); + let end_time = Instant::now(); + let elapsed_time = end_time - start_time; + println!("Time taken: {:?}", elapsed_time); + } + ("file",Some(input_matches)) => { + let input_file = input_matches.value_of("input").unwrap(); + let output_file = input_matches.value_of("output").unwrap(); + let mut file = File::open(input_file).expect("Failed to open JSON file"); + let start_time = Instant::now(); + parse_json::parse_contract(&mut file,output_file); + let end_time = Instant::now(); + let elapsed_time = end_time - start_time; + println!("Time taken: {:?}", elapsed_time); + } + ("dir",Some(dir_matches)) => { + let input_dir = dir_matches.value_of("input").unwrap(); + let output_dir = dir_matches.value_of("output").unwrap(); + let start_time = Instant::now(); + let mut output_vec:Vec = Vec::new(); + let files = fs::read_dir(input_dir).unwrap(); + for ifile in files { + let ifile = ifile.unwrap(); + let path = ifile.path(); + if path.is_file(){ + // Check if the file has a ".json" extension + if let Some(extension) = path.extension() { + if extension == "json" { + let mut file = File::open(ifile.path()).expect("Failed to open JSON file"); + let output_file_i = output_dir.to_owned() + "\\" + &ifile.path().file_name().unwrap().to_str().unwrap(); + parse_json::parse_contract(&mut file,&output_file_i); + } + } + } + } + let end_time = Instant::now(); + let elapsed_time = end_time - start_time; + println!("Time taken to process the dir: {:?}", elapsed_time); + } + ("interactive",Some(interactive_matches)) => { + println!("Welcome to Option pricing CLI"); + println!("Welcome to Option pricing CLI"); + loop { + println!(" Do you want to price option (1) or calculate implied volatility (2)? or (3) to exit"); + let mut input = String::new(); + print!("{}", input); + io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + let input: u8 = input.trim().parse::().unwrap(); + if input == 1 { + println!("Do you want to use the Black-Sholes (1) or Monte-Carlo (2) option pricing model?"); + let mut model = String::new(); + io::stdin() + .read_line(&mut model) + .expect("Failed to read line"); + let model: u8 = model.trim().parse::().unwrap(); + if model == 1 { + blackscholes::option_pricing(); + } else if model == 2 { + montecarlo::option_pricing(); + } else { + println!("You gave a wrong number! The accepted arguments are 1 and 2.") + } + } else if input == 2 { + blackscholes::implied_volatility(); + } + else if input == 3 { + break; + } + else { + println!("You gave a wrong number! The accepted arguments are 1 and 2.") + } + + } + } + (_, _) => { + println!("No mode specified. Please use --help to see the available options."); + } + } + +} + + + + + + + + + diff --git a/src/rates/build_contracts.rs b/src/rates/build_contracts.rs new file mode 100644 index 0000000..892bf63 --- /dev/null +++ b/src/rates/build_contracts.rs @@ -0,0 +1,103 @@ +use crate::rates; +use crate::rates::deposits::Deposit; +use chrono::{NaiveDate,Local,Weekday}; +use chrono::Datelike; +use crate::rates::fra::FRA; +use crate::core::traits::{Instrument,Rates}; +use crate::core::utils::{Contract, Contracts}; +use crate::rates::utils::TermStructure; + +pub fn build_ir_contracts(data: Contract) -> Box { + let rate_data = data.rate_data.clone().unwrap(); + let mut start_date_str = rate_data.start_date; // Only for 0M case + let mut maturity_date_str = rate_data.maturity_date; + let current_date = Local::today(); + let maturity_date = rates::utils::convert_mm_to_date(maturity_date_str); + let start_date = rates::utils::convert_mm_to_date(start_date_str); + if rate_data.instrument.as_str() == "Deposit" { + let mut deposit = Deposit { + start_date: start_date, + maturity_date: maturity_date, + valuation_date: current_date.naive_utc(), + notional: rate_data.notional, + fix_rate: rate_data.fix_rate, + day_count: rates::utils::DayCountConvention::Act360, + business_day_adjustment: 0, + term_structure: None + }; + match rate_data.day_count.as_str() { + "Act360" |"A360" => { + deposit.day_count = rates::utils::DayCountConvention::Act360; + } + "Act365" |"A365" => { + deposit.day_count = rates::utils::DayCountConvention::Act365; + } + "Thirty360" |"30/360" => { + deposit.day_count = rates::utils::DayCountConvention::Thirty360; + } + _ => {} + } + let mut ird:Box = Box::new(deposit); + return ird; + } + else if rate_data.instrument.as_str()=="FRA" { + //let mut start_date_str = rate_data.start_date; + //let mut maturity_date_str = rate_data.maturity_date; + //let current_date = Local::today(); + //let maturity_date = rates::utils::convert_mm_to_date(maturity_date_str); + //let start_date = rates::utils::convert_mm_to_date(start_date_str); + let mut fra = FRA { + start_date: start_date, + maturity_date: maturity_date, + valuation_date: current_date.naive_utc(), + notional: rate_data.notional, + currency: rate_data.currency, + fix_rate: rate_data.fix_rate, + day_count: rates::utils::DayCountConvention::Act360, + business_day_adjustment: 0, + term_structure: None + }; + match rate_data.day_count.as_str() { + "Act360" |"A360" => { + fra.day_count = rates::utils::DayCountConvention::Act360; + } + "Act365" |"A365" => { + fra.day_count = rates::utils::DayCountConvention::Act365; + } + "Thirty360" |"30/360" => { + fra.day_count = rates::utils::DayCountConvention::Thirty360; + } + _ => {} + } + let ird:Box = Box::new(fra); + return ird; + } + else { + panic!("Invalid asset"); + } +} + +pub fn build_ir_contracts_from_json(data: Vec) -> Vec> { + let mut irds:Vec> = Vec::new(); + for contract in data { + let ird = build_ir_contracts(contract); + irds.push(ird); + } + return irds; +} +pub fn build_term_structure(mut contracts:Vec>) -> TermStructure { + let mut ts:rates::utils::TermStructure = rates::utils::TermStructure::new(vec![],vec![],vec![], + rates::utils::DayCountConvention::Act360); + let mut contract = contracts[0].as_mut(); + ts.discount_factor.push(contract.get_maturity_discount_factor()); + ts.date.push(contract.get_maturity_date()); + ts.rate.push(contract.get_rate()); + for i in 1..contracts.len(){ + let mut contract = contracts[i].as_mut(); + contract.set_term_structure(ts.clone()); + ts.discount_factor.push(contract.get_maturity_discount_factor()); + ts.date.push(contract.get_maturity_date()); + ts.rate.push(contract.get_rate()); + } + return ts +} \ No newline at end of file diff --git a/src/rates/deposits.rs b/src/rates/deposits.rs new file mode 100644 index 0000000..f9899e5 --- /dev/null +++ b/src/rates/deposits.rs @@ -0,0 +1,134 @@ +use chrono::{Local, NaiveDate}; +use crate::core::traits::{Instrument,Rates}; +use crate::rates::utils::{DayCountConvention,TermStructure}; +/* +"" An deposit is an agreement to borrow money interbank at the Ibor fixing rate starting on the start + date and repaid on the maturity date with the interest amount calculated according to a day + count convention and dates calculated according to a calendar and business day adjustment rule. + */ +pub struct Deposit { + pub start_date: NaiveDate, + pub maturity_date: NaiveDate, + pub valuation_date: NaiveDate, + pub notional: f64, + pub fix_rate: f64, + pub day_count: DayCountConvention, + pub business_day_adjustment: i8, + pub term_structure: Option, +} +impl Deposit { + pub fn new(start_date: NaiveDate, maturity_date: NaiveDate, valuation_date: NaiveDate, + notional: f64, fix_rate: f64, day_count: DayCountConvention, + business_day_adjustment: i8) -> Deposit { + Deposit { + start_date, + maturity_date, + valuation_date, + notional, + fix_rate, + day_count, + business_day_adjustment, + term_structure: None, + } + } + pub fn builder(start_date: String,maturity_date:String,notional: f64, fix_rate: f64,day_count: String) ->Deposit{ + + let today = Local::today(); + let start_date = NaiveDate::parse_from_str(&start_date, "%Y-%m-%d").expect("Invalid date format"); + let maturity_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); + let mut deposit = Deposit { + start_date: start_date, + maturity_date: maturity_date, + valuation_date: today.naive_utc(), + notional: 1000000.0, + fix_rate: 0.05, + day_count: DayCountConvention::Act360, + business_day_adjustment: 0, + term_structure: None, + }; + match day_count.as_str() { + "Act360" |"A360" => { + deposit.day_count = DayCountConvention::Act360; + } + "Act365" |"A365" => { + deposit.day_count = DayCountConvention::Act365; + } + "Thirty360" |"30/360" => { + deposit.day_count = DayCountConvention::Thirty360; + } + _ => {} + } + return deposit; + } + pub fn get_start_date(&self) -> NaiveDate { + self.start_date + } + + pub fn get_notional(&self) -> f64 { + self.notional + } + pub fn get_rate(&self) -> f64 { + let df = self.get_discount_factor(); + let year_fraction = self.get_year_fraction(self.start_date); + -df.ln() / year_fraction + } + pub fn get_business_day_adjustment(&self) -> i8 { + self.business_day_adjustment + } + pub fn get_year_fraction(&self,date:NaiveDate) -> f64 { + let duration = self.maturity_date.signed_duration_since(date); + let year_fraction = duration.num_days() as f64 / self.day_count.num_of_days() as f64; + year_fraction + } + pub fn get_discount_factor(&self) -> f64 { + let year_fraction = self.get_year_fraction(self.start_date); + let discount_factor = 1.0 / (1.0 + self.fix_rate * year_fraction); + discount_factor + } + pub fn get_remaining_interest_amount(&self) -> f64 { + let year_fraction = self.get_year_fraction(self.valuation_date); + let interest_amount = self.notional * self.fix_rate * year_fraction; + interest_amount + } + pub fn get_value(&self) -> f64 { + //let discount_factor = self.get_discount_factor(); + // + //let pv = self.notional * discount_factor + interest_amount; + let value = (1.0 + self.fix_rate * self.get_year_fraction(self.start_date)) * self.notional; + value + } + + pub fn get_pv(&self,curve:&TermStructure) -> f64 { + let df = curve.interpolate_log_linear(self.valuation_date,self.maturity_date); + let value = self.get_value() * df; + return value; + } +} +impl Rates for Deposit{ + fn get_implied_rates(&self) -> f64 { + let curve = self.term_structure.as_ref().expect("Term structure is not set"); + let df = curve.interpolate_log_linear(self.valuation_date,self.maturity_date); + let implied_rate = (1.0/df - 1.0)/self.get_year_fraction(self.valuation_date); + return implied_rate; + } + fn get_maturity_date(&self) -> NaiveDate { + self.maturity_date + } + fn get_rate(&self) -> f64 { + self.fix_rate + } + fn get_maturity_discount_factor(&self) -> f64 { + self.get_discount_factor() + } + fn get_day_count(&self) -> &DayCountConvention { + &self.day_count + } + fn set_term_structure(&mut self,term_structure:TermStructure) { + self.term_structure = Some(term_structure); + } +} +// impl Instrument for Deposit { +// fn npv(&self) -> f64 { +// self.get_pv() +// } +// } diff --git a/src/rates/fra.rs b/src/rates/fra.rs new file mode 100644 index 0000000..1f241ab --- /dev/null +++ b/src/rates/fra.rs @@ -0,0 +1,108 @@ +use chrono::{Local, NaiveDate}; +use crate::core::traits::{Instrument,Rates}; +use crate::rates::utils::{DayCountConvention,TermStructure}; + +/* + A forward rate agreement or simply "forward contract" is an agreement to exchange a fixed pre-agreed rate for a + floating rate is not known until some specified + future fixing date. The FRA payment occurs on or soon after this date + on the FRA settlement date. Typically the timing gap is two days. + + */ + +pub struct FRA { + pub start_date: NaiveDate, //The date the FRA starts to accrue interest + pub maturity_date: NaiveDate, + pub valuation_date: NaiveDate, + pub notional: f64, + pub currency: String, + pub fix_rate: f64, + pub day_count: DayCountConvention, + pub business_day_adjustment: i8, + pub term_structure: Option, +} +impl FRA { + pub fn new(start_date: NaiveDate, maturity_date: NaiveDate, valuation_date: NaiveDate, + notional: f64, fix_rate: f64, day_count: DayCountConvention, + business_day_adjustment: i8) -> FRA { + FRA { + start_date, + maturity_date, + valuation_date, + notional, + currency: String::from("USD"), + fix_rate, + day_count, + business_day_adjustment, + term_structure: None, + } + } + pub fn builder(start_date: String,maturity_date:String,notional: f64, fix_rate: f64,day_count: String) ->FRA{ + + let today = Local::today(); + let start_date = NaiveDate::parse_from_str(&start_date, "%Y-%m-%d").expect("Invalid date format"); + let maturity_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); + let mut fra = FRA { + start_date: start_date, + maturity_date: maturity_date, + valuation_date: today.naive_utc(), + notional: notional, + currency: String::from("USD"), + fix_rate: fix_rate, + day_count: DayCountConvention::Act360, + business_day_adjustment: 0, + term_structure: None, + }; + match day_count.as_str() { + "Act360" |"A360" => { + fra.day_count = DayCountConvention::Act360; + } + "Act365" |"A365" => { + fra.day_count = DayCountConvention::Act365; + } + "Thirty360" |"30/360" => { + fra.day_count = DayCountConvention::Thirty360; + } + _ => {} + } + fra + } + pub fn get_year_fraction(&self,date:NaiveDate) -> f64 { + let duration = self.maturity_date.signed_duration_since(date); + let year_fraction = duration.num_days() as f64 / self.day_count.num_of_days() as f64; + year_fraction + } + pub fn get_discount_factor(&self,df_start_date:f64) -> f64 { + let year_fraction = self.get_year_fraction(self.start_date); + let discount_factor = df_start_date / (1.0 + self.fix_rate * year_fraction); + discount_factor + } +} + +impl Rates for FRA{ + fn get_implied_rates(&self) -> f64 { + let curve = self.term_structure.as_ref().expect("Term structure is not set"); + let df = curve.interpolate_log_linear(self.valuation_date,self.maturity_date); + let implied_rate = (1.0/df - 1.0)/self.get_year_fraction(self.valuation_date); + return implied_rate; + } + fn get_maturity_date(&self) -> NaiveDate { + self.maturity_date + } + fn get_rate(&self) -> f64 { + let df = self.get_maturity_discount_factor(); + let time = self.get_year_fraction(self.valuation_date); + -df.ln() / time + } + fn get_maturity_discount_factor(&self) -> f64 { + let curve = self.term_structure.as_ref().expect("Term structure is not set"); + let df = curve.interpolate_log_linear(self.valuation_date,self.start_date); + self.get_discount_factor(df) + } + fn get_day_count(&self) -> &DayCountConvention { + &self.day_count + } + fn set_term_structure(&mut self,term_structure:TermStructure) { + self.term_structure = Some(term_structure); + } +} diff --git a/src/rates/mod.rs b/src/rates/mod.rs new file mode 100644 index 0000000..6997b30 --- /dev/null +++ b/src/rates/mod.rs @@ -0,0 +1,4 @@ +pub mod deposits; +pub(crate) mod utils; +pub mod fra; +pub mod build_contracts; diff --git a/src/rates/utils.rs b/src/rates/utils.rs new file mode 100644 index 0000000..e362269 --- /dev/null +++ b/src/rates/utils.rs @@ -0,0 +1,168 @@ +use chrono::{Local, NaiveDate, Weekday}; +use chrono::Datelike; +use crate::rates::deposits::Deposit; +use crate::core::traits::Rates; +use serde::{Serialize,Deserialize}; +fn is_weekend(date: NaiveDate) -> bool { + // Check if the day of the week is Saturday (6) or Sunday (7) + let day_of_week = date.weekday(); + day_of_week == Weekday::Sat || day_of_week == Weekday::Sun +} + +fn is_holiday(date: NaiveDate) -> bool { + // Check if the date is Christmas + if date.month() == 12 && date.day() == 25{ + return true; + } + else if date.month() == 1 && date.day() == 1{ // Check if the date is New Year's Day + return true; + } + else if date.month() == 1 && date.weekday() == Weekday::Mon + && date.day() > 14 && date.day() <= 21 { + // Check if the date is Martin Luther King Jr. Day + return true; + } + else if date.month() == 11 && + date.weekday() == Weekday::Thu && date.day() > 21 && date.day() <= 28{ + // Check if the date is in November and falls on the fourth Thursday Thanksgiving Day + return true; + } + else if date.month() == 9 && date.weekday() == Weekday::Mon && date.day() <= 7{ + // Check if the date is in September and falls on the first Monday Labor Day + return true; + } + return false; +} + +fn adjust_for_weekend(mut date: NaiveDate) -> NaiveDate { + // Increment the date until it's not a weekend + while is_holiday(date) || is_holiday(date) { + date = date.succ(); + + } + date +} +#[derive(Clone,Debug,Serialize,Deserialize)] +pub enum DayCountConvention{ + Act365, + Act360, + Thirty360, +} +impl DayCountConvention{ + pub fn num_of_days(&self) -> usize + { + match self { + DayCountConvention::Act365 => 365, + DayCountConvention::Act360 => 360, + DayCountConvention::Thirty360 => 360, + } + } + pub fn get_year_fraction(&self,start_date:NaiveDate,maturity_date:NaiveDate) -> f64 { + let duration = maturity_date.signed_duration_since(start_date); + let year_fraction = duration.num_days() as f64 / self.num_of_days() as f64; + year_fraction + } +} + +#[derive(Clone,Debug)] +pub struct TermStructure { + pub date: Vec, + pub discount_factor: Vec, + pub rate: Vec, + pub day_count: DayCountConvention, +} + +impl TermStructure { + pub fn new(date: Vec, discount_factor: Vec,rate:Vec,day_count:DayCountConvention) -> TermStructure { + TermStructure { + date, + discount_factor, + rate, + day_count + } + } + + pub fn interpolate_log_linear(&self,val_date:NaiveDate,maturity_date:NaiveDate)-> f64{ + let year_fraction = self.get_year_fraction(val_date); + let target_yf = maturity_date.signed_duration_since(val_date).num_days() as f64 + / self.day_count.num_of_days() as f64; + let mut df1 = 1.0; + let mut df2 = 1.0; + let mut t1 = 0.0; + let mut t2 = 0.0; + for (i, time) in year_fraction.iter().enumerate() { + if time==&target_yf{ + return self.discount_factor[i]; + } + else if time< &target_yf { + t1 = *time; + df1 = self.discount_factor[i]; + } + else if time> &target_yf { + t2 = *time; + df2 = self.discount_factor[i]; + break; + } + + } + let log_df1 = f64::ln(df1); + let log_df2 = f64::ln(df2); + let w = (target_yf - t1) / (t2 - t1); + let log_df = log_df1 + w * (log_df2 - log_df1); + let df = f64::exp(log_df); + return df; + //let dfs = self.discount_factor; + + } + pub fn get_year_fraction(&self,val_date:NaiveDate) -> Vec { + let mut year_fraction_vec:Vec = Vec::new(); + for time in self.date.iter() { + let duration = time.signed_duration_since(val_date); + let year_fraction = duration.num_days() as f64 / self.day_count.num_of_days() as f64; + year_fraction_vec.push(year_fraction); + } + year_fraction_vec + } + pub fn rates(&self,val_date:NaiveDate) -> Vec { + let mut rates:Vec = Vec::new(); + for i in 0..self.discount_factor.len() { + let rate = (1.0 / self.discount_factor[i] - 1.0) / self.day_count.get_year_fraction(val_date,self.date[i]); + rates.push(rate); + } + return rates; + } + pub fn build_term_structure(&self,valuation_date:NaiveDate,deposits:Vec) -> TermStructure { + let mut discount_factor:Vec = Vec::new(); + let mut rate:Vec = Vec::new(); + let mut dates:Vec = Vec::new(); + for deposit in deposits.iter() { + discount_factor.push(deposit.get_discount_factor()); + dates.push(deposit.get_maturity_date()); + rate.push(deposit.get_rate()); + } + let day_count = deposits[0].day_count.clone(); + let mut term_structure = TermStructure::new(dates,discount_factor,rate,day_count); + return term_structure; + } +} + +pub fn convert_mm_to_date(mut date: String) -> NaiveDate { + let current_date = Local::today(); + date.pop(); + let month = date.parse::().unwrap(); + + let (new_year, new_month) = if current_date.month() + month > 12 { + let year = ((current_date.month() + month) / 12) as i32; + let m:u32 = (year * 12) as u32; + let new_month = current_date.month() + month; + (current_date.year() + year, new_month-m) + } else { + (current_date.year(), current_date.month() + month) + }; + let date_in_months = current_date.with_year(new_year).unwrap_or(current_date) + .with_month(new_month).unwrap_or(current_date); + let mut maturity_date = date_in_months.naive_utc(); + maturity_date = adjust_for_weekend(maturity_date); + return maturity_date; +} + diff --git a/src/utils/RNG.rs b/src/utils/RNG.rs new file mode 100644 index 0000000..ba7c186 --- /dev/null +++ b/src/utils/RNG.rs @@ -0,0 +1,138 @@ +use rand::Rng; +use rand::distributions::{Standard,Uniform}; +use rand::distributions::Distribution; +use rand_distr::StandardNormal; +use std::f64::consts::PI; +use libm::cos; +use libm::sin; +use std::io::Write; // bring trait into scope +use byteorder::{ByteOrder, LittleEndian,BigEndian}; +use byteorder::WriteBytesExt; +use byteorder::ReadBytesExt; +use std::fs::File; +use std::io::prelude::*; +use std::fs; +use std::path::Path; +use std::env::temp_dir; +fn generate_standard_normal_marsaglia_polar() -> (f64, f64) { + let mut rng = rand::thread_rng(); + let mut X = 0.0; + let mut Y = 0.0; + let mut S = 0.0f64; + + while(true) { + X = Uniform::new(0.0,1.0).sample(&mut rng)*2.0 -1.0; + Y = Uniform::new(0.0,1.0).sample(&mut rng)*2.0 -1.0; + S = X*X + Y*Y; + if S<1.0f64 && S != 0.0f64 { + break; + } + } + + let I = ((-2.0 * S.ln()) / S).sqrt(); + (I*X,I*Y) + +} + +fn generate_standard_normal_box() -> (f64, f64) { + let mut rng = rand::thread_rng(); + + let r:f64 = Uniform::new(0.0,1.0).sample(&mut rng); + let p:f64 = Uniform::new(0.0,1.0).sample(&mut rng); + + let tmp:f64 = (-2.0*r.ln()).sqrt(); + (tmp*cos(p*2.0*PI),tmp*sin(p*2.0*PI)) + +} + +pub struct MonteCarloSimulation{ + pub antithetic:bool, + pub moment_matching:bool, + pub dimentation: u64, + pub size: u64, + pub standard_normal_vector: Vec, + pub standard_normal_matrix: Vec> +} +impl MonteCarloSimulation{ + pub fn set_standard_normal_vector(&mut self) { + let mut dir = temp_dir(); + dir.push("rng1d"); + let rng_dir = dir.as_path(); + if !rng_dir.exists() { + let _ = fs::create_dir(rng_dir); + } + dir.push("1dt.bin"); + let path = dir.as_path(); + let mut rn_vec:Vec = Vec::new(); + if path.exists() { + rn_vec = read_from_file_byteorder(path).unwrap(); + self.standard_normal_vector = rn_vec; + } + else{ + let mut rng = rand::thread_rng(); + let mut rn_vec:Vec = Vec::new(); + + for i in 0..self.size{ + let rn = rng.sample(StandardNormal); + rn_vec.push(rn); + if self.antithetic{ + rn_vec.push(-rn) + } + } + if self.moment_matching{ + let sum = rn_vec.iter().sum::() as f64; + let mean = sum / rn_vec.len() as f64; + let variance = rn_vec.iter().map(|x| { + let diff = mean -(*x as f64); + diff*diff + }).sum::()/rn_vec.len() as f64; + let std_dev = variance.sqrt(); + let mut mo_rn_vec = vec![]; + for i in 0..rn_vec.len() { + let mo_rn = (rn_vec[i]-mean)/std_dev as f64; + mo_rn_vec.push(mo_rn) + } + rn_vec = mo_rn_vec; + } + self.standard_normal_vector = rn_vec.clone(); + write_to_file_byteorder(&rn_vec, path).unwrap(); + } + } + pub fn get_standard_normal_vector(&self) ->&Vec{ + let ptr = &self.standard_normal_vector; + ptr + } + +} + +pub fn get_matrix_standard_normal(size_n:u64,size_m:u64)-> Vec> { + // let mut dir = temp_dir(); + // dir.push("rng2d"); + // dir.push("1dt.bin"); + // let path = dir.as_path(); + + let mut rng = rand::thread_rng(); + let mut rn_vec_n:Vec> = Vec::new(); + for i in 0..size_n{ + let mut rn_vec_m:Vec = Vec::new(); + for j in 0..size_m{ + rn_vec_m.push(rng.sample(StandardNormal)); + } + rn_vec_n.push(rn_vec_m); + } + rn_vec_n +} +fn write_to_file_byteorder>(data: &[f64], path: P) -> std::io::Result<()> { + let mut file = File::create(path)?; + for f in data { + file.write_f64::(*f)?; + } + Ok(()) +} +fn read_from_file_byteorder>(path: P) -> std::io::Result> { + let mut file = File::open(path)?; + let buf_len = file.metadata()?.len() / 8; // 8 bytes for one f64 + let mut buf: Vec = vec![0.0; buf_len.try_into().unwrap()]; + file.read_f64_into::(&mut buf)?; + Ok(buf) +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..b6ecdde --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,4 @@ +pub mod read_csv; +pub mod RNG; +pub mod stochastic_processes; +pub mod parse_json; diff --git a/src/utils/parse_cli.rs b/src/utils/parse_cli.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/parse_json.rs b/src/utils/parse_json.rs new file mode 100644 index 0000000..2abb6b2 --- /dev/null +++ b/src/utils/parse_json.rs @@ -0,0 +1,210 @@ +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::fs; +use byteorder::{ByteOrder, LittleEndian,BigEndian}; +use std::io::Read; +use chrono::{Datelike, Local, NaiveDate}; +use crate::core::quotes::Quote; +use crate::core::termstructure::YieldTermStructure; +use crate::equity::vanila_option::{EquityOption}; +//use crate::core::utils::{dN, N}; +//use super::vanila_option::{EquityOption}; +use crate::equity::utils::{Engine}; +use crate::cmdty::cmdty_option::{CmdtyOption}; +use crate::core::trade; +use crate::cmdty::cmdty_option; +use crate::core::traits::{Instrument, Rates}; +use crate::core::utils; +use crate::core::utils::{CombinedContract, ContractOutput, Contracts, OutputJson,EngineType}; +use crate::core::utils::ContractStyle; +use crate::core::traits::Greeks; +use std::io::Write; +use std::env::temp_dir; +//use crate::read_csv::read_ts; +use crate::rates; +use crate::rates::deposits::Deposit; +use crate::rates::build_contracts::{build_ir_contracts, build_ir_contracts_from_json, build_term_structure}; +use crate::equity::build_contracts::{build_volatility_surface, build_eq_contracts_from_json}; +pub fn build_curve(mut file: &mut File,output_filename: &str)->() { + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Failed to read JSON file"); + let list_contracts: Contracts = serde_json::from_str(&contents).expect("Failed to deserialize JSON"); + if list_contracts.contracts.len() == 0 { + panic!("No contracts found in JSON file"); + } + else if list_contracts.asset=="EQ"{ + let mut contracts:Vec> = build_eq_contracts_from_json(list_contracts.contracts); + let vol_surface = build_volatility_surface(contracts); + let mut dir = std::path::PathBuf::from(output_filename); + + dir.push("vol_surface"); + let vol_dir = dir.as_path(); + if !vol_dir.exists() { + let _ = fs::create_dir(vol_dir); + } + dir.push("vol_surface.json"); + //Todo write Vol Surface to file + + let mut file = File::create(dir).expect("Failed to create file"); + let mut output: String = String::new(); + let serialized_vol_surface = serde_json::to_string(&vol_surface).unwrap(); + file.write_all(serialized_vol_surface.as_bytes()).expect("Failed to write to file"); + + } + else if list_contracts.asset=="CO"{ + panic!("Commodity contracts not supported"); + } + else if list_contracts.asset=="IR"{ + let mut contracts:Vec> = build_ir_contracts_from_json(list_contracts.contracts); + let ts = build_term_structure(contracts); + let mut dir = std::path::PathBuf::from(output_filename); + + dir.push("term_structure"); + let ts_dir = dir.as_path(); + if !ts_dir.exists() { + let _ = fs::create_dir(ts_dir); + } + dir.push("term_structure.csv"); + let mut file = File::create(dir).expect("Failed to create file"); + let mut output: String = String::new(); + for i in 0..ts.date.len(){ + output.push_str(&format!("{},{},{}\n",ts.date[i],ts.discount_factor[i],ts.rate[i])); + } + file.write_all(output.as_bytes()).expect("Failed to write to file"); + } + else{ + panic!("Asset class not supported"); + } +} + + +pub fn parse_contract(mut file: &mut File,output_filename: &str) { + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Failed to read JSON file"); + + let list_contracts: utils::Contracts = serde_json::from_str(&contents).expect("Failed to deserialize JSON"); + + //let data: utils::Contract = serde_json::from_str(&contents).expect("Failed to deserialize JSON"); + //let mut output: String = String::new(); + let mut output_vec:Vec = Vec::new(); + for data in list_contracts.contracts.into_iter() { + output_vec.push(process_contract(data)); + } + + let mut file = File::create(output_filename).expect("Failed to create file"); + //let mut output:OutputJson = OutputJson{contracts:output_vec}; + let output_str = output_vec.join(","); + //let output_json = serde_json::to_string(&output_vec).expect("Failed to generate output"); + file.write_all(output_str.as_bytes()).expect("Failed to write to file"); +} +pub fn process_contract(data: utils::Contract) -> String { + //println!("Processing {:?}",data); + let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0]; + let rates = vec![0.05,0.05,0.06,0.07,0.08,0.9,0.9,0.10]; + let ts = YieldTermStructure::new(date,rates); + + + if data.action=="PV" && data.asset=="EQ"{ + //let market_data = data.market_data.clone().unwrap(); + let option = EquityOption::from_json(data.clone()); + + let contract_output = utils::ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None }; + println!("Theoretical Price ${}", contract_output.pv); + println!("Delta ${}", contract_output.delta); + let combined_ = utils::CombinedContract{ + contract: data, + output:contract_output + }; + let output_json = serde_json::to_string(&combined_).expect("Failed to generate output"); + return output_json; + } + else if data.action=="PV" && data.asset=="CO"{ + let market_data = data.market_data.clone().unwrap(); + let curr_quote = Quote::new( market_data.underlying_price); + let option_type = &market_data.option_type; + let side: trade::OptionType; + match option_type.trim() { + "C" | "c" | "Call" | "call" => side = trade::OptionType::Call, + "P" | "p" | "Put" | "put" => side = trade:: OptionType::Put, + _ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."), + } + let maturity_date = &market_data.maturity; + let today = Local::today(); + let future_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format"); + let duration = future_date.signed_duration_since(today.naive_utc()); + let year_fraction = duration.num_days() as f64 / 365.0; + let vol = Some(market_data.volatility).unwrap(); + + let sim = market_data.simulation; + if data.pricer=="Analytical"{ + let mut option: CmdtyOption = CmdtyOption { + option_type: side, + transection: trade::Transection::Buy, + current_price: curr_quote, + strike_price: market_data.strike_price, + volatility: vol.unwrap(), + time_to_maturity: year_fraction, + transection_price: 0.0, + term_structure: ts, + engine: cmdty_option::Engine::Black76, + simulation: Option::from(sim.unwrap_or(10000)), + time_to_future_maturity: None, + risk_free_rate: None + }; + let contract_output = utils::ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None }; + println!("Theoretical Price ${}", contract_output.pv); + println!("Delta ${}", contract_output.delta); + let combined_ = utils::CombinedContract{ + contract: data, + output:contract_output + }; + let output_json = serde_json::to_string(&combined_).expect("Failed to generate output"); + return output_json; + + + } + + } + else if data.action=="PV" && data.asset=="IR"{ + //println!("Processing {:?}",data); + let rate_data = data.rate_data.clone().unwrap(); + let mut start_date_str = rate_data.start_date; // Only for 0M case + let mut maturity_date_str = rate_data.maturity_date; + let current_date = Local::today(); + let maturity_date = rates::utils::convert_mm_to_date(maturity_date_str); + let start_date = rates::utils::convert_mm_to_date(start_date_str); + println!("Maturity Date {:?}",maturity_date); + let mut deposit = rates::deposits::Deposit { + start_date: start_date, + maturity_date: maturity_date, + valuation_date: current_date.naive_utc(), + notional: rate_data.notional, + fix_rate: rate_data.fix_rate, + day_count: rates::utils::DayCountConvention::Act360, + business_day_adjustment: 0, + term_structure: None + }; + match rate_data.day_count.as_str() { + "Act360" |"A360" => { + deposit.day_count = rates::utils::DayCountConvention::Act360; + } + "Act365" |"A365" => { + deposit.day_count = rates::utils::DayCountConvention::Act365; + } + "Thirty360" |"30/360" => { + deposit.day_count = rates::utils::DayCountConvention::Thirty360; + } + _ => {} + } + let df = deposit.get_discount_factor(); + println!("Discount Factor {:?}",df); + return "Work in progress".to_string(); + } + else{ + panic!("Invalid action"); + } + return "Invalid Action".to_string(); + +} \ No newline at end of file diff --git a/src/utils/read_csv.rs b/src/utils/read_csv.rs new file mode 100644 index 0000000..c8cab0b --- /dev/null +++ b/src/utils/read_csv.rs @@ -0,0 +1,11 @@ +use csv; +pub fn read_ts(path: &str){ + let mut reader = csv::Reader::from_path(path).unwrap(); + for record in reader.records() { + let r = record.unwrap(); + println!("{:?}", &r[0]); + println!("{:?}", &r[1]); + + } + +} diff --git a/src/utils/stochastic_processes.rs b/src/utils/stochastic_processes.rs new file mode 100644 index 0000000..ed9d0f9 --- /dev/null +++ b/src/utils/stochastic_processes.rs @@ -0,0 +1,4 @@ +pub trait StochasticProcess{ + fn drift(&self)-> f64; + fn diffusion(&self)-> f64; +}