quantark 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (399) hide show
  1. quantark/__init__.py +3 -0
  2. quantark/_compat.py +150 -0
  3. quantark/asset/__init__.py +8 -0
  4. quantark/asset/bond/__init__.py +2 -0
  5. quantark/asset/bond/engine/__init__.py +44 -0
  6. quantark/asset/bond/engine/analytical/__init__.py +12 -0
  7. quantark/asset/bond/engine/analytical/black_engine.py +583 -0
  8. quantark/asset/bond/engine/analytical/bond_forward_engine.py +390 -0
  9. quantark/asset/bond/engine/analytical/bond_futures_engine.py +569 -0
  10. quantark/asset/bond/engine/convertible/__init__.py +12 -0
  11. quantark/asset/bond/engine/convertible/convertible_bond_engine.py +800 -0
  12. quantark/asset/bond/engine/discount/__init__.py +10 -0
  13. quantark/asset/bond/engine/discount/bond_discount_engine.py +517 -0
  14. quantark/asset/bond/engine/discount/frn_engine.py +913 -0
  15. quantark/asset/bond/engine/pde/__init__.py +14 -0
  16. quantark/asset/bond/engine/pde/convertible/__init__.py +21 -0
  17. quantark/asset/bond/engine/pde/convertible/jump_diffusion_engine.py +603 -0
  18. quantark/asset/bond/engine/pde/convertible/pde_params.py +59 -0
  19. quantark/asset/bond/engine/pde/convertible/tf_engine.py +546 -0
  20. quantark/asset/bond/engine/tree/__init__.py +14 -0
  21. quantark/asset/bond/engine/tree/convertible/__init__.py +21 -0
  22. quantark/asset/bond/engine/tree/convertible/binomial_engine.py +488 -0
  23. quantark/asset/bond/engine/tree/convertible/tree_params.py +72 -0
  24. quantark/asset/bond/engine/tree/convertible/trinomial_engine.py +1341 -0
  25. quantark/asset/bond/product/__init__.py +37 -0
  26. quantark/asset/bond/product/base_bond_product.py +114 -0
  27. quantark/asset/bond/product/convertible/__init__.py +16 -0
  28. quantark/asset/bond/product/convertible/convertible_bond.py +595 -0
  29. quantark/asset/bond/product/couponbond/__init__.py +12 -0
  30. quantark/asset/bond/product/couponbond/fixed_bond.py +285 -0
  31. quantark/asset/bond/product/couponbond/frn.py +538 -0
  32. quantark/asset/bond/product/forward/__init__.py +9 -0
  33. quantark/asset/bond/product/forward/base_bond_forward.py +92 -0
  34. quantark/asset/bond/product/forward/bond_forward.py +335 -0
  35. quantark/asset/bond/product/futures/__init__.py +8 -0
  36. quantark/asset/bond/product/futures/bond_futures.py +532 -0
  37. quantark/asset/bond/product/option/__init__.py +9 -0
  38. quantark/asset/bond/product/option/euro_short_term_bond_option.py +231 -0
  39. quantark/asset/bond/riskmeasures/__init__.py +13 -0
  40. quantark/asset/bond/riskmeasures/bond_greeks_calculator.py +484 -0
  41. quantark/asset/bond/schedule/__init__.py +21 -0
  42. quantark/asset/bond/schedule/cashflow.py +595 -0
  43. quantark/asset/equity/__init__.py +11 -0
  44. quantark/asset/equity/analysis/__init__.py +4 -0
  45. quantark/asset/equity/analysis/autocallable_path_analyzer.py +257 -0
  46. quantark/asset/equity/engine/__init__.py +84 -0
  47. quantark/asset/equity/engine/analytical/__init__.py +37 -0
  48. quantark/asset/equity/engine/analytical/american_option_engine.py +682 -0
  49. quantark/asset/equity/engine/analytical/asian_option_analytical_engine.py +1102 -0
  50. quantark/asset/equity/engine/analytical/barrier_analytical_engine.py +455 -0
  51. quantark/asset/equity/engine/analytical/black_scholes_engine.py +322 -0
  52. quantark/asset/equity/engine/analytical/deltaone_engine.py +340 -0
  53. quantark/asset/equity/engine/analytical/digital_option_engine.py +168 -0
  54. quantark/asset/equity/engine/analytical/double_barrier_option_engine.py +481 -0
  55. quantark/asset/equity/engine/analytical/double_sharkfin_option_analytical_engine.py +508 -0
  56. quantark/asset/equity/engine/analytical/one_touch_analytical_engine.py +302 -0
  57. quantark/asset/equity/engine/analytical/range_accrual_analytical_engine.py +396 -0
  58. quantark/asset/equity/engine/analytical/single_sharkfin_option_analytical_engine.py +229 -0
  59. quantark/asset/equity/engine/base_engine.py +137 -0
  60. quantark/asset/equity/engine/event_stats.py +85 -0
  61. quantark/asset/equity/engine/mc/__init__.py +31 -0
  62. quantark/asset/equity/engine/mc/american_option_mc_engine.py +485 -0
  63. quantark/asset/equity/engine/mc/asian_option_mc_engine.py +678 -0
  64. quantark/asset/equity/engine/mc/barrier_option_mc_engine.py +726 -0
  65. quantark/asset/equity/engine/mc/digital_option_mc_engine.py +419 -0
  66. quantark/asset/equity/engine/mc/double_sharkfin_option_mc_engine.py +676 -0
  67. quantark/asset/equity/engine/mc/euro_mc_engine.py +423 -0
  68. quantark/asset/equity/engine/mc/phoenix_mc_engine.py +1206 -0
  69. quantark/asset/equity/engine/mc/range_accrual_mc_engine.py +738 -0
  70. quantark/asset/equity/engine/mc/single_sharkfin_option_mc_engine.py +549 -0
  71. quantark/asset/equity/engine/mc/snowball_mc_engine.py +2250 -0
  72. quantark/asset/equity/engine/pde/__init__.py +36 -0
  73. quantark/asset/equity/engine/pde/american_pde_solver.py +211 -0
  74. quantark/asset/equity/engine/pde/barrier_pde_solver.py +692 -0
  75. quantark/asset/equity/engine/pde/base_pde_solver.py +994 -0
  76. quantark/asset/equity/engine/pde/double_barrier_pde_solver.py +510 -0
  77. quantark/asset/equity/engine/pde/double_one_touch_pde_solver.py +435 -0
  78. quantark/asset/equity/engine/pde/european_pde_solver.py +170 -0
  79. quantark/asset/equity/engine/pde/ko_reset_snowball_pde_solver.py +477 -0
  80. quantark/asset/equity/engine/pde/one_touch_pde_solver.py +439 -0
  81. quantark/asset/equity/engine/pde/phoenix_pde_solver.py +613 -0
  82. quantark/asset/equity/engine/pde/snowball_pde_solver.py +1810 -0
  83. quantark/asset/equity/engine/pde/spatial_grid.py +750 -0
  84. quantark/asset/equity/engine/pde/time_grid.py +308 -0
  85. quantark/asset/equity/engine/pde_engine.py +238 -0
  86. quantark/asset/equity/engine/quad/__init__.py +23 -0
  87. quantark/asset/equity/engine/quad/discrete_quad_engine.py +106 -0
  88. quantark/asset/equity/engine/quad/european_quad_engine.py +325 -0
  89. quantark/asset/equity/engine/quad/ko_reset_snowball_quad_engine.py +362 -0
  90. quantark/asset/equity/engine/quad/phoenix_quad_engine.py +614 -0
  91. quantark/asset/equity/engine/quad/quad_adapters.py +1260 -0
  92. quantark/asset/equity/engine/quad/quad_core.py +513 -0
  93. quantark/asset/equity/engine/quad/quad_math.py +219 -0
  94. quantark/asset/equity/engine/quad/snowball_quad_engine.py +1137 -0
  95. quantark/asset/equity/engine/validation/script/benchmark_check_american_analytical.py +117 -0
  96. quantark/asset/equity/engine/validation/script/benchmark_check_american_pde.py +114 -0
  97. quantark/asset/equity/engine/validation/script/benchmark_check_asian_analytical.py +440 -0
  98. quantark/asset/equity/engine/validation/script/benchmark_check_barrier_analytical.py +269 -0
  99. quantark/asset/equity/engine/validation/script/benchmark_check_barrier_pde_solver.py +636 -0
  100. quantark/asset/equity/engine/validation/script/benchmark_check_digital_option.py +256 -0
  101. quantark/asset/equity/engine/validation/script/benchmark_check_snowball_pde_solver.py +807 -0
  102. quantark/asset/equity/engine/validation/script/boundary_check_american_analytical.py +290 -0
  103. quantark/asset/equity/engine/validation/script/boundary_check_american_pde.py +242 -0
  104. quantark/asset/equity/engine/validation/script/boundary_check_asian_analytical.py +612 -0
  105. quantark/asset/equity/engine/validation/script/boundary_check_barrier_analytical.py +434 -0
  106. quantark/asset/equity/engine/validation/script/boundary_check_barrier_pde_solver.py +748 -0
  107. quantark/asset/equity/engine/validation/script/boundary_check_digital_option.py +575 -0
  108. quantark/asset/equity/engine/validation/script/boundary_check_snowball_pde_solver.py +1101 -0
  109. quantark/asset/equity/engine/validation/script/greeks_check_digital_option.py +349 -0
  110. quantark/asset/equity/engine/validation/script/mc_comparison_barrier_pde.py +270 -0
  111. quantark/asset/equity/engine/validation/script/quick_mc_compare.py +51 -0
  112. quantark/asset/equity/engine/validation/script/validation_stepdown_improved.py +97 -0
  113. quantark/asset/equity/param/__init__.py +24 -0
  114. quantark/asset/equity/param/engine_param_profiles.py +325 -0
  115. quantark/asset/equity/param/engine_params.py +728 -0
  116. quantark/asset/equity/process/__init__.py +7 -0
  117. quantark/asset/equity/process/bsm/__init__.py +7 -0
  118. quantark/asset/equity/process/bsm/bsm_process.py +108 -0
  119. quantark/asset/equity/process/bsm/qmc_brownian_bridge.py +401 -0
  120. quantark/asset/equity/process/bsm/qmc_path_generator.py +694 -0
  121. quantark/asset/equity/process/bsm/qmc_rqmc_driver.py +163 -0
  122. quantark/asset/equity/process/bsm/qmc_sobol.py +195 -0
  123. quantark/asset/equity/process/bsm/qmc_variance_reduction.py +292 -0
  124. quantark/asset/equity/product/__init__.py +8 -0
  125. quantark/asset/equity/product/base_equity_product.py +72 -0
  126. quantark/asset/equity/product/deltaone/__init__.py +22 -0
  127. quantark/asset/equity/product/deltaone/base_deltaone_product.py +147 -0
  128. quantark/asset/equity/product/deltaone/futures.py +485 -0
  129. quantark/asset/equity/product/deltaone/spot_instrument.py +118 -0
  130. quantark/asset/equity/product/option/__init__.py +104 -0
  131. quantark/asset/equity/product/option/american_option.py +114 -0
  132. quantark/asset/equity/product/option/asian_option.py +531 -0
  133. quantark/asset/equity/product/option/barrier_option.py +289 -0
  134. quantark/asset/equity/product/option/base_equity_option.py +659 -0
  135. quantark/asset/equity/product/option/digital_option.py +102 -0
  136. quantark/asset/equity/product/option/double_barrier_option.py +286 -0
  137. quantark/asset/equity/product/option/double_one_touch_option.py +310 -0
  138. quantark/asset/equity/product/option/double_sharkfin_option.py +466 -0
  139. quantark/asset/equity/product/option/european_vanilla_option.py +103 -0
  140. quantark/asset/equity/product/option/ko_reset_snowball_option.py +563 -0
  141. quantark/asset/equity/product/option/observation_schedule.py +530 -0
  142. quantark/asset/equity/product/option/one_touch_option.py +287 -0
  143. quantark/asset/equity/product/option/phoenix_config.py +116 -0
  144. quantark/asset/equity/product/option/phoenix_helpers.py +576 -0
  145. quantark/asset/equity/product/option/phoenix_option.py +1167 -0
  146. quantark/asset/equity/product/option/range_accrual_config.py +288 -0
  147. quantark/asset/equity/product/option/range_accrual_helpers.py +608 -0
  148. quantark/asset/equity/product/option/range_accrual_option.py +526 -0
  149. quantark/asset/equity/product/option/single_sharkfin_option.py +420 -0
  150. quantark/asset/equity/product/option/snowball_config.py +261 -0
  151. quantark/asset/equity/product/option/snowball_helpers.py +977 -0
  152. quantark/asset/equity/product/option/snowball_option.py +1242 -0
  153. quantark/asset/equity/report/__init__.py +15 -0
  154. quantark/asset/equity/report/autocallable_risk_report.py +2118 -0
  155. quantark/asset/equity/report/plotting.py +87 -0
  156. quantark/asset/equity/report/snowball_risk_comparison_report.py +2230 -0
  157. quantark/asset/equity/report/surfaces.py +123 -0
  158. quantark/asset/equity/report/term_structure.py +126 -0
  159. quantark/asset/equity/riskmeasures/__init__.py +7 -0
  160. quantark/asset/equity/riskmeasures/greeks_calculator.py +1204 -0
  161. quantark/asset/rate/__init__.py +58 -0
  162. quantark/asset/rate/engine/__init__.py +25 -0
  163. quantark/asset/rate/engine/cap_floor_engine.py +514 -0
  164. quantark/asset/rate/engine/fra_engine.py +286 -0
  165. quantark/asset/rate/engine/irs_discount_engine.py +891 -0
  166. quantark/asset/rate/engine/swaption_engine.py +587 -0
  167. quantark/asset/rate/product/__init__.py +67 -0
  168. quantark/asset/rate/product/cap_floor.py +550 -0
  169. quantark/asset/rate/product/fra.py +219 -0
  170. quantark/asset/rate/product/irs.py +1223 -0
  171. quantark/asset/rate/product/swaption.py +372 -0
  172. quantark/backtest/__init__.py +153 -0
  173. quantark/backtest/base.py +263 -0
  174. quantark/backtest/dashboard.py +874 -0
  175. quantark/backtest/equity/__init__.py +35 -0
  176. quantark/backtest/equity/config.py +118 -0
  177. quantark/backtest/equity/engine.py +408 -0
  178. quantark/backtest/equity/hedge_executor.py +374 -0
  179. quantark/backtest/equity/metrics.py +396 -0
  180. quantark/backtest/equity/results.py +232 -0
  181. quantark/backtest/equity/state.py +252 -0
  182. quantark/backtest/examples/__init__.py +4 -0
  183. quantark/backtest/examples/advanced_backtest.py +345 -0
  184. quantark/backtest/examples/basic_delta_hedge.py +246 -0
  185. quantark/backtest/examples/fi_dv01_hedge.py +267 -0
  186. quantark/backtest/fi/__init__.py +30 -0
  187. quantark/backtest/fi/config.py +114 -0
  188. quantark/backtest/fi/engine.py +378 -0
  189. quantark/backtest/fi/hedge_executor.py +254 -0
  190. quantark/backtest/fi/metrics.py +308 -0
  191. quantark/backtest/fi/results.py +193 -0
  192. quantark/backtest/fi/state.py +212 -0
  193. quantark/backtest/logger.py +393 -0
  194. quantark/backtest/otc/__init__.py +74 -0
  195. quantark/backtest/otc/_replay.py +637 -0
  196. quantark/backtest/otc/book_engine.py +587 -0
  197. quantark/backtest/otc/config.py +175 -0
  198. quantark/backtest/otc/dashboard.py +1006 -0
  199. quantark/backtest/otc/engine.py +420 -0
  200. quantark/backtest/otc/engine_factory.py +138 -0
  201. quantark/backtest/otc/market.py +216 -0
  202. quantark/backtest/otc/results.py +107 -0
  203. quantark/backtest/otc/state.py +166 -0
  204. quantark/backtest/report_generator.py +608 -0
  205. quantark/backtest/strategy/__init__.py +28 -0
  206. quantark/backtest/strategy/base_strategy.py +235 -0
  207. quantark/backtest/strategy/convexity_neutral_strategy.py +247 -0
  208. quantark/backtest/strategy/delta_neutral_strategy.py +283 -0
  209. quantark/backtest/strategy/dv01_neutral_strategy.py +283 -0
  210. quantark/backtest/transaction_costs.py +485 -0
  211. quantark/backtest/visualizer.py +1019 -0
  212. quantark/cashleg/__init__.py +31 -0
  213. quantark/cashleg/accrual_leg.py +120 -0
  214. quantark/cashleg/base.py +48 -0
  215. quantark/cashleg/base_amount.py +60 -0
  216. quantark/cashleg/deterministic_leg.py +39 -0
  217. quantark/cashleg/event_distribution.py +262 -0
  218. quantark/cashleg/fixed_payoff_leg.py +92 -0
  219. quantark/cashleg/leg_schedule.py +95 -0
  220. quantark/cashleg/leg_valuator.py +40 -0
  221. quantark/dynamicscenario/__init__.py +97 -0
  222. quantark/dynamicscenario/base.py +297 -0
  223. quantark/dynamicscenario/config.py +122 -0
  224. quantark/dynamicscenario/engine.py +703 -0
  225. quantark/dynamicscenario/equity/__init__.py +14 -0
  226. quantark/dynamicscenario/fi/__init__.py +24 -0
  227. quantark/dynamicscenario/fi/config.py +149 -0
  228. quantark/dynamicscenario/fi/engine.py +500 -0
  229. quantark/dynamicscenario/fi/results.py +503 -0
  230. quantark/dynamicscenario/path/__init__.py +17 -0
  231. quantark/dynamicscenario/path/day_path.py +397 -0
  232. quantark/dynamicscenario/path/fi_path_library.py +488 -0
  233. quantark/dynamicscenario/path/path_builder.py +726 -0
  234. quantark/dynamicscenario/path/path_library.py +620 -0
  235. quantark/dynamicscenario/report/__init__.py +12 -0
  236. quantark/dynamicscenario/report/dynamic_report.py +1175 -0
  237. quantark/dynamicscenario/report/visualizer.py +1586 -0
  238. quantark/dynamicscenario/results/__init__.py +19 -0
  239. quantark/dynamicscenario/results/dynamic_results.py +579 -0
  240. quantark/dynamicscenario/results/result_exporter.py +438 -0
  241. quantark/param/__init__.py +75 -0
  242. quantark/param/basis/__init__.py +19 -0
  243. quantark/param/basis/basis_yield.py +301 -0
  244. quantark/param/div/__init__.py +16 -0
  245. quantark/param/div/dividend_yield.py +123 -0
  246. quantark/param/index/__init__.py +52 -0
  247. quantark/param/index/rate_index.py +568 -0
  248. quantark/param/quote/__init__.py +7 -0
  249. quantark/param/quote/spot_quote.py +35 -0
  250. quantark/param/rrf/__init__.py +22 -0
  251. quantark/param/rrf/rate_curve.py +436 -0
  252. quantark/param/vol/__init__.py +6 -0
  253. quantark/param/vol/vol_surface.py +118 -0
  254. quantark/portfolio/__init__.py +61 -0
  255. quantark/portfolio/base.py +203 -0
  256. quantark/portfolio/equity/__init__.py +17 -0
  257. quantark/portfolio/equity/portfolio.py +391 -0
  258. quantark/portfolio/equity/position.py +368 -0
  259. quantark/portfolio/fi/__init__.py +14 -0
  260. quantark/portfolio/fi/portfolio.py +424 -0
  261. quantark/portfolio/fi/position.py +272 -0
  262. quantark/portfolio/portfolio_snapshot.py +221 -0
  263. quantark/portfolio/portfolio_storage.py +414 -0
  264. quantark/priceenv/__init__.py +7 -0
  265. quantark/priceenv/pricing_environment.py +196 -0
  266. quantark/rfq/__init__.py +32 -0
  267. quantark/rfq/builders.py +102 -0
  268. quantark/rfq/models.py +214 -0
  269. quantark/rfq/registry.py +611 -0
  270. quantark/rfq/service.py +237 -0
  271. quantark/simm/__init__.py +155 -0
  272. quantark/simm/calibration/__init__.py +206 -0
  273. quantark/simm/calibration/accessors.py +439 -0
  274. quantark/simm/calibration/commodity.py +156 -0
  275. quantark/simm/calibration/credit_non_qualifying.py +79 -0
  276. quantark/simm/calibration/credit_qualifying.py +130 -0
  277. quantark/simm/calibration/cross_risk.py +39 -0
  278. quantark/simm/calibration/equity.py +125 -0
  279. quantark/simm/calibration/fx.py +92 -0
  280. quantark/simm/calibration/ir.py +152 -0
  281. quantark/simm/calibration/version.py +33 -0
  282. quantark/simm/config.py +186 -0
  283. quantark/simm/crif/__init__.py +35 -0
  284. quantark/simm/crif/models.py +230 -0
  285. quantark/simm/crif/parser.py +585 -0
  286. quantark/simm/engines/__init__.py +62 -0
  287. quantark/simm/engines/aggregation/__init__.py +67 -0
  288. quantark/simm/engines/aggregation/addon.py +141 -0
  289. quantark/simm/engines/aggregation/bucket_aggregator.py +298 -0
  290. quantark/simm/engines/aggregation/concentration.py +349 -0
  291. quantark/simm/engines/aggregation/product_class_aggregator.py +183 -0
  292. quantark/simm/engines/aggregation/risk_class_aggregator.py +403 -0
  293. quantark/simm/engines/aggregation/simm_calculator.py +430 -0
  294. quantark/simm/engines/aggregation/weighted_sensitivity.py +272 -0
  295. quantark/simm/engines/base.py +231 -0
  296. quantark/simm/engines/classification/__init__.py +10 -0
  297. quantark/simm/engines/classification/bucket_mapper.py +347 -0
  298. quantark/simm/engines/factory.py +137 -0
  299. quantark/simm/engines/portfolio_adapter.py +336 -0
  300. quantark/simm/engines/result.py +176 -0
  301. quantark/simm/engines/risk_class/__init__.py +18 -0
  302. quantark/simm/engines/risk_class/equity_engine.py +263 -0
  303. quantark/simm/engines/risk_class/ir_engine.py +264 -0
  304. quantark/simm/report/__init__.py +17 -0
  305. quantark/simm/report/crif_export.py +284 -0
  306. quantark/simm/report/excel_generator.py +401 -0
  307. quantark/simm/report/html_generator.py +840 -0
  308. quantark/simm/results/__init__.py +38 -0
  309. quantark/simm/results/attribution.py +313 -0
  310. quantark/simm/results/simm_result.py +339 -0
  311. quantark/simm/results/whatif.py +268 -0
  312. quantark/simm/sensitivity.py +533 -0
  313. quantark/simm/taxonomy.py +416 -0
  314. quantark/stresstest/__init__.py +67 -0
  315. quantark/stresstest/base.py +116 -0
  316. quantark/stresstest/config.py +5 -0
  317. quantark/stresstest/engine.py +5 -0
  318. quantark/stresstest/equity/__init__.py +17 -0
  319. quantark/stresstest/equity/config.py +69 -0
  320. quantark/stresstest/equity/engine.py +272 -0
  321. quantark/stresstest/equity/report/__init__.py +7 -0
  322. quantark/stresstest/equity/report/report_generator.py +423 -0
  323. quantark/stresstest/equity/report/visualizer.py +328 -0
  324. quantark/stresstest/equity/results.py +145 -0
  325. quantark/stresstest/fi/__init__.py +15 -0
  326. quantark/stresstest/fi/config.py +59 -0
  327. quantark/stresstest/fi/engine.py +213 -0
  328. quantark/stresstest/fi/metrics.py +60 -0
  329. quantark/stresstest/fi/results.py +64 -0
  330. quantark/stresstest/report/__init__.py +12 -0
  331. quantark/stresstest/report/report_generator.py +5 -0
  332. quantark/stresstest/report/visualizer.py +5 -0
  333. quantark/stresstest/results/__init__.py +16 -0
  334. quantark/stresstest/results/result_aggregator.py +325 -0
  335. quantark/stresstest/results/result_exporter.py +286 -0
  336. quantark/stresstest/results/stress_results.py +5 -0
  337. quantark/stresstest/scenario/__init__.py +13 -0
  338. quantark/stresstest/scenario/scenario.py +242 -0
  339. quantark/stresstest/scenario/scenario_builder.py +376 -0
  340. quantark/stresstest/scenario/scenario_library.py +435 -0
  341. quantark/stresstest/scenario/scenario_storage.py +224 -0
  342. quantark/stresstest/stress/__init__.py +13 -0
  343. quantark/stresstest/stress/stress_applicator.py +590 -0
  344. quantark/stresstest/stress/stress_types.py +142 -0
  345. quantark/util/__init__.py +23 -0
  346. quantark/util/barrier_shift.py +44 -0
  347. quantark/util/calendar/__init__.py +27 -0
  348. quantark/util/calendar/business_calendar.py +584 -0
  349. quantark/util/calendar/day_counter.py +517 -0
  350. quantark/util/calendar/holidayfile/china.csv +1920 -0
  351. quantark/util/calendar/holidayfile/china_sse.csv +1462 -0
  352. quantark/util/enum/__init__.py +81 -0
  353. quantark/util/enum/bond_enums.py +112 -0
  354. quantark/util/enum/deltaone_enums.py +16 -0
  355. quantark/util/enum/engine_enums.py +137 -0
  356. quantark/util/enum/greeks_enums.py +29 -0
  357. quantark/util/enum/option_enums.py +221 -0
  358. quantark/util/exceptions.py +66 -0
  359. quantark/util/marketdata/__init__.py +39 -0
  360. quantark/util/marketdata/adapter/base_adapter.py +203 -0
  361. quantark/util/marketdata/adapter/mock_adapter.py +265 -0
  362. quantark/util/marketdata/converter.py +289 -0
  363. quantark/util/marketdata/example_usage.py +314 -0
  364. quantark/util/marketdata/generator/__init__.py +7 -0
  365. quantark/util/marketdata/generator/mock_generator.py +466 -0
  366. quantark/util/marketdata/models.py +358 -0
  367. quantark/util/marketdata/storage/__init__.py +7 -0
  368. quantark/util/marketdata/storage/parquet_storage.py +340 -0
  369. quantark/util/numerical/__init__.py +98 -0
  370. quantark/util/numerical/comparison.py +219 -0
  371. quantark/util/numerical/constants.py +98 -0
  372. quantark/util/numerical/formatting.py +380 -0
  373. quantark/util/numerical/pnl.py +17 -0
  374. quantark/util/numerical/safe_math.py +238 -0
  375. quantark/util/numerical/validation.py +315 -0
  376. quantark/var/__init__.py +39 -0
  377. quantark/var/attribution.py +398 -0
  378. quantark/var/backtest/__init__.py +7 -0
  379. quantark/var/backtest/var_backtester.py +309 -0
  380. quantark/var/base.py +63 -0
  381. quantark/var/config.py +219 -0
  382. quantark/var/engines/__init__.py +13 -0
  383. quantark/var/engines/historical.py +925 -0
  384. quantark/var/engines/monte_carlo.py +870 -0
  385. quantark/var/engines/parametric.py +1199 -0
  386. quantark/var/results/__init__.py +16 -0
  387. quantark/var/results/incremental_var_result.py +131 -0
  388. quantark/var/results/var_report.py +346 -0
  389. quantark/var/results/var_result.py +134 -0
  390. quantark/var/risk_factors/__init__.py +22 -0
  391. quantark/var/risk_factors/base.py +41 -0
  392. quantark/var/risk_factors/equity_factors.py +158 -0
  393. quantark/var/risk_factors/fi_factors.py +99 -0
  394. quantark-0.1.0.dist-info/METADATA +351 -0
  395. quantark-0.1.0.dist-info/RECORD +399 -0
  396. quantark-0.1.0.dist-info/WHEEL +4 -0
  397. quantark-0.1.0.dist-info/licenses/LICENSE +202 -0
  398. quantark-0.1.0.dist-info/licenses/NOTICE +2 -0
  399. quantark_compat.pth +1 -0
