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,195 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from django.db.models import F, OuterRef, Subquery, Sum
|
|
4
|
+
from django.utils.functional import cached_property
|
|
5
|
+
from wbcore import viewsets
|
|
6
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
7
|
+
from wbcore.contrib.directory.models import EmployerEmployeeRelationship
|
|
8
|
+
from wbcore.contrib.directory.serializers import NewPersonModelSerializer
|
|
9
|
+
from wbcore.contrib.directory.serializers import (
|
|
10
|
+
PersonModelSerializer as BasePersonModelSerializer,
|
|
11
|
+
)
|
|
12
|
+
from wbcore.contrib.directory.viewsets.entries import (
|
|
13
|
+
CompanyModelViewSet as OriginalCompanyModelViewSet,
|
|
14
|
+
)
|
|
15
|
+
from wbcore.contrib.directory.viewsets.entries import (
|
|
16
|
+
PersonModelViewSet as OriginalPersonModelViewSet,
|
|
17
|
+
)
|
|
18
|
+
from wbcore.utils.strings import format_number
|
|
19
|
+
|
|
20
|
+
from .configs import (
|
|
21
|
+
AssetAllocationDisplay,
|
|
22
|
+
AssetAllocationModelEndpointConfig,
|
|
23
|
+
CompanyModelDisplay,
|
|
24
|
+
CompanyPreviewConfig,
|
|
25
|
+
GeographicFocusDisplay,
|
|
26
|
+
GeographicFocusModelEndpointConfig,
|
|
27
|
+
PersonModelDisplay,
|
|
28
|
+
)
|
|
29
|
+
from .filters import CompanyFilter, PersonFilter
|
|
30
|
+
from .models import AssetAllocation, AssetAllocationType, GeographicFocus
|
|
31
|
+
from .serializers import (
|
|
32
|
+
AssetAllocationModelSerializer,
|
|
33
|
+
AssetAllocationTypeModelSerializer,
|
|
34
|
+
AssetAllocationTypeRepresentationSerializer,
|
|
35
|
+
CompanyModelListSerializer,
|
|
36
|
+
CompanyModelSerializer,
|
|
37
|
+
GeographicFocusModelSerializer,
|
|
38
|
+
PersonModelListSerializer,
|
|
39
|
+
PersonModelSerializer,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CompanyModelViewSet(OriginalCompanyModelViewSet):
|
|
44
|
+
LIST_DOCUMENTATION = "wbportfolio/markdown/documentation/company.md"
|
|
45
|
+
display_config_class = CompanyModelDisplay
|
|
46
|
+
preview_config_class = CompanyPreviewConfig
|
|
47
|
+
filterset_class = CompanyFilter
|
|
48
|
+
ordering_fields = OriginalCompanyModelViewSet.ordering_fields + (
|
|
49
|
+
"invested_assets_under_management_usd__nulls_last",
|
|
50
|
+
"potential__nulls_last",
|
|
51
|
+
"asset_under_management__nulls_last",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@cached_property
|
|
55
|
+
def fx_rate_date(self) -> date:
|
|
56
|
+
try:
|
|
57
|
+
return CurrencyFXRates.objects.latest("date").date
|
|
58
|
+
except CurrencyFXRates.DoesNotExist:
|
|
59
|
+
return date.today()
|
|
60
|
+
|
|
61
|
+
def get_serializer_class(self):
|
|
62
|
+
if self.get_action() in ["list", "list-metadata"]:
|
|
63
|
+
return CompanyModelListSerializer
|
|
64
|
+
return CompanyModelSerializer
|
|
65
|
+
|
|
66
|
+
def get_aggregates(self, queryset, **kwargs):
|
|
67
|
+
sum_potential = queryset.aggregate(sum_potential=Sum("potential")).get("sum_potential", 0.0)
|
|
68
|
+
sum_assets_under_management_usd = queryset.aggregate(
|
|
69
|
+
sum_assets_under_management_usd=Sum("assets_under_management_usd")
|
|
70
|
+
).get("sum_assets_under_management_usd", 0.0)
|
|
71
|
+
sum_invested_assets_under_management_usd = queryset.aggregate(
|
|
72
|
+
sum_invested_assets_under_management_usd=Sum("invested_assets_under_management_usd")
|
|
73
|
+
).get("sum_invested_assets_under_management_usd", 0.0)
|
|
74
|
+
return {
|
|
75
|
+
"potential": {"Σ": format_number(sum_potential)},
|
|
76
|
+
"asset_under_management": {"Σ": format_number(sum_assets_under_management_usd)},
|
|
77
|
+
"invested_assets_under_management_usd": {"Σ": format_number(sum_invested_assets_under_management_usd)},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def get_queryset(self):
|
|
81
|
+
return (
|
|
82
|
+
super()
|
|
83
|
+
.get_queryset()
|
|
84
|
+
.annotate(
|
|
85
|
+
asset_under_management=F("portfolio_data__assets_under_management"),
|
|
86
|
+
assets_under_management_currency=F("portfolio_data__assets_under_management_currency"),
|
|
87
|
+
assets_under_management_currency_repr=F("portfolio_data__assets_under_management_currency__symbol"),
|
|
88
|
+
fx=CurrencyFXRates.get_fx_rates_subquery(
|
|
89
|
+
self.fx_rate_date, currency="assets_under_management_currency", lookup_expr="exact"
|
|
90
|
+
),
|
|
91
|
+
assets_under_management_usd=F("asset_under_management") * F("fx"),
|
|
92
|
+
invested_assets_under_management_usd=F("portfolio_data__invested_assets_under_management_usd"),
|
|
93
|
+
investment_discretion=F("portfolio_data__investment_discretion"),
|
|
94
|
+
potential=F("portfolio_data__potential"),
|
|
95
|
+
potential_currency=F("portfolio_data__potential_currency"),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class PersonModelViewSet(OriginalPersonModelViewSet):
|
|
101
|
+
LIST_DOCUMENTATION = "wbportfolio/markdown/documentation/person.md"
|
|
102
|
+
display_config_class = PersonModelDisplay
|
|
103
|
+
filterset_class = PersonFilter
|
|
104
|
+
ordering_fields = OriginalPersonModelViewSet.ordering_fields + (
|
|
105
|
+
"invested_assets_under_management_usd__nulls_last",
|
|
106
|
+
"asset_under_management__nulls_last",
|
|
107
|
+
"potential",
|
|
108
|
+
)
|
|
109
|
+
serializer_class = PersonModelSerializer
|
|
110
|
+
|
|
111
|
+
def get_serializer_class(self) -> BasePersonModelSerializer:
|
|
112
|
+
if self.get_action() in ["list", "list-metadata"]:
|
|
113
|
+
return PersonModelListSerializer
|
|
114
|
+
elif "pk" not in self.kwargs:
|
|
115
|
+
return NewPersonModelSerializer
|
|
116
|
+
return super().get_serializer_class()
|
|
117
|
+
|
|
118
|
+
def get_queryset(self):
|
|
119
|
+
qs = super().get_queryset()
|
|
120
|
+
qs = qs.annotate(
|
|
121
|
+
asset_under_management=Subquery(
|
|
122
|
+
EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
|
|
123
|
+
"employer__portfolio_data__assets_under_management"
|
|
124
|
+
)[:1],
|
|
125
|
+
),
|
|
126
|
+
asset_under_management_currency_repr=Subquery(
|
|
127
|
+
EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
|
|
128
|
+
"employer__portfolio_data__assets_under_management_currency__key"
|
|
129
|
+
)[:1],
|
|
130
|
+
),
|
|
131
|
+
invested_assets_under_management_usd=Subquery(
|
|
132
|
+
EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
|
|
133
|
+
"employer__portfolio_data__invested_assets_under_management_usd"
|
|
134
|
+
)[:1],
|
|
135
|
+
),
|
|
136
|
+
potential=Subquery(
|
|
137
|
+
EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
|
|
138
|
+
"employer__portfolio_data__potential"
|
|
139
|
+
)[:1],
|
|
140
|
+
),
|
|
141
|
+
potential_currency=Subquery(
|
|
142
|
+
EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
|
|
143
|
+
"employer__portfolio_data__potential_currency"
|
|
144
|
+
)[:1],
|
|
145
|
+
),
|
|
146
|
+
assets_under_management_currency=Subquery(
|
|
147
|
+
EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
|
|
148
|
+
"employer__portfolio_data__assets_under_management_currency"
|
|
149
|
+
)[:1],
|
|
150
|
+
),
|
|
151
|
+
investment_discretion=EmployerEmployeeRelationship.objects.filter(
|
|
152
|
+
primary=True, employee__id=OuterRef("id")
|
|
153
|
+
).values("employer__portfolio_data__investment_discretion")[:1],
|
|
154
|
+
)
|
|
155
|
+
return qs
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class AssetAllocationTypeRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
159
|
+
queryset = AssetAllocationType.objects.all()
|
|
160
|
+
serializer_class = AssetAllocationTypeRepresentationSerializer
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class AssetAllocationTypeModelViewSet(viewsets.ModelViewSet):
|
|
164
|
+
queryset = AssetAllocationType.objects.all()
|
|
165
|
+
serializer_class = AssetAllocationTypeModelSerializer
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class AssetAllocationModelViewSet(viewsets.ModelViewSet):
|
|
169
|
+
queryset = AssetAllocation.objects.all()
|
|
170
|
+
serializer_class = AssetAllocationModelSerializer
|
|
171
|
+
|
|
172
|
+
display_config_class = AssetAllocationDisplay
|
|
173
|
+
endpoint_config_class = AssetAllocationModelEndpointConfig
|
|
174
|
+
|
|
175
|
+
def get_queryset(self):
|
|
176
|
+
qs = super().get_queryset()
|
|
177
|
+
if company_id := self.kwargs.get("company_id", None):
|
|
178
|
+
qs = qs.filter(company_id=company_id)
|
|
179
|
+
return qs.select_related(
|
|
180
|
+
"asset_type",
|
|
181
|
+
"company",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class GeographicFocusModelViewSet(viewsets.ModelViewSet):
|
|
186
|
+
queryset = GeographicFocus.objects.all()
|
|
187
|
+
serializer_class = GeographicFocusModelSerializer
|
|
188
|
+
|
|
189
|
+
display_config_class = GeographicFocusDisplay
|
|
190
|
+
endpoint_config_class = GeographicFocusModelEndpointConfig
|
|
191
|
+
|
|
192
|
+
def get_queryset(self):
|
|
193
|
+
if company_id := self.kwargs.get("company_id", None):
|
|
194
|
+
return super().get_queryset().filter(company_id=company_id)
|
|
195
|
+
return super().get_queryset()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from wbfdm.models import Cash, InstrumentPrice
|
|
2
|
+
from wbportfolio.models import FeeProductPercentage, Fees, Product
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def fees_calculation(price_id):
|
|
6
|
+
price = InstrumentPrice.objects.get(id=price_id)
|
|
7
|
+
if price.calculated:
|
|
8
|
+
raise ValueError("Cannot compute fees on calculated price")
|
|
9
|
+
currency = price.instrument.currency
|
|
10
|
+
underlying_instrument = Cash.objects.filter(currency=currency).first()
|
|
11
|
+
product = Product.objects.get(id=price.instrument.id)
|
|
12
|
+
portfolio = product.portfolio
|
|
13
|
+
previous_price = price.previous_price
|
|
14
|
+
|
|
15
|
+
if previous_price:
|
|
16
|
+
shares = product.total_shares(previous_price.date)
|
|
17
|
+
multiplicator = shares * previous_price.net_value
|
|
18
|
+
else:
|
|
19
|
+
shares = product.total_shares(price.date)
|
|
20
|
+
multiplicator = shares * product.share_price
|
|
21
|
+
|
|
22
|
+
product_net_management_fees = product.get_fees_percent(price.date, FeeProductPercentage.Type.MANAGEMENT)
|
|
23
|
+
product_net_performance_fees = product.get_fees_percent(price.date, FeeProductPercentage.Type.PERFORMANCE)
|
|
24
|
+
product_gross_performance_fees = product.get_fees_percent(
|
|
25
|
+
price.date, FeeProductPercentage.Type.PERFORMANCE, net=False
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if previous_price and previous_price.date.weekday() == 4:
|
|
29
|
+
# The previous InstrumentPrice was a Friday. This means we have to calculate for the weekend
|
|
30
|
+
previous_total_value = shares * previous_price.net_value
|
|
31
|
+
|
|
32
|
+
sat_bank_fees = (product.bank_fees / 360) * previous_total_value
|
|
33
|
+
sat_management_fees = (product_net_management_fees / 360) * previous_total_value
|
|
34
|
+
previous_total_value = previous_total_value - sat_bank_fees - sat_management_fees
|
|
35
|
+
|
|
36
|
+
sun_bank_fees = (product.bank_fees / 360) * previous_total_value
|
|
37
|
+
sun_management_fees = (product_net_management_fees / 360) * previous_total_value
|
|
38
|
+
previous_total_value = previous_total_value - sun_bank_fees - sun_management_fees
|
|
39
|
+
|
|
40
|
+
mon_bank_fees = (product.bank_fees / 360) * previous_total_value
|
|
41
|
+
mon_management_fees = (product_net_management_fees / 360) * previous_total_value
|
|
42
|
+
bank_fees = sat_bank_fees + sun_bank_fees + mon_bank_fees
|
|
43
|
+
management_fees = sat_management_fees + sun_management_fees + mon_management_fees
|
|
44
|
+
else:
|
|
45
|
+
bank_fees = (product.bank_fees / 360) * multiplicator
|
|
46
|
+
management_fees = (product_net_management_fees / 360) * multiplicator
|
|
47
|
+
|
|
48
|
+
value = (
|
|
49
|
+
price.net_value or price.gross_value
|
|
50
|
+
) # Flipped around, so that if we have a good net price from the bank, we rather use that one. Issue is, that if the gross price is completely off, the performance fees can be really really high.
|
|
51
|
+
multiplicator = max(0, value - product.get_high_water_mark(price.date)) * shares
|
|
52
|
+
performance_fees_net = product_net_performance_fees * multiplicator
|
|
53
|
+
performance_fees_gross = product_gross_performance_fees * multiplicator
|
|
54
|
+
base_fields = [
|
|
55
|
+
"total_value",
|
|
56
|
+
"total_value_fx_portfolio",
|
|
57
|
+
"total_value_gross",
|
|
58
|
+
"total_value_gross_fx_portfolio",
|
|
59
|
+
]
|
|
60
|
+
yield {
|
|
61
|
+
"portfolio": portfolio,
|
|
62
|
+
"linked_product": product,
|
|
63
|
+
"transaction_date": price.date,
|
|
64
|
+
"transaction_subtype": Fees.Type.MANAGEMENT,
|
|
65
|
+
"underlying_instrument": underlying_instrument,
|
|
66
|
+
"currency": currency,
|
|
67
|
+
"calculated": True,
|
|
68
|
+
**{f: management_fees for f in base_fields},
|
|
69
|
+
}
|
|
70
|
+
yield {
|
|
71
|
+
"portfolio": portfolio,
|
|
72
|
+
"linked_product": product,
|
|
73
|
+
"transaction_date": price.date,
|
|
74
|
+
"transaction_subtype": Fees.Type.ISSUER,
|
|
75
|
+
"underlying_instrument": underlying_instrument,
|
|
76
|
+
"currency": currency,
|
|
77
|
+
"calculated": True,
|
|
78
|
+
**{f: bank_fees for f in base_fields},
|
|
79
|
+
}
|
|
80
|
+
yield {
|
|
81
|
+
"portfolio": portfolio,
|
|
82
|
+
"linked_product": product,
|
|
83
|
+
"transaction_date": price.date,
|
|
84
|
+
"transaction_subtype": Fees.Type.PERFORMANCE,
|
|
85
|
+
"underlying_instrument": underlying_instrument,
|
|
86
|
+
"currency": currency,
|
|
87
|
+
"calculated": True,
|
|
88
|
+
"total_value": performance_fees_net,
|
|
89
|
+
"total_value_fx_portfolio": performance_fees_net,
|
|
90
|
+
"total_value_gross": performance_fees_gross,
|
|
91
|
+
"total_value_gross_fx_portfolio": performance_fees_gross,
|
|
92
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from wbportfolio.models import Portfolio
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def callback(
|
|
10
|
+
portfolio: Portfolio,
|
|
11
|
+
sync_date: date,
|
|
12
|
+
rebalancing_freq: Optional[str] = "B",
|
|
13
|
+
equally_weighted: Optional[bool] = False,
|
|
14
|
+
composite: Optional[bool] = False,
|
|
15
|
+
base_assets: Optional[Dict] = dict(),
|
|
16
|
+
**kwargs: Any,
|
|
17
|
+
):
|
|
18
|
+
"""Recursively calculates the position for a portfolio
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
portfolio {portfolio.Portfolio} -- The Portfolio on which the assets will be computed
|
|
22
|
+
sync_date {datetime.date} -- The date on which the assets will be computed
|
|
23
|
+
|
|
24
|
+
Keyword Arguments:
|
|
25
|
+
portfolio {portfolio.Portfolio} -- The core portfolio from which the computed position are created (default: {None})
|
|
26
|
+
adjusted_weighting {int} -- the adjusted weight of the current level of index (default: {1})
|
|
27
|
+
adjusted_currency_fx_rate {int} -- the adjusted currency exchange rate on the current level of index (default: {1})
|
|
28
|
+
|
|
29
|
+
Yields:
|
|
30
|
+
tuple[dict, dict] -- Two dictionaries: One with filter parameters and one with default values
|
|
31
|
+
"""
|
|
32
|
+
assets = portfolio.assets.filter(date=sync_date)
|
|
33
|
+
if composite:
|
|
34
|
+
last_trade_proposals = portfolio.trade_proposals.filter(trade_date__lte=sync_date)
|
|
35
|
+
if last_trade_proposals.exists():
|
|
36
|
+
base_assets = last_trade_proposals.latest("trade_date").base_assets
|
|
37
|
+
if assets.exists() and assets.filter(date=sync_date).exists():
|
|
38
|
+
for asset in assets.all():
|
|
39
|
+
new_weight = asset.weighting
|
|
40
|
+
if pd.date_range(end=sync_date, periods=1, freq=rebalancing_freq)[0] == pd.Timestamp(sync_date):
|
|
41
|
+
if equally_weighted:
|
|
42
|
+
new_weight = Decimal(1 / assets.count())
|
|
43
|
+
elif base_assets and (proposed_weight := base_assets.get(asset.underlying_instrument.id, None)):
|
|
44
|
+
new_weight = proposed_weight
|
|
45
|
+
yield asset._build_dto(), asset._build_dto(new_weight)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from dynamic_preferences.preferences import Section
|
|
2
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
3
|
+
from dynamic_preferences.types import (
|
|
4
|
+
IntegerPreference,
|
|
5
|
+
LongStringPreference,
|
|
6
|
+
StringPreference,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
portfolio = Section("wbportfolio")
|
|
10
|
+
commission = Section("wbcommission")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@global_preferences_registry.register
|
|
14
|
+
class TimedeltaImportInstrumentPrice(IntegerPreference):
|
|
15
|
+
section = portfolio
|
|
16
|
+
name = "timedelta_import_instrument_price"
|
|
17
|
+
default = 2
|
|
18
|
+
|
|
19
|
+
verbose_name = "The Timedelta for the instrument price import windows"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@global_preferences_registry.register
|
|
23
|
+
class DaysToRecomputeRebateFromFeesThreshold(IntegerPreference):
|
|
24
|
+
section = commission
|
|
25
|
+
name = "days_to_recompute_rebate_from_fees_threshold"
|
|
26
|
+
default = 90 # 1 quarter
|
|
27
|
+
|
|
28
|
+
verbose_name = "Number of Days to recompute rebate from fees"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@global_preferences_registry.register
|
|
32
|
+
class MonthlyNetNewMoneyTarget(IntegerPreference):
|
|
33
|
+
section = portfolio
|
|
34
|
+
name = "monthly_nnm_target"
|
|
35
|
+
default = int(1e6)
|
|
36
|
+
|
|
37
|
+
verbose_name = "Monthly Net New Money Target"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@global_preferences_registry.register
|
|
41
|
+
class AccountHoldingReconciliationNotificationTitle(StringPreference):
|
|
42
|
+
section = portfolio
|
|
43
|
+
name = "account_holding_reconciliation_notification_title"
|
|
44
|
+
default = "Account Holdings Reconciliation"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@global_preferences_registry.register
|
|
48
|
+
class AccountHoldingReconciliationNotificationBody(LongStringPreference):
|
|
49
|
+
section = portfolio
|
|
50
|
+
name = "account_holding_reconciliation_notification_body"
|
|
51
|
+
default = """To validate your holdings, please review the reconciliation for the account {account} on {reconciliation_date}."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@global_preferences_registry.register
|
|
55
|
+
class AccountHoldingReconciliationNotificationBodyUpdate(LongStringPreference):
|
|
56
|
+
section = portfolio
|
|
57
|
+
name = "account_holding_reconciliation_notification_body_update"
|
|
58
|
+
default = """A reconcilation has been updated and requires your review. Please review the reconciliation for the account {account} on {reconciliation_date}."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from .adjustments import AdjustmentFactory
|
|
2
|
+
from .assets import AssetPositionFactory
|
|
3
|
+
from .claim import (
|
|
4
|
+
ApprovedClaimFactory,
|
|
5
|
+
ClaimFactory,
|
|
6
|
+
NegativeClaimFactory,
|
|
7
|
+
PositiveClaimFactory,
|
|
8
|
+
)
|
|
9
|
+
from .custodians import CustodianFactory
|
|
10
|
+
from .dividends import DividendTransactionsFactory
|
|
11
|
+
from .fees import FeesFactory
|
|
12
|
+
from wbfdm.factories.instrument_prices import InstrumentPriceFactory
|
|
13
|
+
from .portfolios import (
|
|
14
|
+
InstrumentPortfolioThroughModelFactory,
|
|
15
|
+
ModelPortfolioFactory,
|
|
16
|
+
ModelPortfolioWithBaseProductFactory,
|
|
17
|
+
PortfolioFactory,
|
|
18
|
+
)
|
|
19
|
+
from .portfolio_swing_pricings import PortfolioSwingPricingFactory
|
|
20
|
+
from .portfolio_cash_targets import PortfolioCashTargetFactory
|
|
21
|
+
from .portfolio_cash_flow import DailyPortfolioCashFlowFactory
|
|
22
|
+
from .product_groups import ProductGroupFactory, ProductGroupRepresentantFactory
|
|
23
|
+
from .products import IndexProductFactory, ProductFactory, WhiteLabelProductFactory
|
|
24
|
+
from .reconciliations import AccountReconciliationFactory, AccountReconciliationLineFactory
|
|
25
|
+
from .roles import ManagerPortfolioRoleFactory, ProductPortfolioRoleFactory
|
|
26
|
+
from .synchronization import (
|
|
27
|
+
CrontabScheduleFactory,
|
|
28
|
+
PeriodicTaskFactory,
|
|
29
|
+
PortfolioSynchronizationFactory,
|
|
30
|
+
PriceComputationFactory,
|
|
31
|
+
SynchronizationTaskFactory,
|
|
32
|
+
)
|
|
33
|
+
from .trades import CustomerTradeFactory, TradeFactory, TradeProposalFactory
|
|
34
|
+
from .transactions import TransactionFactory
|
|
35
|
+
from .indexes import IndexFactory
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from pandas.tseries.offsets import BDay
|
|
4
|
+
from wbportfolio.models import Adjustment
|
|
5
|
+
|
|
6
|
+
fake = Faker()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AdjustmentFactory(factory.django.DjangoModelFactory):
|
|
10
|
+
date = factory.LazyAttribute(lambda o: (fake.future_date() + BDay(0)).date())
|
|
11
|
+
instrument = factory.SubFactory("wbfdm.factories.instruments.InstrumentFactory")
|
|
12
|
+
factor = factory.Faker("pydecimal", min_value=2, max_value=10)
|
|
13
|
+
last_handler = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
|
|
14
|
+
status = Adjustment.Status.PENDING
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
model = Adjustment
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from datetime import date, timedelta
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
import factory
|
|
6
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
7
|
+
from wbfdm.factories import InstrumentPriceFactory
|
|
8
|
+
from wbportfolio.models import AssetPosition
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_weekday(o):
|
|
12
|
+
if o.underlying_instrument.id and o.underlying_instrument.assets.exists():
|
|
13
|
+
latest_position = o.underlying_instrument.assets.latest("date").date
|
|
14
|
+
else:
|
|
15
|
+
latest_position = date(2020, 1, 1)
|
|
16
|
+
|
|
17
|
+
latest_position += timedelta(days=1)
|
|
18
|
+
while latest_position.weekday() > 4:
|
|
19
|
+
latest_position += timedelta(days=1)
|
|
20
|
+
|
|
21
|
+
return latest_position
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AssetPositionFactory(factory.django.DjangoModelFactory):
|
|
25
|
+
class Meta:
|
|
26
|
+
model = AssetPosition
|
|
27
|
+
django_get_or_create = ("portfolio", "underlying_instrument", "date", "portfolio_created")
|
|
28
|
+
|
|
29
|
+
underlying_instrument = factory.SubFactory("wbfdm.factories.instruments.InstrumentFactory")
|
|
30
|
+
|
|
31
|
+
date = factory.LazyAttribute(lambda o: get_weekday(o))
|
|
32
|
+
asset_valuation_date = factory.LazyAttribute(lambda o: o.date)
|
|
33
|
+
|
|
34
|
+
initial_price = factory.Faker("pydecimal", min_value=100, max_value=120, right_digits=4)
|
|
35
|
+
underlying_instrument_price = factory.LazyAttribute(
|
|
36
|
+
lambda o: InstrumentPriceFactory.create(
|
|
37
|
+
instrument=o.underlying_instrument, calculated=False, date=o.date, net_value=o.initial_price
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
initial_shares = factory.LazyAttribute(lambda o: Decimal(random.randint(10, 10000)))
|
|
42
|
+
initial_currency_fx_rate = Decimal(1)
|
|
43
|
+
weighting = factory.LazyAttribute(lambda o: Decimal(random.random()))
|
|
44
|
+
|
|
45
|
+
portfolio = factory.SubFactory("wbportfolio.factories.portfolios.PortfolioFactory")
|
|
46
|
+
portfolio_created = None
|
|
47
|
+
|
|
48
|
+
currency = factory.LazyAttribute(lambda o: o.underlying_instrument.currency)
|
|
49
|
+
currency_fx_rate_instrument_to_usd = factory.LazyAttribute(
|
|
50
|
+
lambda o: CurrencyFXRates.objects.get_or_create(
|
|
51
|
+
currency=o.currency, date=o.date, defaults={"value": Decimal(1.0)}
|
|
52
|
+
)[0]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
is_estimated = False
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _create(cls, model_class, *args, **kwargs):
|
|
59
|
+
if (asset_valuation_date := kwargs.get("asset_valuation_date", None)) and (
|
|
60
|
+
currency := kwargs.get("currency", None)
|
|
61
|
+
):
|
|
62
|
+
kwargs["currency_fx_rate_instrument_to_usd"] = CurrencyFXRates.objects.get_or_create(
|
|
63
|
+
date=asset_valuation_date, currency=currency, defaults={"value": 1.0}
|
|
64
|
+
)[0]
|
|
65
|
+
if (asset_valuation_date := kwargs.get("asset_valuation_date", None)) and (
|
|
66
|
+
portfolio := kwargs.get("portfolio", None)
|
|
67
|
+
):
|
|
68
|
+
kwargs["currency_fx_rate_portfolio_to_usd"] = CurrencyFXRates.objects.get_or_create(
|
|
69
|
+
date=asset_valuation_date, currency=portfolio.currency, defaults={"value": 1.0}
|
|
70
|
+
)[0]
|
|
71
|
+
return super()._create(model_class, *args, **kwargs)
|
|
72
|
+
|
|
73
|
+
# @factory.pre
|
|
74
|
+
# def generate_currency_rates(self, create, extracted, **kwargs):
|
|
75
|
+
#
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from pandas.tseries.offsets import BDay
|
|
3
|
+
from wbfdm.factories.instrument_prices import InstrumentPriceFactory
|
|
4
|
+
from wbportfolio.models.transactions.claim import Claim
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ClaimFactory(factory.django.DjangoModelFactory):
|
|
8
|
+
status = Claim.Status.DRAFT
|
|
9
|
+
account = factory.SubFactory("wbcrm.factories.AccountFactory")
|
|
10
|
+
trade = factory.SubFactory("wbportfolio.factories.CustomerTradeFactory")
|
|
11
|
+
product = factory.LazyAttribute(lambda x: x.trade.product)
|
|
12
|
+
claimant = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
|
|
13
|
+
creator = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
|
|
14
|
+
date = factory.LazyAttribute(lambda x: (x.trade.transaction_date - BDay(0)).date())
|
|
15
|
+
bank = factory.Faker("company")
|
|
16
|
+
reference = factory.Faker("company")
|
|
17
|
+
shares = factory.LazyAttribute(lambda x: x.trade.shares)
|
|
18
|
+
nominal_amount = factory.LazyAttribute(lambda x: x.shares * x.product.share_price)
|
|
19
|
+
external_id = factory.Sequence(lambda n: f"{n:06}")
|
|
20
|
+
|
|
21
|
+
@factory.post_generation
|
|
22
|
+
def create_product_val(self, create, extracted, **kwargs):
|
|
23
|
+
if extracted is None or extracted is True:
|
|
24
|
+
InstrumentPriceFactory.create(instrument=self.product, date=self.date, calculated=False)
|
|
25
|
+
|
|
26
|
+
class Meta:
|
|
27
|
+
model = Claim
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PositiveClaimFactory(ClaimFactory):
|
|
31
|
+
shares = factory.Faker("pydecimal", min_value=0, max_value=1000000)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NegativeClaimFactory(ClaimFactory):
|
|
35
|
+
shares = factory.Faker("pydecimal", min_value=-1000000, max_value=0)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ApprovedClaimFactory(ClaimFactory):
|
|
39
|
+
status = Claim.Status.APPROVED
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from wbportfolio.models import Custodian
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CustodianFactory(factory.django.DjangoModelFactory):
|
|
6
|
+
name = factory.Faker("company")
|
|
7
|
+
mapping = factory.List([factory.Faker("company") for _ in range(5)])
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
model = Custodian
|
|
11
|
+
django_get_or_create = ("name",)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
from wbportfolio.models import DividendTransaction
|
|
5
|
+
|
|
6
|
+
from .transactions import TransactionFactory
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DividendTransactionsFactory(TransactionFactory):
|
|
10
|
+
class Meta:
|
|
11
|
+
model = DividendTransaction
|
|
12
|
+
|
|
13
|
+
shares = factory.LazyAttribute(lambda o: random.randint(10, 10000))
|
|
14
|
+
price = factory.LazyAttribute(lambda o: random.randint(10, 10000))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from wbportfolio.models import Fees
|
|
4
|
+
|
|
5
|
+
from .transactions import TransactionFactory
|
|
6
|
+
|
|
7
|
+
faker = Faker()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FeesFactory(TransactionFactory):
|
|
11
|
+
class Meta:
|
|
12
|
+
model = Fees
|
|
13
|
+
|
|
14
|
+
transaction_subtype = factory.Faker("random_element", elements=[x[0] for x in Fees.Type.choices])
|
|
15
|
+
linked_product = factory.SubFactory("wbportfolio.factories.ProductFactory")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from wbfdm.factories import InstrumentFactory, InstrumentTypeFactory
|
|
6
|
+
from wbportfolio.models import Index
|
|
7
|
+
|
|
8
|
+
fake = Faker()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class IndexFactory(InstrumentFactory):
|
|
12
|
+
risk_scale = factory.LazyAttribute(lambda o: random.randint(1, 7))
|
|
13
|
+
|
|
14
|
+
instrument_type = factory.LazyAttribute(lambda o: InstrumentTypeFactory.create(name="Index", key="index"))
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
model = Index
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from wbportfolio.factories.portfolios import PortfolioFactory
|
|
4
|
+
from wbportfolio.models import DailyPortfolioCashFlow
|
|
5
|
+
|
|
6
|
+
fake = Faker()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DailyPortfolioCashFlowFactory(factory.django.DjangoModelFactory):
|
|
10
|
+
class Meta:
|
|
11
|
+
model = DailyPortfolioCashFlow
|
|
12
|
+
|
|
13
|
+
value_date = factory.Faker("date")
|
|
14
|
+
portfolio = factory.SubFactory(PortfolioFactory)
|
|
15
|
+
|
|
16
|
+
total_assets = factory.Faker("pydecimal", left_digits=7, right_digits=4, min_value=0.01)
|
|
17
|
+
cash = factory.Faker("pydecimal", left_digits=7, right_digits=4, min_value=0.01)
|
|
18
|
+
cash_flow_forecast = factory.LazyAttribute(lambda o: fake.pydecimal(min_value=0.01, max_value=o.total_assets))
|
|
19
|
+
|
|
20
|
+
pending = factory.Faker("pybool")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from wbportfolio.factories.portfolios import PortfolioFactory
|
|
3
|
+
from wbportfolio.models import PortfolioCashTarget
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PortfolioCashTargetFactory(factory.django.DjangoModelFactory):
|
|
7
|
+
class Meta:
|
|
8
|
+
model = PortfolioCashTarget
|
|
9
|
+
|
|
10
|
+
valid_date = factory.Faker("date")
|
|
11
|
+
portfolio = factory.SubFactory(PortfolioFactory)
|
|
12
|
+
min_target = factory.Faker("pydecimal", left_digits=0, right_digits=4, max_value=0.01)
|
|
13
|
+
target = factory.Faker("pydecimal", left_digits=0, right_digits=4, max_value=0.01)
|
|
14
|
+
max_target = factory.Faker("pydecimal", left_digits=0, right_digits=4, min_value=0.01)
|
|
15
|
+
comment = factory.Faker("paragraph", nb_sentences=1)
|