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.
- quantark/__init__.py +3 -0
- quantark/_compat.py +150 -0
- quantark/asset/__init__.py +8 -0
- quantark/asset/bond/__init__.py +2 -0
- quantark/asset/bond/engine/__init__.py +44 -0
- quantark/asset/bond/engine/analytical/__init__.py +12 -0
- quantark/asset/bond/engine/analytical/black_engine.py +583 -0
- quantark/asset/bond/engine/analytical/bond_forward_engine.py +390 -0
- quantark/asset/bond/engine/analytical/bond_futures_engine.py +569 -0
- quantark/asset/bond/engine/convertible/__init__.py +12 -0
- quantark/asset/bond/engine/convertible/convertible_bond_engine.py +800 -0
- quantark/asset/bond/engine/discount/__init__.py +10 -0
- quantark/asset/bond/engine/discount/bond_discount_engine.py +517 -0
- quantark/asset/bond/engine/discount/frn_engine.py +913 -0
- quantark/asset/bond/engine/pde/__init__.py +14 -0
- quantark/asset/bond/engine/pde/convertible/__init__.py +21 -0
- quantark/asset/bond/engine/pde/convertible/jump_diffusion_engine.py +603 -0
- quantark/asset/bond/engine/pde/convertible/pde_params.py +59 -0
- quantark/asset/bond/engine/pde/convertible/tf_engine.py +546 -0
- quantark/asset/bond/engine/tree/__init__.py +14 -0
- quantark/asset/bond/engine/tree/convertible/__init__.py +21 -0
- quantark/asset/bond/engine/tree/convertible/binomial_engine.py +488 -0
- quantark/asset/bond/engine/tree/convertible/tree_params.py +72 -0
- quantark/asset/bond/engine/tree/convertible/trinomial_engine.py +1341 -0
- quantark/asset/bond/product/__init__.py +37 -0
- quantark/asset/bond/product/base_bond_product.py +114 -0
- quantark/asset/bond/product/convertible/__init__.py +16 -0
- quantark/asset/bond/product/convertible/convertible_bond.py +595 -0
- quantark/asset/bond/product/couponbond/__init__.py +12 -0
- quantark/asset/bond/product/couponbond/fixed_bond.py +285 -0
- quantark/asset/bond/product/couponbond/frn.py +538 -0
- quantark/asset/bond/product/forward/__init__.py +9 -0
- quantark/asset/bond/product/forward/base_bond_forward.py +92 -0
- quantark/asset/bond/product/forward/bond_forward.py +335 -0
- quantark/asset/bond/product/futures/__init__.py +8 -0
- quantark/asset/bond/product/futures/bond_futures.py +532 -0
- quantark/asset/bond/product/option/__init__.py +9 -0
- quantark/asset/bond/product/option/euro_short_term_bond_option.py +231 -0
- quantark/asset/bond/riskmeasures/__init__.py +13 -0
- quantark/asset/bond/riskmeasures/bond_greeks_calculator.py +484 -0
- quantark/asset/bond/schedule/__init__.py +21 -0
- quantark/asset/bond/schedule/cashflow.py +595 -0
- quantark/asset/equity/__init__.py +11 -0
- quantark/asset/equity/analysis/__init__.py +4 -0
- quantark/asset/equity/analysis/autocallable_path_analyzer.py +257 -0
- quantark/asset/equity/engine/__init__.py +84 -0
- quantark/asset/equity/engine/analytical/__init__.py +37 -0
- quantark/asset/equity/engine/analytical/american_option_engine.py +682 -0
- quantark/asset/equity/engine/analytical/asian_option_analytical_engine.py +1102 -0
- quantark/asset/equity/engine/analytical/barrier_analytical_engine.py +455 -0
- quantark/asset/equity/engine/analytical/black_scholes_engine.py +322 -0
- quantark/asset/equity/engine/analytical/deltaone_engine.py +340 -0
- quantark/asset/equity/engine/analytical/digital_option_engine.py +168 -0
- quantark/asset/equity/engine/analytical/double_barrier_option_engine.py +481 -0
- quantark/asset/equity/engine/analytical/double_sharkfin_option_analytical_engine.py +508 -0
- quantark/asset/equity/engine/analytical/one_touch_analytical_engine.py +302 -0
- quantark/asset/equity/engine/analytical/range_accrual_analytical_engine.py +396 -0
- quantark/asset/equity/engine/analytical/single_sharkfin_option_analytical_engine.py +229 -0
- quantark/asset/equity/engine/base_engine.py +137 -0
- quantark/asset/equity/engine/event_stats.py +85 -0
- quantark/asset/equity/engine/mc/__init__.py +31 -0
- quantark/asset/equity/engine/mc/american_option_mc_engine.py +485 -0
- quantark/asset/equity/engine/mc/asian_option_mc_engine.py +678 -0
- quantark/asset/equity/engine/mc/barrier_option_mc_engine.py +726 -0
- quantark/asset/equity/engine/mc/digital_option_mc_engine.py +419 -0
- quantark/asset/equity/engine/mc/double_sharkfin_option_mc_engine.py +676 -0
- quantark/asset/equity/engine/mc/euro_mc_engine.py +423 -0
- quantark/asset/equity/engine/mc/phoenix_mc_engine.py +1206 -0
- quantark/asset/equity/engine/mc/range_accrual_mc_engine.py +738 -0
- quantark/asset/equity/engine/mc/single_sharkfin_option_mc_engine.py +549 -0
- quantark/asset/equity/engine/mc/snowball_mc_engine.py +2250 -0
- quantark/asset/equity/engine/pde/__init__.py +36 -0
- quantark/asset/equity/engine/pde/american_pde_solver.py +211 -0
- quantark/asset/equity/engine/pde/barrier_pde_solver.py +692 -0
- quantark/asset/equity/engine/pde/base_pde_solver.py +994 -0
- quantark/asset/equity/engine/pde/double_barrier_pde_solver.py +510 -0
- quantark/asset/equity/engine/pde/double_one_touch_pde_solver.py +435 -0
- quantark/asset/equity/engine/pde/european_pde_solver.py +170 -0
- quantark/asset/equity/engine/pde/ko_reset_snowball_pde_solver.py +477 -0
- quantark/asset/equity/engine/pde/one_touch_pde_solver.py +439 -0
- quantark/asset/equity/engine/pde/phoenix_pde_solver.py +613 -0
- quantark/asset/equity/engine/pde/snowball_pde_solver.py +1810 -0
- quantark/asset/equity/engine/pde/spatial_grid.py +750 -0
- quantark/asset/equity/engine/pde/time_grid.py +308 -0
- quantark/asset/equity/engine/pde_engine.py +238 -0
- quantark/asset/equity/engine/quad/__init__.py +23 -0
- quantark/asset/equity/engine/quad/discrete_quad_engine.py +106 -0
- quantark/asset/equity/engine/quad/european_quad_engine.py +325 -0
- quantark/asset/equity/engine/quad/ko_reset_snowball_quad_engine.py +362 -0
- quantark/asset/equity/engine/quad/phoenix_quad_engine.py +614 -0
- quantark/asset/equity/engine/quad/quad_adapters.py +1260 -0
- quantark/asset/equity/engine/quad/quad_core.py +513 -0
- quantark/asset/equity/engine/quad/quad_math.py +219 -0
- quantark/asset/equity/engine/quad/snowball_quad_engine.py +1137 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_american_analytical.py +117 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_american_pde.py +114 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_asian_analytical.py +440 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_barrier_analytical.py +269 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_barrier_pde_solver.py +636 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_digital_option.py +256 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_snowball_pde_solver.py +807 -0
- quantark/asset/equity/engine/validation/script/boundary_check_american_analytical.py +290 -0
- quantark/asset/equity/engine/validation/script/boundary_check_american_pde.py +242 -0
- quantark/asset/equity/engine/validation/script/boundary_check_asian_analytical.py +612 -0
- quantark/asset/equity/engine/validation/script/boundary_check_barrier_analytical.py +434 -0
- quantark/asset/equity/engine/validation/script/boundary_check_barrier_pde_solver.py +748 -0
- quantark/asset/equity/engine/validation/script/boundary_check_digital_option.py +575 -0
- quantark/asset/equity/engine/validation/script/boundary_check_snowball_pde_solver.py +1101 -0
- quantark/asset/equity/engine/validation/script/greeks_check_digital_option.py +349 -0
- quantark/asset/equity/engine/validation/script/mc_comparison_barrier_pde.py +270 -0
- quantark/asset/equity/engine/validation/script/quick_mc_compare.py +51 -0
- quantark/asset/equity/engine/validation/script/validation_stepdown_improved.py +97 -0
- quantark/asset/equity/param/__init__.py +24 -0
- quantark/asset/equity/param/engine_param_profiles.py +325 -0
- quantark/asset/equity/param/engine_params.py +728 -0
- quantark/asset/equity/process/__init__.py +7 -0
- quantark/asset/equity/process/bsm/__init__.py +7 -0
- quantark/asset/equity/process/bsm/bsm_process.py +108 -0
- quantark/asset/equity/process/bsm/qmc_brownian_bridge.py +401 -0
- quantark/asset/equity/process/bsm/qmc_path_generator.py +694 -0
- quantark/asset/equity/process/bsm/qmc_rqmc_driver.py +163 -0
- quantark/asset/equity/process/bsm/qmc_sobol.py +195 -0
- quantark/asset/equity/process/bsm/qmc_variance_reduction.py +292 -0
- quantark/asset/equity/product/__init__.py +8 -0
- quantark/asset/equity/product/base_equity_product.py +72 -0
- quantark/asset/equity/product/deltaone/__init__.py +22 -0
- quantark/asset/equity/product/deltaone/base_deltaone_product.py +147 -0
- quantark/asset/equity/product/deltaone/futures.py +485 -0
- quantark/asset/equity/product/deltaone/spot_instrument.py +118 -0
- quantark/asset/equity/product/option/__init__.py +104 -0
- quantark/asset/equity/product/option/american_option.py +114 -0
- quantark/asset/equity/product/option/asian_option.py +531 -0
- quantark/asset/equity/product/option/barrier_option.py +289 -0
- quantark/asset/equity/product/option/base_equity_option.py +659 -0
- quantark/asset/equity/product/option/digital_option.py +102 -0
- quantark/asset/equity/product/option/double_barrier_option.py +286 -0
- quantark/asset/equity/product/option/double_one_touch_option.py +310 -0
- quantark/asset/equity/product/option/double_sharkfin_option.py +466 -0
- quantark/asset/equity/product/option/european_vanilla_option.py +103 -0
- quantark/asset/equity/product/option/ko_reset_snowball_option.py +563 -0
- quantark/asset/equity/product/option/observation_schedule.py +530 -0
- quantark/asset/equity/product/option/one_touch_option.py +287 -0
- quantark/asset/equity/product/option/phoenix_config.py +116 -0
- quantark/asset/equity/product/option/phoenix_helpers.py +576 -0
- quantark/asset/equity/product/option/phoenix_option.py +1167 -0
- quantark/asset/equity/product/option/range_accrual_config.py +288 -0
- quantark/asset/equity/product/option/range_accrual_helpers.py +608 -0
- quantark/asset/equity/product/option/range_accrual_option.py +526 -0
- quantark/asset/equity/product/option/single_sharkfin_option.py +420 -0
- quantark/asset/equity/product/option/snowball_config.py +261 -0
- quantark/asset/equity/product/option/snowball_helpers.py +977 -0
- quantark/asset/equity/product/option/snowball_option.py +1242 -0
- quantark/asset/equity/report/__init__.py +15 -0
- quantark/asset/equity/report/autocallable_risk_report.py +2118 -0
- quantark/asset/equity/report/plotting.py +87 -0
- quantark/asset/equity/report/snowball_risk_comparison_report.py +2230 -0
- quantark/asset/equity/report/surfaces.py +123 -0
- quantark/asset/equity/report/term_structure.py +126 -0
- quantark/asset/equity/riskmeasures/__init__.py +7 -0
- quantark/asset/equity/riskmeasures/greeks_calculator.py +1204 -0
- quantark/asset/rate/__init__.py +58 -0
- quantark/asset/rate/engine/__init__.py +25 -0
- quantark/asset/rate/engine/cap_floor_engine.py +514 -0
- quantark/asset/rate/engine/fra_engine.py +286 -0
- quantark/asset/rate/engine/irs_discount_engine.py +891 -0
- quantark/asset/rate/engine/swaption_engine.py +587 -0
- quantark/asset/rate/product/__init__.py +67 -0
- quantark/asset/rate/product/cap_floor.py +550 -0
- quantark/asset/rate/product/fra.py +219 -0
- quantark/asset/rate/product/irs.py +1223 -0
- quantark/asset/rate/product/swaption.py +372 -0
- quantark/backtest/__init__.py +153 -0
- quantark/backtest/base.py +263 -0
- quantark/backtest/dashboard.py +874 -0
- quantark/backtest/equity/__init__.py +35 -0
- quantark/backtest/equity/config.py +118 -0
- quantark/backtest/equity/engine.py +408 -0
- quantark/backtest/equity/hedge_executor.py +374 -0
- quantark/backtest/equity/metrics.py +396 -0
- quantark/backtest/equity/results.py +232 -0
- quantark/backtest/equity/state.py +252 -0
- quantark/backtest/examples/__init__.py +4 -0
- quantark/backtest/examples/advanced_backtest.py +345 -0
- quantark/backtest/examples/basic_delta_hedge.py +246 -0
- quantark/backtest/examples/fi_dv01_hedge.py +267 -0
- quantark/backtest/fi/__init__.py +30 -0
- quantark/backtest/fi/config.py +114 -0
- quantark/backtest/fi/engine.py +378 -0
- quantark/backtest/fi/hedge_executor.py +254 -0
- quantark/backtest/fi/metrics.py +308 -0
- quantark/backtest/fi/results.py +193 -0
- quantark/backtest/fi/state.py +212 -0
- quantark/backtest/logger.py +393 -0
- quantark/backtest/otc/__init__.py +74 -0
- quantark/backtest/otc/_replay.py +637 -0
- quantark/backtest/otc/book_engine.py +587 -0
- quantark/backtest/otc/config.py +175 -0
- quantark/backtest/otc/dashboard.py +1006 -0
- quantark/backtest/otc/engine.py +420 -0
- quantark/backtest/otc/engine_factory.py +138 -0
- quantark/backtest/otc/market.py +216 -0
- quantark/backtest/otc/results.py +107 -0
- quantark/backtest/otc/state.py +166 -0
- quantark/backtest/report_generator.py +608 -0
- quantark/backtest/strategy/__init__.py +28 -0
- quantark/backtest/strategy/base_strategy.py +235 -0
- quantark/backtest/strategy/convexity_neutral_strategy.py +247 -0
- quantark/backtest/strategy/delta_neutral_strategy.py +283 -0
- quantark/backtest/strategy/dv01_neutral_strategy.py +283 -0
- quantark/backtest/transaction_costs.py +485 -0
- quantark/backtest/visualizer.py +1019 -0
- quantark/cashleg/__init__.py +31 -0
- quantark/cashleg/accrual_leg.py +120 -0
- quantark/cashleg/base.py +48 -0
- quantark/cashleg/base_amount.py +60 -0
- quantark/cashleg/deterministic_leg.py +39 -0
- quantark/cashleg/event_distribution.py +262 -0
- quantark/cashleg/fixed_payoff_leg.py +92 -0
- quantark/cashleg/leg_schedule.py +95 -0
- quantark/cashleg/leg_valuator.py +40 -0
- quantark/dynamicscenario/__init__.py +97 -0
- quantark/dynamicscenario/base.py +297 -0
- quantark/dynamicscenario/config.py +122 -0
- quantark/dynamicscenario/engine.py +703 -0
- quantark/dynamicscenario/equity/__init__.py +14 -0
- quantark/dynamicscenario/fi/__init__.py +24 -0
- quantark/dynamicscenario/fi/config.py +149 -0
- quantark/dynamicscenario/fi/engine.py +500 -0
- quantark/dynamicscenario/fi/results.py +503 -0
- quantark/dynamicscenario/path/__init__.py +17 -0
- quantark/dynamicscenario/path/day_path.py +397 -0
- quantark/dynamicscenario/path/fi_path_library.py +488 -0
- quantark/dynamicscenario/path/path_builder.py +726 -0
- quantark/dynamicscenario/path/path_library.py +620 -0
- quantark/dynamicscenario/report/__init__.py +12 -0
- quantark/dynamicscenario/report/dynamic_report.py +1175 -0
- quantark/dynamicscenario/report/visualizer.py +1586 -0
- quantark/dynamicscenario/results/__init__.py +19 -0
- quantark/dynamicscenario/results/dynamic_results.py +579 -0
- quantark/dynamicscenario/results/result_exporter.py +438 -0
- quantark/param/__init__.py +75 -0
- quantark/param/basis/__init__.py +19 -0
- quantark/param/basis/basis_yield.py +301 -0
- quantark/param/div/__init__.py +16 -0
- quantark/param/div/dividend_yield.py +123 -0
- quantark/param/index/__init__.py +52 -0
- quantark/param/index/rate_index.py +568 -0
- quantark/param/quote/__init__.py +7 -0
- quantark/param/quote/spot_quote.py +35 -0
- quantark/param/rrf/__init__.py +22 -0
- quantark/param/rrf/rate_curve.py +436 -0
- quantark/param/vol/__init__.py +6 -0
- quantark/param/vol/vol_surface.py +118 -0
- quantark/portfolio/__init__.py +61 -0
- quantark/portfolio/base.py +203 -0
- quantark/portfolio/equity/__init__.py +17 -0
- quantark/portfolio/equity/portfolio.py +391 -0
- quantark/portfolio/equity/position.py +368 -0
- quantark/portfolio/fi/__init__.py +14 -0
- quantark/portfolio/fi/portfolio.py +424 -0
- quantark/portfolio/fi/position.py +272 -0
- quantark/portfolio/portfolio_snapshot.py +221 -0
- quantark/portfolio/portfolio_storage.py +414 -0
- quantark/priceenv/__init__.py +7 -0
- quantark/priceenv/pricing_environment.py +196 -0
- quantark/rfq/__init__.py +32 -0
- quantark/rfq/builders.py +102 -0
- quantark/rfq/models.py +214 -0
- quantark/rfq/registry.py +611 -0
- quantark/rfq/service.py +237 -0
- quantark/simm/__init__.py +155 -0
- quantark/simm/calibration/__init__.py +206 -0
- quantark/simm/calibration/accessors.py +439 -0
- quantark/simm/calibration/commodity.py +156 -0
- quantark/simm/calibration/credit_non_qualifying.py +79 -0
- quantark/simm/calibration/credit_qualifying.py +130 -0
- quantark/simm/calibration/cross_risk.py +39 -0
- quantark/simm/calibration/equity.py +125 -0
- quantark/simm/calibration/fx.py +92 -0
- quantark/simm/calibration/ir.py +152 -0
- quantark/simm/calibration/version.py +33 -0
- quantark/simm/config.py +186 -0
- quantark/simm/crif/__init__.py +35 -0
- quantark/simm/crif/models.py +230 -0
- quantark/simm/crif/parser.py +585 -0
- quantark/simm/engines/__init__.py +62 -0
- quantark/simm/engines/aggregation/__init__.py +67 -0
- quantark/simm/engines/aggregation/addon.py +141 -0
- quantark/simm/engines/aggregation/bucket_aggregator.py +298 -0
- quantark/simm/engines/aggregation/concentration.py +349 -0
- quantark/simm/engines/aggregation/product_class_aggregator.py +183 -0
- quantark/simm/engines/aggregation/risk_class_aggregator.py +403 -0
- quantark/simm/engines/aggregation/simm_calculator.py +430 -0
- quantark/simm/engines/aggregation/weighted_sensitivity.py +272 -0
- quantark/simm/engines/base.py +231 -0
- quantark/simm/engines/classification/__init__.py +10 -0
- quantark/simm/engines/classification/bucket_mapper.py +347 -0
- quantark/simm/engines/factory.py +137 -0
- quantark/simm/engines/portfolio_adapter.py +336 -0
- quantark/simm/engines/result.py +176 -0
- quantark/simm/engines/risk_class/__init__.py +18 -0
- quantark/simm/engines/risk_class/equity_engine.py +263 -0
- quantark/simm/engines/risk_class/ir_engine.py +264 -0
- quantark/simm/report/__init__.py +17 -0
- quantark/simm/report/crif_export.py +284 -0
- quantark/simm/report/excel_generator.py +401 -0
- quantark/simm/report/html_generator.py +840 -0
- quantark/simm/results/__init__.py +38 -0
- quantark/simm/results/attribution.py +313 -0
- quantark/simm/results/simm_result.py +339 -0
- quantark/simm/results/whatif.py +268 -0
- quantark/simm/sensitivity.py +533 -0
- quantark/simm/taxonomy.py +416 -0
- quantark/stresstest/__init__.py +67 -0
- quantark/stresstest/base.py +116 -0
- quantark/stresstest/config.py +5 -0
- quantark/stresstest/engine.py +5 -0
- quantark/stresstest/equity/__init__.py +17 -0
- quantark/stresstest/equity/config.py +69 -0
- quantark/stresstest/equity/engine.py +272 -0
- quantark/stresstest/equity/report/__init__.py +7 -0
- quantark/stresstest/equity/report/report_generator.py +423 -0
- quantark/stresstest/equity/report/visualizer.py +328 -0
- quantark/stresstest/equity/results.py +145 -0
- quantark/stresstest/fi/__init__.py +15 -0
- quantark/stresstest/fi/config.py +59 -0
- quantark/stresstest/fi/engine.py +213 -0
- quantark/stresstest/fi/metrics.py +60 -0
- quantark/stresstest/fi/results.py +64 -0
- quantark/stresstest/report/__init__.py +12 -0
- quantark/stresstest/report/report_generator.py +5 -0
- quantark/stresstest/report/visualizer.py +5 -0
- quantark/stresstest/results/__init__.py +16 -0
- quantark/stresstest/results/result_aggregator.py +325 -0
- quantark/stresstest/results/result_exporter.py +286 -0
- quantark/stresstest/results/stress_results.py +5 -0
- quantark/stresstest/scenario/__init__.py +13 -0
- quantark/stresstest/scenario/scenario.py +242 -0
- quantark/stresstest/scenario/scenario_builder.py +376 -0
- quantark/stresstest/scenario/scenario_library.py +435 -0
- quantark/stresstest/scenario/scenario_storage.py +224 -0
- quantark/stresstest/stress/__init__.py +13 -0
- quantark/stresstest/stress/stress_applicator.py +590 -0
- quantark/stresstest/stress/stress_types.py +142 -0
- quantark/util/__init__.py +23 -0
- quantark/util/barrier_shift.py +44 -0
- quantark/util/calendar/__init__.py +27 -0
- quantark/util/calendar/business_calendar.py +584 -0
- quantark/util/calendar/day_counter.py +517 -0
- quantark/util/calendar/holidayfile/china.csv +1920 -0
- quantark/util/calendar/holidayfile/china_sse.csv +1462 -0
- quantark/util/enum/__init__.py +81 -0
- quantark/util/enum/bond_enums.py +112 -0
- quantark/util/enum/deltaone_enums.py +16 -0
- quantark/util/enum/engine_enums.py +137 -0
- quantark/util/enum/greeks_enums.py +29 -0
- quantark/util/enum/option_enums.py +221 -0
- quantark/util/exceptions.py +66 -0
- quantark/util/marketdata/__init__.py +39 -0
- quantark/util/marketdata/adapter/base_adapter.py +203 -0
- quantark/util/marketdata/adapter/mock_adapter.py +265 -0
- quantark/util/marketdata/converter.py +289 -0
- quantark/util/marketdata/example_usage.py +314 -0
- quantark/util/marketdata/generator/__init__.py +7 -0
- quantark/util/marketdata/generator/mock_generator.py +466 -0
- quantark/util/marketdata/models.py +358 -0
- quantark/util/marketdata/storage/__init__.py +7 -0
- quantark/util/marketdata/storage/parquet_storage.py +340 -0
- quantark/util/numerical/__init__.py +98 -0
- quantark/util/numerical/comparison.py +219 -0
- quantark/util/numerical/constants.py +98 -0
- quantark/util/numerical/formatting.py +380 -0
- quantark/util/numerical/pnl.py +17 -0
- quantark/util/numerical/safe_math.py +238 -0
- quantark/util/numerical/validation.py +315 -0
- quantark/var/__init__.py +39 -0
- quantark/var/attribution.py +398 -0
- quantark/var/backtest/__init__.py +7 -0
- quantark/var/backtest/var_backtester.py +309 -0
- quantark/var/base.py +63 -0
- quantark/var/config.py +219 -0
- quantark/var/engines/__init__.py +13 -0
- quantark/var/engines/historical.py +925 -0
- quantark/var/engines/monte_carlo.py +870 -0
- quantark/var/engines/parametric.py +1199 -0
- quantark/var/results/__init__.py +16 -0
- quantark/var/results/incremental_var_result.py +131 -0
- quantark/var/results/var_report.py +346 -0
- quantark/var/results/var_result.py +134 -0
- quantark/var/risk_factors/__init__.py +22 -0
- quantark/var/risk_factors/base.py +41 -0
- quantark/var/risk_factors/equity_factors.py +158 -0
- quantark/var/risk_factors/fi_factors.py +99 -0
- quantark-0.1.0.dist-info/METADATA +351 -0
- quantark-0.1.0.dist-info/RECORD +399 -0
- quantark-0.1.0.dist-info/WHEEL +4 -0
- quantark-0.1.0.dist-info/licenses/LICENSE +202 -0
- quantark-0.1.0.dist-info/licenses/NOTICE +2 -0
- quantark_compat.pth +1 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Business day calendar for settlement date adjustments.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from importlib import resources
|
|
8
|
+
from typing import Set, Optional
|
|
9
|
+
import csv
|
|
10
|
+
from quantark.util.exceptions import ValidationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BusinessDayConvention(Enum):
|
|
14
|
+
"""
|
|
15
|
+
Business day conventions for date adjustments.
|
|
16
|
+
|
|
17
|
+
- FOLLOWING: Roll forward to next business day
|
|
18
|
+
- MODIFIED_FOLLOWING: Roll forward unless crosses month, then roll backward
|
|
19
|
+
- PRECEDING: Roll backward to previous business day
|
|
20
|
+
- MODIFIED_PRECEDING: Roll backward unless crosses month, then roll forward
|
|
21
|
+
- UNADJUSTED: No adjustment
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
FOLLOWING = "following"
|
|
25
|
+
MODIFIED_FOLLOWING = "modified_following"
|
|
26
|
+
PRECEDING = "preceding"
|
|
27
|
+
MODIFIED_PRECEDING = "modified_preceding"
|
|
28
|
+
UNADJUSTED = "unadjusted"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CalendarType(Enum):
|
|
32
|
+
"""
|
|
33
|
+
Standard calendar types with predefined holiday schedules.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
US = "us" # US holidays (Federal Reserve)
|
|
37
|
+
UK = "uk" # UK holidays
|
|
38
|
+
TARGET = "target" # Trans-European Automated Real-time Gross settlement (ECB)
|
|
39
|
+
CHINA = "china" # China holidays (national)
|
|
40
|
+
CHINA_SSE = "china_sse" # China holidays (SSE)
|
|
41
|
+
NONE = "none" # No holidays, only weekends
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Calendar:
|
|
45
|
+
"""
|
|
46
|
+
Business day calendar supporting holidays and business day adjustments.
|
|
47
|
+
|
|
48
|
+
A calendar defines which days are business days (non-holidays, non-weekends)
|
|
49
|
+
and provides methods to adjust dates according to business day conventions.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
holidays: Set of holiday dates
|
|
53
|
+
weekend_days: Set of weekend day numbers (0=Monday, 6=Sunday)
|
|
54
|
+
name: Calendar name for identification
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
holidays: Optional[Set[datetime]] = None,
|
|
60
|
+
weekend_days: Optional[Set[int]] = None,
|
|
61
|
+
name: str = "Custom",
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
Initialize calendar.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
holidays: Set of holiday dates (default: empty set)
|
|
68
|
+
weekend_days: Set of weekend day numbers, 0=Mon, 6=Sun (default: {5, 6})
|
|
69
|
+
name: Calendar name
|
|
70
|
+
"""
|
|
71
|
+
self.holidays = holidays if holidays is not None else set()
|
|
72
|
+
self.weekend_days = (
|
|
73
|
+
weekend_days if weekend_days is not None else {5, 6}
|
|
74
|
+
) # Sat, Sun
|
|
75
|
+
self.name = name
|
|
76
|
+
|
|
77
|
+
# Normalize holiday dates to midnight
|
|
78
|
+
self.holidays = {self._normalize_date(d) for d in self.holidays}
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _normalize_date(date: datetime) -> datetime:
|
|
82
|
+
"""Normalize date to midnight."""
|
|
83
|
+
return datetime(date.year, date.month, date.day)
|
|
84
|
+
|
|
85
|
+
def is_business_day(self, date: datetime) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Check if a date is a business day.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
date: Date to check
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if business day, False if weekend or holiday
|
|
94
|
+
"""
|
|
95
|
+
normalized = self._normalize_date(date)
|
|
96
|
+
|
|
97
|
+
# Check if weekend
|
|
98
|
+
if normalized.weekday() in self.weekend_days:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
# Check if holiday
|
|
102
|
+
if normalized in self.holidays:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
def is_holiday(self, date: datetime) -> bool:
|
|
108
|
+
"""
|
|
109
|
+
Check if a date is a holiday.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
date: Date to check
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
True if holiday, False otherwise
|
|
116
|
+
"""
|
|
117
|
+
normalized = self._normalize_date(date)
|
|
118
|
+
return normalized in self.holidays
|
|
119
|
+
|
|
120
|
+
def adjust_date(
|
|
121
|
+
self, date: datetime, convention: BusinessDayConvention
|
|
122
|
+
) -> datetime:
|
|
123
|
+
"""
|
|
124
|
+
Adjust a date according to business day convention.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
date: Date to adjust
|
|
128
|
+
convention: Business day convention to apply
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Adjusted date
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ValidationError: If convention is invalid
|
|
135
|
+
"""
|
|
136
|
+
if convention == BusinessDayConvention.UNADJUSTED:
|
|
137
|
+
return date
|
|
138
|
+
|
|
139
|
+
if convention == BusinessDayConvention.FOLLOWING:
|
|
140
|
+
return self._adjust_following(date)
|
|
141
|
+
|
|
142
|
+
if convention == BusinessDayConvention.MODIFIED_FOLLOWING:
|
|
143
|
+
adjusted = self._adjust_following(date)
|
|
144
|
+
# If adjustment crosses month boundary, use preceding instead
|
|
145
|
+
if adjusted.month != date.month:
|
|
146
|
+
return self._adjust_preceding(date)
|
|
147
|
+
return adjusted
|
|
148
|
+
|
|
149
|
+
if convention == BusinessDayConvention.PRECEDING:
|
|
150
|
+
return self._adjust_preceding(date)
|
|
151
|
+
|
|
152
|
+
if convention == BusinessDayConvention.MODIFIED_PRECEDING:
|
|
153
|
+
adjusted = self._adjust_preceding(date)
|
|
154
|
+
# If adjustment crosses month boundary, use following instead
|
|
155
|
+
if adjusted.month != date.month:
|
|
156
|
+
return self._adjust_following(date)
|
|
157
|
+
return adjusted
|
|
158
|
+
|
|
159
|
+
raise ValidationError(f"Unsupported business day convention: {convention}")
|
|
160
|
+
|
|
161
|
+
def _adjust_following(self, date: datetime) -> datetime:
|
|
162
|
+
"""Roll forward to next business day."""
|
|
163
|
+
current = date
|
|
164
|
+
max_iterations = 10 # Prevent infinite loops
|
|
165
|
+
iterations = 0
|
|
166
|
+
|
|
167
|
+
while not self.is_business_day(current) and iterations < max_iterations:
|
|
168
|
+
current = current + timedelta(days=1)
|
|
169
|
+
iterations += 1
|
|
170
|
+
|
|
171
|
+
if iterations >= max_iterations:
|
|
172
|
+
raise ValidationError(
|
|
173
|
+
f"Could not find business day within {max_iterations} days"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return current
|
|
177
|
+
|
|
178
|
+
def _adjust_preceding(self, date: datetime) -> datetime:
|
|
179
|
+
"""Roll backward to previous business day."""
|
|
180
|
+
current = date
|
|
181
|
+
max_iterations = 10 # Prevent infinite loops
|
|
182
|
+
iterations = 0
|
|
183
|
+
|
|
184
|
+
while not self.is_business_day(current) and iterations < max_iterations:
|
|
185
|
+
current = current - timedelta(days=1)
|
|
186
|
+
iterations += 1
|
|
187
|
+
|
|
188
|
+
if iterations >= max_iterations:
|
|
189
|
+
raise ValidationError(
|
|
190
|
+
f"Could not find business day within {max_iterations} days"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return current
|
|
194
|
+
|
|
195
|
+
def add_business_days(self, date: datetime, num_days: int) -> datetime:
|
|
196
|
+
"""
|
|
197
|
+
Add a number of business days to a date.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
date: Starting date
|
|
201
|
+
num_days: Number of business days to add (can be negative)
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Date after adding business days
|
|
205
|
+
"""
|
|
206
|
+
if num_days == 0:
|
|
207
|
+
return date
|
|
208
|
+
|
|
209
|
+
direction = 1 if num_days > 0 else -1
|
|
210
|
+
remaining = abs(num_days)
|
|
211
|
+
current = date
|
|
212
|
+
|
|
213
|
+
while remaining > 0:
|
|
214
|
+
current = current + timedelta(days=direction)
|
|
215
|
+
if self.is_business_day(current):
|
|
216
|
+
remaining -= 1
|
|
217
|
+
|
|
218
|
+
return current
|
|
219
|
+
|
|
220
|
+
def count_business_days(
|
|
221
|
+
self,
|
|
222
|
+
start_date: datetime,
|
|
223
|
+
end_date: datetime,
|
|
224
|
+
include_start: bool = True,
|
|
225
|
+
include_end: bool = True,
|
|
226
|
+
) -> int:
|
|
227
|
+
"""
|
|
228
|
+
Count business days between two dates.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
start_date: Start date (inclusive by default)
|
|
232
|
+
end_date: End date (inclusive by default)
|
|
233
|
+
include_start: Whether to include start_date if business day
|
|
234
|
+
include_end: Whether to include end_date if business day
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Number of business days in the range
|
|
238
|
+
"""
|
|
239
|
+
if end_date < start_date:
|
|
240
|
+
return 0
|
|
241
|
+
|
|
242
|
+
count = 0
|
|
243
|
+
current = start_date
|
|
244
|
+
while current <= end_date:
|
|
245
|
+
if self.is_business_day(current):
|
|
246
|
+
if current == start_date and not include_start:
|
|
247
|
+
pass
|
|
248
|
+
elif current == end_date and not include_end:
|
|
249
|
+
pass
|
|
250
|
+
else:
|
|
251
|
+
count += 1
|
|
252
|
+
current += timedelta(days=1)
|
|
253
|
+
return count
|
|
254
|
+
|
|
255
|
+
def __repr__(self):
|
|
256
|
+
return f"Calendar(name={self.name}, holidays={len(self.holidays)})"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def create_calendar(
|
|
260
|
+
calendar_type: CalendarType, year_range: tuple = (2020, 2030)
|
|
261
|
+
) -> Calendar:
|
|
262
|
+
"""
|
|
263
|
+
Create a calendar with predefined holidays.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
calendar_type: Type of calendar to create
|
|
267
|
+
year_range: (start_year, end_year) for generating holidays
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Calendar with appropriate holidays
|
|
271
|
+
"""
|
|
272
|
+
if calendar_type == CalendarType.NONE:
|
|
273
|
+
return Calendar(holidays=set(), name="No Holidays")
|
|
274
|
+
|
|
275
|
+
if calendar_type == CalendarType.US:
|
|
276
|
+
return _create_us_calendar(year_range)
|
|
277
|
+
|
|
278
|
+
if calendar_type == CalendarType.UK:
|
|
279
|
+
return _create_uk_calendar(year_range)
|
|
280
|
+
|
|
281
|
+
if calendar_type == CalendarType.TARGET:
|
|
282
|
+
return _create_target_calendar(year_range)
|
|
283
|
+
|
|
284
|
+
if calendar_type == CalendarType.CHINA:
|
|
285
|
+
return _create_china_calendar(year_range)
|
|
286
|
+
|
|
287
|
+
if calendar_type == CalendarType.CHINA_SSE:
|
|
288
|
+
return _create_china_sse_calendar(year_range)
|
|
289
|
+
|
|
290
|
+
raise ValidationError(f"Unsupported calendar type: {calendar_type}")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _create_us_calendar(year_range: tuple) -> Calendar:
|
|
294
|
+
"""
|
|
295
|
+
Create US Federal Reserve holiday calendar.
|
|
296
|
+
|
|
297
|
+
Includes: New Year's Day, MLK Day, Presidents Day, Memorial Day,
|
|
298
|
+
Independence Day, Labor Day, Columbus Day, Veterans Day,
|
|
299
|
+
Thanksgiving, Christmas
|
|
300
|
+
"""
|
|
301
|
+
holidays = set()
|
|
302
|
+
start_year, end_year = year_range
|
|
303
|
+
|
|
304
|
+
for year in range(start_year, end_year + 1):
|
|
305
|
+
# New Year's Day (Jan 1)
|
|
306
|
+
holidays.add(datetime(year, 1, 1))
|
|
307
|
+
|
|
308
|
+
# Martin Luther King Jr. Day (3rd Monday in January)
|
|
309
|
+
holidays.add(_nth_weekday(year, 1, 0, 3))
|
|
310
|
+
|
|
311
|
+
# Presidents Day (3rd Monday in February)
|
|
312
|
+
holidays.add(_nth_weekday(year, 2, 0, 3))
|
|
313
|
+
|
|
314
|
+
# Memorial Day (last Monday in May)
|
|
315
|
+
holidays.add(_last_weekday(year, 5, 0))
|
|
316
|
+
|
|
317
|
+
# Independence Day (July 4)
|
|
318
|
+
holidays.add(datetime(year, 7, 4))
|
|
319
|
+
|
|
320
|
+
# Labor Day (1st Monday in September)
|
|
321
|
+
holidays.add(_nth_weekday(year, 9, 0, 1))
|
|
322
|
+
|
|
323
|
+
# Columbus Day (2nd Monday in October)
|
|
324
|
+
holidays.add(_nth_weekday(year, 10, 0, 2))
|
|
325
|
+
|
|
326
|
+
# Veterans Day (Nov 11)
|
|
327
|
+
holidays.add(datetime(year, 11, 11))
|
|
328
|
+
|
|
329
|
+
# Thanksgiving (4th Thursday in November)
|
|
330
|
+
holidays.add(_nth_weekday(year, 11, 3, 4))
|
|
331
|
+
|
|
332
|
+
# Christmas (Dec 25)
|
|
333
|
+
holidays.add(datetime(year, 12, 25))
|
|
334
|
+
|
|
335
|
+
return Calendar(holidays=holidays, name="US Federal Reserve")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _create_uk_calendar(year_range: tuple) -> Calendar:
|
|
339
|
+
"""
|
|
340
|
+
Create UK holiday calendar.
|
|
341
|
+
|
|
342
|
+
Includes: New Year's Day, Good Friday, Easter Monday, Early May,
|
|
343
|
+
Spring Bank Holiday, Summer Bank Holiday, Christmas, Boxing Day
|
|
344
|
+
"""
|
|
345
|
+
holidays = set()
|
|
346
|
+
start_year, end_year = year_range
|
|
347
|
+
|
|
348
|
+
for year in range(start_year, end_year + 1):
|
|
349
|
+
# New Year's Day
|
|
350
|
+
holidays.add(datetime(year, 1, 1))
|
|
351
|
+
|
|
352
|
+
# Good Friday and Easter Monday (based on Easter calculation)
|
|
353
|
+
easter = _calculate_easter(year)
|
|
354
|
+
holidays.add(easter - timedelta(days=2)) # Good Friday
|
|
355
|
+
holidays.add(easter + timedelta(days=1)) # Easter Monday
|
|
356
|
+
|
|
357
|
+
# Early May Bank Holiday (1st Monday in May)
|
|
358
|
+
holidays.add(_nth_weekday(year, 5, 0, 1))
|
|
359
|
+
|
|
360
|
+
# Spring Bank Holiday (last Monday in May)
|
|
361
|
+
holidays.add(_last_weekday(year, 5, 0))
|
|
362
|
+
|
|
363
|
+
# Summer Bank Holiday (last Monday in August)
|
|
364
|
+
holidays.add(_last_weekday(year, 8, 0))
|
|
365
|
+
|
|
366
|
+
# Christmas Day
|
|
367
|
+
holidays.add(datetime(year, 12, 25))
|
|
368
|
+
|
|
369
|
+
# Boxing Day
|
|
370
|
+
holidays.add(datetime(year, 12, 26))
|
|
371
|
+
|
|
372
|
+
return Calendar(holidays=holidays, name="UK")
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _create_target_calendar(year_range: tuple) -> Calendar:
|
|
376
|
+
"""
|
|
377
|
+
Create TARGET (Trans-European Automated Real-time Gross settlement) calendar.
|
|
378
|
+
Used by European Central Bank.
|
|
379
|
+
|
|
380
|
+
Includes: New Year's Day, Good Friday, Easter Monday, Labour Day,
|
|
381
|
+
Christmas, Boxing Day
|
|
382
|
+
"""
|
|
383
|
+
holidays = set()
|
|
384
|
+
start_year, end_year = year_range
|
|
385
|
+
|
|
386
|
+
for year in range(start_year, end_year + 1):
|
|
387
|
+
# New Year's Day
|
|
388
|
+
holidays.add(datetime(year, 1, 1))
|
|
389
|
+
|
|
390
|
+
# Good Friday and Easter Monday
|
|
391
|
+
easter = _calculate_easter(year)
|
|
392
|
+
holidays.add(easter - timedelta(days=2)) # Good Friday
|
|
393
|
+
holidays.add(easter + timedelta(days=1)) # Easter Monday
|
|
394
|
+
|
|
395
|
+
# Labour Day (May 1)
|
|
396
|
+
holidays.add(datetime(year, 5, 1))
|
|
397
|
+
|
|
398
|
+
# Christmas Day
|
|
399
|
+
holidays.add(datetime(year, 12, 25))
|
|
400
|
+
|
|
401
|
+
# Boxing Day (Dec 26)
|
|
402
|
+
holidays.add(datetime(year, 12, 26))
|
|
403
|
+
|
|
404
|
+
return Calendar(holidays=holidays, name="TARGET")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _load_holidays_from_csv(
|
|
408
|
+
calendar_name: str, year_range: tuple
|
|
409
|
+
) -> Optional[Set[datetime]]:
|
|
410
|
+
start_year, end_year = year_range
|
|
411
|
+
csv_resource = (
|
|
412
|
+
resources.files("quantark.util")
|
|
413
|
+
/ "calendar"
|
|
414
|
+
/ "holidayfile"
|
|
415
|
+
/ f"{calendar_name}.csv"
|
|
416
|
+
)
|
|
417
|
+
if not csv_resource.is_file():
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
holidays: Set[datetime] = set()
|
|
421
|
+
try:
|
|
422
|
+
with csv_resource.open("r", newline="", encoding="utf-8") as handle:
|
|
423
|
+
reader = csv.DictReader(handle)
|
|
424
|
+
for row in reader:
|
|
425
|
+
date_str = (row.get("date") or row.get("Date") or "").strip()
|
|
426
|
+
if not date_str:
|
|
427
|
+
continue
|
|
428
|
+
try:
|
|
429
|
+
parsed = datetime.strptime(date_str, "%Y-%m-%d")
|
|
430
|
+
except ValueError:
|
|
431
|
+
continue
|
|
432
|
+
if start_year <= parsed.year <= end_year:
|
|
433
|
+
holidays.add(datetime(parsed.year, parsed.month, parsed.day))
|
|
434
|
+
except Exception:
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
return holidays or None
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _create_china_calendar(year_range: tuple) -> Calendar:
|
|
441
|
+
"""
|
|
442
|
+
Create China holiday calendar (national).
|
|
443
|
+
|
|
444
|
+
Note: Uses util/calendar/holidayfile/china.csv when available.
|
|
445
|
+
Falls back to a simplified fixed-date set if the CSV is missing.
|
|
446
|
+
|
|
447
|
+
Includes: New Year's Day, Spring Festival (approximate), Qingming,
|
|
448
|
+
Labour Day, Dragon Boat Festival (approximate), Mid-Autumn Festival (approximate),
|
|
449
|
+
National Day
|
|
450
|
+
"""
|
|
451
|
+
holidays = _load_holidays_from_csv("china", year_range)
|
|
452
|
+
if holidays:
|
|
453
|
+
return Calendar(holidays=holidays, name="China")
|
|
454
|
+
|
|
455
|
+
holidays = set()
|
|
456
|
+
start_year, end_year = year_range
|
|
457
|
+
|
|
458
|
+
for year in range(start_year, end_year + 1):
|
|
459
|
+
# New Year's Day (Jan 1)
|
|
460
|
+
holidays.add(datetime(year, 1, 1))
|
|
461
|
+
|
|
462
|
+
# Spring Festival / Chinese New Year (approximate - late Jan/early Feb)
|
|
463
|
+
# Using approximate dates; actual dates vary by lunar calendar
|
|
464
|
+
# Typically a week-long holiday
|
|
465
|
+
for day in range(1, 8):
|
|
466
|
+
holidays.add(datetime(year, 2, day))
|
|
467
|
+
|
|
468
|
+
# Qingming Festival (April 4 or 5)
|
|
469
|
+
holidays.add(datetime(year, 4, 4))
|
|
470
|
+
holidays.add(datetime(year, 4, 5))
|
|
471
|
+
|
|
472
|
+
# Labour Day (May 1-3)
|
|
473
|
+
holidays.add(datetime(year, 5, 1))
|
|
474
|
+
holidays.add(datetime(year, 5, 2))
|
|
475
|
+
holidays.add(datetime(year, 5, 3))
|
|
476
|
+
|
|
477
|
+
# Dragon Boat Festival (approximate - June)
|
|
478
|
+
holidays.add(datetime(year, 6, 12))
|
|
479
|
+
holidays.add(datetime(year, 6, 13))
|
|
480
|
+
holidays.add(datetime(year, 6, 14))
|
|
481
|
+
|
|
482
|
+
# Mid-Autumn Festival (approximate - September)
|
|
483
|
+
holidays.add(datetime(year, 9, 15))
|
|
484
|
+
holidays.add(datetime(year, 9, 16))
|
|
485
|
+
holidays.add(datetime(year, 9, 17))
|
|
486
|
+
|
|
487
|
+
# National Day (Oct 1-7)
|
|
488
|
+
for day in range(1, 8):
|
|
489
|
+
holidays.add(datetime(year, 10, day))
|
|
490
|
+
|
|
491
|
+
return Calendar(holidays=holidays, name="China")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _create_china_sse_calendar(year_range: tuple) -> Calendar:
|
|
495
|
+
"""
|
|
496
|
+
Create China SSE holiday calendar.
|
|
497
|
+
|
|
498
|
+
Note: Uses util/calendar/holidayfile/china_sse.csv when available.
|
|
499
|
+
Falls back to the national China calendar when the CSV is missing.
|
|
500
|
+
"""
|
|
501
|
+
holidays = _load_holidays_from_csv("china_sse", year_range)
|
|
502
|
+
if holidays:
|
|
503
|
+
return Calendar(holidays=holidays, name="China (SSE)")
|
|
504
|
+
|
|
505
|
+
fallback = _create_china_calendar(year_range)
|
|
506
|
+
fallback.name = "China (SSE)"
|
|
507
|
+
return fallback
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _nth_weekday(year: int, month: int, weekday: int, n: int) -> datetime:
|
|
511
|
+
"""
|
|
512
|
+
Find the nth occurrence of a weekday in a month.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
year: Year
|
|
516
|
+
month: Month (1-12)
|
|
517
|
+
weekday: Day of week (0=Monday, 6=Sunday)
|
|
518
|
+
n: Which occurrence (1=first, 2=second, etc.)
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
Date of nth weekday
|
|
522
|
+
"""
|
|
523
|
+
first_day = datetime(year, month, 1)
|
|
524
|
+
first_weekday = first_day.weekday()
|
|
525
|
+
|
|
526
|
+
# Calculate days until first occurrence of target weekday
|
|
527
|
+
days_until = (weekday - first_weekday) % 7
|
|
528
|
+
first_occurrence = first_day + timedelta(days=days_until)
|
|
529
|
+
|
|
530
|
+
# Add weeks to get to nth occurrence
|
|
531
|
+
return first_occurrence + timedelta(weeks=n - 1)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _last_weekday(year: int, month: int, weekday: int) -> datetime:
|
|
535
|
+
"""
|
|
536
|
+
Find the last occurrence of a weekday in a month.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
year: Year
|
|
540
|
+
month: Month (1-12)
|
|
541
|
+
weekday: Day of week (0=Monday, 6=Sunday)
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
Date of last weekday in month
|
|
545
|
+
"""
|
|
546
|
+
# Start from last day of month
|
|
547
|
+
if month == 12:
|
|
548
|
+
last_day = datetime(year, 12, 31)
|
|
549
|
+
else:
|
|
550
|
+
last_day = datetime(year, month + 1, 1) - timedelta(days=1)
|
|
551
|
+
|
|
552
|
+
last_weekday_val = last_day.weekday()
|
|
553
|
+
|
|
554
|
+
# Calculate days back to target weekday
|
|
555
|
+
days_back = (last_weekday_val - weekday) % 7
|
|
556
|
+
return last_day - timedelta(days=days_back)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _calculate_easter(year: int) -> datetime:
|
|
560
|
+
"""
|
|
561
|
+
Calculate Easter Sunday using Meeus/Jones/Butcher algorithm.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
year: Year
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
Date of Easter Sunday
|
|
568
|
+
"""
|
|
569
|
+
a = year % 19
|
|
570
|
+
b = year // 100
|
|
571
|
+
c = year % 100
|
|
572
|
+
d = b // 4
|
|
573
|
+
e = b % 4
|
|
574
|
+
f = (b + 8) // 25
|
|
575
|
+
g = (b - f + 1) // 3
|
|
576
|
+
h = (19 * a + b - d - g + 15) % 30
|
|
577
|
+
i = c // 4
|
|
578
|
+
k = c % 4
|
|
579
|
+
l = (32 + 2 * e + 2 * i - h - k) % 7
|
|
580
|
+
m = (a + 11 * h + 22 * l) // 451
|
|
581
|
+
month = (h + l - 7 * m + 114) // 31
|
|
582
|
+
day = ((h + l - 7 * m + 114) % 31) + 1
|
|
583
|
+
|
|
584
|
+
return datetime(year, month, day)
|