wbportfolio 2.2.1__py2.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.
Potentially problematic release.
This version of wbportfolio might be problematic. Click here for more details.
- wbportfolio/__init__.py +1 -0
- wbportfolio/admin/__init__.py +12 -0
- wbportfolio/admin/asset.py +47 -0
- wbportfolio/admin/custodians.py +9 -0
- wbportfolio/admin/portfolio.py +127 -0
- wbportfolio/admin/portfolio_relationships.py +22 -0
- wbportfolio/admin/product_groups.py +42 -0
- wbportfolio/admin/products.py +80 -0
- wbportfolio/admin/reconciliations.py +14 -0
- wbportfolio/admin/registers.py +17 -0
- wbportfolio/admin/roles.py +19 -0
- wbportfolio/admin/synchronization/__init__.py +2 -0
- wbportfolio/admin/synchronization/admin.py +114 -0
- wbportfolio/admin/synchronization/portfolio_synchronization.py +18 -0
- wbportfolio/admin/synchronization/price_computation.py +21 -0
- wbportfolio/admin/transactions/__init__.py +5 -0
- wbportfolio/admin/transactions/claim.py +16 -0
- wbportfolio/admin/transactions/dividends.py +14 -0
- wbportfolio/admin/transactions/fees.py +35 -0
- wbportfolio/admin/transactions/trades.py +49 -0
- wbportfolio/admin/transactions/transactions.py +37 -0
- wbportfolio/analysis/__init__.py +0 -0
- wbportfolio/analysis/claims.py +235 -0
- wbportfolio/apps.py +5 -0
- wbportfolio/contrib/__init__.py +0 -0
- wbportfolio/contrib/company_portfolio/__init__.py +0 -0
- wbportfolio/contrib/company_portfolio/admin.py +28 -0
- wbportfolio/contrib/company_portfolio/apps.py +29 -0
- wbportfolio/contrib/company_portfolio/configs/__init__.py +3 -0
- wbportfolio/contrib/company_portfolio/configs/display.py +182 -0
- wbportfolio/contrib/company_portfolio/configs/endpoints.py +34 -0
- wbportfolio/contrib/company_portfolio/configs/previews.py +37 -0
- wbportfolio/contrib/company_portfolio/constants.py +1 -0
- wbportfolio/contrib/company_portfolio/dynamic_preferences_registry.py +87 -0
- wbportfolio/contrib/company_portfolio/factories.py +32 -0
- wbportfolio/contrib/company_portfolio/filters.py +127 -0
- wbportfolio/contrib/company_portfolio/management.py +19 -0
- wbportfolio/contrib/company_portfolio/migrations/0001_initial.py +214 -0
- wbportfolio/contrib/company_portfolio/migrations/__init__.py +0 -0
- wbportfolio/contrib/company_portfolio/models.py +334 -0
- wbportfolio/contrib/company_portfolio/scripts.py +76 -0
- wbportfolio/contrib/company_portfolio/serializers.py +303 -0
- wbportfolio/contrib/company_portfolio/tasks.py +19 -0
- wbportfolio/contrib/company_portfolio/tests/__init__.py +0 -0
- wbportfolio/contrib/company_portfolio/tests/conftest.py +161 -0
- wbportfolio/contrib/company_portfolio/tests/test_models.py +161 -0
- wbportfolio/contrib/company_portfolio/urls.py +29 -0
- wbportfolio/contrib/company_portfolio/viewsets.py +195 -0
- wbportfolio/defaults/__init__.py +0 -0
- wbportfolio/defaults/fees/__init__.py +0 -0
- wbportfolio/defaults/fees/default.py +92 -0
- wbportfolio/defaults/portfolio/__init__.py +0 -0
- wbportfolio/defaults/portfolio/default_rebalancing.py +45 -0
- wbportfolio/dynamic_preferences_registry.py +58 -0
- wbportfolio/factories/__init__.py +35 -0
- wbportfolio/factories/adjustments.py +17 -0
- wbportfolio/factories/assets.py +75 -0
- wbportfolio/factories/claim.py +39 -0
- wbportfolio/factories/custodians.py +11 -0
- wbportfolio/factories/dividends.py +14 -0
- wbportfolio/factories/fees.py +15 -0
- wbportfolio/factories/indexes.py +17 -0
- wbportfolio/factories/portfolio_cash_flow.py +20 -0
- wbportfolio/factories/portfolio_cash_targets.py +15 -0
- wbportfolio/factories/portfolio_swing_pricings.py +15 -0
- wbportfolio/factories/portfolios.py +59 -0
- wbportfolio/factories/product_groups.py +28 -0
- wbportfolio/factories/products.py +56 -0
- wbportfolio/factories/pytest_utils.py +121 -0
- wbportfolio/factories/reconciliations.py +23 -0
- wbportfolio/factories/roles.py +20 -0
- wbportfolio/factories/synchronization.py +40 -0
- wbportfolio/factories/trades.py +35 -0
- wbportfolio/factories/transactions.py +21 -0
- wbportfolio/fdm/__init__.py +0 -0
- wbportfolio/fdm/tasks.py +12 -0
- wbportfolio/filters/__init__.py +32 -0
- wbportfolio/filters/assets.py +485 -0
- wbportfolio/filters/assets_and_net_new_money_progression.py +42 -0
- wbportfolio/filters/custodians.py +10 -0
- wbportfolio/filters/esg.py +22 -0
- wbportfolio/filters/performances.py +171 -0
- wbportfolio/filters/portfolios.py +24 -0
- wbportfolio/filters/positions.py +178 -0
- wbportfolio/filters/products.py +157 -0
- wbportfolio/filters/roles.py +26 -0
- wbportfolio/filters/signals.py +92 -0
- wbportfolio/filters/transactions/__init__.py +20 -0
- wbportfolio/filters/transactions/claim.py +394 -0
- wbportfolio/filters/transactions/fees.py +66 -0
- wbportfolio/filters/transactions/trades.py +224 -0
- wbportfolio/filters/transactions/transactions.py +98 -0
- wbportfolio/import_export/__init__.py +0 -0
- wbportfolio/import_export/backends/__init__.py +2 -0
- wbportfolio/import_export/backends/ubs/__init__.py +3 -0
- wbportfolio/import_export/backends/ubs/asset_position.py +45 -0
- wbportfolio/import_export/backends/ubs/fees.py +63 -0
- wbportfolio/import_export/backends/ubs/instrument_price.py +44 -0
- wbportfolio/import_export/backends/ubs/mixin.py +15 -0
- wbportfolio/import_export/backends/utils.py +58 -0
- wbportfolio/import_export/backends/wbfdm/__init__.py +2 -0
- wbportfolio/import_export/backends/wbfdm/adjustment.py +50 -0
- wbportfolio/import_export/backends/wbfdm/dividend.py +16 -0
- wbportfolio/import_export/backends/wbfdm/mixin.py +15 -0
- wbportfolio/import_export/handlers/__init__.py +0 -0
- wbportfolio/import_export/handlers/adjustment.py +39 -0
- wbportfolio/import_export/handlers/asset_position.py +167 -0
- wbportfolio/import_export/handlers/dividend.py +80 -0
- wbportfolio/import_export/handlers/fees.py +58 -0
- wbportfolio/import_export/handlers/portfolio_cash_flow.py +57 -0
- wbportfolio/import_export/handlers/register.py +43 -0
- wbportfolio/import_export/handlers/trade.py +191 -0
- wbportfolio/import_export/parsers/__init__.py +0 -0
- wbportfolio/import_export/parsers/default_mapping.py +30 -0
- wbportfolio/import_export/parsers/jpmorgan/__init__.py +0 -0
- wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +63 -0
- wbportfolio/import_export/parsers/jpmorgan/fees.py +64 -0
- wbportfolio/import_export/parsers/jpmorgan/strategy.py +116 -0
- wbportfolio/import_export/parsers/jpmorgan/valuation.py +41 -0
- wbportfolio/import_export/parsers/leonteq/__init__.py +0 -0
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +47 -0
- wbportfolio/import_export/parsers/leonteq/equity.py +81 -0
- wbportfolio/import_export/parsers/leonteq/fees.py +70 -0
- wbportfolio/import_export/parsers/leonteq/trade.py +94 -0
- wbportfolio/import_export/parsers/leonteq/valuation.py +39 -0
- wbportfolio/import_export/parsers/natixis/__init__.py +0 -0
- wbportfolio/import_export/parsers/natixis/customer_trade.py +62 -0
- wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +66 -0
- wbportfolio/import_export/parsers/natixis/d1_equity.py +80 -0
- wbportfolio/import_export/parsers/natixis/d1_fees.py +58 -0
- wbportfolio/import_export/parsers/natixis/d1_trade.py +70 -0
- wbportfolio/import_export/parsers/natixis/d1_valuation.py +41 -0
- wbportfolio/import_export/parsers/natixis/dividend.py +53 -0
- wbportfolio/import_export/parsers/natixis/equity.py +60 -0
- wbportfolio/import_export/parsers/natixis/fees.py +53 -0
- wbportfolio/import_export/parsers/natixis/trade.py +63 -0
- wbportfolio/import_export/parsers/natixis/utils.py +76 -0
- wbportfolio/import_export/parsers/natixis/valuation.py +46 -0
- wbportfolio/import_export/parsers/refinitiv/__init__.py +0 -0
- wbportfolio/import_export/parsers/refinitiv/adjustment.py +24 -0
- wbportfolio/import_export/parsers/sg_lux/__init__.py +0 -0
- wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +70 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade.py +75 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +140 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +80 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +57 -0
- wbportfolio/import_export/parsers/sg_lux/equity.py +137 -0
- wbportfolio/import_export/parsers/sg_lux/fees.py +56 -0
- wbportfolio/import_export/parsers/sg_lux/perf_fees.py +51 -0
- wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +29 -0
- wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +36 -0
- wbportfolio/import_export/parsers/sg_lux/registers.py +210 -0
- wbportfolio/import_export/parsers/sg_lux/sylk.py +248 -0
- wbportfolio/import_export/parsers/sg_lux/utils.py +36 -0
- wbportfolio/import_export/parsers/sg_lux/valuation.py +53 -0
- wbportfolio/import_export/parsers/societe_generale/__init__.py +0 -0
- wbportfolio/import_export/parsers/societe_generale/customer_trade.py +54 -0
- wbportfolio/import_export/parsers/societe_generale/strategy.py +94 -0
- wbportfolio/import_export/parsers/societe_generale/valuation.py +37 -0
- wbportfolio/import_export/parsers/tellco/__init__.py +0 -0
- wbportfolio/import_export/parsers/tellco/customer_trade.py +64 -0
- wbportfolio/import_export/parsers/tellco/equity.py +86 -0
- wbportfolio/import_export/parsers/tellco/valuation.py +52 -0
- wbportfolio/import_export/parsers/ubs/__init__.py +0 -0
- wbportfolio/import_export/parsers/ubs/api/__init__.py +0 -0
- wbportfolio/import_export/parsers/ubs/api/asset_position.py +106 -0
- wbportfolio/import_export/parsers/ubs/api/fees.py +31 -0
- wbportfolio/import_export/parsers/ubs/api/instrument_price.py +20 -0
- wbportfolio/import_export/parsers/ubs/api/utils.py +0 -0
- wbportfolio/import_export/parsers/ubs/customer_trade.py +60 -0
- wbportfolio/import_export/parsers/ubs/equity.py +97 -0
- wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +67 -0
- wbportfolio/import_export/parsers/ubs/valuation.py +52 -0
- wbportfolio/import_export/parsers/vontobel/__init__.py +0 -0
- wbportfolio/import_export/parsers/vontobel/asset_position.py +97 -0
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +54 -0
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +40 -0
- wbportfolio/import_export/parsers/vontobel/instrument.py +34 -0
- wbportfolio/import_export/parsers/vontobel/management_fees.py +86 -0
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +35 -0
- wbportfolio/import_export/parsers/vontobel/trade.py +38 -0
- wbportfolio/import_export/parsers/vontobel/utils.py +17 -0
- wbportfolio/import_export/parsers/vontobel/valuation.py +29 -0
- wbportfolio/import_export/resources/__init__.py +0 -0
- wbportfolio/import_export/resources/assets.py +68 -0
- wbportfolio/import_export/resources/trades.py +41 -0
- wbportfolio/import_export/utils.py +42 -0
- wbportfolio/metric/__init__.py +0 -0
- wbportfolio/metric/backends/__init__.py +2 -0
- wbportfolio/metric/backends/base.py +86 -0
- wbportfolio/metric/backends/constants.py +222 -0
- wbportfolio/metric/backends/portfolio_base.py +255 -0
- wbportfolio/metric/backends/portfolio_esg.py +66 -0
- wbportfolio/metric/tests/__init__.py +0 -0
- wbportfolio/metric/tests/conftest.py +4 -0
- wbportfolio/metric/tests/test_portfolio_base.py +135 -0
- wbportfolio/metric/tests/test_portfolio_esg.py +69 -0
- wbportfolio/migrations/0001_initial_squashed.py +13848 -0
- wbportfolio/migrations/0002_product_default_sub_account_squashed_0039_alter_assetallocation_company_and_more.py +3836 -0
- wbportfolio/migrations/0040_instrument_financial_instrument.py +26 -0
- wbportfolio/migrations/0041_remove_listresearch_research_ptr_and_more.py +129 -0
- wbportfolio/migrations/0042_instrumentlist_instrumentlistthroughmodel_and_more.py +71 -0
- wbportfolio/migrations/0043_alter_instrumentlistthroughmodel_options_and_more.py +238 -0
- wbportfolio/migrations/0044_alter_instrumentlist_identifier.py +35 -0
- wbportfolio/migrations/0045_alter_instrument_financial_instrument.py +26 -0
- wbportfolio/migrations/0046_add_product_default_account.py +166 -0
- wbportfolio/migrations/0047_remove_product_default_sub_account.py +14 -0
- wbportfolio/migrations/0048_alter_trade_status.py +29 -0
- wbportfolio/migrations/0049_trade_claimed_shares.py +25 -0
- wbportfolio/migrations/0050_fees_fee_date_fees_wbportfolio_transac_1f7a29_idx.py +44 -0
- wbportfolio/migrations/0051_delete_macroreview.py +11 -0
- wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +888 -0
- wbportfolio/migrations/0053_remove_product_group.py +132 -0
- wbportfolio/migrations/0054_portfolioinstrumentpreferredclassificationthroughmodel_and_more.py +270 -0
- wbportfolio/migrations/0055_remove_product__custom_management_rebates_and_more.py +139 -0
- wbportfolio/migrations/0056_remove_companyportfoliodata_assets_under_management_currency_and_more.py +56 -0
- wbportfolio/migrations/0057_alter_portfolio_preferred_instrument_classifications_and_more.py +36 -0
- wbportfolio/migrations/0058_pmsinstrument.py +23 -0
- wbportfolio/migrations/0059_fees_unique_fees.py +51 -0
- wbportfolio/migrations/0060_alter_portfolioportfoliothroughmodel_type.py +21 -0
- wbportfolio/migrations/0061_portfolio_bank_accounts_product_bank_account_and_more.py +175 -0
- wbportfolio/migrations/0062_alter_dailyportfoliocashflow_options.py +20 -0
- wbportfolio/migrations/0063_accountreconciliation_accountreconciliationline_and_more.py +133 -0
- wbportfolio/migrations/0064_alter_portfolio_managers_portfolio_is_tracked_and_more.py +40 -0
- wbportfolio/migrations/0065_alter_portfolio_managers_claim_as_shares_and_more.py +73 -0
- wbportfolio/migrations/0066_assetposition_initial_shares_at_custodian_and_more.py +108 -0
- wbportfolio/migrations/0067_assetposition_unique_asset_position.py +77 -0
- wbportfolio/migrations/0068_trade_internal_trade_trade_marked_as_internal_and_more.py +59 -0
- wbportfolio/migrations/0069_remove_portfolio_is_invested_and_more.py +56 -0
- wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +82 -0
- wbportfolio/migrations/0071_alter_trade_options_alter_trade_order.py +22 -0
- wbportfolio/migrations/__init__.py +0 -0
- wbportfolio/models/__init__.py +26 -0
- wbportfolio/models/adjustments.py +246 -0
- wbportfolio/models/asset.py +869 -0
- wbportfolio/models/custodians.py +101 -0
- wbportfolio/models/indexes.py +33 -0
- wbportfolio/models/mixins/__init__.py +0 -0
- wbportfolio/models/mixins/instruments.py +127 -0
- wbportfolio/models/mixins/liquidity_stress_test.py +1307 -0
- wbportfolio/models/portfolio.py +1039 -0
- wbportfolio/models/portfolio_cash_flow.py +167 -0
- wbportfolio/models/portfolio_cash_targets.py +46 -0
- wbportfolio/models/portfolio_relationship.py +135 -0
- wbportfolio/models/portfolio_swing_pricings.py +51 -0
- wbportfolio/models/product_groups.py +230 -0
- wbportfolio/models/products.py +569 -0
- wbportfolio/models/reconciliations/__init__.py +2 -0
- wbportfolio/models/reconciliations/account_reconciliation_lines.py +192 -0
- wbportfolio/models/reconciliations/account_reconciliations.py +102 -0
- wbportfolio/models/reconciliations/reconciliations.py +25 -0
- wbportfolio/models/registers.py +132 -0
- wbportfolio/models/roles.py +208 -0
- wbportfolio/models/synchronization/__init__.py +3 -0
- wbportfolio/models/synchronization/portfolio_synchronization.py +292 -0
- wbportfolio/models/synchronization/price_computation.py +200 -0
- wbportfolio/models/synchronization/synchronization.py +188 -0
- wbportfolio/models/transactions/__init__.py +7 -0
- wbportfolio/models/transactions/claim.py +634 -0
- wbportfolio/models/transactions/dividends.py +31 -0
- wbportfolio/models/transactions/expiry.py +7 -0
- wbportfolio/models/transactions/fees.py +153 -0
- wbportfolio/models/transactions/trade_proposals.py +502 -0
- wbportfolio/models/transactions/trades.py +704 -0
- wbportfolio/models/transactions/transactions.py +211 -0
- wbportfolio/models/utils.py +12 -0
- wbportfolio/permissions.py +13 -0
- wbportfolio/pms/__init__.py +0 -0
- wbportfolio/pms/statistics/__init__.py +0 -0
- wbportfolio/pms/trading/__init__.py +1 -0
- wbportfolio/pms/trading/handler.py +164 -0
- wbportfolio/pms/typing.py +194 -0
- wbportfolio/preferences.py +6 -0
- wbportfolio/reports/__init__.py +0 -0
- wbportfolio/reports/monthly_position_report.py +74 -0
- wbportfolio/risk_management/__init__.py +0 -0
- wbportfolio/risk_management/backends/__init__.py +11 -0
- wbportfolio/risk_management/backends/accounts.py +166 -0
- wbportfolio/risk_management/backends/controversy_portfolio.py +63 -0
- wbportfolio/risk_management/backends/exposure_portfolio.py +203 -0
- wbportfolio/risk_management/backends/instrument_list_portfolio.py +89 -0
- wbportfolio/risk_management/backends/liquidity_risk.py +86 -0
- wbportfolio/risk_management/backends/liquidity_stress_instrument.py +86 -0
- wbportfolio/risk_management/backends/mixins.py +220 -0
- wbportfolio/risk_management/backends/product_integrity.py +111 -0
- wbportfolio/risk_management/backends/stop_loss_instrument.py +24 -0
- wbportfolio/risk_management/backends/stop_loss_portfolio.py +36 -0
- wbportfolio/risk_management/backends/ucits_portfolio.py +63 -0
- wbportfolio/risk_management/tests/__init__.py +0 -0
- wbportfolio/risk_management/tests/conftest.py +15 -0
- wbportfolio/risk_management/tests/test_accounts.py +98 -0
- wbportfolio/risk_management/tests/test_controversy_portfolio.py +33 -0
- wbportfolio/risk_management/tests/test_exposure_portfolio.py +94 -0
- wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +60 -0
- wbportfolio/risk_management/tests/test_liquidity_risk.py +47 -0
- wbportfolio/risk_management/tests/test_product_integrity.py +55 -0
- wbportfolio/risk_management/tests/test_stop_loss_instrument.py +110 -0
- wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +119 -0
- wbportfolio/risk_management/tests/test_ucits_portfolio.py +39 -0
- wbportfolio/serializers/__init__.py +42 -0
- wbportfolio/serializers/adjustments.py +24 -0
- wbportfolio/serializers/assets.py +166 -0
- wbportfolio/serializers/custodians.py +26 -0
- wbportfolio/serializers/portfolio_cash_flow.py +48 -0
- wbportfolio/serializers/portfolio_cash_targets.py +20 -0
- wbportfolio/serializers/portfolio_relationship.py +53 -0
- wbportfolio/serializers/portfolio_swing_pricing.py +20 -0
- wbportfolio/serializers/portfolios.py +143 -0
- wbportfolio/serializers/positions.py +76 -0
- wbportfolio/serializers/product_group.py +88 -0
- wbportfolio/serializers/products.py +331 -0
- wbportfolio/serializers/reconciliations.py +171 -0
- wbportfolio/serializers/registers.py +72 -0
- wbportfolio/serializers/roles.py +60 -0
- wbportfolio/serializers/signals.py +157 -0
- wbportfolio/serializers/synchronization.py +18 -0
- wbportfolio/serializers/transactions/__init__.py +24 -0
- wbportfolio/serializers/transactions/claim.py +310 -0
- wbportfolio/serializers/transactions/dividends.py +18 -0
- wbportfolio/serializers/transactions/expiry.py +18 -0
- wbportfolio/serializers/transactions/fees.py +32 -0
- wbportfolio/serializers/transactions/trades.py +315 -0
- wbportfolio/serializers/transactions/transactions.py +84 -0
- wbportfolio/tasks.py +125 -0
- wbportfolio/tests/__init__.py +0 -0
- wbportfolio/tests/conftest.py +164 -0
- wbportfolio/tests/models/__init__.py +0 -0
- wbportfolio/tests/models/test_account_reconciliation.py +191 -0
- wbportfolio/tests/models/test_assets.py +193 -0
- wbportfolio/tests/models/test_custodians.py +12 -0
- wbportfolio/tests/models/test_customer_trades.py +113 -0
- wbportfolio/tests/models/test_dividends.py +7 -0
- wbportfolio/tests/models/test_imports.py +192 -0
- wbportfolio/tests/models/test_instrument_mixins.py +48 -0
- wbportfolio/tests/models/test_merge.py +133 -0
- wbportfolio/tests/models/test_portfolio_cash_flow.py +112 -0
- wbportfolio/tests/models/test_portfolio_cash_targets.py +27 -0
- wbportfolio/tests/models/test_portfolio_swing_pricings.py +42 -0
- wbportfolio/tests/models/test_portfolios.py +676 -0
- wbportfolio/tests/models/test_product_groups.py +80 -0
- wbportfolio/tests/models/test_products.py +187 -0
- wbportfolio/tests/models/test_roles.py +82 -0
- wbportfolio/tests/models/test_splits.py +233 -0
- wbportfolio/tests/models/test_synchronization.py +617 -0
- wbportfolio/tests/models/transactions/__init__.py +0 -0
- wbportfolio/tests/models/transactions/test_claim.py +129 -0
- wbportfolio/tests/models/transactions/test_fees.py +65 -0
- wbportfolio/tests/models/transactions/test_trades.py +204 -0
- wbportfolio/tests/models/utils.py +13 -0
- wbportfolio/tests/serializers/__init__.py +0 -0
- wbportfolio/tests/serializers/test_claims.py +21 -0
- wbportfolio/tests/signals.py +151 -0
- wbportfolio/tests/tests.py +31 -0
- wbportfolio/tests/viewsets/__init__.py +0 -0
- wbportfolio/tests/viewsets/test_assets.py +67 -0
- wbportfolio/tests/viewsets/test_performances.py +72 -0
- wbportfolio/tests/viewsets/test_products.py +92 -0
- wbportfolio/tests/viewsets/transactions/__init__.py +0 -0
- wbportfolio/tests/viewsets/transactions/test_claims.py +146 -0
- wbportfolio/urls.py +247 -0
- wbportfolio/utils.py +30 -0
- wbportfolio/viewsets/__init__.py +57 -0
- wbportfolio/viewsets/adjustments.py +46 -0
- wbportfolio/viewsets/assets.py +562 -0
- wbportfolio/viewsets/assets_and_net_new_money_progression.py +117 -0
- wbportfolio/viewsets/charts/__init__.py +1 -0
- wbportfolio/viewsets/charts/assets.py +247 -0
- wbportfolio/viewsets/configs/__init__.py +6 -0
- wbportfolio/viewsets/configs/buttons/__init__.py +23 -0
- wbportfolio/viewsets/configs/buttons/adjustments.py +13 -0
- wbportfolio/viewsets/configs/buttons/assets.py +145 -0
- wbportfolio/viewsets/configs/buttons/claims.py +83 -0
- wbportfolio/viewsets/configs/buttons/custodians.py +76 -0
- wbportfolio/viewsets/configs/buttons/fees.py +14 -0
- wbportfolio/viewsets/configs/buttons/mixins.py +88 -0
- wbportfolio/viewsets/configs/buttons/portfolios.py +115 -0
- wbportfolio/viewsets/configs/buttons/products.py +41 -0
- wbportfolio/viewsets/configs/buttons/reconciliations.py +65 -0
- wbportfolio/viewsets/configs/buttons/registers.py +11 -0
- wbportfolio/viewsets/configs/buttons/signals.py +68 -0
- wbportfolio/viewsets/configs/buttons/trade_proposals.py +25 -0
- wbportfolio/viewsets/configs/buttons/trades.py +144 -0
- wbportfolio/viewsets/configs/display/__init__.py +61 -0
- wbportfolio/viewsets/configs/display/adjustments.py +81 -0
- wbportfolio/viewsets/configs/display/assets.py +265 -0
- wbportfolio/viewsets/configs/display/claim.py +299 -0
- wbportfolio/viewsets/configs/display/custodians.py +24 -0
- wbportfolio/viewsets/configs/display/esg.py +88 -0
- wbportfolio/viewsets/configs/display/fees.py +133 -0
- wbportfolio/viewsets/configs/display/portfolio_cash_flow.py +103 -0
- wbportfolio/viewsets/configs/display/portfolio_relationship.py +38 -0
- wbportfolio/viewsets/configs/display/portfolios.py +125 -0
- wbportfolio/viewsets/configs/display/positions.py +75 -0
- wbportfolio/viewsets/configs/display/product_groups.py +54 -0
- wbportfolio/viewsets/configs/display/product_performance.py +241 -0
- wbportfolio/viewsets/configs/display/products.py +249 -0
- wbportfolio/viewsets/configs/display/reconciliations.py +151 -0
- wbportfolio/viewsets/configs/display/registers.py +71 -0
- wbportfolio/viewsets/configs/display/roles.py +49 -0
- wbportfolio/viewsets/configs/display/trade_proposals.py +97 -0
- wbportfolio/viewsets/configs/display/trades.py +359 -0
- wbportfolio/viewsets/configs/display/transactions.py +55 -0
- wbportfolio/viewsets/configs/endpoints/__init__.py +75 -0
- wbportfolio/viewsets/configs/endpoints/adjustments.py +17 -0
- wbportfolio/viewsets/configs/endpoints/assets.py +115 -0
- wbportfolio/viewsets/configs/endpoints/claim.py +106 -0
- wbportfolio/viewsets/configs/endpoints/custodians.py +6 -0
- wbportfolio/viewsets/configs/endpoints/esg.py +14 -0
- wbportfolio/viewsets/configs/endpoints/fees.py +26 -0
- wbportfolio/viewsets/configs/endpoints/portfolio_relationship.py +23 -0
- wbportfolio/viewsets/configs/endpoints/portfolios.py +43 -0
- wbportfolio/viewsets/configs/endpoints/positions.py +18 -0
- wbportfolio/viewsets/configs/endpoints/product_groups.py +11 -0
- wbportfolio/viewsets/configs/endpoints/product_performance.py +29 -0
- wbportfolio/viewsets/configs/endpoints/products.py +37 -0
- wbportfolio/viewsets/configs/endpoints/reconciliations.py +31 -0
- wbportfolio/viewsets/configs/endpoints/roles.py +9 -0
- wbportfolio/viewsets/configs/endpoints/trade_proposals.py +17 -0
- wbportfolio/viewsets/configs/endpoints/trades.py +82 -0
- wbportfolio/viewsets/configs/endpoints/transactions.py +17 -0
- wbportfolio/viewsets/configs/menu/__init__.py +30 -0
- wbportfolio/viewsets/configs/menu/adjustments.py +8 -0
- wbportfolio/viewsets/configs/menu/assets.py +8 -0
- wbportfolio/viewsets/configs/menu/claim.py +41 -0
- wbportfolio/viewsets/configs/menu/custodians.py +11 -0
- wbportfolio/viewsets/configs/menu/fees.py +13 -0
- wbportfolio/viewsets/configs/menu/instrument_prices.py +10 -0
- wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +8 -0
- wbportfolio/viewsets/configs/menu/portfolios.py +15 -0
- wbportfolio/viewsets/configs/menu/positions.py +14 -0
- wbportfolio/viewsets/configs/menu/product_groups.py +10 -0
- wbportfolio/viewsets/configs/menu/product_performance.py +25 -0
- wbportfolio/viewsets/configs/menu/products.py +15 -0
- wbportfolio/viewsets/configs/menu/reconciliations.py +7 -0
- wbportfolio/viewsets/configs/menu/registers.py +10 -0
- wbportfolio/viewsets/configs/menu/roles.py +16 -0
- wbportfolio/viewsets/configs/menu/trades.py +18 -0
- wbportfolio/viewsets/configs/menu/transactions.py +8 -0
- wbportfolio/viewsets/configs/previews/__init__.py +1 -0
- wbportfolio/viewsets/configs/previews/portfolios.py +21 -0
- wbportfolio/viewsets/configs/titles/__init__.py +65 -0
- wbportfolio/viewsets/configs/titles/adjustments.py +19 -0
- wbportfolio/viewsets/configs/titles/assets.py +57 -0
- wbportfolio/viewsets/configs/titles/assets_and_net_new_money_progression.py +6 -0
- wbportfolio/viewsets/configs/titles/claim.py +81 -0
- wbportfolio/viewsets/configs/titles/custodians.py +12 -0
- wbportfolio/viewsets/configs/titles/esg.py +10 -0
- wbportfolio/viewsets/configs/titles/fees.py +25 -0
- wbportfolio/viewsets/configs/titles/instrument_prices.py +20 -0
- wbportfolio/viewsets/configs/titles/portfolios.py +32 -0
- wbportfolio/viewsets/configs/titles/positions.py +11 -0
- wbportfolio/viewsets/configs/titles/product_groups.py +12 -0
- wbportfolio/viewsets/configs/titles/product_performance.py +16 -0
- wbportfolio/viewsets/configs/titles/products.py +6 -0
- wbportfolio/viewsets/configs/titles/registers.py +12 -0
- wbportfolio/viewsets/configs/titles/roles.py +23 -0
- wbportfolio/viewsets/configs/titles/trades.py +51 -0
- wbportfolio/viewsets/configs/titles/transactions.py +8 -0
- wbportfolio/viewsets/custodians.py +66 -0
- wbportfolio/viewsets/esg.py +165 -0
- wbportfolio/viewsets/mixins.py +48 -0
- wbportfolio/viewsets/portfolio_cash_flow.py +31 -0
- wbportfolio/viewsets/portfolio_cash_targets.py +8 -0
- wbportfolio/viewsets/portfolio_relationship.py +46 -0
- wbportfolio/viewsets/portfolio_swing_pricing.py +8 -0
- wbportfolio/viewsets/portfolios.py +154 -0
- wbportfolio/viewsets/positions.py +292 -0
- wbportfolio/viewsets/product_groups.py +84 -0
- wbportfolio/viewsets/product_performance.py +646 -0
- wbportfolio/viewsets/products.py +529 -0
- wbportfolio/viewsets/reconciliations.py +160 -0
- wbportfolio/viewsets/registers.py +75 -0
- wbportfolio/viewsets/roles.py +44 -0
- wbportfolio/viewsets/signals.py +42 -0
- wbportfolio/viewsets/synchronization.py +25 -0
- wbportfolio/viewsets/transactions/__init__.py +40 -0
- wbportfolio/viewsets/transactions/claim.py +933 -0
- wbportfolio/viewsets/transactions/fees.py +190 -0
- wbportfolio/viewsets/transactions/mixins.py +19 -0
- wbportfolio/viewsets/transactions/trade_proposals.py +93 -0
- wbportfolio/viewsets/transactions/trades.py +395 -0
- wbportfolio/viewsets/transactions/transactions.py +123 -0
- wbportfolio-2.2.1.dist-info/METADATA +21 -0
- wbportfolio-2.2.1.dist-info/RECORD +486 -0
- wbportfolio-2.2.1.dist-info/WHEEL +5 -0
- wbportfolio-2.2.1.dist-info/licenses/LICENSE +4 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from wbfdm.enums import ESGControveryFlag
|
|
4
|
+
from wbportfolio.risk_management.backends.controversy_portfolio import (
|
|
5
|
+
RuleBackend as ControversyPortfolio,
|
|
6
|
+
)
|
|
7
|
+
from wbportfolio.tests.models.utils import PortfolioTestMixin
|
|
8
|
+
|
|
9
|
+
fake = Faker()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.django_db
|
|
13
|
+
class TestControversyPortfolioRUle(PortfolioTestMixin):
|
|
14
|
+
@pytest.mark.parametrize("flag", ESGControveryFlag.values)
|
|
15
|
+
def test_check_rule_exclude(
|
|
16
|
+
self, weekday, portfolio, asset_position_factory, controversy_factory, rule_threshold, flag
|
|
17
|
+
):
|
|
18
|
+
rule_backend = ControversyPortfolio(
|
|
19
|
+
weekday, portfolio, thresholds=[rule_threshold], json_parameters={"flags": [flag]}
|
|
20
|
+
)
|
|
21
|
+
a1 = asset_position_factory.create(portfolio=portfolio, date=weekday)
|
|
22
|
+
asset_position_factory.create(portfolio=portfolio, date=weekday) # noise position
|
|
23
|
+
|
|
24
|
+
# No controversy yet, so this shouldn't trigger the rule
|
|
25
|
+
res = list(rule_backend.check_rule())
|
|
26
|
+
assert len(res) == 0
|
|
27
|
+
|
|
28
|
+
controversy = controversy_factory.create(flag=flag, instrument=a1.underlying_instrument) # noqa
|
|
29
|
+
|
|
30
|
+
res = list(rule_backend.check_rule())[0]
|
|
31
|
+
assert res.breached_object == a1.underlying_instrument
|
|
32
|
+
assert res.breached_object_repr == str(a1.underlying_instrument)
|
|
33
|
+
assert res.severity == rule_threshold.severity
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from psycopg.types.range import NumericRange
|
|
6
|
+
from wbcompliance.factories.risk_management import RuleThresholdFactory
|
|
7
|
+
from wbportfolio.risk_management.backends.exposure_portfolio import (
|
|
8
|
+
RuleBackend as ExposurePortfolioRuleBackend,
|
|
9
|
+
)
|
|
10
|
+
from wbportfolio.tests.models.utils import PortfolioTestMixin
|
|
11
|
+
|
|
12
|
+
fake = Faker()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.django_db
|
|
16
|
+
class TestExposurePortfolioRuleModel(PortfolioTestMixin):
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"group_by",
|
|
19
|
+
[groupby for groupby in ExposurePortfolioRuleBackend.GroupbyChoices.values],
|
|
20
|
+
)
|
|
21
|
+
def test_check_rule_groupby_weighting(
|
|
22
|
+
self,
|
|
23
|
+
weekday,
|
|
24
|
+
group_by,
|
|
25
|
+
instrument_factory,
|
|
26
|
+
cash_factory,
|
|
27
|
+
asset_position_factory,
|
|
28
|
+
instrument_price_factory,
|
|
29
|
+
country_factory,
|
|
30
|
+
currency_factory,
|
|
31
|
+
instrument_type_factory,
|
|
32
|
+
portfolio,
|
|
33
|
+
classification,
|
|
34
|
+
):
|
|
35
|
+
only_type = instrument_type_factory.create()
|
|
36
|
+
only_country = country_factory.create()
|
|
37
|
+
only_currency = currency_factory.create()
|
|
38
|
+
parameters = {
|
|
39
|
+
"group_by": group_by,
|
|
40
|
+
"field": ExposurePortfolioRuleBackend.Field.WEIGHTING.value,
|
|
41
|
+
"asset_classes": [only_type.id],
|
|
42
|
+
"countries": [only_country.id],
|
|
43
|
+
"currencies": [only_currency.id],
|
|
44
|
+
"classifications": [classification.id],
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lower = random.random()
|
|
48
|
+
upper = random.uniform(lower, 1)
|
|
49
|
+
exposure_portfolio_backend = ExposurePortfolioRuleBackend(
|
|
50
|
+
weekday,
|
|
51
|
+
portfolio,
|
|
52
|
+
parameters,
|
|
53
|
+
[RuleThresholdFactory.create(range=NumericRange(lower=lower, upper=upper))], # type: ignore
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
i1 = instrument_factory.create(
|
|
57
|
+
country=only_country, currency=only_currency, instrument_type=only_type, classifications=[classification]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
threshold = exposure_portfolio_backend.thresholds[0]
|
|
61
|
+
instrument_price_factory.create(instrument=i1, date=weekday)
|
|
62
|
+
asset_position_factory.create(
|
|
63
|
+
date=weekday,
|
|
64
|
+
weighting=random.uniform(threshold.range.lower, threshold.range.upper),
|
|
65
|
+
underlying_instrument=i1,
|
|
66
|
+
portfolio=portfolio,
|
|
67
|
+
) # Breached position
|
|
68
|
+
|
|
69
|
+
asset_position_factory.create(
|
|
70
|
+
date=weekday,
|
|
71
|
+
weighting=random.uniform(threshold.range.lower, threshold.range.upper),
|
|
72
|
+
portfolio=portfolio,
|
|
73
|
+
) # Breached position but filtered out
|
|
74
|
+
|
|
75
|
+
i2 = cash_factory.create()
|
|
76
|
+
instrument_price_factory.create(instrument=i2, date=weekday)
|
|
77
|
+
asset_position_factory.create(
|
|
78
|
+
date=weekday,
|
|
79
|
+
weighting=random.uniform(0, threshold.range.lower),
|
|
80
|
+
underlying_instrument=i2,
|
|
81
|
+
portfolio=portfolio,
|
|
82
|
+
) # None breached position
|
|
83
|
+
|
|
84
|
+
incidents = list(exposure_portfolio_backend.check_rule())
|
|
85
|
+
assert len(incidents) == 1
|
|
86
|
+
if group_by == ExposurePortfolioRuleBackend.GroupbyChoices.CURRENCY:
|
|
87
|
+
assert incidents[0].breached_object == only_currency
|
|
88
|
+
elif group_by == ExposurePortfolioRuleBackend.GroupbyChoices.COUNTRY:
|
|
89
|
+
assert incidents[0].breached_object == only_country
|
|
90
|
+
elif group_by in [
|
|
91
|
+
ExposurePortfolioRuleBackend.GroupbyChoices.PRIMARY_CLASSIFICATION,
|
|
92
|
+
ExposurePortfolioRuleBackend.GroupbyChoices.FAVORITE_CLASSIFICATION,
|
|
93
|
+
]:
|
|
94
|
+
assert incidents[0].breached_object == classification
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from wbfdm.models.instruments.instrument_lists import InstrumentListThroughModel
|
|
4
|
+
from wbportfolio.risk_management.backends.instrument_list_portfolio import (
|
|
5
|
+
RuleBackend as InstrumentListPortfolio,
|
|
6
|
+
)
|
|
7
|
+
from wbportfolio.tests.models.utils import PortfolioTestMixin
|
|
8
|
+
|
|
9
|
+
fake = Faker()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.django_db
|
|
13
|
+
class TestInstrumentListPortfolio(PortfolioTestMixin):
|
|
14
|
+
def test_check_rule_exclude(self, weekday, portfolio, asset_position_factory, instrument_list, rule_threshold):
|
|
15
|
+
rule_backend = InstrumentListPortfolio(
|
|
16
|
+
weekday, portfolio, json_parameters={"instrument_lists": [instrument_list.id]}, thresholds=[rule_threshold]
|
|
17
|
+
)
|
|
18
|
+
a1 = asset_position_factory.create(portfolio=portfolio, date=weekday)
|
|
19
|
+
asset_position_factory.create(portfolio=portfolio, date=weekday) # noise position
|
|
20
|
+
|
|
21
|
+
rel = InstrumentListThroughModel.objects.create(
|
|
22
|
+
instrument=a1.underlying_instrument, instrument_list=instrument_list, validated=False
|
|
23
|
+
)
|
|
24
|
+
# we check that even though the insturment is in the list, it's still not validated, so this shouldn't trigger the rule
|
|
25
|
+
res = list(rule_backend.check_rule())
|
|
26
|
+
assert len(res) == 0
|
|
27
|
+
|
|
28
|
+
rel.validated = True
|
|
29
|
+
rel.save()
|
|
30
|
+
res = list(rule_backend.check_rule())[0]
|
|
31
|
+
assert res.breached_object == a1.underlying_instrument
|
|
32
|
+
assert res.breached_object_repr == str(a1.underlying_instrument)
|
|
33
|
+
assert res.severity == rule_threshold.severity
|
|
34
|
+
|
|
35
|
+
def test_check_rule_include(self, weekday, portfolio, asset_position_factory, instrument_list, rule_threshold):
|
|
36
|
+
rule_backend = InstrumentListPortfolio(
|
|
37
|
+
weekday,
|
|
38
|
+
portfolio,
|
|
39
|
+
json_parameters={"exclude": False, "instrument_lists": [instrument_list.id]},
|
|
40
|
+
thresholds=[rule_threshold],
|
|
41
|
+
)
|
|
42
|
+
a1 = asset_position_factory.create(portfolio=portfolio, date=weekday)
|
|
43
|
+
a2 = asset_position_factory.create(portfolio=portfolio, date=weekday)
|
|
44
|
+
|
|
45
|
+
InstrumentListThroughModel.objects.create(
|
|
46
|
+
instrument=a1.underlying_instrument, instrument_list=instrument_list, validated=True
|
|
47
|
+
)
|
|
48
|
+
InstrumentListThroughModel.objects.create(
|
|
49
|
+
instrument=a2.underlying_instrument, instrument_list=instrument_list, validated=True
|
|
50
|
+
)
|
|
51
|
+
# ground truth
|
|
52
|
+
res = list(rule_backend.check_rule())
|
|
53
|
+
assert len(res) == 0
|
|
54
|
+
|
|
55
|
+
a3 = asset_position_factory.create(portfolio=portfolio, date=weekday)
|
|
56
|
+
|
|
57
|
+
res = list(rule_backend.check_rule())[0]
|
|
58
|
+
assert res.breached_object == a3.underlying_instrument
|
|
59
|
+
assert res.breached_object_repr == str(a3.underlying_instrument)
|
|
60
|
+
assert res.severity == rule_threshold.severity
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from psycopg.types.range import NumericRange
|
|
4
|
+
from wbcompliance.factories.risk_management import RuleThresholdFactory
|
|
5
|
+
from wbportfolio.risk_management.backends.liquidity_risk import (
|
|
6
|
+
RuleBackend as LiquidityRiskRuleBackend,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
fake = Faker()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.django_db
|
|
13
|
+
class TestProductRuleModel:
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def liquidity_risk_backend(
|
|
16
|
+
self,
|
|
17
|
+
weekday,
|
|
18
|
+
instrument,
|
|
19
|
+
):
|
|
20
|
+
return LiquidityRiskRuleBackend(
|
|
21
|
+
weekday,
|
|
22
|
+
instrument,
|
|
23
|
+
json_parameters={"liquidation_factor": 3, "redemption_pct": 0.80},
|
|
24
|
+
thresholds=[
|
|
25
|
+
RuleThresholdFactory.create(range=NumericRange(lower=5, upper=None)),
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def test_check_rule_product_liquidity(
|
|
30
|
+
self, weekday, instrument, instrument_price_factory, asset_position_factory, liquidity_risk_backend
|
|
31
|
+
):
|
|
32
|
+
res = list(liquidity_risk_backend.check_rule()) # no position, no risk incident
|
|
33
|
+
assert len(res) == 0
|
|
34
|
+
|
|
35
|
+
shares = 100
|
|
36
|
+
|
|
37
|
+
volume_50d = (shares * liquidity_risk_backend.redemption_pct * liquidity_risk_backend.liquidation_factor) / 6
|
|
38
|
+
instrument_price_factory.create(date=weekday, instrument=instrument, volume_50d=volume_50d, calculated=False)
|
|
39
|
+
asset_position_factory.create(
|
|
40
|
+
date=weekday, underlying_instrument=instrument, is_estimated=False, initial_shares=shares
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
res = list(liquidity_risk_backend.check_rule()) # no position, no risk incident
|
|
44
|
+
assert len(res) == 1
|
|
45
|
+
incident = res[0]
|
|
46
|
+
assert incident.report_details["Total Shares"] == shares
|
|
47
|
+
assert incident.report_details["Volume 50D"] == volume_50d
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from pandas.tseries.offsets import BDay
|
|
4
|
+
from psycopg.types.range import NumericRange
|
|
5
|
+
from wbcompliance.factories.risk_management import RuleThresholdFactory
|
|
6
|
+
from wbportfolio.risk_management.backends.product_integrity import (
|
|
7
|
+
RuleBackend as ProductRuleBackend,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
fake = Faker()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.django_db
|
|
14
|
+
class TestProductRuleModel:
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def product_backend(
|
|
17
|
+
self,
|
|
18
|
+
weekday,
|
|
19
|
+
product,
|
|
20
|
+
):
|
|
21
|
+
return ProductRuleBackend(
|
|
22
|
+
weekday,
|
|
23
|
+
product,
|
|
24
|
+
thresholds=[
|
|
25
|
+
RuleThresholdFactory.create(range=NumericRange(lower=1, upper=2), severity__name="LOW"),
|
|
26
|
+
RuleThresholdFactory.create(range=NumericRange(lower=2, upper=None), severity__name="HIGH"),
|
|
27
|
+
], # detect any -20% perf
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def test_check_rule_product_data_integrity(
|
|
31
|
+
self, weekday, product, instrument_price_factory, asset_position_factory, product_backend
|
|
32
|
+
):
|
|
33
|
+
asset_position_factory.create(date=weekday - BDay(2), portfolio=product.portfolio, is_estimated=False)
|
|
34
|
+
instrument_price_factory.create(date=weekday - BDay(1), instrument=product, calculated=False)
|
|
35
|
+
|
|
36
|
+
res = list(product_backend.check_rule())
|
|
37
|
+
low_severity = list(filter(lambda x: x.severity.name == "LOW", res))[0]
|
|
38
|
+
high_severity = list(filter(lambda x: x.severity.name == "HIGH", res))[0]
|
|
39
|
+
|
|
40
|
+
assert high_severity.report_details["Data Type"] == "Asset Position"
|
|
41
|
+
assert low_severity.report_details["Data Type"] == "Valuation"
|
|
42
|
+
assert high_severity.report_details["Last Datapoint"] == f"{(weekday - BDay(2)).date():%d.%m.%Y}"
|
|
43
|
+
assert low_severity.report_details["Last Datapoint"] == f"{(weekday - BDay(1)).date():%d.%m.%Y}"
|
|
44
|
+
|
|
45
|
+
instrument_price_factory.create(date=weekday, instrument=product, calculated=False)
|
|
46
|
+
asset_position_factory.create(date=weekday - BDay(1), portfolio=product.portfolio, is_estimated=False)
|
|
47
|
+
|
|
48
|
+
res = list(product_backend.check_rule())
|
|
49
|
+
assert len(res) == 1
|
|
50
|
+
assert res[0].report_details["Data Type"] == "Asset Position"
|
|
51
|
+
assert res[0].report_details["Last Datapoint"] == f"{(weekday - BDay(1)).date():%d.%m.%Y}"
|
|
52
|
+
|
|
53
|
+
asset_position_factory.create(date=weekday, portfolio=product.portfolio, is_estimated=False)
|
|
54
|
+
res = list(product_backend.check_rule())
|
|
55
|
+
assert len(res) == 0
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from psycopg.types.range import NumericRange
|
|
7
|
+
from wbcompliance.factories.risk_management import RuleThresholdFactory
|
|
8
|
+
from wbfdm.models import RelatedInstrumentThroughModel
|
|
9
|
+
from wbportfolio.risk_management.backends.stop_loss_instrument import (
|
|
10
|
+
RuleBackend as StopLossInstrumentRuleBackend,
|
|
11
|
+
)
|
|
12
|
+
from wbportfolio.tests.models.utils import PortfolioTestMixin
|
|
13
|
+
|
|
14
|
+
fake = Faker()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.django_db
|
|
18
|
+
class TestStopLossInstrumentRuleModel(PortfolioTestMixin):
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def stop_loss_instrument_backend(self, weekday, date_interval_option, freq, product):
|
|
21
|
+
parameters = {"freq": freq, "date_interval_option": date_interval_option}
|
|
22
|
+
lower = random.random()
|
|
23
|
+
upper = random.uniform(lower, 1)
|
|
24
|
+
return StopLossInstrumentRuleBackend(
|
|
25
|
+
weekday,
|
|
26
|
+
product,
|
|
27
|
+
parameters,
|
|
28
|
+
[RuleThresholdFactory.create(range=NumericRange(lower=lower, upper=upper))], # type: ignore
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"date_interval_option, freq",
|
|
33
|
+
[
|
|
34
|
+
("ROLLING_WINDOWS", StopLossInstrumentRuleBackend.FreqChoices.BUSINESS_DAY),
|
|
35
|
+
*[("FREQUENCY", option) for option in StopLossInstrumentRuleBackend.FreqChoices.values],
|
|
36
|
+
],
|
|
37
|
+
)
|
|
38
|
+
def test_check_rule_frequency(
|
|
39
|
+
self,
|
|
40
|
+
weekday,
|
|
41
|
+
date_interval_option,
|
|
42
|
+
freq,
|
|
43
|
+
product,
|
|
44
|
+
instrument_price_factory,
|
|
45
|
+
stop_loss_instrument_backend,
|
|
46
|
+
):
|
|
47
|
+
d1 = stop_loss_instrument_backend._get_start_interval()
|
|
48
|
+
threshold = stop_loss_instrument_backend.thresholds[0]
|
|
49
|
+
|
|
50
|
+
breach_perf = random.uniform(threshold.range.lower, threshold.range.upper)
|
|
51
|
+
|
|
52
|
+
i1 = instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=product)
|
|
53
|
+
instrument_price_factory.create(
|
|
54
|
+
date=weekday, net_value=Decimal(breach_perf + 1) * i1.net_value, calculated=False, instrument=product
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
res = list(stop_loss_instrument_backend.check_rule())
|
|
58
|
+
assert len(res) == 1
|
|
59
|
+
assert res[0].breached_object.id == product.id
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize(
|
|
62
|
+
"date_interval_option, freq",
|
|
63
|
+
[
|
|
64
|
+
("FREQUENCY", StopLossInstrumentRuleBackend.FreqChoices.WEEKLY_FRIDAY),
|
|
65
|
+
("ROLLING_WINDOWS", StopLossInstrumentRuleBackend.FreqChoices.WEEKLY_FRIDAY),
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
def test_check_rule_frequency_with_benchmark(
|
|
69
|
+
self,
|
|
70
|
+
weekday,
|
|
71
|
+
date_interval_option,
|
|
72
|
+
freq,
|
|
73
|
+
product,
|
|
74
|
+
instrument_factory,
|
|
75
|
+
instrument_price_factory,
|
|
76
|
+
stop_loss_instrument_backend,
|
|
77
|
+
):
|
|
78
|
+
benchmark = instrument_factory.create()
|
|
79
|
+
|
|
80
|
+
threshold = stop_loss_instrument_backend.thresholds[0]
|
|
81
|
+
threshold.range = NumericRange(upper=-0.5, lower=None) # type: ignore
|
|
82
|
+
threshold.save()
|
|
83
|
+
|
|
84
|
+
d1 = stop_loss_instrument_backend._get_start_interval()
|
|
85
|
+
|
|
86
|
+
instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=product)
|
|
87
|
+
instrument_price_factory.create(date=weekday, net_value=100, calculated=False, instrument=product)
|
|
88
|
+
|
|
89
|
+
instrument_price_factory.create(date=d1, net_value=500, calculated=False, instrument=benchmark)
|
|
90
|
+
benchmark_price_2 = instrument_price_factory.create(
|
|
91
|
+
date=weekday, net_value=500, calculated=False, instrument=benchmark
|
|
92
|
+
)
|
|
93
|
+
RelatedInstrumentThroughModel.objects.create(instrument=product, related_instrument=benchmark, is_primary=True)
|
|
94
|
+
setattr(stop_loss_instrument_backend, "dynamic_benchmark_type", "PRIMARY_BENCHMARK")
|
|
95
|
+
|
|
96
|
+
res = list(stop_loss_instrument_backend.check_rule())
|
|
97
|
+
assert len(res) == 0
|
|
98
|
+
|
|
99
|
+
# artificially
|
|
100
|
+
benchmark_price_2.net_value = 1000
|
|
101
|
+
benchmark_price_2.save()
|
|
102
|
+
|
|
103
|
+
res = list(stop_loss_instrument_backend.check_rule())
|
|
104
|
+
assert len(res) == 1
|
|
105
|
+
assert res[0].breached_object.id == product.id
|
|
106
|
+
|
|
107
|
+
setattr(stop_loss_instrument_backend, "static_benchmark", benchmark)
|
|
108
|
+
res = list(stop_loss_instrument_backend.check_rule())
|
|
109
|
+
assert len(res) == 1
|
|
110
|
+
assert res[0].breached_object.id == product.id
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from psycopg.types.range import NumericRange
|
|
7
|
+
from wbcompliance.factories.risk_management import RuleThresholdFactory
|
|
8
|
+
from wbportfolio.risk_management.backends.stop_loss_instrument import (
|
|
9
|
+
RuleBackend as StopLossInstrumentRuleBackend,
|
|
10
|
+
)
|
|
11
|
+
from wbportfolio.tests.models.utils import PortfolioTestMixin
|
|
12
|
+
|
|
13
|
+
fake = Faker()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.django_db
|
|
17
|
+
class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def stop_loss_portfolio_backend(self, weekday, date_interval_option, freq, product):
|
|
20
|
+
parameters = {"freq": freq, "date_interval_option": date_interval_option}
|
|
21
|
+
lower = random.random()
|
|
22
|
+
upper = random.uniform(lower, 1)
|
|
23
|
+
return StopLossInstrumentRuleBackend(
|
|
24
|
+
weekday,
|
|
25
|
+
product,
|
|
26
|
+
parameters,
|
|
27
|
+
[RuleThresholdFactory.create(range=NumericRange(lower=lower, upper=upper))], # type: ignore
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@pytest.mark.parametrize(
|
|
31
|
+
"date_interval_option, freq",
|
|
32
|
+
[
|
|
33
|
+
("ROLLING_WINDOWS", StopLossInstrumentRuleBackend.FreqChoices.BUSINESS_DAY),
|
|
34
|
+
*[("FREQUENCY", option) for option in StopLossInstrumentRuleBackend.FreqChoices.values],
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
def test_check_rule_frequency(
|
|
38
|
+
self,
|
|
39
|
+
weekday,
|
|
40
|
+
date_interval_option,
|
|
41
|
+
freq,
|
|
42
|
+
product,
|
|
43
|
+
portfolio,
|
|
44
|
+
instrument_price_factory,
|
|
45
|
+
asset_position_factory,
|
|
46
|
+
stop_loss_portfolio_backend,
|
|
47
|
+
instrument_portfolio_through_model_factory,
|
|
48
|
+
):
|
|
49
|
+
instrument_portfolio_through_model_factory.create(
|
|
50
|
+
instrument=product,
|
|
51
|
+
portfolio=portfolio,
|
|
52
|
+
primary_portfolio=True,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
d1 = stop_loss_portfolio_backend._get_start_interval()
|
|
56
|
+
|
|
57
|
+
threshold = stop_loss_portfolio_backend.thresholds[0]
|
|
58
|
+
breach_perf = random.uniform(threshold.range.lower, threshold.range.upper)
|
|
59
|
+
|
|
60
|
+
i1 = instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=product)
|
|
61
|
+
instrument_price_factory.create(
|
|
62
|
+
date=weekday, net_value=Decimal(breach_perf + 1) * i1.net_value, calculated=False, instrument=product
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
|
|
66
|
+
asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
|
|
67
|
+
|
|
68
|
+
res = list(stop_loss_portfolio_backend.check_rule())
|
|
69
|
+
assert len(res) == 1
|
|
70
|
+
assert res[0].breached_object.id == product.id
|
|
71
|
+
|
|
72
|
+
@pytest.mark.parametrize(
|
|
73
|
+
"date_interval_option, freq",
|
|
74
|
+
[
|
|
75
|
+
("ROLLING_WINDOWS", StopLossInstrumentRuleBackend.FreqChoices.BUSINESS_DAY),
|
|
76
|
+
*[("FREQUENCY", option) for option in StopLossInstrumentRuleBackend.FreqChoices.values],
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
def test_check_rule_frequency_2(
|
|
80
|
+
self,
|
|
81
|
+
weekday,
|
|
82
|
+
date_interval_option,
|
|
83
|
+
freq,
|
|
84
|
+
product,
|
|
85
|
+
portfolio,
|
|
86
|
+
instrument_price_factory,
|
|
87
|
+
asset_position_factory,
|
|
88
|
+
instrument_factory,
|
|
89
|
+
stop_loss_portfolio_backend,
|
|
90
|
+
instrument_portfolio_through_model_factory,
|
|
91
|
+
):
|
|
92
|
+
instrument_portfolio_through_model_factory.create(
|
|
93
|
+
instrument=product,
|
|
94
|
+
portfolio=portfolio,
|
|
95
|
+
primary_portfolio=True,
|
|
96
|
+
)
|
|
97
|
+
d1 = stop_loss_portfolio_backend._get_start_interval()
|
|
98
|
+
benchmark = instrument_factory.create()
|
|
99
|
+
|
|
100
|
+
threshold = stop_loss_portfolio_backend.thresholds[0]
|
|
101
|
+
threshold.range = NumericRange(upper=-0.5, lower=None) # type: ignore
|
|
102
|
+
threshold.save()
|
|
103
|
+
|
|
104
|
+
instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=product)
|
|
105
|
+
instrument_price_factory.create(date=weekday, net_value=100, calculated=False, instrument=product)
|
|
106
|
+
|
|
107
|
+
instrument_price_factory.create(date=d1, net_value=100, calculated=False, instrument=benchmark)
|
|
108
|
+
instrument_price_factory.create(date=weekday, net_value=300, calculated=False, instrument=benchmark)
|
|
109
|
+
|
|
110
|
+
asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
|
|
111
|
+
asset_position_factory.create(date=weekday, underlying_instrument=product, portfolio=portfolio)
|
|
112
|
+
|
|
113
|
+
res = list(stop_loss_portfolio_backend.check_rule())
|
|
114
|
+
assert len(res) == 0
|
|
115
|
+
|
|
116
|
+
setattr(stop_loss_portfolio_backend, "static_benchmark", benchmark)
|
|
117
|
+
res = list(stop_loss_portfolio_backend.check_rule())
|
|
118
|
+
assert len(res) == 1
|
|
119
|
+
assert res[0].breached_object.id == product.id
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from wbportfolio.risk_management.backends.ucits_portfolio import (
|
|
4
|
+
RuleBackend as UCITPortfolioRuleBackend,
|
|
5
|
+
)
|
|
6
|
+
from wbportfolio.tests.models.utils import PortfolioTestMixin
|
|
7
|
+
|
|
8
|
+
fake = Faker()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.django_db
|
|
12
|
+
class TestUcitsRuleModel(PortfolioTestMixin):
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def ucits_backend(self, weekday, portfolio):
|
|
15
|
+
return UCITPortfolioRuleBackend(weekday, portfolio, [])
|
|
16
|
+
|
|
17
|
+
def testcheck_rule_1(self, weekday, portfolio, asset_position_factory, ucits_backend):
|
|
18
|
+
# Check No single asset can represent more than 10% of the fund's assets;
|
|
19
|
+
asset_position_factory.create(date=weekday, weighting=0.05, portfolio=portfolio)
|
|
20
|
+
asset_position_factory.create(date=weekday, weighting=0.05, portfolio=portfolio)
|
|
21
|
+
a3 = asset_position_factory.create(date=weekday, weighting=0.90, portfolio=portfolio)
|
|
22
|
+
|
|
23
|
+
res = list(ucits_backend.check_rule())
|
|
24
|
+
assert len(res) == 1
|
|
25
|
+
assert res[0].breached_object == a3.underlying_instrument
|
|
26
|
+
|
|
27
|
+
def testcheck_rule_2(self, weekday, portfolio, asset_position_factory, ucits_backend):
|
|
28
|
+
# Check that stock below 5% don't trigger rule
|
|
29
|
+
asset_position_factory.create_batch(4, date=weekday, weighting=0.05, portfolio=portfolio)
|
|
30
|
+
asset_position_factory.create_batch(20, date=weekday, weighting=0.04, portfolio=portfolio)
|
|
31
|
+
|
|
32
|
+
res = list(ucits_backend.check_rule())
|
|
33
|
+
assert len(res) == 0
|
|
34
|
+
|
|
35
|
+
def testcheck_rule_3(self, weekday, portfolio, asset_position_factory, ucits_backend):
|
|
36
|
+
# Check holdings of more than 5% cannot in aggregate exceed 40% of the fund's assets
|
|
37
|
+
asset_position_factory.create_batch(20, date=weekday, weighting=0.05, portfolio=portfolio)
|
|
38
|
+
res = list(ucits_backend.check_rule())
|
|
39
|
+
assert len(res) == 20
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from .assets import (
|
|
2
|
+
AssetPositionInstrumentModelSerializer,
|
|
3
|
+
AssetPositionModelSerializer,
|
|
4
|
+
AssetPositionPortfolioModelSerializer,
|
|
5
|
+
AssetPositionAggregatedPortfolioModelSerializer,
|
|
6
|
+
CashPositionPortfolioModelSerializer,
|
|
7
|
+
)
|
|
8
|
+
from .custodians import CustodianModelSerializer, CustodianRepresentationSerializer
|
|
9
|
+
from .portfolio_relationship import (
|
|
10
|
+
InstrumentPreferedClassificationThroughProductModelSerializer,
|
|
11
|
+
InstrumentPortfolioThroughModelSerializer,
|
|
12
|
+
)
|
|
13
|
+
from .portfolios import (
|
|
14
|
+
ModelPortfolioModelSerializer,
|
|
15
|
+
PortfolioModelSerializer,
|
|
16
|
+
PortfolioPortfolioThroughModelSerializer,
|
|
17
|
+
PortfolioRepresentationSerializer,
|
|
18
|
+
)
|
|
19
|
+
from .portfolio_swing_pricing import PortfolioSwingPricingModelSerializer
|
|
20
|
+
from .portfolio_cash_targets import PortfolioCashTargetModelSerializer
|
|
21
|
+
from .positions import AggregatedAssetPositionModelSerializer
|
|
22
|
+
from .registers import RegisterModelSerializer, RegisterRepresentationSerializer
|
|
23
|
+
from .roles import PortfolioRoleModelSerializer, PortfolioRoleProjectModelSerializer
|
|
24
|
+
from .signals import *
|
|
25
|
+
from .synchronization import (
|
|
26
|
+
PortfolioSynchronizationRepresentationSerializer,
|
|
27
|
+
PriceComputationRepresentationSerializer,
|
|
28
|
+
)
|
|
29
|
+
from .transactions import *
|
|
30
|
+
from .products import (
|
|
31
|
+
ProductRepresentationSerializer,
|
|
32
|
+
ProductCustomerRepresentationSerializer,
|
|
33
|
+
ProductUnlinkedRepresentationSerializer,
|
|
34
|
+
ProductListModelSerializer,
|
|
35
|
+
ProductModelSerializer,
|
|
36
|
+
ProductCustomerModelSerializer,
|
|
37
|
+
ProductFeesModelSerializer,
|
|
38
|
+
)
|
|
39
|
+
from .adjustments import AdjustmentModelSerializer
|
|
40
|
+
from .product_group import ProductGroupModelSerializer, ProductGroupRepresentationSerializer
|
|
41
|
+
from .portfolio_cash_flow import DailyPortfolioCashFlowModelSerializer
|
|
42
|
+
from .reconciliations import AccountReconciliationModelSerializer, AccountReconciliationLineModelSerializer
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from wbcore import serializers as wb_serializers
|
|
2
|
+
from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
|
|
3
|
+
from wbfdm.serializers import SecurityRepresentationSerializer
|
|
4
|
+
from wbportfolio.models import Adjustment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AdjustmentModelSerializer(wb_serializers.ModelSerializer):
|
|
8
|
+
_instrument = SecurityRepresentationSerializer(source="instrument")
|
|
9
|
+
_last_handler = PersonRepresentationSerializer(source="last_handler")
|
|
10
|
+
|
|
11
|
+
class Meta:
|
|
12
|
+
model = Adjustment
|
|
13
|
+
fields = (
|
|
14
|
+
"id",
|
|
15
|
+
"date",
|
|
16
|
+
"instrument",
|
|
17
|
+
"_instrument",
|
|
18
|
+
"status",
|
|
19
|
+
"factor",
|
|
20
|
+
"cumulative_factor",
|
|
21
|
+
"_last_handler",
|
|
22
|
+
"last_handler",
|
|
23
|
+
"_additional_resources",
|
|
24
|
+
)
|