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,191 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from django.db import IntegrityError
|
|
6
|
+
from wbcrm.models import Account
|
|
7
|
+
from wbportfolio.models import AccountReconciliation, Claim
|
|
8
|
+
from wbportfolio.models.products import Product
|
|
9
|
+
from wbportfolio.models.reconciliations.account_reconciliation_lines import (
|
|
10
|
+
AccountReconciliationLine,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from wbcore.contrib.authentication.models import User
|
|
15
|
+
from wbportfolio.factories import AccountReconciliationLineFactory, ClaimFactory
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestAccountReconciliation:
|
|
19
|
+
def test_str(self):
|
|
20
|
+
account = Account(computed_str="Test Account")
|
|
21
|
+
reconciliation = AccountReconciliation(account=account, reconciliation_date=date(2021, 1, 1))
|
|
22
|
+
assert str(reconciliation) == "Test Account (01.01.2021)"
|
|
23
|
+
|
|
24
|
+
def test_get_endpoint_basename(self):
|
|
25
|
+
assert AccountReconciliation.get_endpoint_basename() == "wbportfolio:accountreconciliation"
|
|
26
|
+
|
|
27
|
+
def test_get_representation_endpoint(self):
|
|
28
|
+
assert (
|
|
29
|
+
AccountReconciliation.get_representation_endpoint()
|
|
30
|
+
== "wbportfolio:accountreconciliationrepresentation-list"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def test_get_representation_value_key(self):
|
|
34
|
+
assert AccountReconciliation.get_representation_value_key() == "id"
|
|
35
|
+
|
|
36
|
+
def test_get_representation_label_key(self):
|
|
37
|
+
assert AccountReconciliation.get_representation_label_key() == "{{account}} {{reconciliation_date}}"
|
|
38
|
+
|
|
39
|
+
@pytest.mark.django_db
|
|
40
|
+
def test_create_for_date_and_account(self, claim_factory: "ClaimFactory", account: Account, user: "User"):
|
|
41
|
+
for _ in range(10):
|
|
42
|
+
claim_factory.create(account=account, date=date(2020, 12, 30), status=Claim.Status.APPROVED)
|
|
43
|
+
|
|
44
|
+
AccountReconciliation.objects.create(
|
|
45
|
+
reconciliation_date=date(2021, 1, 1),
|
|
46
|
+
account=account,
|
|
47
|
+
creator=user,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
assert (
|
|
51
|
+
AccountReconciliationLine.objects.count() == Claim.objects.distinct("product").count()
|
|
52
|
+
), "There should be as many lines as unique products for claims"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestAccountReconciliationLine:
|
|
56
|
+
def test_str(self):
|
|
57
|
+
account = Account(computed_str="Test Account")
|
|
58
|
+
reconciliation = AccountReconciliation(account=account, reconciliation_date=date(2021, 1, 1))
|
|
59
|
+
product = Product(computed_str="Test Product")
|
|
60
|
+
line = AccountReconciliationLine(reconciliation=reconciliation, product=product, shares=100)
|
|
61
|
+
|
|
62
|
+
assert str(line) == "Test Account (01.01.2021): Test Product 100 shares"
|
|
63
|
+
|
|
64
|
+
def test_get_endpoint_basename(self):
|
|
65
|
+
assert AccountReconciliationLine.get_endpoint_basename() == "wbportfolio:accountreconciliationline"
|
|
66
|
+
|
|
67
|
+
def test_representation_endpoint(self):
|
|
68
|
+
assert (
|
|
69
|
+
AccountReconciliationLine.get_representation_endpoint()
|
|
70
|
+
== "wbportfolio:accountreconciliationlinerepresentation-list"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def test_representation_value_key(self):
|
|
74
|
+
assert AccountReconciliationLine.get_representation_value_key() == "id"
|
|
75
|
+
|
|
76
|
+
def test_representation_label_key(self):
|
|
77
|
+
assert AccountReconciliationLine.get_representation_label_key() == "{{reconciliation}}"
|
|
78
|
+
|
|
79
|
+
def test_save_calculate_nominal_value(self, mocker):
|
|
80
|
+
mocker.patch("django.db.models.Model.save")
|
|
81
|
+
product = Product(share_price=100)
|
|
82
|
+
line = AccountReconciliationLine(product=product, shares=100, nominal_value=None)
|
|
83
|
+
line.save()
|
|
84
|
+
|
|
85
|
+
assert line.nominal_value == 100 * 100
|
|
86
|
+
|
|
87
|
+
def test_save_calculate_shares(self, mocker):
|
|
88
|
+
mocker.patch("django.db.models.Model.save")
|
|
89
|
+
product = Product(share_price=100)
|
|
90
|
+
line = AccountReconciliationLine(product=product, nominal_value=10000, shares=None)
|
|
91
|
+
line.save()
|
|
92
|
+
|
|
93
|
+
assert line.shares == 100
|
|
94
|
+
|
|
95
|
+
def test_save_calculate_nominal_value_external(self, mocker):
|
|
96
|
+
mocker.patch("django.db.models.Model.save")
|
|
97
|
+
product = Product(share_price=100)
|
|
98
|
+
line = AccountReconciliationLine(product=product, shares_external=100, nominal_value_external=None)
|
|
99
|
+
line.save()
|
|
100
|
+
|
|
101
|
+
assert line.nominal_value_external == 100 * 100
|
|
102
|
+
|
|
103
|
+
def test_save_calculate_shares_external(self, mocker):
|
|
104
|
+
mocker.patch("django.db.models.Model.save")
|
|
105
|
+
product = Product(share_price=100)
|
|
106
|
+
line = AccountReconciliationLine(product=product, nominal_value_external=10000, shares_external=None)
|
|
107
|
+
line.save()
|
|
108
|
+
|
|
109
|
+
assert line.shares_external == 100
|
|
110
|
+
|
|
111
|
+
@pytest.mark.django_db
|
|
112
|
+
def test_unique_constraint(
|
|
113
|
+
self,
|
|
114
|
+
account_reconciliation: AccountReconciliation,
|
|
115
|
+
product: Product,
|
|
116
|
+
account_reconciliation_line_factory: "AccountReconciliationLineFactory",
|
|
117
|
+
):
|
|
118
|
+
account_reconciliation_line_factory.create(reconciliation=account_reconciliation, product=product)
|
|
119
|
+
with pytest.raises(IntegrityError):
|
|
120
|
+
account_reconciliation_line_factory.create(reconciliation=account_reconciliation, product=product)
|
|
121
|
+
|
|
122
|
+
@pytest.mark.django_db
|
|
123
|
+
def test_currency_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
124
|
+
assert (
|
|
125
|
+
AccountReconciliationLine.objects.all().annotate_currency().first().currency
|
|
126
|
+
== account_reconciliation_line.product.currency.symbol
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@pytest.mark.django_db
|
|
130
|
+
def test_assets_under_management_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
131
|
+
assert (
|
|
132
|
+
AccountReconciliationLine.objects.all().annotate_assets_under_management().first().assets_under_management
|
|
133
|
+
== account_reconciliation_line.shares * account_reconciliation_line.price
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@pytest.mark.django_db
|
|
137
|
+
def test_assets_under_management_external_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
138
|
+
assert (
|
|
139
|
+
AccountReconciliationLine.objects.all()
|
|
140
|
+
.annotate_assets_under_management_external()
|
|
141
|
+
.first()
|
|
142
|
+
.assets_under_management_external
|
|
143
|
+
== account_reconciliation_line.shares_external * account_reconciliation_line.price
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
@pytest.mark.django_db
|
|
147
|
+
@pytest.mark.parametrize("account_reconciliation_line__shares", [100])
|
|
148
|
+
@pytest.mark.parametrize("account_reconciliation_line__shares_external", [101])
|
|
149
|
+
def test_is_not_equal_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
150
|
+
assert not AccountReconciliationLine.objects.all().annotate_is_equal().first().is_equal
|
|
151
|
+
|
|
152
|
+
@pytest.mark.django_db
|
|
153
|
+
@pytest.mark.parametrize("account_reconciliation_line__shares", [100])
|
|
154
|
+
@pytest.mark.parametrize("account_reconciliation_line__shares_external", [100])
|
|
155
|
+
def test_is_equal_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
156
|
+
assert AccountReconciliationLine.objects.all().annotate_is_equal().first().is_equal
|
|
157
|
+
|
|
158
|
+
@pytest.mark.django_db
|
|
159
|
+
def test_shares_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
160
|
+
assert (
|
|
161
|
+
AccountReconciliationLine.objects.all().annotate_shares_diff().first().shares_diff
|
|
162
|
+
== account_reconciliation_line.shares_external - account_reconciliation_line.shares
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@pytest.mark.django_db
|
|
166
|
+
def test_pct_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
167
|
+
assert (
|
|
168
|
+
AccountReconciliationLine.objects.all().annotate_pct_diff().first().pct_diff
|
|
169
|
+
== (account_reconciliation_line.shares_external - account_reconciliation_line.shares)
|
|
170
|
+
/ account_reconciliation_line.shares
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@pytest.mark.django_db
|
|
174
|
+
def test_nominal_value_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
175
|
+
assert (
|
|
176
|
+
AccountReconciliationLine.objects.all().annotate_nominal_value_diff().first().nominal_value_diff
|
|
177
|
+
== account_reconciliation_line.nominal_value_external - account_reconciliation_line.nominal_value
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
@pytest.mark.django_db
|
|
181
|
+
def test_assets_under_management_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
|
|
182
|
+
assert (
|
|
183
|
+
AccountReconciliationLine.objects.all()
|
|
184
|
+
.annotate_assets_under_management()
|
|
185
|
+
.annotate_assets_under_management_external()
|
|
186
|
+
.annotate_assets_under_management_diff()
|
|
187
|
+
.first()
|
|
188
|
+
.assets_under_management_diff
|
|
189
|
+
== (account_reconciliation_line.shares_external * account_reconciliation_line.price)
|
|
190
|
+
- (account_reconciliation_line.shares * account_reconciliation_line.price)
|
|
191
|
+
)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pandas.tseries.offsets import BDay
|
|
5
|
+
from wbportfolio.models import AssetPosition, PortfolioRole
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.django_db
|
|
9
|
+
class TestAssetPositionModel:
|
|
10
|
+
def test_init(self, asset_position):
|
|
11
|
+
assert asset_position.id is not None
|
|
12
|
+
|
|
13
|
+
def test_currency_group_by(self, asset_position_factory, equity_factory, portfolio, currency):
|
|
14
|
+
asset_position_factory.create(
|
|
15
|
+
portfolio=portfolio, currency=currency, underlying_instrument=equity_factory.create()
|
|
16
|
+
)
|
|
17
|
+
asset_position_factory.create(
|
|
18
|
+
portfolio=portfolio, currency=currency, underlying_instrument=equity_factory.create()
|
|
19
|
+
)
|
|
20
|
+
assert (
|
|
21
|
+
AssetPosition.currency_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def test_country_group_by(self, asset_position_factory, portfolio, equity):
|
|
25
|
+
asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
|
|
26
|
+
asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
|
|
27
|
+
assert AssetPosition.country_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
|
|
28
|
+
|
|
29
|
+
def test_exchange_group_by(self, asset_position_factory, portfolio, exchange):
|
|
30
|
+
asset_position_factory.create(portfolio=portfolio, exchange=exchange)
|
|
31
|
+
asset_position_factory.create(portfolio=portfolio, exchange=exchange)
|
|
32
|
+
assert (
|
|
33
|
+
AssetPosition.exchange_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def test_cash_group_by(self, asset_position_factory, portfolio, cash, equity):
|
|
37
|
+
asset_position_factory.create(portfolio=portfolio, underlying_instrument=cash)
|
|
38
|
+
asset_position_factory.create(portfolio=portfolio, underlying_instrument=cash)
|
|
39
|
+
asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
|
|
40
|
+
assert AssetPosition.cash_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 2
|
|
41
|
+
|
|
42
|
+
def test_equity_group_by(self, asset_position_factory, portfolio, equity):
|
|
43
|
+
asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
|
|
44
|
+
asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
|
|
45
|
+
assert AssetPosition.equity_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
|
|
46
|
+
|
|
47
|
+
def test_marketcap_group_by(
|
|
48
|
+
self, asset_position_factory, equity_factory, portfolio, instrument_price_factory, currency_fx_rates_factory
|
|
49
|
+
):
|
|
50
|
+
fx_rate = currency_fx_rates_factory.create(value=1)
|
|
51
|
+
i1 = instrument_price_factory.create(instrument=equity_factory.create(), market_capitalization=1000000000)
|
|
52
|
+
i2 = instrument_price_factory.create(instrument=equity_factory.create(), market_capitalization=30000000000)
|
|
53
|
+
asset_position_factory.create(
|
|
54
|
+
portfolio=portfolio,
|
|
55
|
+
underlying_instrument_price=i1,
|
|
56
|
+
currency_fx_rate_instrument_to_usd=fx_rate,
|
|
57
|
+
currency_fx_rate_portfolio_to_usd=fx_rate,
|
|
58
|
+
) # small
|
|
59
|
+
asset_position_factory.create(
|
|
60
|
+
portfolio=portfolio,
|
|
61
|
+
underlying_instrument_price=i2,
|
|
62
|
+
currency_fx_rate_instrument_to_usd=fx_rate,
|
|
63
|
+
currency_fx_rate_portfolio_to_usd=fx_rate,
|
|
64
|
+
) # larg
|
|
65
|
+
|
|
66
|
+
qs = AssetPosition.marketcap_group_by(AssetPosition.objects.all())
|
|
67
|
+
assert qs.values("aggregated_title").distinct().count() == 2
|
|
68
|
+
assert set(qs.distinct().values_list("aggregated_title", flat=True)) == {"10B to 50B", "< 2B"}
|
|
69
|
+
|
|
70
|
+
def test_liquidity_group_by(self, asset_position_factory, equity_factory, portfolio, instrument_price_factory):
|
|
71
|
+
a1 = asset_position_factory.create(
|
|
72
|
+
portfolio=portfolio, initial_shares=1, underlying_instrument=equity_factory.create()
|
|
73
|
+
) # small
|
|
74
|
+
a2 = asset_position_factory.create(
|
|
75
|
+
portfolio=portfolio, initial_shares=1, underlying_instrument=equity_factory.create()
|
|
76
|
+
) # larg
|
|
77
|
+
|
|
78
|
+
instrument_price_factory.create(instrument=a1.underlying_instrument, date=a1.date, volume_50d=1000000)
|
|
79
|
+
instrument_price_factory.create(instrument=a2.underlying_instrument, date=a2.date, volume_50d=3000000)
|
|
80
|
+
|
|
81
|
+
qs = AssetPosition.liquidity_group_by(AssetPosition.objects.all())
|
|
82
|
+
assert qs.values("aggregated_title").distinct().count() == 1
|
|
83
|
+
|
|
84
|
+
def test_get_shown_positions_superuser(self, portfolio, superuser, asset_position_factory):
|
|
85
|
+
superuser.is_superuser = True
|
|
86
|
+
superuser.save()
|
|
87
|
+
asset_position_factory.create_batch(10, portfolio=portfolio)
|
|
88
|
+
qs = AssetPosition.get_shown_positions(superuser.profile)
|
|
89
|
+
assert qs.count() == 10
|
|
90
|
+
|
|
91
|
+
def test_get_shown_positions_analyst(
|
|
92
|
+
self, portfolio_factory, product_factory, person, user, asset_position_factory, product_portfolio_role_factory
|
|
93
|
+
):
|
|
94
|
+
user.profile = person
|
|
95
|
+
user.save()
|
|
96
|
+
p2 = portfolio_factory.create()
|
|
97
|
+
product = product_factory.create()
|
|
98
|
+
product_portfolio_role_factory.create(
|
|
99
|
+
person=person, instrument=product, role_type=PortfolioRole.RoleType.ANALYST
|
|
100
|
+
)
|
|
101
|
+
asset_position_factory.create_batch(10, portfolio=product.portfolio)
|
|
102
|
+
asset_position_factory.create_batch(10, portfolio=p2)
|
|
103
|
+
qs = AssetPosition.get_shown_positions(person)
|
|
104
|
+
assert qs.count() == 10
|
|
105
|
+
|
|
106
|
+
def test_analytical_objects(self, asset_position_factory, portfolio, equity_factory):
|
|
107
|
+
# Data creation
|
|
108
|
+
equity1 = equity_factory.create()
|
|
109
|
+
equity2 = equity_factory.create()
|
|
110
|
+
asset_position_factory.create_batch(5, underlying_instrument=equity1, portfolio=portfolio)
|
|
111
|
+
asset_position_factory.create_batch(5, underlying_instrument=equity2, portfolio=portfolio)
|
|
112
|
+
|
|
113
|
+
# We test calculation with equity1. equity2 allows to certify the temporal continuity of the portfolio.
|
|
114
|
+
qs_assets_equity1 = AssetPosition.analytical_objects.order_by("date").filter(underlying_instrument=equity1)
|
|
115
|
+
qs_prices_equity1 = equity1.prices.order_by("date")
|
|
116
|
+
|
|
117
|
+
# ap holds for AssetPosition and ep holds for Equity Price.
|
|
118
|
+
ap1, ap2 = qs_assets_equity1[:2]
|
|
119
|
+
ep1, ep2, ep3, ep4 = qs_prices_equity1[:4]
|
|
120
|
+
|
|
121
|
+
# Basic Performance
|
|
122
|
+
assert ap2.performance == float(ap2.price_fx_usd / ap1.price_fx_usd - 1)
|
|
123
|
+
assert ap2.performance == pytest.approx(float(ep2._net_value_usd / ep1._net_value_usd - Decimal(1)))
|
|
124
|
+
|
|
125
|
+
# We sell the equity (ap3 deleted) and then we buy it again (back in ap4).
|
|
126
|
+
# This is like having a missing data in between.
|
|
127
|
+
qs_assets_equity1.exclude(id__in=[ap1.id, ap2.id]).first().delete() # ap3
|
|
128
|
+
ap4 = qs_assets_equity1.exclude(id__in=[ap1.id, ap2.id]).first()
|
|
129
|
+
|
|
130
|
+
assert ap4.performance is None
|
|
131
|
+
assert ep4._net_value_usd / ep3._net_value_usd is not None
|
|
132
|
+
|
|
133
|
+
def test_get_underlying_instrument_price(self, asset_position_factory, instrument_price_factory):
|
|
134
|
+
"""
|
|
135
|
+
In this test, we confirm that an asset position with no matching real price for the same date and underlying instrument will result in an empty underlying_instrument_price
|
|
136
|
+
"""
|
|
137
|
+
a1 = asset_position_factory.create(underlying_instrument_price=None)
|
|
138
|
+
a1.save()
|
|
139
|
+
assert a1.underlying_instrument_price is None
|
|
140
|
+
p2_calculated = instrument_price_factory.create(calculated=True)
|
|
141
|
+
a2 = asset_position_factory.create(
|
|
142
|
+
underlying_instrument_price=None, underlying_instrument=p2_calculated.instrument, date=p2_calculated.date
|
|
143
|
+
)
|
|
144
|
+
a2.save()
|
|
145
|
+
assert a2.underlying_instrument_price is None
|
|
146
|
+
p2_real = instrument_price_factory.create(
|
|
147
|
+
calculated=False, date=p2_calculated.date, instrument=p2_calculated.instrument
|
|
148
|
+
)
|
|
149
|
+
a2.save()
|
|
150
|
+
assert a2.underlying_instrument_price == p2_real
|
|
151
|
+
|
|
152
|
+
def test_change_currency_recompute_currency_fx_rate_for_price_and_assets(
|
|
153
|
+
self, weekday, instrument, currency_fx_rates_factory, instrument_price_factory, asset_position_factory
|
|
154
|
+
):
|
|
155
|
+
fx_rate = currency_fx_rates_factory.create(currency=instrument.currency, date=weekday)
|
|
156
|
+
fx_rate_other = currency_fx_rates_factory.create(date=weekday)
|
|
157
|
+
|
|
158
|
+
p = instrument_price_factory.create(instrument=instrument, date=weekday)
|
|
159
|
+
a = asset_position_factory.create(underlying_instrument=instrument, date=weekday)
|
|
160
|
+
|
|
161
|
+
assert p.currency_fx_rate_to_usd == fx_rate
|
|
162
|
+
assert a.currency_fx_rate_instrument_to_usd == fx_rate
|
|
163
|
+
|
|
164
|
+
instrument.currency = fx_rate_other.currency
|
|
165
|
+
instrument.save()
|
|
166
|
+
|
|
167
|
+
p.refresh_from_db()
|
|
168
|
+
a.refresh_from_db()
|
|
169
|
+
|
|
170
|
+
assert p.currency_fx_rate_to_usd == fx_rate_other
|
|
171
|
+
assert a.currency_fx_rate_instrument_to_usd == fx_rate_other
|
|
172
|
+
|
|
173
|
+
def test_create_price_set_assetposition(self, asset_position_factory, instrument_price_factory):
|
|
174
|
+
p0 = instrument_price_factory.create()
|
|
175
|
+
d1 = (p0.date + BDay(1)).date()
|
|
176
|
+
a1 = asset_position_factory.create(
|
|
177
|
+
date=d1, underlying_instrument=p0.instrument, underlying_instrument_price=None
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# check it is linked to the only price
|
|
181
|
+
assert a1.underlying_instrument_price == p0
|
|
182
|
+
p1 = instrument_price_factory.create(instrument=p0.instrument, date=d1)
|
|
183
|
+
a1.refresh_from_db()
|
|
184
|
+
assert a1.underlying_instrument_price == p1
|
|
185
|
+
|
|
186
|
+
d_1 = (p0.date - BDay(1)).date()
|
|
187
|
+
a_1 = asset_position_factory.create(
|
|
188
|
+
date=d_1, underlying_instrument=p1.instrument, underlying_instrument_price=None
|
|
189
|
+
)
|
|
190
|
+
assert a_1.underlying_instrument_price is None
|
|
191
|
+
p_1 = instrument_price_factory.create(instrument=p1.instrument, date=d_1)
|
|
192
|
+
a_1.refresh_from_db()
|
|
193
|
+
assert a_1.underlying_instrument_price == p_1
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@pytest.mark.django_db
|
|
5
|
+
class TestCustodianModel:
|
|
6
|
+
def test_init(self, custodian_factory):
|
|
7
|
+
custodian = custodian_factory.create()
|
|
8
|
+
assert custodian.id is not None
|
|
9
|
+
|
|
10
|
+
def test_str(self, custodian_factory):
|
|
11
|
+
custodian = custodian_factory.create()
|
|
12
|
+
assert str(custodian) == f"{custodian.name}"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from django.db.models import ProtectedError
|
|
6
|
+
from faker import Faker
|
|
7
|
+
from wbportfolio.models import Trade
|
|
8
|
+
from wbportfolio.models.transactions.claim import Claim
|
|
9
|
+
|
|
10
|
+
fake = Faker()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.django_db
|
|
14
|
+
class TestCustomerTradeModel:
|
|
15
|
+
def test_delete_without_claims(self, trade):
|
|
16
|
+
"""
|
|
17
|
+
Simple test to check if a trade without claim is properly deleted
|
|
18
|
+
"""
|
|
19
|
+
trade.delete()
|
|
20
|
+
with pytest.raises(Trade.DoesNotExist):
|
|
21
|
+
trade.refresh_from_db()
|
|
22
|
+
|
|
23
|
+
@pytest.mark.parametrize("claim_status", Claim.Status.names)
|
|
24
|
+
def test_delete_trade_with_claim(self, customer_trade_factory, claim_factory, claim_status):
|
|
25
|
+
"""
|
|
26
|
+
Simple test to check if a trade with claim a valid claim (Pending or Approved9 can't be deleted but
|
|
27
|
+
a trade with a draft, withdrawn or auto approved claim can.
|
|
28
|
+
"""
|
|
29
|
+
customer_trade = customer_trade_factory.create()
|
|
30
|
+
claim = claim_factory.create(trade=customer_trade, status=claim_status)
|
|
31
|
+
|
|
32
|
+
# If claim is among the non approved type, we expect the deletion to succeed and the claim to be unlinked
|
|
33
|
+
if claim.status in [Claim.Status.DRAFT, Claim.Status.AUTO_MATCHED, Claim.Status.WITHDRAWN]:
|
|
34
|
+
customer_trade.delete()
|
|
35
|
+
claim.refresh_from_db()
|
|
36
|
+
assert not claim.trade
|
|
37
|
+
with pytest.raises(Trade.DoesNotExist):
|
|
38
|
+
customer_trade.refresh_from_db()
|
|
39
|
+
else:
|
|
40
|
+
# If the claim is approved or pending, and in absence of any other similar trades to reassign the claim to, we expect the deletion to failed
|
|
41
|
+
with pytest.raises(ProtectedError):
|
|
42
|
+
customer_trade.delete()
|
|
43
|
+
|
|
44
|
+
def test_delete_trade_with_approved_claims_but_similar_trades_summing_0(
|
|
45
|
+
self, customer_trade_factory, claim_factory
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
A test to check that even trade with approved claims attach can be deleted if the sum of the shares of the similar trades the same date equals to 0
|
|
49
|
+
"""
|
|
50
|
+
trade1 = customer_trade_factory.create()
|
|
51
|
+
claim1 = claim_factory.create(trade=trade1, status=Claim.Status.APPROVED)
|
|
52
|
+
trade1.marked_for_deletion = True
|
|
53
|
+
trade1.save()
|
|
54
|
+
|
|
55
|
+
# Claims is attached, no other similar trade is present, we don't expect the deletion to be possible
|
|
56
|
+
with pytest.raises(ProtectedError):
|
|
57
|
+
trade1.delete()
|
|
58
|
+
|
|
59
|
+
# We create a negative trade that will annihilate trade1
|
|
60
|
+
trade2 = customer_trade_factory.create(
|
|
61
|
+
underlying_instrument=trade1.underlying_instrument,
|
|
62
|
+
portfolio=trade1.portfolio,
|
|
63
|
+
transaction_date=trade1.transaction_date,
|
|
64
|
+
shares=-trade1.shares,
|
|
65
|
+
)
|
|
66
|
+
# That we claim
|
|
67
|
+
claim2 = claim_factory.create(trade=trade2, status=Claim.Status.APPROVED)
|
|
68
|
+
trade2.marked_for_deletion = True
|
|
69
|
+
trade2.save()
|
|
70
|
+
|
|
71
|
+
# Now, sum of all trades linked to this product at that date sums to 0, we can destroy all trades and unliked the associated claims
|
|
72
|
+
trade1.delete()
|
|
73
|
+
claim1.refresh_from_db()
|
|
74
|
+
claim2.refresh_from_db()
|
|
75
|
+
assert not claim1.trade
|
|
76
|
+
assert claim1.status == Claim.Status.DRAFT
|
|
77
|
+
assert not claim2.trade
|
|
78
|
+
assert claim2.status == Claim.Status.DRAFT
|
|
79
|
+
|
|
80
|
+
# trade2 is unclaimed and can be safely deleted
|
|
81
|
+
trade2.delete()
|
|
82
|
+
with pytest.raises(Trade.DoesNotExist):
|
|
83
|
+
trade2.refresh_from_db()
|
|
84
|
+
|
|
85
|
+
@pytest.mark.parametrize(
|
|
86
|
+
"allowed_timedeltda", [random.randint(-Trade.TRADE_WINDOW_INTERVAL, Trade.TRADE_WINDOW_INTERVAL)]
|
|
87
|
+
)
|
|
88
|
+
def test_trade_delete_shift_claims(self, customer_trade_factory, claim_factory, allowed_timedeltda):
|
|
89
|
+
"""
|
|
90
|
+
Test main functionality: when a trade is linked to an approved claim and is marked for deletion, we try to find the closest trade non marked for deletion or pending and unclaimed.
|
|
91
|
+
|
|
92
|
+
If this succeed, the claim is forwarded to this other trade. Otherwise, it will fail.
|
|
93
|
+
"""
|
|
94
|
+
trade1 = customer_trade_factory.create()
|
|
95
|
+
claim1 = claim_factory.create(trade=trade1, status=Claim.Status.APPROVED)
|
|
96
|
+
trade1.marked_for_deletion = True
|
|
97
|
+
trade1.save()
|
|
98
|
+
|
|
99
|
+
# We expect the claim to be reassigned to this similar "non marked for deletion" trade
|
|
100
|
+
similar_unclaimed_trade = customer_trade_factory.create(
|
|
101
|
+
marked_for_deletion=False,
|
|
102
|
+
pending=False,
|
|
103
|
+
underlying_instrument=trade1.underlying_instrument,
|
|
104
|
+
portfolio=trade1.portfolio,
|
|
105
|
+
transaction_subtype=trade1.transaction_subtype,
|
|
106
|
+
transaction_date=trade1.transaction_date + timedelta(days=allowed_timedeltda),
|
|
107
|
+
shares=trade1.shares,
|
|
108
|
+
)
|
|
109
|
+
trade1.delete()
|
|
110
|
+
with pytest.raises(Trade.DoesNotExist):
|
|
111
|
+
trade1.refresh_from_db()
|
|
112
|
+
claim1.refresh_from_db()
|
|
113
|
+
assert claim1.trade == similar_unclaimed_trade
|