wbportfolio 1.52.0__py2.py3-none-any.whl → 1.59.4__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/admin/__init__.py +3 -1
- wbportfolio/admin/indexes.py +1 -1
- wbportfolio/admin/orders/__init__.py +2 -0
- wbportfolio/admin/orders/order_proposals.py +16 -0
- wbportfolio/admin/orders/orders.py +32 -0
- wbportfolio/admin/portfolio.py +11 -5
- wbportfolio/admin/product_groups.py +1 -1
- wbportfolio/admin/products.py +2 -1
- wbportfolio/admin/{transactions/rebalancing.py → rebalancing.py} +1 -1
- wbportfolio/admin/transactions/__init__.py +0 -2
- wbportfolio/admin/transactions/dividends.py +40 -4
- wbportfolio/admin/transactions/fees.py +24 -14
- wbportfolio/admin/transactions/trades.py +34 -27
- wbportfolio/analysis/claims.py +5 -6
- wbportfolio/api_clients/ubs.py +162 -0
- wbportfolio/constants.py +1 -0
- wbportfolio/contrib/company_portfolio/configs/display.py +22 -10
- wbportfolio/contrib/company_portfolio/configs/previews.py +3 -3
- wbportfolio/contrib/company_portfolio/filters.py +10 -10
- wbportfolio/contrib/company_portfolio/models.py +69 -39
- wbportfolio/contrib/company_portfolio/scripts.py +7 -2
- wbportfolio/contrib/company_portfolio/serializers.py +32 -22
- wbportfolio/contrib/company_portfolio/tasks.py +12 -1
- wbportfolio/contrib/company_portfolio/tests/conftest.py +2 -2
- wbportfolio/defaults/fees/default.py +7 -15
- wbportfolio/factories/__init__.py +2 -2
- wbportfolio/factories/assets.py +1 -1
- wbportfolio/factories/dividends.py +8 -3
- wbportfolio/factories/fees.py +8 -4
- wbportfolio/factories/orders/__init__.py +2 -0
- wbportfolio/factories/orders/order_proposals.py +21 -0
- wbportfolio/factories/orders/orders.py +34 -0
- wbportfolio/factories/portfolios.py +2 -1
- wbportfolio/factories/product_groups.py +3 -3
- wbportfolio/factories/products.py +3 -3
- wbportfolio/factories/rebalancing.py +1 -1
- wbportfolio/factories/trades.py +12 -16
- wbportfolio/filters/assets.py +18 -4
- wbportfolio/filters/orders/__init__.py +2 -0
- wbportfolio/filters/orders/order_proposals.py +55 -0
- wbportfolio/filters/orders/orders.py +11 -0
- wbportfolio/filters/portfolios.py +38 -1
- wbportfolio/filters/positions.py +0 -1
- wbportfolio/filters/transactions/__init__.py +1 -2
- wbportfolio/filters/transactions/fees.py +5 -12
- wbportfolio/filters/transactions/trades.py +16 -8
- wbportfolio/filters/transactions/utils.py +42 -0
- wbportfolio/import_export/backends/ubs/__init__.py +1 -0
- wbportfolio/import_export/backends/ubs/asset_position.py +6 -7
- wbportfolio/import_export/backends/ubs/fees.py +10 -20
- wbportfolio/import_export/backends/ubs/instrument_price.py +6 -6
- wbportfolio/import_export/backends/ubs/trade.py +48 -0
- wbportfolio/import_export/backends/utils.py +0 -17
- wbportfolio/import_export/handlers/asset_position.py +22 -10
- wbportfolio/import_export/handlers/dividend.py +8 -8
- wbportfolio/import_export/handlers/fees.py +13 -23
- wbportfolio/import_export/handlers/orders.py +71 -0
- wbportfolio/import_export/handlers/trade.py +53 -77
- wbportfolio/import_export/parsers/default_mapping.py +1 -1
- wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +2 -2
- wbportfolio/import_export/parsers/jpmorgan/fees.py +4 -4
- wbportfolio/import_export/parsers/jpmorgan/strategy.py +59 -85
- wbportfolio/import_export/parsers/jpmorgan/valuation.py +2 -2
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
- wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
- wbportfolio/import_export/parsers/leonteq/trade.py +2 -6
- wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
- wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
- wbportfolio/import_export/parsers/natixis/equity.py +22 -4
- wbportfolio/import_export/parsers/natixis/fees.py +7 -9
- wbportfolio/import_export/parsers/natixis/utils.py +13 -19
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
- wbportfolio/import_export/parsers/sg_lux/equity.py +10 -10
- wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/sylk.py +12 -11
- wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/valuation.py +4 -2
- wbportfolio/import_export/parsers/societe_generale/strategy.py +5 -5
- wbportfolio/import_export/parsers/tellco/customer_trade.py +2 -1
- wbportfolio/import_export/parsers/tellco/valuation.py +4 -3
- wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
- wbportfolio/import_export/parsers/ubs/api/trade.py +39 -0
- wbportfolio/import_export/parsers/ubs/customer_trade.py +7 -5
- wbportfolio/import_export/parsers/ubs/equity.py +3 -2
- wbportfolio/import_export/parsers/ubs/valuation.py +2 -1
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
- wbportfolio/import_export/parsers/vontobel/management_fees.py +12 -20
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +5 -8
- wbportfolio/import_export/parsers/vontobel/valuation_api.py +4 -1
- wbportfolio/import_export/resources/trades.py +3 -3
- wbportfolio/import_export/utils.py +3 -1
- wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
- wbportfolio/metric/backends/base.py +2 -2
- wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
- wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
- wbportfolio/migrations/0078_trade_drift_factor.py +26 -0
- wbportfolio/migrations/0079_alter_trade_drift_factor.py +19 -0
- wbportfolio/migrations/0080_alter_trade_drift_factor_alter_trade_weighting.py +19 -0
- wbportfolio/migrations/0081_alter_trade_drift_factor.py +19 -0
- wbportfolio/migrations/0082_remove_tradeproposal_creator_and_more.py +93 -0
- wbportfolio/migrations/0083_order_alter_trade_options_and_more.py +181 -0
- wbportfolio/migrations/0084_orderproposal_min_order_value.py +25 -0
- wbportfolio/migrations/0085_order_desired_target_weight.py +26 -0
- wbportfolio/migrations/0086_orderproposal_total_cash_weight.py +19 -0
- wbportfolio/migrations/0087_product_order_routing_custodian_adapter.py +94 -0
- wbportfolio/migrations/0088_orderproposal_total_effective_portfolio_contribution.py +19 -0
- wbportfolio/migrations/0089_orderproposal_min_weighting.py +71 -0
- wbportfolio/migrations/0090_dividendtransaction_price_fx_portfolio_and_more.py +44 -0
- wbportfolio/migrations/0091_remove_order_execution_confirmed_and_more.py +32 -0
- wbportfolio/migrations/0092_order_quantization_error_alter_orderproposal_status.py +49 -0
- wbportfolio/migrations/0093_remove_portfolioportfoliothroughmodel_unique_primary_and_more.py +35 -0
- wbportfolio/models/__init__.py +2 -0
- wbportfolio/models/adjustments.py +1 -1
- wbportfolio/models/asset.py +28 -170
- wbportfolio/models/builder.py +323 -0
- wbportfolio/models/custodians.py +3 -3
- wbportfolio/models/exceptions.py +1 -1
- wbportfolio/models/graphs/portfolio.py +1 -1
- wbportfolio/models/graphs/utils.py +11 -11
- wbportfolio/models/mixins/instruments.py +7 -0
- wbportfolio/models/mixins/liquidity_stress_test.py +4 -4
- wbportfolio/models/orders/__init__.py +2 -0
- wbportfolio/models/orders/order_proposals.py +1414 -0
- wbportfolio/models/orders/orders.py +410 -0
- wbportfolio/models/portfolio.py +311 -289
- wbportfolio/models/portfolio_relationship.py +6 -0
- wbportfolio/models/products.py +12 -0
- wbportfolio/models/{transactions/rebalancing.py → rebalancing.py} +40 -27
- wbportfolio/models/roles.py +4 -10
- wbportfolio/models/transactions/__init__.py +0 -4
- wbportfolio/models/transactions/claim.py +7 -6
- wbportfolio/models/transactions/dividends.py +42 -5
- wbportfolio/models/transactions/fees.py +55 -22
- wbportfolio/models/transactions/trades.py +121 -442
- wbportfolio/models/transactions/transactions.py +78 -158
- wbportfolio/models/utils.py +100 -1
- wbportfolio/order_routing/__init__.py +35 -0
- wbportfolio/order_routing/adapters/__init__.py +65 -0
- wbportfolio/order_routing/adapters/ubs.py +195 -0
- wbportfolio/order_routing/router.py +33 -0
- wbportfolio/order_routing/tests/__init__.py +0 -0
- wbportfolio/order_routing/tests/test_router.py +110 -0
- wbportfolio/permissions.py +7 -0
- wbportfolio/pms/analytics/portfolio.py +17 -9
- wbportfolio/pms/analytics/utils.py +9 -0
- wbportfolio/pms/trading/__init__.py +0 -1
- wbportfolio/pms/trading/optimizer.py +61 -0
- wbportfolio/pms/typing.py +198 -63
- wbportfolio/rebalancing/base.py +12 -1
- wbportfolio/rebalancing/decorators.py +1 -1
- wbportfolio/rebalancing/models/composite.py +4 -8
- wbportfolio/rebalancing/models/equally_weighted.py +13 -11
- wbportfolio/rebalancing/models/market_capitalization_weighted.py +21 -14
- wbportfolio/rebalancing/models/model_portfolio.py +14 -18
- wbportfolio/risk_management/backends/__init__.py +1 -0
- wbportfolio/risk_management/backends/controversy_portfolio.py +2 -2
- wbportfolio/risk_management/backends/esg_aggregation_portfolio.py +64 -0
- wbportfolio/risk_management/backends/exposure_portfolio.py +4 -4
- wbportfolio/risk_management/backends/instrument_list_portfolio.py +3 -3
- wbportfolio/risk_management/tests/test_esg_aggregation_portfolio.py +49 -0
- wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -1
- wbportfolio/risk_management/tests/test_stop_loss_instrument.py +2 -2
- wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -1
- wbportfolio/serializers/__init__.py +1 -0
- wbportfolio/serializers/orders/__init__.py +2 -0
- wbportfolio/serializers/orders/order_proposals.py +115 -0
- wbportfolio/serializers/orders/orders.py +283 -0
- wbportfolio/serializers/portfolios.py +7 -7
- wbportfolio/serializers/positions.py +2 -2
- wbportfolio/serializers/rebalancing.py +1 -1
- wbportfolio/serializers/signals.py +9 -12
- wbportfolio/serializers/transactions/__init__.py +1 -10
- wbportfolio/serializers/transactions/claim.py +2 -2
- wbportfolio/serializers/transactions/dividends.py +37 -9
- wbportfolio/serializers/transactions/fees.py +39 -10
- wbportfolio/serializers/transactions/trades.py +55 -157
- wbportfolio/tasks.py +43 -5
- wbportfolio/tests/analysis/__init__.py +0 -0
- wbportfolio/tests/analysis/test_claims.py +85 -0
- wbportfolio/tests/conftest.py +12 -12
- wbportfolio/tests/models/orders/__init__.py +0 -0
- wbportfolio/tests/models/orders/test_order_proposals.py +1046 -0
- wbportfolio/tests/models/test_assets.py +7 -3
- wbportfolio/tests/models/test_imports.py +9 -13
- wbportfolio/tests/models/test_portfolios.py +102 -95
- wbportfolio/tests/models/test_products.py +11 -0
- wbportfolio/tests/models/test_splits.py +1 -6
- wbportfolio/tests/models/test_utils.py +140 -0
- wbportfolio/tests/models/transactions/test_fees.py +7 -13
- wbportfolio/tests/models/transactions/test_rebalancing.py +5 -5
- wbportfolio/tests/models/transactions/test_trades.py +0 -20
- wbportfolio/tests/pms/test_analytics.py +22 -3
- wbportfolio/tests/rebalancing/test_models.py +51 -57
- wbportfolio/tests/signals.py +10 -20
- wbportfolio/tests/tests.py +3 -1
- wbportfolio/tests/viewsets/test_products.py +1 -0
- wbportfolio/urls.py +10 -13
- wbportfolio/viewsets/__init__.py +9 -4
- wbportfolio/viewsets/assets.py +3 -204
- wbportfolio/viewsets/charts/__init__.py +6 -1
- wbportfolio/viewsets/charts/assets.py +344 -154
- wbportfolio/viewsets/configs/buttons/__init__.py +2 -2
- wbportfolio/viewsets/configs/buttons/assets.py +1 -1
- wbportfolio/viewsets/configs/buttons/mixins.py +4 -4
- wbportfolio/viewsets/configs/buttons/portfolios.py +45 -1
- wbportfolio/viewsets/configs/buttons/products.py +32 -2
- wbportfolio/viewsets/configs/display/__init__.py +2 -5
- wbportfolio/viewsets/configs/display/assets.py +6 -19
- wbportfolio/viewsets/configs/display/fees.py +3 -3
- wbportfolio/viewsets/configs/display/portfolios.py +5 -5
- wbportfolio/viewsets/configs/display/products.py +1 -1
- wbportfolio/viewsets/configs/display/rebalancing.py +2 -2
- wbportfolio/viewsets/configs/display/reconciliations.py +4 -4
- wbportfolio/viewsets/configs/display/trades.py +1 -189
- wbportfolio/viewsets/configs/endpoints/__init__.py +3 -7
- wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
- wbportfolio/viewsets/configs/endpoints/trades.py +0 -41
- wbportfolio/viewsets/configs/menu/__init__.py +1 -1
- wbportfolio/viewsets/configs/menu/orders.py +11 -0
- wbportfolio/viewsets/configs/titles/__init__.py +2 -3
- wbportfolio/viewsets/configs/titles/fees.py +4 -8
- wbportfolio/viewsets/esg.py +3 -5
- wbportfolio/viewsets/mixins.py +5 -1
- wbportfolio/viewsets/orders/__init__.py +6 -0
- wbportfolio/viewsets/orders/configs/__init__.py +4 -0
- wbportfolio/viewsets/orders/configs/buttons/__init__.py +2 -0
- wbportfolio/viewsets/orders/configs/buttons/order_proposals.py +188 -0
- wbportfolio/viewsets/orders/configs/buttons/orders.py +113 -0
- wbportfolio/viewsets/orders/configs/displays/__init__.py +2 -0
- wbportfolio/viewsets/orders/configs/displays/order_proposals.py +157 -0
- wbportfolio/viewsets/orders/configs/displays/orders.py +232 -0
- wbportfolio/viewsets/orders/configs/endpoints/__init__.py +2 -0
- wbportfolio/viewsets/orders/configs/endpoints/order_proposals.py +21 -0
- wbportfolio/viewsets/orders/configs/endpoints/orders.py +28 -0
- wbportfolio/viewsets/orders/configs/titles/__init__.py +0 -0
- wbportfolio/viewsets/orders/configs/titles/orders.py +0 -0
- wbportfolio/viewsets/orders/order_proposals.py +252 -0
- wbportfolio/viewsets/orders/orders.py +277 -0
- wbportfolio/viewsets/portfolios.py +36 -12
- wbportfolio/viewsets/positions.py +3 -2
- wbportfolio/viewsets/products.py +6 -6
- wbportfolio/viewsets/{transactions/rebalancing.py → rebalancing.py} +2 -2
- wbportfolio/viewsets/transactions/__init__.py +3 -14
- wbportfolio/viewsets/transactions/fees.py +22 -22
- wbportfolio/viewsets/transactions/trades.py +1 -180
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/METADATA +3 -1
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/RECORD +252 -203
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/WHEEL +1 -1
- wbportfolio/admin/transactions/transactions.py +0 -38
- wbportfolio/factories/transactions.py +0 -22
- wbportfolio/fdm/tasks.py +0 -13
- wbportfolio/filters/transactions/transactions.py +0 -99
- wbportfolio/models/transactions/expiry.py +0 -7
- wbportfolio/models/transactions/trade_proposals.py +0 -704
- wbportfolio/pms/trading/handler.py +0 -161
- wbportfolio/serializers/transactions/expiry.py +0 -18
- wbportfolio/serializers/transactions/trade_proposals.py +0 -76
- wbportfolio/serializers/transactions/transactions.py +0 -85
- wbportfolio/tests/models/transactions/test_trade_proposals.py +0 -410
- wbportfolio/viewsets/configs/buttons/trade_proposals.py +0 -66
- wbportfolio/viewsets/configs/display/trade_proposals.py +0 -100
- wbportfolio/viewsets/configs/display/transactions.py +0 -55
- wbportfolio/viewsets/configs/endpoints/trade_proposals.py +0 -18
- wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
- wbportfolio/viewsets/configs/menu/transactions.py +0 -9
- wbportfolio/viewsets/configs/titles/transactions.py +0 -9
- wbportfolio/viewsets/signals.py +0 -43
- wbportfolio/viewsets/transactions/trade_proposals.py +0 -139
- wbportfolio/viewsets/transactions/transactions.py +0 -122
- /wbportfolio/{fdm → api_clients}/__init__.py +0 -0
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from wbcore import filters
|
|
2
|
-
from wbcore.contrib.directory.filters import CompanyFilter as
|
|
3
|
-
from wbcore.contrib.directory.filters import PersonFilter as
|
|
2
|
+
from wbcore.contrib.directory.filters import CompanyFilter as BaseCompanyFilter
|
|
3
|
+
from wbcore.contrib.directory.filters import PersonFilter as BasePersonFilter
|
|
4
4
|
from wbcore.contrib.directory.models import Company, Person
|
|
5
5
|
|
|
6
6
|
|
|
@@ -99,29 +99,29 @@ class EntryPortfolioFilter(filters.FilterSet):
|
|
|
99
99
|
return queryset
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
class CompanyFilter(
|
|
102
|
+
class CompanyFilter(BaseCompanyFilter, EntryPortfolioFilter):
|
|
103
103
|
@classmethod
|
|
104
104
|
def get_filter_class_for_remote_filter(cls):
|
|
105
105
|
"""
|
|
106
106
|
Define which filterset class sender to user for remote filter registration
|
|
107
107
|
"""
|
|
108
|
-
return
|
|
108
|
+
return BaseCompanyFilter
|
|
109
109
|
|
|
110
|
-
class Meta(
|
|
110
|
+
class Meta(BaseCompanyFilter.Meta):
|
|
111
111
|
fields = {
|
|
112
|
-
**
|
|
112
|
+
**BaseCompanyFilter.Meta.fields,
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
class PersonFilter(
|
|
116
|
+
class PersonFilter(BasePersonFilter, EntryPortfolioFilter):
|
|
117
117
|
@classmethod
|
|
118
118
|
def get_filter_class_for_remote_filter(cls):
|
|
119
119
|
"""
|
|
120
120
|
Define which filterset class sender to user for remote filter registration
|
|
121
121
|
"""
|
|
122
|
-
return
|
|
122
|
+
return BasePersonFilter
|
|
123
123
|
|
|
124
|
-
class Meta(
|
|
124
|
+
class Meta(BasePersonFilter.Meta):
|
|
125
125
|
fields = {
|
|
126
|
-
**
|
|
126
|
+
**BasePersonFilter.Meta.fields,
|
|
127
127
|
}
|
|
@@ -3,6 +3,7 @@ from datetime import date
|
|
|
3
3
|
from decimal import Decimal
|
|
4
4
|
|
|
5
5
|
from django.conf import settings
|
|
6
|
+
from django.core.cache import cache
|
|
6
7
|
from django.db import models
|
|
7
8
|
from django.db.models.signals import post_save
|
|
8
9
|
from django.dispatch import receiver
|
|
@@ -16,7 +17,12 @@ from wbportfolio.models import Claim, Product
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def get_total_assets_under_management(val_date: date) -> Decimal:
|
|
19
|
-
|
|
20
|
+
cache_key = f"total_assets_under_management:{val_date.isoformat()}"
|
|
21
|
+
return cache.get_or_set(
|
|
22
|
+
cache_key,
|
|
23
|
+
lambda: sum([product.get_total_aum_usd(val_date) for product in Product.active_objects.all()]),
|
|
24
|
+
60 * 60 * 24,
|
|
25
|
+
)
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
def get_lost_client_customer_status():
|
|
@@ -44,9 +50,8 @@ class Updater:
|
|
|
44
50
|
self.val_date = val_date
|
|
45
51
|
self.total_assets_under_management = get_total_assets_under_management(val_date)
|
|
46
52
|
|
|
47
|
-
def update_company_data(self,
|
|
53
|
+
def update_company_data(self, company_portfolio_data) -> tuple[str, str]:
|
|
48
54
|
# save company portfolio data
|
|
49
|
-
company_portfolio_data = CompanyPortfolioData.objects.get_or_create(company=company)[0]
|
|
50
55
|
if (
|
|
51
56
|
invested_assets_under_management_usd := company_portfolio_data.get_assets_under_management_usd(
|
|
52
57
|
self.val_date
|
|
@@ -55,19 +60,11 @@ class Updater:
|
|
|
55
60
|
company_portfolio_data.invested_assets_under_management_usd = invested_assets_under_management_usd
|
|
56
61
|
if (potential := company_portfolio_data.get_potential(self.val_date)) is not None:
|
|
57
62
|
company_portfolio_data.potential = potential
|
|
58
|
-
company_portfolio_data.save()
|
|
59
63
|
|
|
60
64
|
# update the company object itself
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
company.save()
|
|
65
|
-
|
|
66
|
-
# def update_all_companies(self, val_date: date):
|
|
67
|
-
# for company in tqdm(qs, total=qs.count()):
|
|
68
|
-
# with suppress(CompanyPortfolioData.DoesNotExist):
|
|
69
|
-
# company_portfolio = CompanyPortfolioData.objects.get(company=company)
|
|
70
|
-
# company_portfolio.update_data(date.today())
|
|
65
|
+
tier = company_portfolio_data.get_tiering(self.total_assets_under_management)
|
|
66
|
+
customer_status = company_portfolio_data.get_customer_status()
|
|
67
|
+
return customer_status, tier
|
|
71
68
|
|
|
72
69
|
|
|
73
70
|
class CompanyPortfolioData(models.Model):
|
|
@@ -104,14 +101,6 @@ class CompanyPortfolioData(models.Model):
|
|
|
104
101
|
verbose_name="AUM",
|
|
105
102
|
help_text="The Assets under Management (AUM) that is managed by this company or this person's primary employer.",
|
|
106
103
|
)
|
|
107
|
-
invested_assets_under_management_usd = models.DecimalField(
|
|
108
|
-
max_digits=17,
|
|
109
|
-
decimal_places=2,
|
|
110
|
-
null=True,
|
|
111
|
-
blank=True,
|
|
112
|
-
help_text="The invested Assets under Management (AUM).",
|
|
113
|
-
verbose_name="Invested AUM ($)",
|
|
114
|
-
)
|
|
115
104
|
|
|
116
105
|
investment_discretion = models.CharField(
|
|
117
106
|
max_length=21,
|
|
@@ -120,10 +109,6 @@ class CompanyPortfolioData(models.Model):
|
|
|
120
109
|
help_text="What discretion this company or this person's primary employer has to invest its assets.",
|
|
121
110
|
verbose_name="Investment Discretion",
|
|
122
111
|
)
|
|
123
|
-
|
|
124
|
-
potential = models.DecimalField(
|
|
125
|
-
decimal_places=2, max_digits=19, null=True, blank=True, help_text=potential_help_text
|
|
126
|
-
)
|
|
127
112
|
potential_currency = models.ForeignKey(
|
|
128
113
|
to="currency.Currency",
|
|
129
114
|
related_name="wbportfolio_potential_currencies",
|
|
@@ -132,6 +117,25 @@ class CompanyPortfolioData(models.Model):
|
|
|
132
117
|
on_delete=models.PROTECT,
|
|
133
118
|
)
|
|
134
119
|
|
|
120
|
+
# Dynamic fields
|
|
121
|
+
invested_assets_under_management_usd = models.DecimalField(
|
|
122
|
+
max_digits=17,
|
|
123
|
+
decimal_places=2,
|
|
124
|
+
null=True,
|
|
125
|
+
blank=True,
|
|
126
|
+
help_text="The invested Assets under Management (AUM).",
|
|
127
|
+
verbose_name="Invested AUM ($)",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
potential = models.DecimalField(
|
|
131
|
+
decimal_places=2, max_digits=19, null=True, blank=True, help_text=potential_help_text
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def update(self):
|
|
135
|
+
with suppress(CurrencyFXRates.DoesNotExist):
|
|
136
|
+
val_date = CurrencyFXRates.objects.latest("date").date
|
|
137
|
+
self.company.customer_status, self.company.tier = Updater(val_date).update_company_data(self)
|
|
138
|
+
|
|
135
139
|
def get_assets_under_management_usd(self, val_date: date) -> Decimal:
|
|
136
140
|
return Claim.objects.filter(status=Claim.Status.APPROVED).filter_for_customer(
|
|
137
141
|
self.company
|
|
@@ -140,16 +144,17 @@ class CompanyPortfolioData(models.Model):
|
|
|
140
144
|
)["invested_aum_usd"] or Decimal(0)
|
|
141
145
|
|
|
142
146
|
def _get_default_potential(self, val_date: date) -> Decimal:
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
if self.assets_under_management:
|
|
148
|
+
with suppress(CurrencyFXRates.DoesNotExist):
|
|
149
|
+
fx = CurrencyFXRates.objects.get(currency=self.assets_under_management_currency, date=val_date).value
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
aum_usd = self.assets_under_management / fx
|
|
152
|
+
aum_potential = Decimal(0)
|
|
153
|
+
for asset_allocation in self.company.asset_allocations.all():
|
|
154
|
+
aum_potential += aum_usd * asset_allocation.percent * asset_allocation.max_investment
|
|
155
|
+
invested_aum = self.invested_assets_under_management_usd or Decimal(0.0)
|
|
151
156
|
|
|
152
|
-
|
|
157
|
+
return aum_potential - invested_aum
|
|
153
158
|
|
|
154
159
|
def get_potential(self, val_date: date) -> Decimal:
|
|
155
160
|
if module_path := getattr(settings, "PORTFOLIO_COMPANY_DATA_POTENTIAL_METHOD", None):
|
|
@@ -225,11 +230,6 @@ class CompanyPortfolioData(models.Model):
|
|
|
225
230
|
verbose_name_plural = "Company Portfolio Data"
|
|
226
231
|
|
|
227
232
|
|
|
228
|
-
@receiver(post_save, sender="directory.Company")
|
|
229
|
-
def create_company_portfolio_data(sender, instance, created, **kwargs):
|
|
230
|
-
CompanyPortfolioData.objects.get_or_create(company=instance)
|
|
231
|
-
|
|
232
|
-
|
|
233
233
|
class AssetAllocationType(WBModel):
|
|
234
234
|
name = models.CharField(max_length=255)
|
|
235
235
|
default_max_investment = models.DecimalField(
|
|
@@ -329,3 +329,33 @@ class GeographicFocus(models.Model):
|
|
|
329
329
|
@classmethod
|
|
330
330
|
def get_representation_label_key(cls):
|
|
331
331
|
return "{{company}}: {{percent}} {{country}}"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@receiver(post_save, sender=AssetAllocation)
|
|
335
|
+
@receiver(post_save, sender=GeographicFocus)
|
|
336
|
+
def post_save_company_data(sender, instance, created, **kwargs):
|
|
337
|
+
company = instance.company
|
|
338
|
+
portfolio_data, created = CompanyPortfolioData.objects.get_or_create(company=company)
|
|
339
|
+
if not created:
|
|
340
|
+
portfolio_data.update()
|
|
341
|
+
portfolio_data.save()
|
|
342
|
+
portfolio_data.company.save()
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@receiver(post_save, sender="directory.Company")
|
|
346
|
+
def handle_company_portfolio_data(sender, instance, created, **kwargs):
|
|
347
|
+
# create default asset allocation type (equity 50/50)
|
|
348
|
+
if not instance.asset_allocations.exists():
|
|
349
|
+
equity_asset_type = AssetAllocationType.objects.get_or_create(name="Equity")[0]
|
|
350
|
+
AssetAllocation.objects.create(
|
|
351
|
+
company=instance,
|
|
352
|
+
asset_type=equity_asset_type,
|
|
353
|
+
percent=0.5,
|
|
354
|
+
max_investment=0.5,
|
|
355
|
+
)
|
|
356
|
+
if created:
|
|
357
|
+
portfolio_data, created = CompanyPortfolioData.objects.get_or_create(company=instance)
|
|
358
|
+
if not created:
|
|
359
|
+
portfolio_data.update()
|
|
360
|
+
portfolio_data.save()
|
|
361
|
+
portfolio_data.company.save()
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
3
|
|
|
3
4
|
from wbcore.contrib.currency.models import Currency
|
|
4
5
|
from wbcore.contrib.directory.models import Company
|
|
5
6
|
|
|
7
|
+
logger = logging.getLogger("pms")
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
def get_currency_and_assets_under_management(
|
|
8
11
|
aum_string, currency_mapping, default_currency, multiplier_mapping, default_multiplier
|
|
@@ -72,5 +75,7 @@ def assign_aum():
|
|
|
72
75
|
portfolio_data.assets_under_management_currency = currency
|
|
73
76
|
try:
|
|
74
77
|
portfolio_data.save()
|
|
75
|
-
except Exception:
|
|
76
|
-
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(
|
|
80
|
+
f"while we try to save the customer portfolio aum data for {company}, we encounter the error: {e}"
|
|
81
|
+
)
|
|
@@ -91,7 +91,7 @@ def update_portfolio_data(company_portfolio_data, portfolio_data):
|
|
|
91
91
|
|
|
92
92
|
if investment_discretion := portfolio_data["investment_discretion"]:
|
|
93
93
|
company_portfolio_data.investment_discretion = investment_discretion
|
|
94
|
-
|
|
94
|
+
company_portfolio_data.update()
|
|
95
95
|
company_portfolio_data.save()
|
|
96
96
|
|
|
97
97
|
|
|
@@ -103,22 +103,11 @@ class CompanyPortfolioDataMixin(serializers.ModelSerializer):
|
|
|
103
103
|
read_only=True,
|
|
104
104
|
**_get_assets_under_management_kwargs(field_name="invested_assets_under_management_usd"),
|
|
105
105
|
)
|
|
106
|
-
|
|
107
|
-
assets_under_management_currency = serializers.PrimaryKeyRelatedField(
|
|
108
|
-
required=False,
|
|
109
|
-
queryset=Currency.objects.all(),
|
|
110
|
-
default=None,
|
|
111
|
-
label=CompanyPortfolioData.assets_under_management_currency.field.verbose_name,
|
|
112
|
-
)
|
|
106
|
+
|
|
113
107
|
assets_under_management_currency_repr = serializers.CharField(read_only=True, required=False)
|
|
114
|
-
|
|
108
|
+
|
|
115
109
|
potential = serializers.DecimalField(**_get_potential_kwargs())
|
|
116
|
-
|
|
117
|
-
required=False,
|
|
118
|
-
queryset=Currency.objects.all(),
|
|
119
|
-
label=CompanyPortfolioData.potential_currency.field.verbose_name,
|
|
120
|
-
)
|
|
121
|
-
_potential_currency = CurrencyRepresentationSerializer(source="potential_currency")
|
|
110
|
+
investment_discretion = serializers.ChoiceField(**_get_investment_discretion_kwargs())
|
|
122
111
|
|
|
123
112
|
# Not sure why read_only_fields does not work...
|
|
124
113
|
tier = serializers.ChoiceField(
|
|
@@ -144,10 +133,6 @@ class CompanyPortfolioDataMixin(serializers.ModelSerializer):
|
|
|
144
133
|
"asset_under_management", # TODO: add an s after asset - After removing this field from the base model
|
|
145
134
|
"assets_under_management_currency_repr",
|
|
146
135
|
"invested_assets_under_management_usd",
|
|
147
|
-
"potential_currency",
|
|
148
|
-
"_potential_currency",
|
|
149
|
-
"assets_under_management_currency",
|
|
150
|
-
"_assets_under_management_currency",
|
|
151
136
|
"investment_discretion",
|
|
152
137
|
"potential",
|
|
153
138
|
"tier",
|
|
@@ -156,6 +141,19 @@ class CompanyPortfolioDataMixin(serializers.ModelSerializer):
|
|
|
156
141
|
|
|
157
142
|
class CompanyModelSerializer(CompanyPortfolioDataMixin, BaseCompanyModelSerializer):
|
|
158
143
|
SERIALIZER_CLASS_FOR_REMOTE_ADDITIONAL_RESOURCES = BasePersonModelSerializer
|
|
144
|
+
assets_under_management_currency = serializers.PrimaryKeyRelatedField(
|
|
145
|
+
required=False,
|
|
146
|
+
queryset=Currency.objects.all(),
|
|
147
|
+
default=None,
|
|
148
|
+
label=CompanyPortfolioData.assets_under_management_currency.field.verbose_name,
|
|
149
|
+
)
|
|
150
|
+
_assets_under_management_currency = CurrencyRepresentationSerializer(source="assets_under_management_currency")
|
|
151
|
+
potential_currency = serializers.PrimaryKeyRelatedField(
|
|
152
|
+
required=False,
|
|
153
|
+
queryset=Currency.objects.all(),
|
|
154
|
+
label=CompanyPortfolioData.potential_currency.field.verbose_name,
|
|
155
|
+
)
|
|
156
|
+
_potential_currency = CurrencyRepresentationSerializer(source="potential_currency")
|
|
159
157
|
|
|
160
158
|
def update(self, instance, validated_data):
|
|
161
159
|
validated_data, portfolio_data = get_portfolio_data(validated_data)
|
|
@@ -209,6 +207,20 @@ class CompanyModelListSerializer(CompanyPortfolioDataMixin, BaseCompanyModelList
|
|
|
209
207
|
class PersonModelSerializer(CompanyPortfolioDataMixin, BasePersonModelSerializer):
|
|
210
208
|
SERIALIZER_CLASS_FOR_REMOTE_ADDITIONAL_RESOURCES = BasePersonModelSerializer
|
|
211
209
|
|
|
210
|
+
assets_under_management_currency = serializers.PrimaryKeyRelatedField(
|
|
211
|
+
required=False,
|
|
212
|
+
queryset=Currency.objects.all(),
|
|
213
|
+
default=None,
|
|
214
|
+
label=CompanyPortfolioData.assets_under_management_currency.field.verbose_name,
|
|
215
|
+
)
|
|
216
|
+
_assets_under_management_currency = CurrencyRepresentationSerializer(source="assets_under_management_currency")
|
|
217
|
+
potential_currency = serializers.PrimaryKeyRelatedField(
|
|
218
|
+
required=False,
|
|
219
|
+
queryset=Currency.objects.all(),
|
|
220
|
+
label=CompanyPortfolioData.potential_currency.field.verbose_name,
|
|
221
|
+
)
|
|
222
|
+
_potential_currency = CurrencyRepresentationSerializer(source="potential_currency")
|
|
223
|
+
|
|
212
224
|
asset_under_management = serializers.DecimalField(
|
|
213
225
|
**_get_assets_under_management_kwargs(field_name="assets_under_management"),
|
|
214
226
|
read_only=True,
|
|
@@ -243,11 +255,9 @@ class PersonModelListSerializer(CompanyPortfolioDataMixin, BasePersonModelListSe
|
|
|
243
255
|
|
|
244
256
|
|
|
245
257
|
class AssetAllocationTypeRepresentationSerializer(serializers.RepresentationSerializer):
|
|
246
|
-
_detail = serializers.HyperlinkField(reverse_name="company_portfolio:assetallocationtyperepresentation-detail")
|
|
247
|
-
|
|
248
258
|
class Meta:
|
|
249
259
|
model = AssetAllocationType
|
|
250
|
-
fields = ("id", "name"
|
|
260
|
+
fields = ("id", "name")
|
|
251
261
|
|
|
252
262
|
|
|
253
263
|
class AssetAllocationTypeModelSerializer(serializers.ModelSerializer):
|
|
@@ -19,5 +19,16 @@ def update_all_portfolio_data(val_date: date | None = None):
|
|
|
19
19
|
has_account=Exists(Account.objects.filter(owner=OuterRef("pk"))),
|
|
20
20
|
has_portfolio_data=Exists(CompanyPortfolioData.objects.filter(company=OuterRef("pk"))),
|
|
21
21
|
)
|
|
22
|
+
company_objs = []
|
|
23
|
+
portfolio_data_objs = []
|
|
22
24
|
for company in tqdm(qs, total=qs.count()):
|
|
23
|
-
|
|
25
|
+
portfolio_data = CompanyPortfolioData.objects.get_or_create(company=company)[0]
|
|
26
|
+
company.customer_status, company.tier = updater.update_company_data(portfolio_data)
|
|
27
|
+
portfolio_data_objs.append(portfolio_data)
|
|
28
|
+
company_objs.append(company)
|
|
29
|
+
if company_objs:
|
|
30
|
+
Company.objects.bulk_update(company_objs, ["customer_status", "tier"])
|
|
31
|
+
if portfolio_data_objs:
|
|
32
|
+
CompanyPortfolioData.objects.bulk_update(
|
|
33
|
+
portfolio_data_objs, ["invested_assets_under_management_usd", "potential"]
|
|
34
|
+
)
|
|
@@ -60,7 +60,7 @@ from wbportfolio.factories import (
|
|
|
60
60
|
ProductGroupRepresentantFactory,
|
|
61
61
|
ProductPortfolioRoleFactory,
|
|
62
62
|
TradeFactory,
|
|
63
|
-
|
|
63
|
+
OrderProposalFactory,
|
|
64
64
|
WhiteLabelProductFactory,
|
|
65
65
|
)
|
|
66
66
|
|
|
@@ -95,7 +95,7 @@ register(ModelPortfolioFactory)
|
|
|
95
95
|
register(ModelPortfolioWithBaseProductFactory, "model_portfolio_with_base_product")
|
|
96
96
|
register(TradeFactory)
|
|
97
97
|
register(CustomerTradeFactory)
|
|
98
|
-
register(
|
|
98
|
+
register(OrderProposalFactory)
|
|
99
99
|
register(DividendTransactionsFactory)
|
|
100
100
|
register(FeesFactory)
|
|
101
101
|
register(WhiteLabelProductFactory, "white_label_product")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from wbfdm.models import
|
|
1
|
+
from wbfdm.models import InstrumentPrice
|
|
2
2
|
|
|
3
3
|
from wbportfolio.models import FeeProductPercentage, Fees, Product
|
|
4
4
|
|
|
@@ -8,7 +8,6 @@ def fees_calculation(price_id):
|
|
|
8
8
|
if price.calculated:
|
|
9
9
|
raise ValueError("Cannot compute fees on calculated price")
|
|
10
10
|
currency = price.instrument.currency
|
|
11
|
-
underlying_instrument = Cash.objects.filter(currency=currency).first()
|
|
12
11
|
product = Product.objects.get(id=price.instrument.id)
|
|
13
12
|
portfolio = product.portfolio
|
|
14
13
|
previous_price = price.previous_price
|
|
@@ -54,40 +53,33 @@ def fees_calculation(price_id):
|
|
|
54
53
|
performance_fees_gross = product_gross_performance_fees * multiplicator
|
|
55
54
|
base_fields = [
|
|
56
55
|
"total_value",
|
|
57
|
-
"total_value_fx_portfolio",
|
|
58
56
|
"total_value_gross",
|
|
59
|
-
"total_value_gross_fx_portfolio",
|
|
60
57
|
]
|
|
61
58
|
yield {
|
|
62
59
|
"portfolio": portfolio,
|
|
63
|
-
"
|
|
64
|
-
"
|
|
60
|
+
"product": product,
|
|
61
|
+
"fee_date": price.date,
|
|
65
62
|
"transaction_subtype": Fees.Type.MANAGEMENT,
|
|
66
|
-
"underlying_instrument": underlying_instrument,
|
|
67
63
|
"currency": currency,
|
|
68
64
|
"calculated": True,
|
|
69
65
|
**{f: management_fees for f in base_fields},
|
|
70
66
|
}
|
|
71
67
|
yield {
|
|
72
68
|
"portfolio": portfolio,
|
|
73
|
-
"
|
|
74
|
-
"
|
|
69
|
+
"product": product,
|
|
70
|
+
"fee_date": price.date,
|
|
75
71
|
"transaction_subtype": Fees.Type.ISSUER,
|
|
76
|
-
"underlying_instrument": underlying_instrument,
|
|
77
72
|
"currency": currency,
|
|
78
73
|
"calculated": True,
|
|
79
74
|
**{f: bank_fees for f in base_fields},
|
|
80
75
|
}
|
|
81
76
|
yield {
|
|
82
77
|
"portfolio": portfolio,
|
|
83
|
-
"
|
|
84
|
-
"
|
|
78
|
+
"product": product,
|
|
79
|
+
"fee_date": price.date,
|
|
85
80
|
"transaction_subtype": Fees.Type.PERFORMANCE,
|
|
86
|
-
"underlying_instrument": underlying_instrument,
|
|
87
81
|
"currency": currency,
|
|
88
82
|
"calculated": True,
|
|
89
83
|
"total_value": performance_fees_net,
|
|
90
|
-
"total_value_fx_portfolio": performance_fees_net,
|
|
91
84
|
"total_value_gross": performance_fees_gross,
|
|
92
|
-
"total_value_gross_fx_portfolio": performance_fees_gross,
|
|
93
85
|
}
|
|
@@ -21,7 +21,7 @@ from .product_groups import ProductGroupFactory, ProductGroupRepresentantFactory
|
|
|
21
21
|
from .products import IndexProductFactory, ProductFactory, WhiteLabelProductFactory, ModelPortfolioWithBaseProductFactory
|
|
22
22
|
from .reconciliations import AccountReconciliationFactory, AccountReconciliationLineFactory
|
|
23
23
|
from .roles import ManagerPortfolioRoleFactory, ProductPortfolioRoleFactory
|
|
24
|
-
from .trades import CustomerTradeFactory, TradeFactory
|
|
25
|
-
from .
|
|
24
|
+
from .trades import CustomerTradeFactory, TradeFactory
|
|
25
|
+
from .orders import OrderProposalFactory, OrderFactory
|
|
26
26
|
from .indexes import IndexFactory
|
|
27
27
|
from .rebalancing import (RebalancingModelFactory, RebalancerFactory)
|
wbportfolio/factories/assets.py
CHANGED
|
@@ -37,7 +37,7 @@ class AssetPositionFactory(factory.django.DjangoModelFactory):
|
|
|
37
37
|
initial_price = factory.Faker("pydecimal", min_value=100, max_value=120, right_digits=4)
|
|
38
38
|
underlying_quote_price = factory.LazyAttribute(
|
|
39
39
|
lambda o: InstrumentPriceFactory.create(
|
|
40
|
-
instrument=o.
|
|
40
|
+
instrument=o.underlying_quote or o.underlying_instrument,
|
|
41
41
|
calculated=False,
|
|
42
42
|
date=o.date,
|
|
43
43
|
net_value=o.initial_price,
|
|
@@ -4,13 +4,18 @@ import factory
|
|
|
4
4
|
|
|
5
5
|
from wbportfolio.models import DividendTransaction
|
|
6
6
|
|
|
7
|
-
from .transactions import TransactionFactory
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
class DividendTransactionsFactory(TransactionFactory):
|
|
8
|
+
class DividendTransactionsFactory(factory.django.DjangoModelFactory):
|
|
11
9
|
class Meta:
|
|
12
10
|
model = DividendTransaction
|
|
13
11
|
|
|
12
|
+
currency_fx_rate = 1.0
|
|
13
|
+
portfolio = factory.SubFactory("wbportfolio.factories.PortfolioFactory")
|
|
14
|
+
underlying_instrument = factory.SubFactory("wbfdm.factories.InstrumentFactory")
|
|
15
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyFactory")
|
|
16
|
+
value_date = factory.Faker("date_object")
|
|
17
|
+
ex_date = factory.Faker("date_object")
|
|
18
|
+
record_date = factory.LazyAttribute(lambda o: o.ex_date)
|
|
14
19
|
retrocession = 1.0
|
|
15
20
|
shares = factory.LazyAttribute(lambda o: random.randint(10, 10000))
|
|
16
21
|
price = factory.LazyAttribute(lambda o: random.randint(10, 10000))
|
wbportfolio/factories/fees.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
1
3
|
import factory
|
|
2
4
|
from faker import Faker
|
|
3
5
|
|
|
4
6
|
from wbportfolio.models import Fees
|
|
5
7
|
|
|
6
|
-
from .transactions import TransactionFactory
|
|
7
|
-
|
|
8
8
|
faker = Faker()
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class FeesFactory(
|
|
11
|
+
class FeesFactory(factory.django.DjangoModelFactory):
|
|
12
12
|
class Meta:
|
|
13
13
|
model = Fees
|
|
14
14
|
|
|
15
|
+
currency_fx_rate = 1.0
|
|
16
|
+
fee_date = factory.Faker("date_object")
|
|
17
|
+
total_value = factory.LazyAttribute(lambda o: random.randint(1, 1000))
|
|
15
18
|
transaction_subtype = factory.Faker("random_element", elements=[x[0] for x in Fees.Type.choices])
|
|
16
|
-
|
|
19
|
+
product = factory.SubFactory("wbportfolio.factories.products.ProductFactory")
|
|
20
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyFactory")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from pandas._libs.tslibs.offsets import BDay
|
|
4
|
+
from wbcore.contrib.authentication.factories import UserFactory
|
|
5
|
+
from wbcore.contrib.currency.factories import CurrencyFactory
|
|
6
|
+
|
|
7
|
+
from wbportfolio.factories import PortfolioFactory
|
|
8
|
+
from wbportfolio.models import OrderProposal
|
|
9
|
+
|
|
10
|
+
fake = Faker()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrderProposalFactory(factory.django.DjangoModelFactory):
|
|
14
|
+
class Meta:
|
|
15
|
+
model = OrderProposal
|
|
16
|
+
|
|
17
|
+
trade_date = factory.LazyAttribute(lambda o: (fake.date_object() + BDay(1)).date())
|
|
18
|
+
comment = factory.Faker("paragraph")
|
|
19
|
+
portfolio = factory.LazyAttribute(lambda o: PortfolioFactory.create(currency=CurrencyFactory.create(key="USD")))
|
|
20
|
+
creator = factory.LazyAttribute(lambda o: UserFactory.create().profile)
|
|
21
|
+
approver = factory.LazyAttribute(lambda o: UserFactory.create().profile)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from wbfdm.factories import InstrumentPriceFactory
|
|
6
|
+
|
|
7
|
+
from wbportfolio.models import Order
|
|
8
|
+
|
|
9
|
+
fake = Faker()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OrderFactory(factory.django.DjangoModelFactory):
|
|
13
|
+
class Meta:
|
|
14
|
+
model = Order
|
|
15
|
+
|
|
16
|
+
order_proposal = factory.SubFactory("wbportfolio.factories.OrderProposalFactory")
|
|
17
|
+
currency_fx_rate = Decimal(1.0)
|
|
18
|
+
fees = Decimal(0.0)
|
|
19
|
+
underlying_instrument = factory.SubFactory("wbfdm.factories.InstrumentFactory")
|
|
20
|
+
shares = factory.Faker("pydecimal", min_value=10, max_value=1000, right_digits=4)
|
|
21
|
+
|
|
22
|
+
@factory.post_generation
|
|
23
|
+
def create_price(self, create, extracted, **kwargs):
|
|
24
|
+
if create:
|
|
25
|
+
if self.price:
|
|
26
|
+
p = InstrumentPriceFactory.create(
|
|
27
|
+
instrument=self.underlying_instrument, date=self.value_date, calculated=False, net_value=self.price
|
|
28
|
+
)
|
|
29
|
+
else:
|
|
30
|
+
p = InstrumentPriceFactory.create(
|
|
31
|
+
instrument=self.underlying_instrument, date=self.value_date, calculated=False
|
|
32
|
+
)
|
|
33
|
+
self.price = p.net_value
|
|
34
|
+
self.save()
|
|
@@ -15,10 +15,11 @@ class PortfolioFactory(factory.django.DjangoModelFactory):
|
|
|
15
15
|
model = Portfolio
|
|
16
16
|
|
|
17
17
|
name = factory.Sequence(lambda n: f"Portfolio {n}")
|
|
18
|
-
currency = factory.SubFactory("wbcore.contrib.currency.factories.
|
|
18
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyUSDFactory")
|
|
19
19
|
is_manageable = True
|
|
20
20
|
is_tracked = True
|
|
21
21
|
is_lookthrough = False
|
|
22
|
+
only_weighting = True
|
|
22
23
|
invested_timespan = DateRange(date.min, date.max)
|
|
23
24
|
|
|
24
25
|
@factory.post_generation
|
|
@@ -21,10 +21,10 @@ class ProductGroupFactory(InstrumentFactory):
|
|
|
21
21
|
paying_agent = factory.SubFactory("wbcore.contrib.directory.factories.entries.CompanyFactory")
|
|
22
22
|
|
|
23
23
|
@factory.post_generation
|
|
24
|
-
def create_initial_portfolio(
|
|
25
|
-
if
|
|
24
|
+
def create_initial_portfolio(self, *args, **kwargs):
|
|
25
|
+
if self.id and not self.portfolios.exists():
|
|
26
26
|
portfolio = PortfolioFactory.create()
|
|
27
|
-
InstrumentPortfolioThroughModel.objects.create(instrument=
|
|
27
|
+
InstrumentPortfolioThroughModel.objects.create(instrument=self, portfolio=portfolio)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class ProductGroupRepresentantFactory(factory.django.DjangoModelFactory):
|
|
@@ -40,10 +40,10 @@ class ProductFactory(InstrumentFactory):
|
|
|
40
40
|
instrument_type = factory.LazyAttribute(lambda o: InstrumentTypeFactory.create(name="Product", key="product"))
|
|
41
41
|
|
|
42
42
|
@factory.post_generation
|
|
43
|
-
def create_initial_portfolio(
|
|
44
|
-
if
|
|
43
|
+
def create_initial_portfolio(self, *args, **kwargs):
|
|
44
|
+
if self.id and not self.portfolios.exists():
|
|
45
45
|
portfolio = PortfolioFactory.create()
|
|
46
|
-
InstrumentPortfolioThroughModel.objects.create(instrument=
|
|
46
|
+
InstrumentPortfolioThroughModel.objects.create(instrument=self, portfolio=portfolio)
|
|
47
47
|
|
|
48
48
|
# wbportfolio = factory.SubFactory(PortfolioFactory)
|
|
49
49
|
# portfolio_computed = factory.SubFactory(PortfolioFactory)
|
|
@@ -18,6 +18,6 @@ class RebalancerFactory(factory.django.DjangoModelFactory):
|
|
|
18
18
|
portfolio = factory.SubFactory("wbportfolio.factories.portfolios.PortfolioFactory")
|
|
19
19
|
rebalancing_model = factory.SubFactory(RebalancingModelFactory)
|
|
20
20
|
parameters = dict()
|
|
21
|
-
|
|
21
|
+
apply_order_proposal_automatically = False
|
|
22
22
|
frequency = "RRULE:FREQ=MONTHLY;"
|
|
23
23
|
activation_date = None
|