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,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)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
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
|
+
from wbfdm.models import InstrumentType
|
|
12
|
+
from wbportfolio.models.portfolio import Portfolio
|
|
13
|
+
from wbportfolio.models.products import update_outstanding_shares_as_task
|
|
14
|
+
from wbportfolio.utils import string_matching
|
|
15
|
+
|
|
16
|
+
from .register import RegisterImportHandler
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TradeImportHandler(ImportExportHandler):
|
|
20
|
+
MODEL_APP_LABEL: str = "wbportfolio.Trade"
|
|
21
|
+
|
|
22
|
+
def __init__(self, *args, **kwargs):
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
25
|
+
self.register_handler = RegisterImportHandler(self.import_source)
|
|
26
|
+
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
27
|
+
|
|
28
|
+
def _data_changed(self, _object, change_data: Dict[str, Any], initial_data: Dict[str, Any], **kwargs):
|
|
29
|
+
if (new_register := change_data.get("register")) and (current_register := _object.register):
|
|
30
|
+
# we remplace the register only if the new one gives us more information
|
|
31
|
+
if new_register.register_reference == current_register.global_register_reference:
|
|
32
|
+
del change_data["register"]
|
|
33
|
+
return super()._data_changed(_object, change_data, initial_data, **kwargs)
|
|
34
|
+
|
|
35
|
+
def _deserialize(self, data: Dict[str, Any]):
|
|
36
|
+
if external_identifier2 := data.get("external_identifier2", None):
|
|
37
|
+
data["external_identifier2"] = str(external_identifier2)
|
|
38
|
+
if transaction_date_str := data.get("transaction_date", None):
|
|
39
|
+
data["transaction_date"] = datetime.strptime(transaction_date_str, "%Y-%m-%d").date()
|
|
40
|
+
if value_date_str := data.get("value_date", None):
|
|
41
|
+
data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
|
|
42
|
+
if book_date_str := data.get("book_date", None):
|
|
43
|
+
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
44
|
+
if underlying_instrument := data.get("underlying_instrument", None):
|
|
45
|
+
data["underlying_instrument"] = self.instrument_handler.process_object(
|
|
46
|
+
underlying_instrument, only_security=False, read_only=True
|
|
47
|
+
)[0]
|
|
48
|
+
data["portfolio"] = Portfolio._get_or_create_portfolio(
|
|
49
|
+
self.instrument_handler, data.get("portfolio", data["underlying_instrument"])
|
|
50
|
+
)
|
|
51
|
+
if currency_data := data.get("currency", None):
|
|
52
|
+
data["currency"] = self.currency_handler.process_object(currency_data, read_only=True)[0]
|
|
53
|
+
|
|
54
|
+
if register_data := data.get("register", None):
|
|
55
|
+
data["register"] = self.register_handler.process_object(register_data)[0]
|
|
56
|
+
|
|
57
|
+
for field in self.model._meta.get_fields():
|
|
58
|
+
if not (value := data.get(field.name, None)) is None and isinstance(field, models.DecimalField):
|
|
59
|
+
q = 1 / (math.pow(10, 4))
|
|
60
|
+
data[field.name] = Decimal(value).quantize(Decimal(str(q)))
|
|
61
|
+
|
|
62
|
+
if "total_value" in data:
|
|
63
|
+
data["total_value"] = data["price"] * data["shares"]
|
|
64
|
+
if "total_value_fx_portfolio" in data:
|
|
65
|
+
data["total_value_fx_portfolio"] = data["price"] * data["shares"] * data["currency_fx_rate"]
|
|
66
|
+
data["marked_for_deletion"] = data.get("marked_for_deletion", False)
|
|
67
|
+
|
|
68
|
+
def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
|
|
69
|
+
if "transaction_date" not in data: # we might get only book date and not transaction date
|
|
70
|
+
data["transaction_date"] = data["book_date"]
|
|
71
|
+
|
|
72
|
+
return self.model.objects.create(**data, import_source=self.import_source)
|
|
73
|
+
|
|
74
|
+
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
75
|
+
self.import_source.log += "\nGet Trade Instance."
|
|
76
|
+
|
|
77
|
+
if transaction_date := data.get("transaction_date"):
|
|
78
|
+
dates_lookup = {"transaction_date": transaction_date}
|
|
79
|
+
elif book_date := data.get("book_date"):
|
|
80
|
+
dates_lookup = {"book_date": book_date}
|
|
81
|
+
else:
|
|
82
|
+
raise DeserializationError("date lookup is missing from data")
|
|
83
|
+
self.import_source.log += f"\nParameter: Product={data['underlying_instrument']} Trade-Date={transaction_date} Shares={data['shares']}"
|
|
84
|
+
|
|
85
|
+
if history.exists():
|
|
86
|
+
queryset = history
|
|
87
|
+
else:
|
|
88
|
+
queryset = self.model.objects.filter(marked_for_deletion=False)
|
|
89
|
+
|
|
90
|
+
queryset = queryset.filter(
|
|
91
|
+
models.Q(underlying_instrument=data["underlying_instrument"])
|
|
92
|
+
& models.Q(**dates_lookup)
|
|
93
|
+
& models.Q(shares=data["shares"])
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if _id := data.get("id", None):
|
|
97
|
+
self.import_source.log += f"ID {_id} provided -> Load CustomerTrade"
|
|
98
|
+
return self.model.objects.get(id=_id)
|
|
99
|
+
# We need to check for external identifiers
|
|
100
|
+
if external_id := data.get("external_id"):
|
|
101
|
+
self.import_source.log += f"\nExternal Identifier used: {external_id}"
|
|
102
|
+
queryset = queryset.filter(external_id=external_id)
|
|
103
|
+
if queryset.count() == 1:
|
|
104
|
+
self.import_source.log += f"External ID {external_id} provided -> Load CustomerTrade"
|
|
105
|
+
return queryset.first()
|
|
106
|
+
|
|
107
|
+
if portfolio := data.get("portfolio", None):
|
|
108
|
+
queryset = queryset.filter(portfolio=portfolio)
|
|
109
|
+
if queryset.exists():
|
|
110
|
+
if bank := data["bank"]:
|
|
111
|
+
self.import_source.log += (
|
|
112
|
+
f"\n{queryset.count()} Trades found. The bank will tried to be matched against {bank}"
|
|
113
|
+
)
|
|
114
|
+
if queryset.filter(bank=bank).count() >= 1: # exact match
|
|
115
|
+
queryset = queryset.filter(bank=bank)
|
|
116
|
+
else:
|
|
117
|
+
best_result = string_matching(bank, queryset.values_list("bank", flat=True))
|
|
118
|
+
if best_result[1] >= 80:
|
|
119
|
+
possible_trades = queryset.filter(bank=best_result[0])
|
|
120
|
+
if possible_trades.count() > 1 and possible_trades.filter(claims__isnull=False).exists():
|
|
121
|
+
possible_trades = possible_trades.filter(claims__isnull=False)
|
|
122
|
+
if (
|
|
123
|
+
possible_trades.count() >= 1
|
|
124
|
+
): # If count is greater than 1, we get exact match so we return either trades
|
|
125
|
+
queryset = possible_trades
|
|
126
|
+
if (queryset.count() > 1) and (price := data.get("price", None)):
|
|
127
|
+
if queryset.filter(price=price).count() == 1:
|
|
128
|
+
queryset = queryset.filter(price=price)
|
|
129
|
+
if queryset.exists():
|
|
130
|
+
# We try to filter by price as well
|
|
131
|
+
trade = queryset.first()
|
|
132
|
+
if queryset.count() == 1:
|
|
133
|
+
self.import_source.log += f"\nOne Trade found: {trade}"
|
|
134
|
+
if queryset.count() > 1:
|
|
135
|
+
self.import_source.log += f"\nMultiple similar Trades found (returning first trade): {trade}"
|
|
136
|
+
return trade
|
|
137
|
+
self.import_source.log += "\nNo trade was successfully matched."
|
|
138
|
+
|
|
139
|
+
def _get_history(self, history: Dict[str, Any]) -> models.QuerySet:
|
|
140
|
+
trades = self.model.objects.filter(
|
|
141
|
+
exclude_from_history=False,
|
|
142
|
+
pending=False,
|
|
143
|
+
transaction_subtype__in=[
|
|
144
|
+
self.model.Type.SUBSCRIPTION,
|
|
145
|
+
self.model.Type.REDEMPTION,
|
|
146
|
+
], # we cannot exclude marked for deleted trade because otherwise they are never consider in the history
|
|
147
|
+
)
|
|
148
|
+
if transaction_date := history.get("transaction_date"):
|
|
149
|
+
trades = trades.filter(transaction_date__lte=transaction_date)
|
|
150
|
+
elif book_date := history.get("book_date"):
|
|
151
|
+
trades = trades.filter(book_date__lte=book_date)
|
|
152
|
+
if "underlying_instrument" in history:
|
|
153
|
+
trades = trades.filter(underlying_instrument__id=history["underlying_instrument"])
|
|
154
|
+
elif "underlying_instruments" in history:
|
|
155
|
+
trades = trades.filter(underlying_instrument__id__in=history["underlying_instruments"])
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError("We cannot estimate history without at least the underlying instrument")
|
|
158
|
+
return trades
|
|
159
|
+
|
|
160
|
+
def _post_processing_objects(
|
|
161
|
+
self,
|
|
162
|
+
created_objs: list[models.Model],
|
|
163
|
+
modified_objs: list[models.Model],
|
|
164
|
+
unmodified_objs: list[models.Model],
|
|
165
|
+
):
|
|
166
|
+
for instrument in set(
|
|
167
|
+
map(lambda x: x.underlying_instrument, filter(lambda t: t.is_customer_trade, created_objs + modified_objs))
|
|
168
|
+
):
|
|
169
|
+
if instrument.instrument_type.key == "product":
|
|
170
|
+
update_outstanding_shares_as_task.delay(instrument.id)
|
|
171
|
+
|
|
172
|
+
def _post_processing_updated_object(self, _object):
|
|
173
|
+
if _object.marked_for_deletion:
|
|
174
|
+
_object.marked_for_deletion = False
|
|
175
|
+
_object.save()
|
|
176
|
+
self.import_source.log += "\nMarked for deletion reverted"
|
|
177
|
+
if _object.underlying_instrument.instrument_type == InstrumentType.PRODUCT:
|
|
178
|
+
_object.link_to_internal_trade()
|
|
179
|
+
|
|
180
|
+
def _post_processing_created_object(self, _object):
|
|
181
|
+
self._post_processing_updated_object(_object)
|
|
182
|
+
|
|
183
|
+
def _post_processing_history(self, history: models.QuerySet):
|
|
184
|
+
self.import_source.log += "===================="
|
|
185
|
+
self.import_source.log += "It was a historical import and the following Trades have to be deleted:"
|
|
186
|
+
for trade in history.order_by("transaction_date"):
|
|
187
|
+
self.import_source.log += (
|
|
188
|
+
f"{trade.transaction_date:%d.%m.%Y}: {trade.shares} {trade.bank} ==> Marked for deletion"
|
|
189
|
+
)
|
|
190
|
+
trade.marked_for_deletion = True
|
|
191
|
+
trade.save()
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse(import_source):
|
|
6
|
+
data = []
|
|
7
|
+
if (
|
|
8
|
+
(data_backend := import_source.source.data_backend)
|
|
9
|
+
and (backend_class := data_backend.backend_class)
|
|
10
|
+
and (default_mapping := getattr(backend_class, "DEFAULT_MAPPING"))
|
|
11
|
+
):
|
|
12
|
+
df = pd.read_json(import_source.file, orient="records")
|
|
13
|
+
if not df.empty:
|
|
14
|
+
if date_label := getattr(backend_class, "DATE_LABEL", None):
|
|
15
|
+
df[date_label] = pd.to_datetime(df[date_label])
|
|
16
|
+
df = df.sort_values(by=date_label, ascending=True)
|
|
17
|
+
if reindex_method := getattr(backend_class, "REINDEX_METHOD", None):
|
|
18
|
+
df = df.set_index(date_label)
|
|
19
|
+
timeline = pd.date_range(start=df.index.min(), end=df.index.max(), freq="B")
|
|
20
|
+
df = df.reindex(timeline, method=reindex_method)
|
|
21
|
+
df = df.reset_index(names=date_label)
|
|
22
|
+
df[date_label] = df[date_label].dt.strftime("%Y-%m-%d")
|
|
23
|
+
df = df.replace([np.inf, -np.inf, np.nan], None).rename(columns=default_mapping)
|
|
24
|
+
df = df.drop(columns=df.columns.difference([*default_mapping.values()]))
|
|
25
|
+
|
|
26
|
+
if none_nullable_fields := getattr(backend_class, "NONE_NULLABLE_FIELDS", None):
|
|
27
|
+
df = df.dropna(subset=none_nullable_fields, how="any")
|
|
28
|
+
|
|
29
|
+
data = df.to_dict("records")
|
|
30
|
+
return {"data": data}
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from wbportfolio.import_export.utils import convert_string_to_number
|
|
7
|
+
from wbportfolio.models import Product, Trade
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def file_name_parse(file_name):
|
|
11
|
+
dates = re.findall("([0-9]{8})", file_name)
|
|
12
|
+
|
|
13
|
+
assert len(dates) > 0
|
|
14
|
+
|
|
15
|
+
parts_dict = {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
|
|
16
|
+
|
|
17
|
+
isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
|
|
18
|
+
|
|
19
|
+
if len(isin) > 0:
|
|
20
|
+
parts_dict["isin"] = isin[0]
|
|
21
|
+
|
|
22
|
+
return parts_dict
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse(import_source):
|
|
26
|
+
# Load files into a CSV DictReader
|
|
27
|
+
df = pd.read_csv(import_source.file, encoding="latin1", delimiter=",")
|
|
28
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
29
|
+
# Parse the Parts of the filename into the different parts
|
|
30
|
+
parts = file_name_parse(import_source.file.name)
|
|
31
|
+
df["Trade Date"] = pd.to_datetime(df["Trade Date"], format="%Y%m%d")
|
|
32
|
+
# Get the valuation date from the parts list
|
|
33
|
+
valuation_date = parts["valuation_date"]
|
|
34
|
+
|
|
35
|
+
# Iterate through the CSV File and parse the data into a list
|
|
36
|
+
data = list()
|
|
37
|
+
for nominal_data in df.to_dict("records"):
|
|
38
|
+
product = Product.objects.get(isin=nominal_data["ISIN"])
|
|
39
|
+
shares = convert_string_to_number(nominal_data["Quantity"])
|
|
40
|
+
|
|
41
|
+
# Check whether it is a buy or a sell and convert the value correspondely
|
|
42
|
+
shares = shares if nominal_data["Side"] == "S" else shares * -1
|
|
43
|
+
portfolio = product.primary_portfolio
|
|
44
|
+
data.append(
|
|
45
|
+
{
|
|
46
|
+
"underlying_instrument": {"id": product.id, "instrument_type": "product"},
|
|
47
|
+
"transaction_date": nominal_data["Trade Date"].strftime("%Y-%m-%d"),
|
|
48
|
+
"shares": shares,
|
|
49
|
+
"portfolio": portfolio.id,
|
|
50
|
+
# 'currency': product.currency.key,
|
|
51
|
+
"transaction_subtype": Trade.Type.REDEMPTION if shares < 0 else Trade.Type.SUBSCRIPTION,
|
|
52
|
+
"bank": nominal_data["CounterParty Name"],
|
|
53
|
+
"price": convert_string_to_number(nominal_data["Price"]),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
import_data = {"data": data}
|
|
57
|
+
if "isin" in parts:
|
|
58
|
+
product = Product.objects.get(isin=parts["isin"])
|
|
59
|
+
import_data["history"] = {
|
|
60
|
+
"underlying_instrument": product.id,
|
|
61
|
+
"transaction_date": valuation_date.strftime("%Y-%m-%d"),
|
|
62
|
+
}
|
|
63
|
+
return import_data
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from wbportfolio.import_export.utils import convert_string_to_number
|
|
8
|
+
from wbportfolio.models import Fees, Product
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("importers.parsers.jpmorgan.fee")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def file_name_parse(file_name):
|
|
14
|
+
dates = re.findall("([0-9]{8})", file_name)
|
|
15
|
+
|
|
16
|
+
assert len(dates) == 1, "Not exactly 1 date found in the filename"
|
|
17
|
+
|
|
18
|
+
return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse(import_source):
|
|
22
|
+
df = pd.read_csv(import_source.file, encoding="utf-16", delimiter=",")
|
|
23
|
+
df["Date"] = pd.to_datetime(df["Date"])
|
|
24
|
+
|
|
25
|
+
columns_fees = df.columns.difference(["ISIN", "CCY"])
|
|
26
|
+
df[columns_fees] = df[columns_fees].where(pd.notnull(df[columns_fees]), 0)
|
|
27
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
28
|
+
|
|
29
|
+
# Iterate through the CSV File and parse the data into a list
|
|
30
|
+
data = list()
|
|
31
|
+
|
|
32
|
+
for fee_data in df.to_dict("records"):
|
|
33
|
+
product = Product.objects.get(isin=fee_data["ISIN"])
|
|
34
|
+
fee_date = fee_data["Date"]
|
|
35
|
+
base_data = {
|
|
36
|
+
"portfolio": product.primary_portfolio.id,
|
|
37
|
+
"linked_product": product.id,
|
|
38
|
+
"transaction_date": fee_date.strftime("%Y-%m-%d"),
|
|
39
|
+
"calculated": False,
|
|
40
|
+
}
|
|
41
|
+
data.append(
|
|
42
|
+
{
|
|
43
|
+
"transaction_subtype": Fees.Type.PERFORMANCE,
|
|
44
|
+
"total_value": round(convert_string_to_number(fee_data.get("Perf_Fee * Units", 0)), 4),
|
|
45
|
+
"total_value_gross": round(convert_string_to_number(fee_data.get("Perf_Fee * Units", 0)), 4),
|
|
46
|
+
**base_data,
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
data.append(
|
|
50
|
+
{
|
|
51
|
+
"transaction_subtype": Fees.Type.MANAGEMENT,
|
|
52
|
+
"total_value": round(convert_string_to_number(fee_data.get("Mgmt_Fee * Units", 0)), 4),
|
|
53
|
+
**base_data,
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
data.append(
|
|
57
|
+
{
|
|
58
|
+
"transaction_subtype": Fees.Type.ISSUER,
|
|
59
|
+
"total_value": round(convert_string_to_number(fee_data.get("JPM_Fee * Units", 0)), 4),
|
|
60
|
+
**base_data,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return {"data": data}
|