wbportfolio 1.43.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 +153 -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 +248 -0
- wbportfolio/filters/transactions/transactions.py +98 -0
- wbportfolio/fixtures/product_factsheets.yaml +1 -0
- wbportfolio/fixtures/wbportfolio.yaml.gz +0 -0
- wbportfolio/fixtures/wbrisk_management.yaml.gz +0 -0
- wbportfolio/import_export/__init__.py +0 -0
- wbportfolio/import_export/backends/__init__.py +2 -0
- wbportfolio/import_export/backends/refinitiv/adjustment.py +40 -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 +161 -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/jinja2/wbportfolio/sql/aum_nnm.sql +119 -0
- wbportfolio/kpi_handlers/nnm.py +163 -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/llm/wbcrm/analyze_relationship.py +58 -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 +173 -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/static/wbportfolio/css/macro_review.css +17 -0
- wbportfolio/static/wbportfolio/markdown/documentation/account_holding_reconciliation.md +16 -0
- wbportfolio/static/wbportfolio/markdown/documentation/aggregate_asset_position_liquidity.md +25 -0
- wbportfolio/static/wbportfolio/markdown/documentation/company.md +78 -0
- wbportfolio/static/wbportfolio/markdown/documentation/earnings_instrument.md +14 -0
- wbportfolio/static/wbportfolio/markdown/documentation/financial_analysis_instrument_ratios.md +94 -0
- wbportfolio/static/wbportfolio/markdown/documentation/financial_statistics.md +44 -0
- wbportfolio/static/wbportfolio/markdown/documentation/person.md +70 -0
- wbportfolio/tasks.py +125 -0
- wbportfolio/templates/portfolio/email/customer_report.html +6 -0
- wbportfolio/templates/portfolio/email/customer_trade_notification.html +26 -0
- wbportfolio/templates/portfolio/email/email_base_template.html +420 -0
- wbportfolio/templates/portfolio/email/rebalancing_report.html +34 -0
- wbportfolio/templates/portfolio/macro/macro_review.html +88 -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-1.43.1.dist-info/METADATA +22 -0
- wbportfolio-1.43.1.dist-info/RECORD +506 -0
- wbportfolio-1.43.1.dist-info/WHEEL +5 -0
- wbportfolio-1.43.1.dist-info/licenses/LICENSE +4 -0
|
@@ -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
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
from django.db import models
|
|
7
|
+
from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
8
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
9
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
10
|
+
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DividendImportHandler(ImportExportHandler):
|
|
14
|
+
MODEL_APP_LABEL = "wbportfolio.DividendTransaction"
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
19
|
+
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
20
|
+
|
|
21
|
+
def _deserialize(self, data):
|
|
22
|
+
data["transaction_date"] = datetime.strptime(data["transaction_date"], "%Y-%m-%d").date()
|
|
23
|
+
data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
|
|
24
|
+
from wbportfolio.models import Portfolio
|
|
25
|
+
|
|
26
|
+
data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
|
|
27
|
+
instrument = self.instrument_handler.process_object(
|
|
28
|
+
data["underlying_instrument"], only_security=False, read_only=True
|
|
29
|
+
)[0]
|
|
30
|
+
if not instrument:
|
|
31
|
+
raise DeserializationError("Can't process this data: underlying instrument not found")
|
|
32
|
+
data["underlying_instrument"] = instrument
|
|
33
|
+
if "currency" not in data:
|
|
34
|
+
data["currency"] = data["portfolio"].currency
|
|
35
|
+
else:
|
|
36
|
+
data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
|
|
37
|
+
|
|
38
|
+
for field in self.model._meta.get_fields():
|
|
39
|
+
if not (value := data.get(field.name, None)) is None and isinstance(field, models.DecimalField):
|
|
40
|
+
q = 1 / (math.pow(10, 4))
|
|
41
|
+
data[field.name] = Decimal(value).quantize(Decimal(str(q)))
|
|
42
|
+
|
|
43
|
+
def _get_instance(self, data, history=None, **kwargs):
|
|
44
|
+
self.import_source.log += "\nGet DividendTransaction Instance."
|
|
45
|
+
self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Underlying={data['underlying_instrument']} Date={data['transaction_date']}"
|
|
46
|
+
dividends = history if history is not None else self.model.objects
|
|
47
|
+
|
|
48
|
+
dividends = dividends.filter(
|
|
49
|
+
portfolio=data["portfolio"],
|
|
50
|
+
transaction_date=data["transaction_date"],
|
|
51
|
+
value_date=data["value_date"],
|
|
52
|
+
underlying_instrument=data["underlying_instrument"],
|
|
53
|
+
price_gross=data["price_gross"],
|
|
54
|
+
)
|
|
55
|
+
if dividends.count() == 1:
|
|
56
|
+
self.import_source.log += "\nDividendTransaction Instance Found." ""
|
|
57
|
+
return dividends.first()
|
|
58
|
+
|
|
59
|
+
def _create_instance(self, data, **kwargs):
|
|
60
|
+
self.import_source.log += "\nCreate DividendTransaction."
|
|
61
|
+
return self.model.objects.create(**data, import_source=self.import_source)
|
|
62
|
+
|
|
63
|
+
def _get_history(self: models.Model, history: Dict[str, Any]) -> models.QuerySet:
|
|
64
|
+
val_date = datetime.strptime(history["transaction_date"], "%Y-%m-%d")
|
|
65
|
+
if portfolio_id := history.get("portfolio", None):
|
|
66
|
+
dividends = self.model.objects.filter(transaction_date__lte=val_date, portfolio=portfolio_id)
|
|
67
|
+
if underlying_instrument_id := history.get("underlying_instrument", None):
|
|
68
|
+
dividends = dividends.filter(underlying_instrument=underlying_instrument_id)
|
|
69
|
+
return dividends
|
|
70
|
+
|
|
71
|
+
def _post_processing_history(self: models.Model, history: models.QuerySet):
|
|
72
|
+
self.import_source.log += "===================="
|
|
73
|
+
self.import_source.log += (
|
|
74
|
+
"It was a historical import and the following DividendTransaction have to be deleted:"
|
|
75
|
+
)
|
|
76
|
+
for dividend in history.order_by("transaction_date"):
|
|
77
|
+
self.import_source.log += (
|
|
78
|
+
f"\n{dividend.transaction_date:%d.%m.%Y}: {dividend.shares} {dividend.price} ==> Deleted"
|
|
79
|
+
)
|
|
80
|
+
dividend.delete()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
4
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
5
|
+
from wbfdm.models.instruments import Cash
|
|
6
|
+
from wbportfolio.models.products import Product
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FeesImportHandler(ImportExportHandler):
|
|
10
|
+
MODEL_APP_LABEL: str = "wbportfolio.Fees"
|
|
11
|
+
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
15
|
+
|
|
16
|
+
def _deserialize(self, data):
|
|
17
|
+
data["transaction_date"] = datetime.strptime(data["transaction_date"], "%Y-%m-%d").date()
|
|
18
|
+
data["fee_date"] = data["transaction_date"]
|
|
19
|
+
if value_date_str := data.get("value_date", None):
|
|
20
|
+
data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
|
|
21
|
+
if book_date_str := data.get("book_date", None):
|
|
22
|
+
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
23
|
+
|
|
24
|
+
from wbportfolio.models import Portfolio
|
|
25
|
+
|
|
26
|
+
data["linked_product"] = Product.objects.get(id=data["linked_product"])
|
|
27
|
+
if "porfolio" in data:
|
|
28
|
+
data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
|
|
29
|
+
else:
|
|
30
|
+
data["portfolio"] = data["linked_product"].primary_portfolio
|
|
31
|
+
data["underlying_instrument"] = Cash.objects.filter(currency=data["portfolio"].currency).first()
|
|
32
|
+
if "currency" not in data:
|
|
33
|
+
data["currency"] = data["portfolio"].currency
|
|
34
|
+
else:
|
|
35
|
+
data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
|
|
36
|
+
data["currency_fx_rate"] = 1.0
|
|
37
|
+
data["total_value"] = data.get("total_value", data.get("total_value_gross", None))
|
|
38
|
+
data["total_value_gross"] = data.get("total_value_gross", data["total_value"])
|
|
39
|
+
data["calculated"] = data.get("calculated", False)
|
|
40
|
+
|
|
41
|
+
def _get_instance(self, data, history=None, **kwargs):
|
|
42
|
+
self.import_source.log += "\nGet Fees Instance."
|
|
43
|
+
self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Date={data['transaction_date']}"
|
|
44
|
+
fees = self.model.objects.filter(
|
|
45
|
+
linked_product=data["linked_product"],
|
|
46
|
+
fee_date=data["fee_date"],
|
|
47
|
+
transaction_subtype=data["transaction_subtype"],
|
|
48
|
+
calculated=data["calculated"],
|
|
49
|
+
)
|
|
50
|
+
if fees.exists():
|
|
51
|
+
if fees.count() > 1:
|
|
52
|
+
raise ValueError(f'Number of similar fees found > 1: {fees.values_list("id", flat=True)}')
|
|
53
|
+
self.import_source.log += "\nFees Instance Found." ""
|
|
54
|
+
return fees.first()
|
|
55
|
+
|
|
56
|
+
def _create_instance(self, data, **kwargs):
|
|
57
|
+
self.import_source.log += "\nCreate Fees."
|
|
58
|
+
return self.model.objects.create(**data, import_source=self.import_source)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from django.contrib.auth import get_user_model
|
|
7
|
+
from django.db.models import Q
|
|
8
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
9
|
+
from wbcore.contrib.notifications.dispatch import send_notification
|
|
10
|
+
from wbportfolio.models import Portfolio
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from wbportfolio.models.portfolio_cash_flow import DailyPortfolioCashFlow
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DailyPortfolioCashFlowImportHandler(ImportExportHandler):
|
|
17
|
+
MODEL_APP_LABEL = "wbportfolio.DailyPortfolioCashFlow"
|
|
18
|
+
|
|
19
|
+
def _deserialize(self, data):
|
|
20
|
+
data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
|
|
21
|
+
data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
|
|
22
|
+
if "cash" in data:
|
|
23
|
+
data["cash"] = Decimal(data["cash"])
|
|
24
|
+
|
|
25
|
+
if "total_assets" in data:
|
|
26
|
+
data["total_assets"] = Decimal(data["total_assets"])
|
|
27
|
+
|
|
28
|
+
if "cash_flow_forecast" in data:
|
|
29
|
+
data["cash_flow_forecast"] = Decimal(data["cash_flow_forecast"])
|
|
30
|
+
|
|
31
|
+
def _get_instance(self, data, history=None, **kwargs):
|
|
32
|
+
self.import_source.log += "\nGet Daily Portfolio Cash Flow Instance."
|
|
33
|
+
self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Value Date={data['value_date']}"
|
|
34
|
+
|
|
35
|
+
with suppress(self.model.DoesNotExist):
|
|
36
|
+
return self.model.objects.get(portfolio=data["portfolio"], value_date=data["value_date"])
|
|
37
|
+
|
|
38
|
+
def _post_processing_created_object(self, _object: "DailyPortfolioCashFlow"):
|
|
39
|
+
if not _object.portfolio.daily_cashflows.filter(value_date__gt=_object.value_date).exists():
|
|
40
|
+
if _object.proposed_rebalancing != Decimal(0):
|
|
41
|
+
color = "red" if _object.proposed_rebalancing < 0 else "green"
|
|
42
|
+
for user in (
|
|
43
|
+
get_user_model()
|
|
44
|
+
.objects.filter(
|
|
45
|
+
Q(user_permissions__codename="view_dailyportfoliocashflow")
|
|
46
|
+
| Q(groups__permissions__codename="view_dailyportfoliocashflow")
|
|
47
|
+
)
|
|
48
|
+
.distinct()
|
|
49
|
+
):
|
|
50
|
+
send_notification(
|
|
51
|
+
code="wbportfolio.dailyportfoliocashflow.notify_rebalance",
|
|
52
|
+
title=f"Proposed Rebalancing in the portfolio: {_object.portfolio}",
|
|
53
|
+
body=f"The workbench proposes to rebalance the portfolio {_object.portfolio} by <span style='color:{color}'>{_object.proposed_rebalancing:+,.2f}</span>",
|
|
54
|
+
user=user,
|
|
55
|
+
reverse_name="wbportfolio:portfoliocashflow-detail",
|
|
56
|
+
reverse_args=[_object.pk],
|
|
57
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
from django.db import models
|
|
5
|
+
from wbcore.contrib.geography.models import Geography
|
|
6
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RegisterImportHandler(ImportExportHandler):
|
|
10
|
+
MODEL_APP_LABEL = "wbportfolio.Register"
|
|
11
|
+
|
|
12
|
+
def _deserialize(self, data: Dict[str, Any]):
|
|
13
|
+
if "opened" in data:
|
|
14
|
+
data["opened"] = datetime.strptime(data["opened"], "%Y-%m-%d").date()
|
|
15
|
+
if residence_id := data.get("residence"):
|
|
16
|
+
data["residence"] = Geography.countries.get(id=residence_id)
|
|
17
|
+
if citizenship_id := data.get("citizenship"):
|
|
18
|
+
data["citizenship"] = Geography.countries.get(id=citizenship_id)
|
|
19
|
+
if outlet_country_id := data.get("outlet_country"):
|
|
20
|
+
data["outlet_country"] = Geography.countries.get(id=outlet_country_id)
|
|
21
|
+
if custodian_country_id := data.get("custodian_country"):
|
|
22
|
+
data["custodian_country"] = Geography.countries.get(id=custodian_country_id)
|
|
23
|
+
|
|
24
|
+
outlet_city = data.pop("outlet_city", None)
|
|
25
|
+
if (outlet_country := data.get("outlet_country", None)) and outlet_city:
|
|
26
|
+
if res := outlet_country.lookup_descendants(outlet_city, level=Geography.Level.CITY.value):
|
|
27
|
+
data["outlet_city"] = res
|
|
28
|
+
|
|
29
|
+
custodian_city = data.pop("custodian_city", None)
|
|
30
|
+
if (custodian_country := data.get("custodian_country", None)) and custodian_city:
|
|
31
|
+
if res := custodian_country.lookup_descendants(custodian_city, level=Geography.Level.CITY.value):
|
|
32
|
+
data["custodian_city"] = res
|
|
33
|
+
|
|
34
|
+
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
35
|
+
self.import_source.log += "Get Register Instance."
|
|
36
|
+
self.import_source.log += f"Parameter: Reference={data['register_reference']}"
|
|
37
|
+
if isinstance(data, int):
|
|
38
|
+
return self.model.objects.get(id=data)
|
|
39
|
+
return self.model.objects.filter(register_reference=data["register_reference"]).first()
|
|
40
|
+
|
|
41
|
+
def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
|
|
42
|
+
self.import_source.log += "Create Register."
|
|
43
|
+
return self.model.objects.create(import_source=self.import_source, **data)
|