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,98 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
from psycopg.types.range import DateRange
|
|
4
|
+
from wbcore import filters as wb_filters
|
|
5
|
+
from wbportfolio.models import Fees, Portfolio, Trade, Transaction
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_transaction_gte_default(field, request, view):
|
|
9
|
+
filter_date = date.today() - timedelta(days=90)
|
|
10
|
+
qs = Transaction.objects.none()
|
|
11
|
+
if "instrument_id" in view.kwargs:
|
|
12
|
+
qs = Transaction.objects.filter(underlying_instrument__id=view.kwargs["instrument_id"])
|
|
13
|
+
elif "portfolio_id" in view.kwargs:
|
|
14
|
+
qs = Transaction.objects.filter(portfolio__id=view.kwargs["portfolio_id"])
|
|
15
|
+
if qs.exists():
|
|
16
|
+
filter_date = qs.earliest("transaction_date").transaction_date
|
|
17
|
+
return filter_date
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_transaction_underlying_type_choices(*args):
|
|
21
|
+
models = [Fees, Trade]
|
|
22
|
+
choices = []
|
|
23
|
+
for model in models:
|
|
24
|
+
for choice in model.Type.choices:
|
|
25
|
+
choices.append(choice)
|
|
26
|
+
return choices
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_transaction_lte_default(field, request, view):
|
|
30
|
+
filter_date = date.today() + timedelta(days=7)
|
|
31
|
+
qs = Transaction.objects.none()
|
|
32
|
+
if "instrument_id" in view.kwargs:
|
|
33
|
+
qs = Transaction.objects.filter(underlying_instrument__id=view.kwargs["instrument_id"])
|
|
34
|
+
elif "portfolio_id" in view.kwargs:
|
|
35
|
+
qs = Transaction.objects.filter(portfolio__id=view.kwargs["portfolio_id"])
|
|
36
|
+
if qs.exists():
|
|
37
|
+
filter_date = qs.latest("transaction_date").transaction_date
|
|
38
|
+
return filter_date
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_transaction_default_date_range(*args, **kwargs):
|
|
42
|
+
return DateRange(get_transaction_gte_default(*args, **kwargs), get_transaction_lte_default(*args, **kwargs))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TransactionFilterSet(wb_filters.FilterSet):
|
|
46
|
+
transaction_date = wb_filters.DateRangeFilter(
|
|
47
|
+
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
48
|
+
label="Date Range",
|
|
49
|
+
default=get_transaction_default_date_range,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
portfolio = wb_filters.ModelChoiceFilter(
|
|
53
|
+
label="Portfolio",
|
|
54
|
+
queryset=Portfolio.objects.all(),
|
|
55
|
+
endpoint=Portfolio.get_representation_endpoint(),
|
|
56
|
+
value_key=Portfolio.get_representation_value_key(),
|
|
57
|
+
label_key=Portfolio.get_representation_label_key(),
|
|
58
|
+
)
|
|
59
|
+
total_value_usd__gte = wb_filters.NumberFilter(
|
|
60
|
+
lookup_expr="gte", field_name="total_value_usd", label="Total Value ($)"
|
|
61
|
+
)
|
|
62
|
+
total_value_usd__lte = wb_filters.NumberFilter(
|
|
63
|
+
lookup_expr="lte",
|
|
64
|
+
label="Total Value ($)",
|
|
65
|
+
field_name="total_value_usd",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
transaction_underlying_type = wb_filters.ChoiceFilter(
|
|
69
|
+
label="Underlying Type",
|
|
70
|
+
choices=get_transaction_underlying_type_choices(),
|
|
71
|
+
method="filter_transaction_underlying_type",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def filter_transaction_underlying_type(self, queryset, name, value):
|
|
75
|
+
if value:
|
|
76
|
+
queryset = queryset.filter(transaction_underlying_type=value)
|
|
77
|
+
return queryset
|
|
78
|
+
|
|
79
|
+
class Meta:
|
|
80
|
+
model = Transaction
|
|
81
|
+
fields = {
|
|
82
|
+
"transaction_type": ["exact"],
|
|
83
|
+
"underlying_instrument": ["exact"],
|
|
84
|
+
"currency": ["exact"],
|
|
85
|
+
"total_value": ["gte", "exact", "lte"],
|
|
86
|
+
"total_value_fx_portfolio": ["gte", "exact", "lte"],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TransactionPortfolioFilterSet(TransactionFilterSet):
|
|
91
|
+
portfolio = transaction_date__gte = transaction_date__lte = None
|
|
92
|
+
transaction_date = wb_filters.DateFilter(
|
|
93
|
+
label="Transaction Date",
|
|
94
|
+
lookup_expr="exact",
|
|
95
|
+
field_name="transaction_date",
|
|
96
|
+
default=get_transaction_lte_default,
|
|
97
|
+
required=True,
|
|
98
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import date, datetime
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from django.db import models
|
|
7
|
+
from pandas.tseries.offsets import BDay
|
|
8
|
+
from wbcore.contrib.io.backends import AbstractDataBackend, register
|
|
9
|
+
|
|
10
|
+
from ..utils import process_request
|
|
11
|
+
from .mixin import DataBackendMixin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register("Asset Position", provider_key="ubs", save_data_in_import_source=True, passive_only=True)
|
|
15
|
+
class DataBackend(DataBackendMixin, AbstractDataBackend):
|
|
16
|
+
def __init__(
|
|
17
|
+
self, import_credential: Optional[models.Model] = None, ubs_bank: Optional[models.Model] = None, **kwargs
|
|
18
|
+
):
|
|
19
|
+
if not ubs_bank:
|
|
20
|
+
raise ValueError("The ubs company objects needs to be passed to this backend")
|
|
21
|
+
self.ubs_bank = ubs_bank
|
|
22
|
+
if not import_credential or not import_credential.authentication_token:
|
|
23
|
+
raise ValueError("UBS backend needs a valid import credential object")
|
|
24
|
+
self.authentication_token = import_credential.authentication_token
|
|
25
|
+
|
|
26
|
+
def get_files(
|
|
27
|
+
self,
|
|
28
|
+
execution_time: datetime,
|
|
29
|
+
start: date = None,
|
|
30
|
+
obj_external_ids: list[str] = None,
|
|
31
|
+
**kwargs,
|
|
32
|
+
) -> BytesIO:
|
|
33
|
+
execution_date = (execution_time - BDay(1)).date()
|
|
34
|
+
|
|
35
|
+
endpoint = "https://neo.ubs.com/api/ged-amc/external/report/v1/valuation/{0}/{1}"
|
|
36
|
+
if obj_external_ids:
|
|
37
|
+
for external_id in obj_external_ids:
|
|
38
|
+
res_json = process_request(
|
|
39
|
+
self.authentication_token, endpoint.format(external_id, execution_date.strftime("%Y-%m-%d"))
|
|
40
|
+
)
|
|
41
|
+
if res_json:
|
|
42
|
+
content_file = BytesIO()
|
|
43
|
+
content_file.write(json.dumps(res_json).encode())
|
|
44
|
+
file_name = f"ubs_positions_{external_id}_{execution_date:%Y-%m-%d}_{datetime.timestamp(execution_time)}.json"
|
|
45
|
+
yield file_name, content_file
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from django.db import models
|
|
7
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
8
|
+
from wbcore.contrib.io.backends import AbstractDataBackend, register
|
|
9
|
+
|
|
10
|
+
from ..utils import process_request
|
|
11
|
+
from .mixin import DataBackendMixin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register("Fees", provider_key="ubs", save_data_in_import_source=True, passive_only=True)
|
|
15
|
+
class DataBackend(DataBackendMixin, AbstractDataBackend):
|
|
16
|
+
def __init__(
|
|
17
|
+
self, import_credential: Optional[models.Model] = None, ubs_bank: Optional[models.Model] = None, **kwargs
|
|
18
|
+
):
|
|
19
|
+
if not ubs_bank:
|
|
20
|
+
raise ValueError("The ubs company objects needs to be passed to this backend")
|
|
21
|
+
self.ubs_bank = ubs_bank
|
|
22
|
+
if not import_credential or not import_credential.authentication_token:
|
|
23
|
+
raise ValueError("UBS backend needs a valid import credential object")
|
|
24
|
+
self.authentication_token = import_credential.authentication_token
|
|
25
|
+
|
|
26
|
+
def get_files(
|
|
27
|
+
self,
|
|
28
|
+
execution_time: datetime,
|
|
29
|
+
obj_external_ids: list[str] = None,
|
|
30
|
+
**kwargs,
|
|
31
|
+
) -> BytesIO:
|
|
32
|
+
execution_date = execution_time.date()
|
|
33
|
+
|
|
34
|
+
mngt_fees_endpoint = "https://neo.ubs.com/api/ged-amc/external/fee/v1/management/{0}"
|
|
35
|
+
perf_fees_endpoint = "https://neo.ubs.com/api/ged-amc/external/fee/v1/performance/{0}"
|
|
36
|
+
if obj_external_ids:
|
|
37
|
+
for external_id in obj_external_ids:
|
|
38
|
+
start = kwargs.get("start", None)
|
|
39
|
+
if not start:
|
|
40
|
+
start = global_preferences_registry.manager()["wbfdm__default_start_date_historical_import"]
|
|
41
|
+
mngt_res = process_request(
|
|
42
|
+
self.authentication_token,
|
|
43
|
+
mngt_fees_endpoint.format(external_id),
|
|
44
|
+
{"fromDate": start.strftime("%Y-%m-%d"), "toDate": execution_date.strftime("%Y-%m-%d")},
|
|
45
|
+
)
|
|
46
|
+
perf_res = process_request(
|
|
47
|
+
self.authentication_token,
|
|
48
|
+
perf_fees_endpoint.format(external_id),
|
|
49
|
+
{"fromDate": start.strftime("%Y-%m-%d"), "toDate": execution_date.strftime("%Y-%m-%d")},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if mngt_res or perf_res:
|
|
53
|
+
res_json = {
|
|
54
|
+
"performance_fees": perf_res.get("fees", []),
|
|
55
|
+
"management_fees": mngt_res.get("fees", []),
|
|
56
|
+
"isin": external_id,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if res_json:
|
|
60
|
+
content_file = BytesIO()
|
|
61
|
+
content_file.write(json.dumps(res_json).encode())
|
|
62
|
+
file_name = f"ubs_fees_{external_id}_{start:%Y-%m-%d}_{execution_date:%Y-%m-%d}_{datetime.timestamp(execution_time)}.json"
|
|
63
|
+
yield file_name, content_file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from django.db import models
|
|
7
|
+
from pandas.tseries.offsets import BDay
|
|
8
|
+
from wbcore.contrib.io.backends import AbstractDataBackend, register
|
|
9
|
+
|
|
10
|
+
from ..utils import process_request
|
|
11
|
+
from .mixin import DataBackendMixin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register("Instrument Prices", provider_key="ubs", save_data_in_import_source=True, passive_only=True)
|
|
15
|
+
class DataBackend(DataBackendMixin, AbstractDataBackend):
|
|
16
|
+
def __init__(
|
|
17
|
+
self, import_credential: Optional[models.Model] = None, ubs_bank: Optional[models.Model] = None, **kwargs
|
|
18
|
+
):
|
|
19
|
+
if not ubs_bank:
|
|
20
|
+
raise ValueError("The ubs company objects needs to be passed to this backend")
|
|
21
|
+
self.ubs_bank = ubs_bank
|
|
22
|
+
if not import_credential or not import_credential.authentication_token:
|
|
23
|
+
raise ValueError("UBS backend needs a valid import credential object")
|
|
24
|
+
self.authentication_token = import_credential.authentication_token
|
|
25
|
+
|
|
26
|
+
def get_files(
|
|
27
|
+
self,
|
|
28
|
+
execution_time: datetime,
|
|
29
|
+
obj_external_ids: list[str] = None,
|
|
30
|
+
**kwargs,
|
|
31
|
+
) -> BytesIO:
|
|
32
|
+
execution_date = (execution_time - BDay(1)).date()
|
|
33
|
+
endpoint = "https://neo.ubs.com/api/ged-amc/external/report/v1/valuation/{0}/{1}"
|
|
34
|
+
if obj_external_ids:
|
|
35
|
+
for external_id in obj_external_ids:
|
|
36
|
+
res_json = process_request(
|
|
37
|
+
self.authentication_token, endpoint.format(external_id, execution_date.strftime("%Y-%m-%d"))
|
|
38
|
+
)
|
|
39
|
+
res_json.pop("constituents", None)
|
|
40
|
+
if res_json:
|
|
41
|
+
content_file = BytesIO()
|
|
42
|
+
content_file.write(json.dumps(res_json).encode())
|
|
43
|
+
file_name = f"ubs_instrument_price_{external_id}_{execution_date:%Y-%m-%d}_{int(datetime.timestamp(execution_time))}.json"
|
|
44
|
+
yield file_name, content_file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from django.db import models
|
|
4
|
+
from wbportfolio.models.products import Product
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DataBackendMixin:
|
|
8
|
+
def is_object_valid(self, obj: models.Model) -> bool:
|
|
9
|
+
return super().is_object_valid(obj) and obj.is_active_at_date(date.today()) and obj.isin
|
|
10
|
+
|
|
11
|
+
def get_default_queryset(self):
|
|
12
|
+
return Product.objects.filter(bank=self.ubs_bank, isin__isnull=False)
|
|
13
|
+
|
|
14
|
+
def get_provider_id(self, obj: models.Model) -> str:
|
|
15
|
+
return obj.isin
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import requests
|
|
5
|
+
from django.db.models import Q
|
|
6
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
7
|
+
from wbfdm.models import Instrument
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_timedelta_import_instrument_price():
|
|
11
|
+
return global_preferences_registry.manager()["wbportfolio__timedelta_import_instrument_price"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def process_request(authentication_token: str, endpoint: str | None = None, kwargs={}) -> pd.DataFrame:
|
|
15
|
+
headers = {"Authorization": authentication_token}
|
|
16
|
+
r = requests.get(endpoint, params=kwargs, headers=headers)
|
|
17
|
+
if r.status_code == requests.codes.ok:
|
|
18
|
+
with suppress(
|
|
19
|
+
requests.exceptions.JSONDecodeError
|
|
20
|
+
): # we catch any json decode error because the UBS api doesn't seem to respect HTTP status code rule (i.e. returns 200 even though the http content is malformed)
|
|
21
|
+
r_json = r.json()
|
|
22
|
+
if r_json.get("status", "") == "SUCCESS":
|
|
23
|
+
return r_json
|
|
24
|
+
raise ValueError(f"Issue while processing request: {r.content}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def filter_active_instruments(_date, queryset=None):
|
|
28
|
+
if not queryset:
|
|
29
|
+
queryset = Instrument.objects
|
|
30
|
+
queryset = queryset.filter(Q(delisted_date__isnull=True) | Q(delisted_date__gte=_date))
|
|
31
|
+
queryset = queryset.filter(
|
|
32
|
+
Q(refinitiv_mnemonic_code__isnull=False) | Q(refinitiv_identifier_code__isnull=False) | Q(isin__isnull=False)
|
|
33
|
+
)
|
|
34
|
+
return queryset.distinct()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def chunked_queryset(queryset, chunk_size):
|
|
38
|
+
"""Slice a queryset into chunks."""
|
|
39
|
+
|
|
40
|
+
start_pk = 0
|
|
41
|
+
queryset = queryset.order_by("pk")
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
# No entry left
|
|
45
|
+
if not queryset.filter(pk__gt=start_pk).exists():
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Fetch chunk_size entries if possible
|
|
50
|
+
end_pk = queryset.filter(pk__gt=start_pk).values_list("pk", flat=True)[chunk_size - 1]
|
|
51
|
+
|
|
52
|
+
# Fetch rest entries if less than chunk_size left
|
|
53
|
+
except IndexError:
|
|
54
|
+
end_pk = queryset.values_list("pk", flat=True).last()
|
|
55
|
+
|
|
56
|
+
yield queryset.filter(pk__gt=start_pk).filter(pk__lte=end_pk)
|
|
57
|
+
|
|
58
|
+
start_pk = end_pk
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
|
|
5
|
+
from django.core.serializers.json import DjangoJSONEncoder
|
|
6
|
+
from pandas.tseries.offsets import BDay
|
|
7
|
+
from wbcore.contrib.io.backends import AbstractDataBackend, register
|
|
8
|
+
from wbfdm.models import Instrument
|
|
9
|
+
from wbportfolio.import_export.backends.utils import (
|
|
10
|
+
get_timedelta_import_instrument_price,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .mixin import DataBackendMixin
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@register("Adjustment", provider_key="wbfdm", save_data_in_import_source=False, passive_only=False)
|
|
17
|
+
class DataBackend(DataBackendMixin, AbstractDataBackend):
|
|
18
|
+
DATE_LABEL = "adjustment_date"
|
|
19
|
+
DEFAULT_MAPPING = {
|
|
20
|
+
"adjustment_date": "date",
|
|
21
|
+
"adjustment_factor": "factor",
|
|
22
|
+
"cumulative_adjustment_factor": "cumulative_factor",
|
|
23
|
+
"instrument_id": "instrument",
|
|
24
|
+
}
|
|
25
|
+
NONE_NULLABLE_FIELDS = list(DEFAULT_MAPPING.values())
|
|
26
|
+
|
|
27
|
+
def get_default_queryset(self):
|
|
28
|
+
return Instrument.objects.filter(is_investable_universe=True)
|
|
29
|
+
|
|
30
|
+
def get_files(
|
|
31
|
+
self,
|
|
32
|
+
execution_time: datetime,
|
|
33
|
+
**kwargs,
|
|
34
|
+
) -> BytesIO:
|
|
35
|
+
execution_date = execution_time.date()
|
|
36
|
+
start = kwargs.get("start", (execution_date - BDay(get_timedelta_import_instrument_price())).date())
|
|
37
|
+
data = []
|
|
38
|
+
for dict_dto in self.get_default_queryset().dl.adjustments(
|
|
39
|
+
from_date=start,
|
|
40
|
+
to_date=execution_date,
|
|
41
|
+
):
|
|
42
|
+
if dict_dto["adjustment_factor"] is not None:
|
|
43
|
+
data.append(dict_dto)
|
|
44
|
+
if data:
|
|
45
|
+
content_file = BytesIO()
|
|
46
|
+
content_file.write(json.dumps(data, cls=DjangoJSONEncoder).encode())
|
|
47
|
+
file_name = (
|
|
48
|
+
f"adjustment_{start:%Y-%m-%d}-{execution_date:%Y-%m-%d}_{datetime.timestamp(execution_time)}.json"
|
|
49
|
+
)
|
|
50
|
+
yield file_name, content_file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from wbcore.contrib.io.backends import AbstractDataBackend, register
|
|
2
|
+
|
|
3
|
+
from .mixin import DataBackendMixin
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@register("Dividend", provider_key="wbfdm", save_data_in_import_source=False, passive_only=False)
|
|
7
|
+
class DataBackend(DataBackendMixin, AbstractDataBackend):
|
|
8
|
+
TIMEDELTA = 1
|
|
9
|
+
ATTRIBUTE_NAME = "dividends"
|
|
10
|
+
FILE_NAME = "dividend"
|
|
11
|
+
DEFAULT_MAPPING = {
|
|
12
|
+
"instrument_id": "instrument",
|
|
13
|
+
"rate": "weighting",
|
|
14
|
+
"ex_dividend_date": "value_date",
|
|
15
|
+
"payment_date": "transaction_date",
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
MUTUAL_FUND_TIMEDELTA_DAY_SHIFT = 7
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DataBackendMixin:
|
|
9
|
+
DATE_LABEL = "valuation_date"
|
|
10
|
+
|
|
11
|
+
def is_object_valid(self, obj: models.Model) -> bool:
|
|
12
|
+
return super().is_object_valid(obj) and obj.assets.exists() and obj.is_active_at_date(date.today())
|
|
13
|
+
|
|
14
|
+
def get_provider_id(self, obj: models.Model) -> str:
|
|
15
|
+
return obj.id
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
4
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
5
|
+
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AdjustmentImportHandler(ImportExportHandler):
|
|
9
|
+
MODEL_APP_LABEL = "wbportfolio.Adjustment"
|
|
10
|
+
|
|
11
|
+
def __init__(self, *args, **kwargs):
|
|
12
|
+
super().__init__(*args, **kwargs)
|
|
13
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
14
|
+
|
|
15
|
+
def _deserialize(self, data):
|
|
16
|
+
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
|
17
|
+
data["instrument"] = self.instrument_handler.process_object(
|
|
18
|
+
data["instrument"], only_security=True, read_only=True
|
|
19
|
+
)[0]
|
|
20
|
+
if data["factor"] is None:
|
|
21
|
+
raise DeserializationError("Can't process this data: no factor")
|
|
22
|
+
|
|
23
|
+
def _get_instance(self, data, history=None, **kwargs):
|
|
24
|
+
self.import_source.log += "\nGet Adjustment Instance."
|
|
25
|
+
self.import_source.log += f"\nParameter: Instrument={data['instrument']} Date={data['date']}"
|
|
26
|
+
|
|
27
|
+
if exact_adjustment := data["instrument"].pms_adjustments.filter(date=data["date"]).first():
|
|
28
|
+
return exact_adjustment
|
|
29
|
+
potential_adjustments = data["instrument"].pms_adjustments.filter(
|
|
30
|
+
date__gte=data["date"] - timedelta(days=7),
|
|
31
|
+
date__lte=data["date"] + timedelta(days=7),
|
|
32
|
+
factor=data["factor"],
|
|
33
|
+
)
|
|
34
|
+
if potential_adjustments.exists():
|
|
35
|
+
return potential_adjustments.first()
|
|
36
|
+
|
|
37
|
+
def _create_instance(self, data, **kwargs):
|
|
38
|
+
self.import_source.log += "\nCreate Adjustment datapoint."
|
|
39
|
+
return self.model.objects.create(**data, import_source=self.import_source)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from itertools import chain
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from django.db import models
|
|
8
|
+
from wbcore.contrib.authentication.authentication import User
|
|
9
|
+
from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
10
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
11
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
12
|
+
from wbcore.contrib.notifications.dispatch import send_notification
|
|
13
|
+
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
14
|
+
from wbfdm.import_export.handlers.instrument_price import InstrumentPriceImportHandler
|
|
15
|
+
from wbfdm.models.exchanges import Exchange
|
|
16
|
+
from wbportfolio.models.roles import PortfolioRole
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AssetPositionImportHandler(ImportExportHandler):
|
|
20
|
+
MODEL_APP_LABEL: str = "wbportfolio.AssetPosition"
|
|
21
|
+
PRICE_DATE_TIMEDELTA: int = 7
|
|
22
|
+
MAX_PRICE_DATE_TIMEDELTA: int = 360
|
|
23
|
+
|
|
24
|
+
def __init__(self, *args, **kwargs):
|
|
25
|
+
super().__init__(*args, **kwargs)
|
|
26
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
27
|
+
self.instrument_price_handler = InstrumentPriceImportHandler(self.import_source)
|
|
28
|
+
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
29
|
+
|
|
30
|
+
def _deserialize(self, data: Dict[str, Any]):
|
|
31
|
+
from wbportfolio.models import Portfolio
|
|
32
|
+
|
|
33
|
+
portfolio_data = data.pop("portfolio", None)
|
|
34
|
+
underlying_instrument_data = data.pop("underlying_instrument", None)
|
|
35
|
+
if "currency" in data:
|
|
36
|
+
data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
|
|
37
|
+
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
|
38
|
+
if data.get("asset_valuation_date", None):
|
|
39
|
+
data["asset_valuation_date"] = datetime.strptime(data["asset_valuation_date"], "%Y-%m-%d").date()
|
|
40
|
+
else:
|
|
41
|
+
data["asset_valuation_date"] = data["date"]
|
|
42
|
+
|
|
43
|
+
if exchange_data := data.pop("exchange", None):
|
|
44
|
+
sanitized_dict = {k: v for k, v in exchange_data.items() if v is not None}
|
|
45
|
+
if sanitized_dict:
|
|
46
|
+
data["exchange"] = Exchange.dict_to_model(sanitized_dict)
|
|
47
|
+
|
|
48
|
+
data["portfolio"] = Portfolio._get_or_create_portfolio(self.instrument_handler, portfolio_data)
|
|
49
|
+
data["underlying_instrument"] = self.instrument_handler.process_object(
|
|
50
|
+
underlying_instrument_data, only_security=False, read_only=True
|
|
51
|
+
)[0]
|
|
52
|
+
|
|
53
|
+
# number type deserialization and sanitization
|
|
54
|
+
# ensure the provided Decimal field are of type Decimal
|
|
55
|
+
decimal_fields = ["initial_currency_fx_rate", "initial_price", "initial_shares", "weighting"]
|
|
56
|
+
for field in decimal_fields:
|
|
57
|
+
if not (value := data.get(field, None)) is None:
|
|
58
|
+
data[field] = Decimal(value)
|
|
59
|
+
|
|
60
|
+
# Ensure that for shares and weighting, a None value default to 0
|
|
61
|
+
if "initial_shares" in data and data["initial_shares"] is None:
|
|
62
|
+
data["initial_shares"] = Decimal(0)
|
|
63
|
+
if "weighting" in data and data["weighting"] is None:
|
|
64
|
+
data["weighting"] = Decimal(0)
|
|
65
|
+
|
|
66
|
+
# if the initial price is not provided, we try to get it directly from the dataloader
|
|
67
|
+
if data.get("initial_price") is None:
|
|
68
|
+
try:
|
|
69
|
+
data["initial_price"] = data["underlying_instrument"].get_price(
|
|
70
|
+
data["date"], price_date_timedelta=self.PRICE_DATE_TIMEDELTA
|
|
71
|
+
)
|
|
72
|
+
except ValueError:
|
|
73
|
+
# If we cannot find a price with the default timedelta, we try with a bigger range (in case the instrument stopped trading for some time for instance)
|
|
74
|
+
try:
|
|
75
|
+
data["initial_price"] = data["underlying_instrument"].get_price(
|
|
76
|
+
data["date"], price_date_timedelta=self.MAX_PRICE_DATE_TIMEDELTA
|
|
77
|
+
)
|
|
78
|
+
except ValueError:
|
|
79
|
+
raise DeserializationError("Price not provided but can not be found automatically")
|
|
80
|
+
|
|
81
|
+
def _process_raw_data(self, data: Dict[str, Any]):
|
|
82
|
+
if prices := data.get("prices", None):
|
|
83
|
+
self.import_source.log += "Instrument Prices found: Importing"
|
|
84
|
+
self.instrument_price_handler.process({"data": prices})
|
|
85
|
+
|
|
86
|
+
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
87
|
+
self.import_source.log += "\nTrying to get asset position instance"
|
|
88
|
+
position = data["portfolio"].assets.filter(
|
|
89
|
+
date=data["date"], underlying_instrument=data["underlying_instrument"]
|
|
90
|
+
)
|
|
91
|
+
if position.exists():
|
|
92
|
+
self.import_source.log += "\nAsset Position found."
|
|
93
|
+
if position.count() > 1:
|
|
94
|
+
position = position.filter(is_estimated=False)
|
|
95
|
+
if position.count() > 1:
|
|
96
|
+
raise ValueError(f'We should find only one Assetposition:{position.values_list("id", flat=True)}')
|
|
97
|
+
return position.first()
|
|
98
|
+
self.import_source.log += "\nAsset Position not found. A new one will be created"
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
|
|
102
|
+
instance = self.model(
|
|
103
|
+
portfolio=data["portfolio"],
|
|
104
|
+
underlying_instrument=data["underlying_instrument"],
|
|
105
|
+
date=data["date"],
|
|
106
|
+
asset_valuation_date=data["asset_valuation_date"],
|
|
107
|
+
weighting=data.get("weighting", None),
|
|
108
|
+
initial_price=data["initial_price"],
|
|
109
|
+
initial_shares=data.get("initial_shares", None),
|
|
110
|
+
initial_currency_fx_rate=data.get("initial_currency_fx_rate", 1),
|
|
111
|
+
import_source=self.import_source,
|
|
112
|
+
currency=data.get("currency", None),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return self._save_object(instance, **kwargs)
|
|
116
|
+
|
|
117
|
+
def _save_object(self, _object, **kwargs):
|
|
118
|
+
_object.underlying_instrument_price = (
|
|
119
|
+
None # detech possibly already attached instrument price to retrigger the save mechanism
|
|
120
|
+
)
|
|
121
|
+
_object.save(create_underlying_instrument_price_if_missing=True)
|
|
122
|
+
return _object
|
|
123
|
+
|
|
124
|
+
def _post_processing_objects(
|
|
125
|
+
self,
|
|
126
|
+
created_objs: List[models.Model],
|
|
127
|
+
modified_objs: List[models.Model],
|
|
128
|
+
unmodified_objs: List[models.Model],
|
|
129
|
+
):
|
|
130
|
+
from wbportfolio.models.portfolio import trigger_portfolio_change_as_task
|
|
131
|
+
|
|
132
|
+
portfolio_to_resynch = defaultdict(set)
|
|
133
|
+
imported_ids = defaultdict(set)
|
|
134
|
+
for obj in chain(created_objs, modified_objs, unmodified_objs):
|
|
135
|
+
portfolio_to_resynch[obj.portfolio].add(obj.date)
|
|
136
|
+
imported_ids[obj.portfolio].add(obj.id)
|
|
137
|
+
|
|
138
|
+
for portfolio, dates in portfolio_to_resynch.items():
|
|
139
|
+
# We remove leftovers positions from wrongly imported file
|
|
140
|
+
leftovers_positions = self.model.objects.filter(portfolio=portfolio, date__in=dates)
|
|
141
|
+
for obj_id in imported_ids[portfolio]:
|
|
142
|
+
leftovers_positions = leftovers_positions.exclude(id=obj_id)
|
|
143
|
+
for position in leftovers_positions:
|
|
144
|
+
position.delete()
|
|
145
|
+
for val_date in sorted(dates):
|
|
146
|
+
trigger_portfolio_change_as_task.delay(portfolio.id, val_date, recompute_weighting=True)
|
|
147
|
+
|
|
148
|
+
portfolios = set([(obj.portfolio, obj.date) for obj in chain(created_objs, modified_objs)])
|
|
149
|
+
|
|
150
|
+
for portfolio, val_date in portfolios:
|
|
151
|
+
custodian = portfolio.dependency_through.filter(type="CUSTODIAN")
|
|
152
|
+
if custodian.exists():
|
|
153
|
+
differences = portfolio.check_related_portfolio_at_date(
|
|
154
|
+
val_date, custodian.first().dependency_portfolio
|
|
155
|
+
)
|
|
156
|
+
if differences.exists():
|
|
157
|
+
for user in User.objects.filter(
|
|
158
|
+
profile_id__in=PortfolioRole.portfolio_managers().values_list("person", flat=True)
|
|
159
|
+
):
|
|
160
|
+
send_notification(
|
|
161
|
+
code="wbportfolio.portfolio.check_custodian_portfolio",
|
|
162
|
+
title="There is a discrepency between two portfolios",
|
|
163
|
+
body=f"There has been a discrepency between two portfolios: {portfolio} and {custodian.first().dependency_portfolio}.",
|
|
164
|
+
user=user,
|
|
165
|
+
reverse_name="wbportfolio:portfolio-modelcompositionpandas-list",
|
|
166
|
+
reverse_args=[portfolio.id],
|
|
167
|
+
)
|