@@ -0,0 +1,466 @@
1
+ """
2
+ Double sharkfin option product definition.
3
+
4
+ Double sharkfin options are capped double-barrier structures. The product
5
+ knocks out if either the upper or lower barrier is hit. If no barrier is hit,
6
+ it pays a vanilla-style call or put payoff capped by the relevant barrier.
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from datetime import datetime
11
+ from math import ceil
12
+ from typing import List, Optional, Sequence
13
+
14
+ from quantark.util.enum import (
15
+ ExerciseType,
16
+ ObservationAggregation,
17
+ ObservationFrequency,
18
+ ObservationType,
19
+ OptionType,
20
+ )
21
+ from quantark.util.exceptions import ValidationError
22
+ from quantark.util.numerical import is_close
23
+
24
+ from .base_equity_option import BaseEquityOption
25
+ from .observation_schedule import ObservationRecord, ObservationSchedule
26
+
27
+
28
+ @dataclass
29
+ class DoubleSharkfinOption(BaseEquityOption):
30
+ """
31
+ Double sharkfin option with upper and lower knock-out barriers.
32
+
33
+ Payoff states:
34
+ 1. Either barrier hit: knock_out_rebate
35
+ 2. No barrier hit:
36
+ - CALL: no_hit_rebate + participation_rate * max(min(S, U) - K, 0)
37
+ - PUT: no_hit_rebate + participation_rate * max(K - max(S, L), 0)
38
+
39
+ Monitoring styles:
40
+ - EXPIRY: barriers checked only at terminal spot
41
+ - DISCRETE: barriers checked on observation dates, including daily schedules
42
+ - CONTINUOUS: barriers checked continuously by the pricing engine/path logic
43
+
44
+ Attributes:
45
+ strike: Strike price.
46
+ option_type: CALL or PUT payoff direction.
47
+ upper_barrier: Upper knock-out barrier level.
48
+ lower_barrier: Lower knock-out barrier level.
49
+ maturity: Time to maturity in years.
50
+ participation_rate: Participation in the capped no-hit payoff.
51
+ knock_out_rebate: Fixed payoff if either barrier is hit.
52
+ no_hit_rebate: Fixed payoff added when no barrier is hit.
53
+ pay_at_hit: If True, pay knock-out rebate immediately on hit;
54
+ otherwise pay it at expiry.
55
+ observation_type: EXPIRY, DISCRETE, or CONTINUOUS monitoring.
56
+ observation_dates: Legacy discrete observation times in years.
57
+ observation_schedule: Preferred discrete observation schedule.
58
+ observation_frequency: Frequency used when generating a regular schedule.
59
+ contract_multiplier: Underlying units represented by one contract.
60
+ """
61
+
62
+ upper_barrier: float = 0.0
63
+ lower_barrier: float = 0.0
64
+ participation_rate: float = 1.0
65
+ knock_out_rebate: float = 0.0
66
+ no_hit_rebate: float = 0.0
67
+ pay_at_hit: bool = False
68
+ observation_type: ObservationType = ObservationType.CONTINUOUS
69
+ observation_dates: Optional[List[float]] = None
70
+ observation_schedule: Optional[ObservationSchedule] = None
71
+ observation_frequency: ObservationFrequency = ObservationFrequency.CUSTOM
72
+ use_business_days_for_frequency: bool = True
73
+ business_days_in_year: float = 252.0
74
+
75
+ def __init__(
76
+ self,
77
+ strike: float,
78
+ option_type: OptionType,
79
+ upper_barrier: float,
80
+ lower_barrier: float,
81
+ maturity: Optional[float] = None,
82
+ exercise_date: Optional[datetime] = None,
83
+ settlement_date: Optional[datetime] = None,
84
+ participation_rate: float = 1.0,
85
+ knock_out_rebate: float = 0.0,
86
+ no_hit_rebate: float = 0.0,
87
+ pay_at_hit: bool = False,
88
+ observation_type: ObservationType = ObservationType.CONTINUOUS,
89
+ observation_dates: Optional[List[float]] = None,
90
+ observation_schedule: Optional[ObservationSchedule] = None,
91
+ observation_frequency: ObservationFrequency = ObservationFrequency.CUSTOM,
92
+ use_business_days_for_frequency: bool = True,
93
+ business_days_in_year: float = 252.0,
94
+ contract_multiplier: float = 1.0,
95
+ ):
96
+ """
97
+ Initialize a double sharkfin option.
98
+
99
+ Args:
100
+ strike: Strike price.
101
+ option_type: CALL or PUT payoff direction.
102
+ upper_barrier: Upper knock-out barrier level.
103
+ lower_barrier: Lower knock-out barrier level.
104
+ maturity: Time to maturity in years (optional if exercise_date provided).
105
+ exercise_date: Expiration date (optional if maturity provided).
106
+ settlement_date: Settlement date.
107
+ participation_rate: Participation in the capped no-hit payoff.
108
+ knock_out_rebate: Fixed payoff if either barrier is hit.
109
+ no_hit_rebate: Fixed payoff added if no barrier is hit.
110
+ pay_at_hit: If True, knock-out rebate is paid immediately on hit.
111
+ If False, it is paid at expiry.
112
+ observation_type: EXPIRY, DISCRETE, or CONTINUOUS monitoring.
113
+ observation_dates: Discrete observation times in year fractions.
114
+ observation_schedule: Preferred discrete observation schedule.
115
+ observation_frequency: Frequency for generated discrete schedules.
116
+ use_business_days_for_frequency: Use business-day spacing for frequency.
117
+ business_days_in_year: Business-day count for generated schedules.
118
+ contract_multiplier: Underlying units represented by one contract.
119
+
120
+ Raises:
121
+ ValidationError: If parameters are invalid.
122
+ """
123
+ if maturity is None and exercise_date is None:
124
+ maturity = 0.0
125
+ elif maturity is None:
126
+ maturity = 0.0
127
+
128
+ self.upper_barrier = upper_barrier
129
+ self.lower_barrier = lower_barrier
130
+ self.participation_rate = participation_rate
131
+ self.knock_out_rebate = knock_out_rebate
132
+ self.no_hit_rebate = no_hit_rebate
133
+ self.pay_at_hit = pay_at_hit
134
+ self.observation_type = observation_type
135
+ self.observation_dates = observation_dates
136
+ self.observation_schedule = observation_schedule
137
+ self.observation_frequency = observation_frequency
138
+ self.use_business_days_for_frequency = use_business_days_for_frequency
139
+ self.business_days_in_year = business_days_in_year
140
+
141
+ super().__init__(
142
+ strike=strike,
143
+ option_type=option_type,
144
+ exercise_type=ExerciseType.EUROPEAN,
145
+ maturity=maturity,
146
+ exercise_date=exercise_date,
147
+ settlement_date=settlement_date,
148
+ contract_multiplier=contract_multiplier,
149
+ )
150
+
151
+ def validate(self) -> None:
152
+ """
153
+ Validate double sharkfin option parameters.
154
+
155
+ Raises:
156
+ ValidationError: If any parameter is invalid.
157
+ """
158
+ super().validate()
159
+
160
+ if self.upper_barrier <= 0:
161
+ raise ValidationError(
162
+ f"Upper barrier must be positive, got {self.upper_barrier}"
163
+ )
164
+ if self.lower_barrier <= 0:
165
+ raise ValidationError(
166
+ f"Lower barrier must be positive, got {self.lower_barrier}"
167
+ )
168
+ if self.lower_barrier >= self.upper_barrier:
169
+ raise ValidationError(
170
+ f"Lower barrier ({self.lower_barrier}) must be less than "
171
+ f"upper barrier ({self.upper_barrier})"
172
+ )
173
+ if self.participation_rate < 0:
174
+ raise ValidationError(
175
+ f"Participation rate must be non-negative, got {self.participation_rate}"
176
+ )
177
+ if self.knock_out_rebate < 0:
178
+ raise ValidationError(
179
+ f"Knock-out rebate must be non-negative, got {self.knock_out_rebate}"
180
+ )
181
+ if self.no_hit_rebate < 0:
182
+ raise ValidationError(
183
+ f"No-hit rebate must be non-negative, got {self.no_hit_rebate}"
184
+ )
185
+ if not isinstance(self.pay_at_hit, bool):
186
+ raise ValidationError(f"pay_at_hit must be boolean, got {self.pay_at_hit}")
187
+ if not isinstance(self.observation_type, ObservationType):
188
+ raise ValidationError(f"Invalid observation type: {self.observation_type}")
189
+ if not isinstance(self.observation_frequency, ObservationFrequency):
190
+ raise ValidationError(
191
+ f"Invalid observation frequency: {self.observation_frequency}"
192
+ )
193
+ if self.business_days_in_year <= 0:
194
+ raise ValidationError(
195
+ f"business_days_in_year must be positive, got {self.business_days_in_year}"
196
+ )
197
+
198
+ self._validate_strike_in_corridor()
199
+ self._normalize_observation_schedule()
200
+
201
+ def _validate_strike_in_corridor(self) -> None:
202
+ """Validate that the strike is inside the double sharkfin corridor."""
203
+ if not (self.lower_barrier < self.strike < self.upper_barrier):
204
+ raise ValidationError(
205
+ "Double sharkfin requires lower_barrier < strike < upper_barrier, "
206
+ f"got lower_barrier={self.lower_barrier}, strike={self.strike}, "
207
+ f"upper_barrier={self.upper_barrier}"
208
+ )
209
+
210
+ def _normalize_observation_schedule(self) -> None:
211
+ """Normalize discrete monitoring inputs into ObservationSchedule."""
212
+ if self.observation_type in (ObservationType.CONTINUOUS, ObservationType.EXPIRY):
213
+ if self.observation_schedule is not None:
214
+ raise ValidationError(
215
+ "ObservationSchedule requires DISCRETE observation_type."
216
+ )
217
+ self.observation_dates = self.observation_dates or []
218
+ return
219
+
220
+ if self.observation_schedule is not None:
221
+ normalized_schedule = ObservationSchedule(
222
+ records=[
223
+ ObservationRecord(
224
+ observation_time=rec.observation_time,
225
+ observation_date=rec.observation_date,
226
+ upper_barrier=(
227
+ rec.upper_barrier
228
+ if rec.upper_barrier is not None
229
+ else self.upper_barrier
230
+ ),
231
+ lower_barrier=(
232
+ rec.lower_barrier
233
+ if rec.lower_barrier is not None
234
+ else self.lower_barrier
235
+ ),
236
+ payoff=(
237
+ rec.payoff
238
+ if rec.payoff is not None
239
+ else self.knock_out_rebate
240
+ ),
241
+ return_rate=rec.return_rate,
242
+ is_rate_annualized=rec.is_rate_annualized,
243
+ initial_date=rec.initial_date,
244
+ settlement_date=rec.settlement_date,
245
+ maturity_date=rec.maturity_date,
246
+ day_count_convention=rec.day_count_convention,
247
+ tenor_end=rec.tenor_end,
248
+ day_count_fraction=rec.day_count_fraction,
249
+ )
250
+ for rec in self.observation_schedule.records
251
+ ],
252
+ aggregation_mode=self.observation_schedule.aggregation_mode,
253
+ frequency=self.observation_schedule.frequency,
254
+ )
255
+ self._validate_observation_times(normalized_schedule.times)
256
+ normalized_schedule.validate(require_double=True)
257
+ self.observation_schedule = normalized_schedule
258
+ if self.observation_schedule.times:
259
+ self.observation_dates = self.observation_schedule.times
260
+ return
261
+
262
+ if self.observation_dates is None or len(self.observation_dates) == 0:
263
+ self.observation_dates = self._generate_observation_dates()
264
+ self._validate_observation_times(self.observation_dates)
265
+
266
+ self.observation_schedule = ObservationSchedule.from_legacy(
267
+ observation_dates=self.observation_dates,
268
+ default_barrier=None,
269
+ default_payoff=self.knock_out_rebate,
270
+ aggregation_mode=ObservationAggregation.STOP_FIRST_HIT,
271
+ frequency=self.observation_frequency,
272
+ upper_barrier=self.upper_barrier,
273
+ lower_barrier=self.lower_barrier,
274
+ )
275
+ self.observation_dates = self.observation_schedule.times
276
+
277
+ def _validate_observation_times(self, times: List[float]) -> None:
278
+ """Validate numeric observation times when present."""
279
+ if any(t < 0 for t in times):
280
+ raise ValidationError("Observation dates must be non-negative.")
281
+ if times != sorted(times):
282
+ raise ValidationError("Observation dates must be sorted in ascending order.")
283
+
284
+ def _generate_observation_dates(self) -> List[float]:
285
+ """
286
+ Generate regular discrete observation times from observation_frequency.
287
+
288
+ Returns:
289
+ Observation times in years.
290
+
291
+ Raises:
292
+ ValidationError: If a schedule cannot be generated.
293
+ """
294
+ if self.observation_frequency == ObservationFrequency.CUSTOM:
295
+ raise ValidationError(
296
+ "Observation dates required for discrete double sharkfin monitoring "
297
+ "when observation_frequency is CUSTOM."
298
+ )
299
+ if self.maturity is None or self.maturity <= 0:
300
+ raise ValidationError(
301
+ "Positive maturity is required to generate discrete observations."
302
+ )
303
+
304
+ days_in_year = (
305
+ self.business_days_in_year
306
+ if self.use_business_days_for_frequency
307
+ else 365.0
308
+ )
309
+ dt = self.observation_frequency.to_year_fraction(
310
+ use_business_days=self.use_business_days_for_frequency,
311
+ days_in_year=days_in_year,
312
+ )
313
+ if dt <= 0:
314
+ raise ValidationError(f"Observation frequency produced invalid dt={dt}")
315
+
316
+ count = max(1, int(ceil(self.maturity / dt)))
317
+ times = [min((idx + 1) * dt, self.maturity) for idx in range(count)]
318
+ if not is_close(times[-1], self.maturity):
319
+ times.append(self.maturity)
320
+ return times
321
+
322
+ def is_barrier_hit(self, spot: float) -> bool:
323
+ """
324
+ Check whether a single observed spot hits either sharkfin barrier.
325
+
326
+ Args:
327
+ spot: Observed underlying price.
328
+
329
+ Returns:
330
+ True if either barrier is hit.
331
+ """
332
+ if spot < 0:
333
+ raise ValidationError(f"Spot price must be non-negative, got {spot}")
334
+ return spot >= self.upper_barrier or spot <= self.lower_barrier
335
+
336
+ def has_barrier_hit(self, path: Sequence[float]) -> bool:
337
+ """
338
+ Check whether any observed spot in a path hits either barrier.
339
+
340
+ Args:
341
+ path: Sequence of observed underlying prices.
342
+
343
+ Returns:
344
+ True if any observation hits either barrier.
345
+ """
346
+ if len(path) == 0:
347
+ raise ValidationError("Path must contain at least one observed spot.")
348
+ return any(self.is_barrier_hit(spot) for spot in path)
349
+
350
+ def is_in_corridor(self, spot: float) -> bool:
351
+ """
352
+ Check whether a spot is strictly inside the sharkfin corridor.
353
+
354
+ Args:
355
+ spot: Observed underlying price.
356
+
357
+ Returns:
358
+ True if spot is between lower and upper barriers.
359
+ """
360
+ if spot < 0:
361
+ raise ValidationError(f"Spot price must be non-negative, got {spot}")
362
+ return self.lower_barrier < spot < self.upper_barrier
363
+
364
+ def get_no_hit_payoff(self, spot: float) -> float:
365
+ """
366
+ Calculate the capped no-hit payoff.
367
+
368
+ Args:
369
+ spot: Terminal underlying price.
370
+
371
+ Returns:
372
+ Payoff assuming neither barrier was hit.
373
+ """
374
+ if spot < 0:
375
+ raise ValidationError(f"Spot price must be non-negative, got {spot}")
376
+
377
+ if self.is_call():
378
+ capped_spot = min(spot, self.upper_barrier)
379
+ intrinsic = max(capped_spot - self.strike, 0.0)
380
+ else:
381
+ capped_spot = max(spot, self.lower_barrier)
382
+ intrinsic = max(self.strike - capped_spot, 0.0)
383
+
384
+ payoff = self.no_hit_rebate + self.participation_rate * intrinsic
385
+ return payoff * self.contract_multiplier
386
+
387
+ def get_barrier_payoff(self) -> float:
388
+ """
389
+ Calculate payoff when either sharkfin barrier has been hit.
390
+
391
+ Returns:
392
+ Knock-out rebate scaled by contract multiplier.
393
+ """
394
+ return self.knock_out_rebate * self.contract_multiplier
395
+
396
+ def get_payoff(self, spot: float, barrier_hit: Optional[bool] = None) -> float:
397
+ """
398
+ Calculate the terminal double sharkfin payoff.
399
+
400
+ Args:
401
+ spot: Terminal underlying price.
402
+ barrier_hit: Optional observed barrier state. When omitted, terminal
403
+ spot is used to infer a hit. Pricing engines for DISCRETE or
404
+ CONTINUOUS monitoring should pass the path-derived barrier state.
405
+
406
+ Returns:
407
+ Sharkfin payoff scaled by contract multiplier.
408
+ """
409
+ if spot < 0:
410
+ raise ValidationError(f"Spot price must be non-negative, got {spot}")
411
+
412
+ if barrier_hit is None:
413
+ barrier_hit = self.is_barrier_hit(spot)
414
+
415
+ if barrier_hit:
416
+ return self.get_barrier_payoff()
417
+ return self.get_no_hit_payoff(spot)
418
+
419
+ def get_observation_times(self) -> List[float]:
420
+ """
421
+ Get discrete observation times.
422
+
423
+ Returns:
424
+ Observation times in years. Empty for EXPIRY or CONTINUOUS monitoring.
425
+ """
426
+ if self.observation_schedule is not None:
427
+ return self.observation_schedule.times
428
+ return self.observation_dates or []
429
+
430
+ @property
431
+ def corridor_width(self) -> float:
432
+ """Get the width of the corridor in price terms."""
433
+ return self.upper_barrier - self.lower_barrier
434
+
435
+ @property
436
+ def corridor_width_log(self) -> float:
437
+ """Get the width of the corridor in log-price terms."""
438
+ from quantark.util.numerical import safe_log
439
+
440
+ return safe_log(self.upper_barrier / self.lower_barrier)
441
+
442
+ def time_shift(self, time_bump: float, bumped_date: datetime, pricing_env) -> bool:
443
+ """Shift observation schedule and maturity for theta bumping."""
444
+ schedule = getattr(self, "observation_schedule", None)
445
+ if schedule is not None:
446
+ if schedule.uses_dates():
447
+ pricing_env.valuation_date = bumped_date
448
+ bumped_schedule = schedule.time_shift(time_bump, bumped_date)
449
+ if bumped_schedule is None:
450
+ return True
451
+ self.observation_schedule = bumped_schedule
452
+ if bumped_schedule.uses_times():
453
+ self.observation_dates = bumped_schedule.times
454
+
455
+ if getattr(self, "exercise_date", None) is None and self.maturity is not None:
456
+ self.maturity -= time_bump
457
+
458
+ return False
459
+
460
+ def __repr__(self) -> str:
461
+ return (
462
+ "DoubleSharkfinOption("
463
+ f"{self.option_type}, K={self.strike:.2f}, "
464
+ f"L={self.lower_barrier:.2f}, U={self.upper_barrier:.2f}, "
465
+ f"T={self.maturity:.4f})"
466
+ )
@@ -0,0 +1,103 @@
1
+ """
2
+ European vanilla option implementation.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+ from datetime import datetime
8
+ from .base_equity_option import BaseEquityOption
9
+ from quantark.util.enum import OptionType, ExerciseType
10
+ from quantark.util.exceptions import ValidationError
11
+
12
+
13
+ @dataclass
14
+ class EuropeanVanillaOption(BaseEquityOption):
15
+ """
16
+ European vanilla call or put option.
17
+
18
+ A European option can only be exercised at maturity.
19
+
20
+ Attributes:
21
+ strike: Strike price
22
+ maturity: Time to maturity in years
23
+ option_type: CALL or PUT
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ strike: float,
29
+ option_type: OptionType,
30
+ maturity: Optional[float] = None,
31
+ exercise_date: Optional[datetime] = None,
32
+ settlement_date: Optional[datetime] = None,
33
+ contract_multiplier: float = 1.0,
34
+ ):
35
+ """
36
+ Initialize European vanilla option.
37
+
38
+ Args:
39
+ strike: Strike price
40
+ option_type: CALL or PUT
41
+ maturity: Time to maturity in years (optional if exercise_date provided)
42
+ exercise_date: Date when option can be exercised (optional if maturity provided)
43
+ settlement_date: Date when settlement occurs (optional, defaults to exercise_date)
44
+
45
+ Note:
46
+ Either maturity OR exercise_date must be provided (not both).
47
+ """
48
+ # Default maturity to 0 if not provided (will be validated)
49
+ if maturity is None and exercise_date is None:
50
+ maturity = 0.0 # Will trigger validation error
51
+ elif maturity is None:
52
+ maturity = 0.0 # Placeholder when using dates
53
+
54
+ super().__init__(
55
+ strike=strike,
56
+ maturity=maturity,
57
+ option_type=option_type,
58
+ exercise_type=ExerciseType.EUROPEAN,
59
+ exercise_date=exercise_date,
60
+ settlement_date=settlement_date,
61
+ contract_multiplier=contract_multiplier,
62
+ )
63
+
64
+ def get_payoff(self, spot: float) -> float:
65
+ """
66
+ Calculate the option payoff at maturity.
67
+
68
+ For a call: max(S - K, 0)
69
+ For a put: max(K - S, 0)
70
+
71
+ Args:
72
+ spot: Spot price at maturity
73
+
74
+ Returns:
75
+ Option payoff
76
+ """
77
+ if spot < 0:
78
+ raise ValidationError(f"Spot price must be non-negative, got {spot}")
79
+
80
+ if self.is_call():
81
+ intrinsic = max(spot - self.strike, 0.0)
82
+ else: # put
83
+ intrinsic = max(self.strike - spot, 0.0)
84
+
85
+ return intrinsic * self.contract_multiplier
86
+
87
+ def intrinsic_value(self, spot: float) -> float:
88
+ """
89
+ Calculate the intrinsic value of the option.
90
+
91
+ Args:
92
+ spot: Current spot price
93
+
94
+ Returns:
95
+ Intrinsic value
96
+ """
97
+ return self.get_payoff(spot)
98
+
99
+ def __repr__(self):
100
+ return (
101
+ f"EuropeanVanillaOption("
102
+ f"{self.option_type}, K={self.strike:.2f}, T={self.maturity:.4f})"
103
+ )