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,192 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from django.forms.models import model_to_dict
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from pandas.tseries.offsets import BDay
|
|
7
|
+
from wbfdm.import_export.handlers.instrument_price import InstrumentPriceImportHandler
|
|
8
|
+
from wbfdm.models import InstrumentPrice
|
|
9
|
+
from wbportfolio.import_export.handlers.asset_position import AssetPositionImportHandler
|
|
10
|
+
from wbportfolio.import_export.handlers.fees import FeesImportHandler
|
|
11
|
+
from wbportfolio.import_export.handlers.trade import TradeImportHandler
|
|
12
|
+
from wbportfolio.models import AssetPosition, Fees, Trade
|
|
13
|
+
|
|
14
|
+
fake = Faker()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.django_db
|
|
18
|
+
class TestImportMixinModel:
|
|
19
|
+
def test_import_trade(self, import_source, product, portfolio, trade_factory):
|
|
20
|
+
def serialize(trade):
|
|
21
|
+
data = model_to_dict(trade)
|
|
22
|
+
data["transaction_date"] = trade.transaction_date.strftime("%Y-%m-%d")
|
|
23
|
+
data["value_date"] = trade.value_date.strftime("%Y-%m-%d")
|
|
24
|
+
data["underlying_instrument"] = {"id": product.id}
|
|
25
|
+
data["portfolio"] = portfolio.id
|
|
26
|
+
data["currency"] = {"key": portfolio.currency.key}
|
|
27
|
+
del data["id"]
|
|
28
|
+
del data["import_source"]
|
|
29
|
+
del data["transaction_ptr"]
|
|
30
|
+
return data
|
|
31
|
+
|
|
32
|
+
trade = trade_factory.build()
|
|
33
|
+
data = {"data": [serialize(trade)]}
|
|
34
|
+
handler = TradeImportHandler(import_source)
|
|
35
|
+
|
|
36
|
+
# Import non existing data
|
|
37
|
+
handler.process(data)
|
|
38
|
+
assert Trade.objects.count() == 1
|
|
39
|
+
|
|
40
|
+
# Import already existing data
|
|
41
|
+
# import_source.data['data'][0]['shares'] *= 2
|
|
42
|
+
|
|
43
|
+
handler.process(data)
|
|
44
|
+
assert Trade.objects.count() == 1
|
|
45
|
+
|
|
46
|
+
def test_import_price(self, import_source, product, instrument_price_factory, instrument):
|
|
47
|
+
def serialize(val):
|
|
48
|
+
data = model_to_dict(val)
|
|
49
|
+
for k, v in data.items():
|
|
50
|
+
if isinstance(v, Decimal):
|
|
51
|
+
data[k] = float(v)
|
|
52
|
+
data["date"] = f"{val.date:%Y-%m-%d}"
|
|
53
|
+
data["instrument"] = {"instrument_type": "product", "id": product.id}
|
|
54
|
+
del data["id"]
|
|
55
|
+
del data["import_source"]
|
|
56
|
+
del data["currency_fx_rate_to_usd"]
|
|
57
|
+
return data
|
|
58
|
+
|
|
59
|
+
val = instrument_price_factory.build(instrument=instrument)
|
|
60
|
+
data = {"data": [serialize(val)]}
|
|
61
|
+
handler = InstrumentPriceImportHandler(import_source)
|
|
62
|
+
# Import non existing data
|
|
63
|
+
handler.process(data)
|
|
64
|
+
assert InstrumentPrice.objects.count() == 1
|
|
65
|
+
|
|
66
|
+
# Import already existing data
|
|
67
|
+
data["data"][0]["net_value"] *= 2
|
|
68
|
+
handler.process(data)
|
|
69
|
+
assert InstrumentPrice.objects.count() == 1
|
|
70
|
+
|
|
71
|
+
def test_import_fees(self, import_source, product_factory, portfolio, cash_factory, fees_factory):
|
|
72
|
+
product = product_factory.create()
|
|
73
|
+
portfolio = product.primary_portfolio
|
|
74
|
+
cash_factory.create(currency=portfolio.currency)
|
|
75
|
+
|
|
76
|
+
def serialize(fees):
|
|
77
|
+
data = model_to_dict(fees)
|
|
78
|
+
data["transaction_date"] = fees.transaction_date.strftime("%Y-%m-%d")
|
|
79
|
+
data["value_date"] = fees.value_date.strftime("%Y-%m-%d")
|
|
80
|
+
data["portfolio"] = portfolio.id
|
|
81
|
+
data["linked_product"] = product.id
|
|
82
|
+
data["portfolio"] = portfolio.id
|
|
83
|
+
data["currency"] = {"key": portfolio.currency.key}
|
|
84
|
+
del data["calculated"]
|
|
85
|
+
del data["id"]
|
|
86
|
+
del data["import_source"]
|
|
87
|
+
del data["transaction_ptr"]
|
|
88
|
+
return data
|
|
89
|
+
|
|
90
|
+
fees = fees_factory.build(calculated=False)
|
|
91
|
+
data = {"data": [serialize(fees)]}
|
|
92
|
+
handler = FeesImportHandler(import_source)
|
|
93
|
+
# Import non existing data
|
|
94
|
+
handler.process(data)
|
|
95
|
+
assert Fees.objects.count() == 1
|
|
96
|
+
|
|
97
|
+
# Import already existing data
|
|
98
|
+
data["data"][0]["total_value"] *= 2
|
|
99
|
+
handler.process(data)
|
|
100
|
+
assert Fees.objects.count() == 1
|
|
101
|
+
|
|
102
|
+
def _serialize_position(self, pos, instrument, underlying):
|
|
103
|
+
return {
|
|
104
|
+
"date": f"{pos.date:%Y-%m-%d}",
|
|
105
|
+
"initial_shares": pos.initial_shares,
|
|
106
|
+
"initial_price": pos.initial_price,
|
|
107
|
+
"weighting": pos.weighting,
|
|
108
|
+
"initial_currency_fx_rate": Decimal(1),
|
|
109
|
+
"asset_valuation_date": f"{pos.asset_valuation_date:%Y-%m-%d}",
|
|
110
|
+
"currency": {"key": underlying.currency.key},
|
|
111
|
+
"portfolio": {"id": instrument.id, "instrument_type": instrument.security_instrument_type.key},
|
|
112
|
+
"underlying_instrument": {
|
|
113
|
+
"instrument_type": underlying.instrument_type.key,
|
|
114
|
+
"currency": {"key": underlying.currency.key},
|
|
115
|
+
"ticker": underlying.ticker,
|
|
116
|
+
"name": underlying.name,
|
|
117
|
+
"exchange": {"bbg_composite": "US"},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@pytest.mark.parametrize("val_date", [(fake.date_object())])
|
|
122
|
+
def test_import_assetposition_product(
|
|
123
|
+
self,
|
|
124
|
+
val_date,
|
|
125
|
+
import_source,
|
|
126
|
+
product_factory,
|
|
127
|
+
currency,
|
|
128
|
+
equity_factory,
|
|
129
|
+
index_factory,
|
|
130
|
+
cash_factory,
|
|
131
|
+
asset_position_factory,
|
|
132
|
+
):
|
|
133
|
+
val_date = (val_date - BDay(0)).date()
|
|
134
|
+
product_portfolio = product_factory.create()
|
|
135
|
+
instruments = [
|
|
136
|
+
equity_factory.create(currency=currency, instrument_type__name="Equity"),
|
|
137
|
+
cash_factory.create(currency=currency, instrument_type__name="Cash"),
|
|
138
|
+
product_factory.create(currency=currency, instrument_type__name="Product"),
|
|
139
|
+
index_factory.create(currency=currency, instrument_type__name="Index"),
|
|
140
|
+
]
|
|
141
|
+
data = {
|
|
142
|
+
"data": [
|
|
143
|
+
self._serialize_position(
|
|
144
|
+
asset_position_factory.build(date=val_date, underlying_instrument=instrument),
|
|
145
|
+
product_portfolio,
|
|
146
|
+
instrument,
|
|
147
|
+
)
|
|
148
|
+
for instrument in instruments
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
handler = AssetPositionImportHandler(import_source)
|
|
152
|
+
|
|
153
|
+
# Import non existing data
|
|
154
|
+
handler.process(data)
|
|
155
|
+
for instrument in instruments:
|
|
156
|
+
# we check that the position was created
|
|
157
|
+
a = AssetPosition.objects.get(
|
|
158
|
+
underlying_instrument=instrument, date=val_date, portfolio=product_portfolio.portfolio
|
|
159
|
+
)
|
|
160
|
+
assert a
|
|
161
|
+
# and as this is an import handle by the assetposition handler, we expect the underlying instrument price to be created from the position initial price
|
|
162
|
+
assert InstrumentPrice.objects.get(
|
|
163
|
+
instrument=instrument, date=val_date, calculated=False, net_value=a.initial_price
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Import already existing data
|
|
167
|
+
data["data"][0]["initial_price"] *= 2
|
|
168
|
+
|
|
169
|
+
handler.process(data)
|
|
170
|
+
assert AssetPosition.objects.count() == 4
|
|
171
|
+
|
|
172
|
+
def test_import_assetposition_product_group(
|
|
173
|
+
self, import_source, product_group, currency, equity, asset_position_factory
|
|
174
|
+
):
|
|
175
|
+
positions = asset_position_factory.build(underlying_instrument=equity)
|
|
176
|
+
data = {"data": [self._serialize_position(positions, product_group, equity)]}
|
|
177
|
+
|
|
178
|
+
# Import non existing data
|
|
179
|
+
handler = AssetPositionImportHandler(import_source)
|
|
180
|
+
|
|
181
|
+
handler.process(data)
|
|
182
|
+
assert AssetPosition.objects.count() == 1
|
|
183
|
+
|
|
184
|
+
def test_import_assetposition_index(self, import_source, index, currency, equity, asset_position_factory):
|
|
185
|
+
positions = asset_position_factory.build(underlying_instrument=equity)
|
|
186
|
+
data = {"data": [self._serialize_position(positions, index, equity)]}
|
|
187
|
+
|
|
188
|
+
# Import non existing data
|
|
189
|
+
handler = AssetPositionImportHandler(import_source)
|
|
190
|
+
|
|
191
|
+
handler.process(data)
|
|
192
|
+
assert AssetPosition.objects.count() == 1
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import pytest
|
|
5
|
+
from faker import Faker
|
|
6
|
+
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.django_db
|
|
11
|
+
class TestInstrumentMixin:
|
|
12
|
+
def test_get_latest_valid_price(self, product, instrument_price_factory):
|
|
13
|
+
v1 = instrument_price_factory.create(instrument=product)
|
|
14
|
+
v2 = instrument_price_factory.create(instrument=product)
|
|
15
|
+
instrument_price_factory.create(instrument=product, calculated=True)
|
|
16
|
+
|
|
17
|
+
assert product.get_latest_valid_price() == v2
|
|
18
|
+
assert product.get_latest_valid_price(v1.date) == v1
|
|
19
|
+
|
|
20
|
+
def test_get_earliest_valid_price(self, product, instrument_price_factory):
|
|
21
|
+
instrument_price_factory.create(instrument=product, calculated=True)
|
|
22
|
+
v2 = instrument_price_factory.create(instrument=product)
|
|
23
|
+
v3 = instrument_price_factory.create(instrument=product)
|
|
24
|
+
|
|
25
|
+
assert product.get_earliest_valid_price() == v2
|
|
26
|
+
assert product.get_earliest_valid_price(v3.date) == v3
|
|
27
|
+
|
|
28
|
+
def test_get_price_range(self, product, instrument_price_factory):
|
|
29
|
+
v_low = instrument_price_factory.create(instrument=product, net_value=1)
|
|
30
|
+
instrument_price_factory.create(instrument=product, net_value=10)
|
|
31
|
+
v_high = instrument_price_factory.create(instrument=product, net_value=100)
|
|
32
|
+
|
|
33
|
+
res = product.get_price_range()
|
|
34
|
+
assert res["high"]["price"] == pytest.approx(float(v_high.net_value), rel=1e-4)
|
|
35
|
+
assert res["high"]["date"] == v_high.date
|
|
36
|
+
assert res["low"]["price"] == pytest.approx(float(v_low.net_value), rel=1e-4)
|
|
37
|
+
assert res["low"]["date"] == v_low.date
|
|
38
|
+
assert product.get_price_range(v_low.date - timedelta(days=1)) == dict()
|
|
39
|
+
|
|
40
|
+
def test_get_cumulative_shares(self, weekday, product, customer_trade_factory):
|
|
41
|
+
from_date = weekday
|
|
42
|
+
to_date = (weekday + pd.tseries.offsets.BDay(1)).date()
|
|
43
|
+
assert product.get_cumulative_shares(from_date, to_date).to_list() == [0.0, 0.0]
|
|
44
|
+
|
|
45
|
+
trade = customer_trade_factory.create(
|
|
46
|
+
transaction_date=weekday, underlying_instrument=product, portfolio=product.primary_portfolio
|
|
47
|
+
)
|
|
48
|
+
assert product.get_cumulative_shares(from_date, to_date).to_list() == [0.0, float(trade.shares)]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from pandas.tseries.offsets import BDay
|
|
4
|
+
from wbfdm.factories import InstrumentFactory
|
|
5
|
+
from wbfdm.models import InstrumentPrice
|
|
6
|
+
from wbportfolio.models import (
|
|
7
|
+
Adjustment,
|
|
8
|
+
PortfolioInstrumentPreferredClassificationThroughModel,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
fake = Faker()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.django_db
|
|
15
|
+
class TestMergeInstrument:
|
|
16
|
+
@pytest.fixture()
|
|
17
|
+
def merged_instrument(self):
|
|
18
|
+
return InstrumentFactory.create()
|
|
19
|
+
|
|
20
|
+
@pytest.fixture()
|
|
21
|
+
def main_instrument(self):
|
|
22
|
+
return InstrumentFactory.create()
|
|
23
|
+
|
|
24
|
+
def test_assets(
|
|
25
|
+
self, main_instrument, merged_instrument, asset_position_factory, instrument_price_factory, weekday
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Test if the asset positions attached to the merged instrument are forwarded to the main instrument as well as the underlying instrument price
|
|
29
|
+
"""
|
|
30
|
+
p1 = instrument_price_factory.create(instrument=main_instrument, calculated=False, date=weekday)
|
|
31
|
+
p2 = instrument_price_factory.create(instrument=merged_instrument, calculated=False, date=weekday)
|
|
32
|
+
|
|
33
|
+
a1 = asset_position_factory.create(underlying_instrument=main_instrument, date=weekday)
|
|
34
|
+
a2 = asset_position_factory.create(underlying_instrument=merged_instrument, date=weekday)
|
|
35
|
+
|
|
36
|
+
assert a1.underlying_instrument_price == p1
|
|
37
|
+
assert a2.underlying_instrument_price == p2
|
|
38
|
+
main_instrument.merge(merged_instrument)
|
|
39
|
+
a2.refresh_from_db()
|
|
40
|
+
a1.refresh_from_db()
|
|
41
|
+
|
|
42
|
+
assert a1.underlying_instrument == main_instrument # Make sure this doesn't change
|
|
43
|
+
assert a1.underlying_instrument_price == p1 # Make sure this doesn't change
|
|
44
|
+
assert a2.underlying_instrument == main_instrument
|
|
45
|
+
assert a2.underlying_instrument_price == p1
|
|
46
|
+
|
|
47
|
+
def test_roles(self, main_instrument, merged_instrument, product_portfolio_role_factory):
|
|
48
|
+
"""
|
|
49
|
+
Test if the role attached to the merged instrument are forwarded to the main instrument
|
|
50
|
+
"""
|
|
51
|
+
role = product_portfolio_role_factory.create(instrument=merged_instrument)
|
|
52
|
+
main_instrument.merge(merged_instrument)
|
|
53
|
+
role.refresh_from_db()
|
|
54
|
+
assert role.instrument == main_instrument
|
|
55
|
+
|
|
56
|
+
def test_classifications(self, main_instrument, merged_instrument, portfolio_factory, classification_factory):
|
|
57
|
+
"""
|
|
58
|
+
Test if preferred classification are forwarded to the main instrument
|
|
59
|
+
"""
|
|
60
|
+
main_portfolio = portfolio_factory.create()
|
|
61
|
+
merged_portfolio = portfolio_factory.create()
|
|
62
|
+
|
|
63
|
+
PortfolioInstrumentPreferredClassificationThroughModel.objects.create(
|
|
64
|
+
instrument=main_instrument, portfolio=main_portfolio, classification=classification_factory.create()
|
|
65
|
+
)
|
|
66
|
+
PortfolioInstrumentPreferredClassificationThroughModel.objects.create(
|
|
67
|
+
instrument=merged_instrument, portfolio=merged_portfolio, classification=classification_factory.create()
|
|
68
|
+
)
|
|
69
|
+
main_instrument.merge(merged_instrument)
|
|
70
|
+
|
|
71
|
+
assert set(main_instrument.preferred_portfolio_classifications.all()) == set(
|
|
72
|
+
[main_portfolio, merged_portfolio]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def test_transactions(self, main_instrument, merged_instrument, trade_factory):
|
|
76
|
+
"""
|
|
77
|
+
Test if the attached transactions to the merged instrument are forwarded to the main instrument
|
|
78
|
+
"""
|
|
79
|
+
merged_t = trade_factory.create(underlying_instrument=merged_instrument)
|
|
80
|
+
main_t = trade_factory.create(underlying_instrument=main_instrument)
|
|
81
|
+
|
|
82
|
+
main_instrument.merge(merged_instrument)
|
|
83
|
+
merged_t.refresh_from_db()
|
|
84
|
+
main_t.refresh_from_db()
|
|
85
|
+
|
|
86
|
+
merged_t.underlying_instrument = main_instrument
|
|
87
|
+
main_t.underlying_instrument = main_instrument # make sure this does not change
|
|
88
|
+
|
|
89
|
+
def test_instrument_prices(self, main_instrument, merged_instrument, instrument_price_factory, weekday):
|
|
90
|
+
"""
|
|
91
|
+
Test if the attached prices to the merged instrument are forwarded to the main instrument but does not overlaps them (if price already exists, the overlapping merged price are simply deleted)
|
|
92
|
+
"""
|
|
93
|
+
main_p1 = instrument_price_factory.create(instrument=main_instrument, calculated=False, date=weekday)
|
|
94
|
+
|
|
95
|
+
merged_p1 = instrument_price_factory.create(instrument=merged_instrument, calculated=False, date=weekday)
|
|
96
|
+
merged_p2 = instrument_price_factory.create(
|
|
97
|
+
instrument=merged_instrument, calculated=False, date=weekday + BDay(1)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert main_instrument.valuations.count() == 1
|
|
101
|
+
main_instrument.merge(merged_instrument)
|
|
102
|
+
|
|
103
|
+
assert (
|
|
104
|
+
main_instrument.valuations.get(date=weekday).net_value == main_p1.net_value
|
|
105
|
+
) # Check that the existing price was not overlaps by the merged instrument price at that day
|
|
106
|
+
assert (
|
|
107
|
+
main_instrument.valuations.get(date=weekday + BDay(1)).net_value == merged_p2.net_value
|
|
108
|
+
) # Check that the price not existing from merged instrument was appended to the main instrument serie
|
|
109
|
+
with pytest.raises(InstrumentPrice.DoesNotExist):
|
|
110
|
+
merged_p1.refresh_from_db()
|
|
111
|
+
|
|
112
|
+
def test_adjustments(self, main_instrument, merged_instrument, adjustment_factory, weekday):
|
|
113
|
+
"""
|
|
114
|
+
Test if the attached adjustments to the merged instrument are forwarded to the main instrument but does not overlaps them (if split already exists, the overlapping merged split are simply deleted)
|
|
115
|
+
"""
|
|
116
|
+
main_s1 = adjustment_factory.create(instrument=main_instrument, date=weekday)
|
|
117
|
+
|
|
118
|
+
merged_s1 = adjustment_factory.create(instrument=merged_instrument, date=weekday)
|
|
119
|
+
merged_s2 = adjustment_factory.create(instrument=merged_instrument, date=weekday + BDay(1))
|
|
120
|
+
merged_s2.refresh_from_db() # We refresh from db to cast the decimal factor to its proper number of decimal places (kind of stupid but..)
|
|
121
|
+
main_s1.refresh_from_db()
|
|
122
|
+
|
|
123
|
+
assert main_instrument.pms_adjustments.count() == 1
|
|
124
|
+
main_instrument.merge(merged_instrument)
|
|
125
|
+
|
|
126
|
+
assert (
|
|
127
|
+
main_instrument.pms_adjustments.get(date=weekday).factor == main_s1.factor
|
|
128
|
+
) # Check that the existing split was not overlaps by the merged instrument split at that day
|
|
129
|
+
assert (
|
|
130
|
+
main_instrument.pms_adjustments.get(date=weekday + BDay(1)).factor == merged_s2.factor
|
|
131
|
+
) # Check that the split not existing from merged instrument was appended to the main instrument serie
|
|
132
|
+
with pytest.raises(Adjustment.DoesNotExist):
|
|
133
|
+
merged_s1.refresh_from_db()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from django.db.utils import IntegrityError
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from wbportfolio.factories import DailyPortfolioCashFlowFactory
|
|
10
|
+
from wbportfolio.models.portfolio import Portfolio
|
|
11
|
+
from wbportfolio.models.portfolio_cash_flow import DailyPortfolioCashFlow
|
|
12
|
+
from wbportfolio.models.portfolio_cash_targets import PortfolioCashTarget
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.django_db
|
|
16
|
+
class TestDailyPortfolioCashFlowFactory:
|
|
17
|
+
def test_factory(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
18
|
+
assert daily_portfolio_cash_flow.pk is not None
|
|
19
|
+
|
|
20
|
+
def test_constraint_unique_value_date_portfolio(
|
|
21
|
+
self, portfolio: "Portfolio", daily_portfolio_cash_flow_factory: "DailyPortfolioCashFlowFactory"
|
|
22
|
+
):
|
|
23
|
+
cf1 = daily_portfolio_cash_flow_factory.create(portfolio=portfolio)
|
|
24
|
+
cf2 = daily_portfolio_cash_flow_factory.create(portfolio=portfolio)
|
|
25
|
+
|
|
26
|
+
cf2.value_date = cf1.value_date
|
|
27
|
+
with pytest.raises(IntegrityError):
|
|
28
|
+
cf2.save()
|
|
29
|
+
|
|
30
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__pending", [False])
|
|
31
|
+
def test_estimated_total_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
32
|
+
dpcf = daily_portfolio_cash_flow
|
|
33
|
+
assert dpcf.estimated_total_assets == dpcf.total_assets
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__pending", [True])
|
|
36
|
+
def test_estimated_total_assets_while_pending(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
37
|
+
dpcf = daily_portfolio_cash_flow
|
|
38
|
+
assert dpcf.estimated_total_assets == dpcf.total_assets + dpcf.cash_flow_forecast
|
|
39
|
+
|
|
40
|
+
def test_cash_flow_asset_ratio(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
41
|
+
dpcf = daily_portfolio_cash_flow
|
|
42
|
+
assert dpcf.cash_flow_asset_ratio == dpcf.cash_flow_forecast / dpcf.estimated_total_assets
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__total_assets", [Decimal(0)])
|
|
45
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__cash_flow_forecast", [Decimal(0)])
|
|
46
|
+
def test_cash_flow_asset_ratio_zero_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
47
|
+
assert daily_portfolio_cash_flow.cash_flow_asset_ratio == Decimal(0)
|
|
48
|
+
|
|
49
|
+
def test_cash_pct(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
50
|
+
dpct = daily_portfolio_cash_flow
|
|
51
|
+
assert dpct.cash_pct == dpct.cash / dpct.estimated_total_assets
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__total_assets", [Decimal(0)])
|
|
54
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__cash_flow_forecast", [Decimal(0)])
|
|
55
|
+
def test_cash_pct_zero_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
56
|
+
assert daily_portfolio_cash_flow.cash_pct == Decimal(0)
|
|
57
|
+
|
|
58
|
+
def test_true_cash(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
59
|
+
dpct = daily_portfolio_cash_flow
|
|
60
|
+
assert dpct.true_cash == dpct.cash + dpct.cash_flow_forecast
|
|
61
|
+
|
|
62
|
+
def test_true_cash_pct(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
63
|
+
dpct = daily_portfolio_cash_flow
|
|
64
|
+
assert dpct.true_cash_pct == dpct.true_cash / dpct.estimated_total_assets
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__total_assets", [Decimal(0)])
|
|
67
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__cash_flow_forecast", [Decimal(0)])
|
|
68
|
+
def test_true_cash_pct_zero_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
69
|
+
assert daily_portfolio_cash_flow.true_cash_pct == Decimal(0)
|
|
70
|
+
|
|
71
|
+
def test_target_cash_no_target(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
72
|
+
dpct = daily_portfolio_cash_flow
|
|
73
|
+
assert dpct.target_cash == Decimal(0)
|
|
74
|
+
|
|
75
|
+
def test_target_cash(
|
|
76
|
+
self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow", portfolio_cash_target: "PortfolioCashTarget"
|
|
77
|
+
):
|
|
78
|
+
dpct = daily_portfolio_cash_flow
|
|
79
|
+
portfolio_cash_target.portfolio = dpct.portfolio
|
|
80
|
+
portfolio_cash_target.valid_date = dpct.value_date
|
|
81
|
+
portfolio_cash_target.save()
|
|
82
|
+
dpct.save()
|
|
83
|
+
dpct.refresh_from_db()
|
|
84
|
+
assert dpct.target_cash == pytest.approx(portfolio_cash_target.target * dpct.estimated_total_assets)
|
|
85
|
+
|
|
86
|
+
def test_excess_cash(
|
|
87
|
+
self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow", portfolio_cash_target: "PortfolioCashTarget"
|
|
88
|
+
):
|
|
89
|
+
dpct = daily_portfolio_cash_flow
|
|
90
|
+
portfolio_cash_target.portfolio = dpct.portfolio
|
|
91
|
+
portfolio_cash_target.valid_date = dpct.value_date
|
|
92
|
+
portfolio_cash_target.save()
|
|
93
|
+
dpct.save()
|
|
94
|
+
dpct.refresh_from_db()
|
|
95
|
+
assert dpct.excess_cash == dpct.true_cash - dpct.target_cash
|
|
96
|
+
|
|
97
|
+
def test_cash_from_yesterday(
|
|
98
|
+
self, portfolio: "Portfolio", daily_portfolio_cash_flow_factory: "DailyPortfolioCashFlowFactory"
|
|
99
|
+
):
|
|
100
|
+
cf1 = daily_portfolio_cash_flow_factory.create(portfolio=portfolio, value_date=date(2020, 1, 1))
|
|
101
|
+
cf2 = daily_portfolio_cash_flow_factory.create(
|
|
102
|
+
portfolio=portfolio, value_date=date(2020, 1, 2), pending=True, rebalancing=Decimal(0)
|
|
103
|
+
)
|
|
104
|
+
cf1.refresh_from_db()
|
|
105
|
+
cf2.refresh_from_db()
|
|
106
|
+
assert cf2.cash == cf1.true_cash
|
|
107
|
+
|
|
108
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__cash", [Decimal(1_000_000)])
|
|
109
|
+
@pytest.mark.parametrize("daily_portfolio_cash_flow__pending", [True])
|
|
110
|
+
def test_rebalancing(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
|
|
111
|
+
dpcf = daily_portfolio_cash_flow
|
|
112
|
+
assert dpcf.cash == Decimal(1_000_000) - dpcf.rebalancing
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from django.db.utils import IntegrityError
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from wbportfolio.factories.portfolio_swing_pricings import (
|
|
8
|
+
PortfolioCashTargetFactory,
|
|
9
|
+
)
|
|
10
|
+
from wbportfolio.models.portfolio import Portfolio
|
|
11
|
+
from wbportfolio.models.portfolio_swing_pricings import PortfolioCashTarget
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.django_db
|
|
15
|
+
class TestPortfolioCashTarget:
|
|
16
|
+
def test_factory(self, portfolio_cash_target: "PortfolioCashTarget"):
|
|
17
|
+
assert portfolio_cash_target.pk is not None
|
|
18
|
+
|
|
19
|
+
def test_constraint_unique_valid_date_portfolio(
|
|
20
|
+
self, portfolio: "Portfolio", portfolio_cash_target_factory: "PortfolioCashTargetFactory"
|
|
21
|
+
):
|
|
22
|
+
target1 = portfolio_cash_target_factory.create(portfolio=portfolio)
|
|
23
|
+
target2 = portfolio_cash_target_factory.create(portfolio=portfolio)
|
|
24
|
+
|
|
25
|
+
target2.valid_date = target1.valid_date
|
|
26
|
+
with pytest.raises(IntegrityError):
|
|
27
|
+
target2.save()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from django.db.utils import IntegrityError
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from wbportfolio.factories.portfolio_swing_pricings import (
|
|
9
|
+
PortfolioSwingPricingFactory,
|
|
10
|
+
)
|
|
11
|
+
from wbportfolio.models.portfolio import Portfolio
|
|
12
|
+
from wbportfolio.models.portfolio_swing_pricings import PortfolioSwingPricing
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.django_db
|
|
16
|
+
class TestPortfolioSwingPricing:
|
|
17
|
+
def test_factory(self, portfolio_swing_pricing: "PortfolioSwingPricing"):
|
|
18
|
+
assert portfolio_swing_pricing.pk is not None
|
|
19
|
+
|
|
20
|
+
def test_constraint_negative_polarity(self, portfolio_swing_pricing: "PortfolioSwingPricing"):
|
|
21
|
+
portfolio_swing_pricing.negative_threshold = Decimal(0.1)
|
|
22
|
+
portfolio_swing_pricing.negative_swing_factor = Decimal(0.1)
|
|
23
|
+
|
|
24
|
+
with pytest.raises(IntegrityError):
|
|
25
|
+
portfolio_swing_pricing.save()
|
|
26
|
+
|
|
27
|
+
def test_constraint_positive_polarity(self, portfolio_swing_pricing: "PortfolioSwingPricing"):
|
|
28
|
+
portfolio_swing_pricing.positive_threshold = Decimal(-0.1)
|
|
29
|
+
portfolio_swing_pricing.positive_swing_factor = Decimal(-0.1)
|
|
30
|
+
|
|
31
|
+
with pytest.raises(IntegrityError):
|
|
32
|
+
portfolio_swing_pricing.save()
|
|
33
|
+
|
|
34
|
+
def test_constraint_unique_valid_date_portfolio(
|
|
35
|
+
self, portfolio: "Portfolio", portfolio_swing_pricing_factory: "PortfolioSwingPricingFactory"
|
|
36
|
+
):
|
|
37
|
+
swing1 = portfolio_swing_pricing_factory.create(portfolio=portfolio)
|
|
38
|
+
swing2 = portfolio_swing_pricing_factory.create(portfolio=portfolio)
|
|
39
|
+
|
|
40
|
+
swing2.valid_date = swing1.valid_date
|
|
41
|
+
with pytest.raises(IntegrityError):
|
|
42
|
+
swing2.save()
|