wbportfolio 1.44.5__py2.py3-none-any.whl → 1.45.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/admin/__init__.py +1 -1
- wbportfolio/admin/asset.py +2 -1
- wbportfolio/admin/custodians.py +1 -0
- wbportfolio/admin/indexes.py +15 -0
- wbportfolio/admin/portfolio.py +12 -7
- wbportfolio/admin/portfolio_relationships.py +1 -0
- wbportfolio/admin/product_groups.py +2 -0
- wbportfolio/admin/products.py +2 -1
- wbportfolio/admin/reconciliations.py +1 -0
- wbportfolio/admin/registers.py +1 -0
- wbportfolio/admin/roles.py +1 -0
- wbportfolio/admin/transactions/__init__.py +1 -0
- wbportfolio/admin/transactions/claim.py +1 -0
- wbportfolio/admin/transactions/dividends.py +1 -0
- wbportfolio/admin/transactions/fees.py +1 -0
- wbportfolio/admin/transactions/rebalancing.py +26 -0
- wbportfolio/admin/transactions/trades.py +4 -3
- wbportfolio/admin/transactions/transactions.py +1 -0
- wbportfolio/analysis/claims.py +2 -1
- wbportfolio/contrib/company_portfolio/models.py +3 -6
- wbportfolio/contrib/company_portfolio/tests/conftest.py +0 -12
- wbportfolio/contrib/company_portfolio/tests/test_models.py +1 -0
- wbportfolio/defaults/fees/default.py +1 -0
- wbportfolio/factories/__init__.py +1 -7
- wbportfolio/factories/adjustments.py +1 -0
- wbportfolio/factories/assets.py +13 -7
- wbportfolio/factories/claim.py +1 -0
- wbportfolio/factories/custodians.py +1 -0
- wbportfolio/factories/dividends.py +1 -0
- wbportfolio/factories/fees.py +1 -0
- wbportfolio/factories/indexes.py +1 -0
- wbportfolio/factories/portfolio_cash_flow.py +1 -0
- wbportfolio/factories/portfolio_cash_targets.py +1 -0
- wbportfolio/factories/portfolio_swing_pricings.py +1 -0
- wbportfolio/factories/portfolios.py +3 -0
- wbportfolio/factories/product_groups.py +1 -0
- wbportfolio/factories/products.py +1 -0
- wbportfolio/factories/rebalancing.py +23 -0
- wbportfolio/factories/reconciliations.py +1 -0
- wbportfolio/factories/roles.py +1 -0
- wbportfolio/factories/trades.py +1 -0
- wbportfolio/factories/transactions.py +1 -0
- wbportfolio/fdm/tasks.py +1 -0
- wbportfolio/filters/__init__.py +1 -1
- wbportfolio/filters/assets.py +8 -9
- wbportfolio/filters/assets_and_net_new_money_progression.py +1 -0
- wbportfolio/filters/custodians.py +1 -0
- wbportfolio/filters/esg.py +1 -0
- wbportfolio/filters/performances.py +7 -6
- wbportfolio/filters/portfolios.py +21 -1
- wbportfolio/filters/positions.py +1 -0
- wbportfolio/filters/products.py +1 -0
- wbportfolio/filters/roles.py +1 -0
- wbportfolio/filters/signals.py +1 -0
- wbportfolio/filters/transactions/claim.py +1 -0
- wbportfolio/filters/transactions/fees.py +1 -0
- wbportfolio/filters/transactions/trades.py +2 -1
- wbportfolio/filters/transactions/transactions.py +1 -0
- wbportfolio/import_export/backends/ubs/mixin.py +1 -0
- wbportfolio/import_export/backends/wbfdm/adjustment.py +1 -0
- wbportfolio/import_export/handlers/asset_position.py +11 -13
- wbportfolio/import_export/handlers/fees.py +1 -0
- wbportfolio/import_export/handlers/portfolio_cash_flow.py +1 -0
- wbportfolio/import_export/handlers/trade.py +1 -0
- wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/jpmorgan/fees.py +1 -0
- wbportfolio/import_export/parsers/jpmorgan/strategy.py +5 -4
- wbportfolio/import_export/parsers/jpmorgan/valuation.py +1 -0
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/leonteq/equity.py +13 -12
- wbportfolio/import_export/parsers/leonteq/fees.py +1 -0
- wbportfolio/import_export/parsers/leonteq/trade.py +1 -0
- wbportfolio/import_export/parsers/leonteq/valuation.py +1 -0
- wbportfolio/import_export/parsers/natixis/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_equity.py +3 -2
- wbportfolio/import_export/parsers/natixis/d1_fees.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_valuation.py +1 -0
- wbportfolio/import_export/parsers/natixis/equity.py +5 -5
- wbportfolio/import_export/parsers/natixis/trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/utils.py +8 -7
- wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +2 -1
- wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +2 -1
- wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/equity.py +7 -8
- wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/registers.py +2 -1
- wbportfolio/import_export/parsers/societe_generale/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/societe_generale/strategy.py +8 -9
- wbportfolio/import_export/parsers/societe_generale/valuation.py +1 -0
- wbportfolio/import_export/parsers/tellco/equity.py +5 -4
- wbportfolio/import_export/parsers/ubs/api/asset_position.py +15 -14
- wbportfolio/import_export/parsers/ubs/api/fees.py +1 -0
- wbportfolio/import_export/parsers/ubs/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/ubs/equity.py +3 -2
- wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +1 -0
- wbportfolio/import_export/parsers/ubs/valuation.py +1 -0
- wbportfolio/import_export/parsers/vontobel/asset_position.py +19 -19
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +1 -0
- wbportfolio/import_export/parsers/vontobel/management_fees.py +1 -0
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +1 -0
- wbportfolio/import_export/parsers/vontobel/trade.py +1 -0
- wbportfolio/import_export/parsers/vontobel/valuation_api.py +23 -0
- wbportfolio/import_export/resources/assets.py +4 -3
- wbportfolio/import_export/resources/trades.py +1 -0
- wbportfolio/metric/backends/base.py +1 -0
- wbportfolio/metric/backends/portfolio_base.py +1 -0
- wbportfolio/metric/backends/portfolio_esg.py +1 -0
- wbportfolio/metric/tests/test_portfolio_base.py +1 -0
- wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +1 -131
- wbportfolio/migrations/0067_assetposition_unique_asset_position.py +1 -1
- wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +1 -1
- wbportfolio/migrations/0073_remove_product_price_computation_and_more.py +407 -0
- wbportfolio/models/__init__.py +0 -5
- wbportfolio/models/adjustments.py +8 -2
- wbportfolio/models/asset.py +117 -98
- wbportfolio/models/graphs/portfolio.py +161 -0
- wbportfolio/models/graphs/utils.py +83 -0
- wbportfolio/models/indexes.py +2 -13
- wbportfolio/models/mixins/instruments.py +28 -8
- wbportfolio/models/portfolio.py +538 -332
- wbportfolio/models/portfolio_cash_flow.py +1 -0
- wbportfolio/models/portfolio_relationship.py +6 -2
- wbportfolio/models/product_groups.py +3 -2
- wbportfolio/models/products.py +3 -17
- wbportfolio/models/reconciliations/account_reconciliation_lines.py +1 -0
- wbportfolio/models/reconciliations/account_reconciliations.py +1 -0
- wbportfolio/models/registers.py +1 -0
- wbportfolio/models/transactions/__init__.py +1 -0
- wbportfolio/models/transactions/claim.py +8 -8
- wbportfolio/models/transactions/dividends.py +1 -0
- wbportfolio/models/transactions/fees.py +1 -0
- wbportfolio/models/transactions/rebalancing.py +153 -0
- wbportfolio/models/transactions/trade_proposals.py +153 -155
- wbportfolio/models/transactions/trades.py +48 -40
- wbportfolio/models/transactions/transactions.py +6 -12
- wbportfolio/models/utils.py +1 -0
- wbportfolio/pms/analytics/__init__.py +0 -0
- wbportfolio/pms/analytics/portfolio.py +28 -0
- wbportfolio/pms/trading/handler.py +13 -16
- wbportfolio/pms/typing.py +13 -29
- wbportfolio/rebalancing/__init__.py +0 -0
- wbportfolio/rebalancing/base.py +16 -0
- wbportfolio/rebalancing/decorators.py +17 -0
- wbportfolio/rebalancing/models/__init__.py +3 -0
- wbportfolio/rebalancing/models/composite.py +31 -0
- wbportfolio/rebalancing/models/equally_weighted.py +21 -0
- wbportfolio/rebalancing/models/model_portfolio.py +35 -0
- wbportfolio/reports/monthly_position_report.py +1 -1
- wbportfolio/risk_management/backends/accounts.py +7 -6
- wbportfolio/risk_management/backends/controversy_portfolio.py +1 -0
- wbportfolio/risk_management/backends/exposure_portfolio.py +1 -0
- wbportfolio/risk_management/backends/instrument_list_portfolio.py +1 -0
- wbportfolio/risk_management/backends/liquidity_risk.py +1 -0
- wbportfolio/risk_management/backends/liquidity_stress_instrument.py +1 -0
- wbportfolio/risk_management/backends/mixins.py +1 -0
- wbportfolio/risk_management/backends/product_integrity.py +6 -1
- wbportfolio/risk_management/backends/stop_loss_instrument.py +1 -0
- wbportfolio/risk_management/backends/stop_loss_portfolio.py +1 -0
- wbportfolio/risk_management/backends/ucits_portfolio.py +12 -5
- wbportfolio/risk_management/tests/test_accounts.py +1 -0
- wbportfolio/risk_management/tests/test_controversy_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_liquidity_risk.py +1 -0
- wbportfolio/risk_management/tests/test_product_integrity.py +1 -0
- wbportfolio/risk_management/tests/test_stop_loss_instrument.py +1 -0
- wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_ucits_portfolio.py +2 -1
- wbportfolio/serializers/__init__.py +5 -5
- wbportfolio/serializers/adjustments.py +1 -0
- wbportfolio/serializers/assets.py +18 -19
- wbportfolio/serializers/custodians.py +1 -0
- wbportfolio/serializers/portfolio_cash_flow.py +1 -0
- wbportfolio/serializers/portfolio_cash_targets.py +1 -0
- wbportfolio/serializers/portfolio_relationship.py +1 -0
- wbportfolio/serializers/portfolio_swing_pricing.py +1 -0
- wbportfolio/serializers/portfolios.py +61 -40
- wbportfolio/serializers/positions.py +1 -0
- wbportfolio/serializers/product_group.py +1 -0
- wbportfolio/serializers/products.py +4 -7
- wbportfolio/serializers/rebalancing.py +57 -0
- wbportfolio/serializers/reconciliations.py +2 -1
- wbportfolio/serializers/registers.py +1 -0
- wbportfolio/serializers/roles.py +1 -0
- wbportfolio/serializers/signals.py +10 -15
- wbportfolio/serializers/transactions/__init__.py +1 -1
- wbportfolio/serializers/transactions/claim.py +1 -0
- wbportfolio/serializers/transactions/fees.py +1 -0
- wbportfolio/serializers/transactions/trade_proposals.py +85 -0
- wbportfolio/serializers/transactions/trades.py +9 -51
- wbportfolio/serializers/transactions/transactions.py +4 -3
- wbportfolio/tasks.py +1 -78
- wbportfolio/tests/conftest.py +6 -13
- wbportfolio/tests/models/test_account_reconciliation.py +2 -0
- wbportfolio/tests/models/test_assets.py +27 -19
- wbportfolio/tests/models/test_customer_trades.py +1 -0
- wbportfolio/tests/models/test_imports.py +5 -1
- wbportfolio/tests/models/test_merge.py +5 -4
- wbportfolio/tests/models/test_portfolio_cash_flow.py +8 -6
- wbportfolio/tests/models/test_portfolios.py +619 -154
- wbportfolio/tests/models/test_product_groups.py +1 -0
- wbportfolio/tests/models/test_products.py +6 -3
- wbportfolio/tests/models/test_roles.py +1 -0
- wbportfolio/tests/models/test_splits.py +1 -0
- wbportfolio/tests/models/transactions/test_claim.py +1 -0
- wbportfolio/tests/models/transactions/test_fees.py +1 -0
- wbportfolio/tests/models/transactions/test_rebalancing.py +81 -0
- wbportfolio/tests/models/transactions/test_trades.py +1 -0
- wbportfolio/tests/models/utils.py +1 -0
- wbportfolio/tests/pms/__init__.py +0 -0
- wbportfolio/tests/pms/test_analytics.py +35 -0
- wbportfolio/tests/rebalancing/__init__.py +0 -0
- wbportfolio/tests/rebalancing/test_models.py +127 -0
- wbportfolio/tests/serializers/test_claims.py +1 -0
- wbportfolio/tests/signals.py +1 -7
- wbportfolio/tests/tests.py +2 -0
- wbportfolio/tests/viewsets/test_assets.py +1 -0
- wbportfolio/tests/viewsets/test_performances.py +1 -0
- wbportfolio/tests/viewsets/test_products.py +1 -0
- wbportfolio/tests/viewsets/transactions/test_claims.py +1 -0
- wbportfolio/urls.py +26 -12
- wbportfolio/viewsets/__init__.py +2 -5
- wbportfolio/viewsets/adjustments.py +1 -0
- wbportfolio/viewsets/assets.py +62 -51
- wbportfolio/viewsets/assets_and_net_new_money_progression.py +1 -0
- wbportfolio/viewsets/charts/assets.py +3 -1
- wbportfolio/viewsets/configs/buttons/__init__.py +1 -1
- wbportfolio/viewsets/configs/buttons/assets.py +1 -0
- wbportfolio/viewsets/configs/buttons/custodians.py +1 -0
- wbportfolio/viewsets/configs/buttons/mixins.py +1 -20
- wbportfolio/viewsets/configs/buttons/portfolios.py +90 -76
- wbportfolio/viewsets/configs/buttons/signals.py +1 -0
- wbportfolio/viewsets/configs/buttons/trades.py +1 -0
- wbportfolio/viewsets/configs/display/__init__.py +2 -1
- wbportfolio/viewsets/configs/display/adjustments.py +1 -0
- wbportfolio/viewsets/configs/display/assets.py +7 -6
- wbportfolio/viewsets/configs/display/claim.py +1 -0
- wbportfolio/viewsets/configs/display/portfolios.py +127 -79
- wbportfolio/viewsets/configs/display/product_performance.py +1 -0
- wbportfolio/viewsets/configs/display/rebalancing.py +27 -0
- wbportfolio/viewsets/configs/display/trade_proposals.py +7 -4
- wbportfolio/viewsets/configs/display/trades.py +75 -42
- wbportfolio/viewsets/configs/endpoints/__init__.py +3 -1
- wbportfolio/viewsets/configs/endpoints/assets.py +12 -0
- wbportfolio/viewsets/configs/endpoints/claim.py +1 -0
- wbportfolio/viewsets/configs/endpoints/portfolios.py +23 -7
- wbportfolio/viewsets/configs/endpoints/rebalancing.py +6 -0
- wbportfolio/viewsets/configs/endpoints/reconciliations.py +1 -0
- wbportfolio/viewsets/configs/endpoints/trade_proposals.py +1 -0
- wbportfolio/viewsets/configs/endpoints/trades.py +1 -0
- wbportfolio/viewsets/configs/menu/adjustments.py +1 -0
- wbportfolio/viewsets/configs/menu/assets.py +1 -0
- wbportfolio/viewsets/configs/menu/fees.py +1 -0
- wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +1 -0
- wbportfolio/viewsets/configs/menu/portfolios.py +4 -2
- wbportfolio/viewsets/configs/menu/positions.py +1 -0
- wbportfolio/viewsets/configs/menu/roles.py +1 -0
- wbportfolio/viewsets/configs/menu/transactions.py +1 -0
- wbportfolio/viewsets/configs/previews/portfolios.py +1 -6
- wbportfolio/viewsets/configs/titles/__init__.py +1 -1
- wbportfolio/viewsets/configs/titles/assets.py +1 -0
- wbportfolio/viewsets/configs/titles/fees.py +1 -0
- wbportfolio/viewsets/configs/titles/instrument_prices.py +1 -0
- wbportfolio/viewsets/configs/titles/portfolios.py +13 -11
- wbportfolio/viewsets/configs/titles/roles.py +1 -0
- wbportfolio/viewsets/configs/titles/trades.py +1 -0
- wbportfolio/viewsets/configs/titles/transactions.py +1 -0
- wbportfolio/viewsets/custodians.py +1 -0
- wbportfolio/viewsets/esg.py +1 -0
- wbportfolio/viewsets/mixins.py +1 -0
- wbportfolio/viewsets/portfolio_cash_flow.py +1 -0
- wbportfolio/viewsets/portfolio_cash_targets.py +1 -0
- wbportfolio/viewsets/portfolio_relationship.py +1 -0
- wbportfolio/viewsets/portfolio_swing_pricing.py +1 -0
- wbportfolio/viewsets/portfolios.py +228 -61
- wbportfolio/viewsets/positions.py +3 -2
- wbportfolio/viewsets/product_groups.py +1 -0
- wbportfolio/viewsets/product_performance.py +1 -0
- wbportfolio/viewsets/products.py +1 -0
- wbportfolio/viewsets/reconciliations.py +1 -0
- wbportfolio/viewsets/registers.py +1 -0
- wbportfolio/viewsets/roles.py +1 -0
- wbportfolio/viewsets/signals.py +1 -0
- wbportfolio/viewsets/transactions/__init__.py +1 -0
- wbportfolio/viewsets/transactions/claim.py +2 -1
- wbportfolio/viewsets/transactions/fees.py +1 -0
- wbportfolio/viewsets/transactions/mixins.py +1 -0
- wbportfolio/viewsets/transactions/rebalancing.py +31 -0
- wbportfolio/viewsets/transactions/trade_proposals.py +25 -5
- wbportfolio/viewsets/transactions/trades.py +16 -9
- wbportfolio/viewsets/transactions/transactions.py +1 -0
- {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.1.dist-info}/METADATA +4 -1
- wbportfolio-1.45.1.dist-info/RECORD +521 -0
- wbportfolio/admin/synchronization/__init__.py +0 -2
- wbportfolio/admin/synchronization/admin.py +0 -114
- wbportfolio/admin/synchronization/portfolio_synchronization.py +0 -18
- wbportfolio/admin/synchronization/price_computation.py +0 -21
- wbportfolio/defaults/portfolio/default_rebalancing.py +0 -45
- wbportfolio/factories/pytest_utils.py +0 -121
- wbportfolio/factories/synchronization.py +0 -40
- wbportfolio/models/synchronization/__init__.py +0 -3
- wbportfolio/models/synchronization/portfolio_synchronization.py +0 -292
- wbportfolio/models/synchronization/price_computation.py +0 -200
- wbportfolio/models/synchronization/synchronization.py +0 -188
- wbportfolio/serializers/synchronization.py +0 -18
- wbportfolio/tests/models/test_synchronization.py +0 -617
- wbportfolio/viewsets/synchronization.py +0 -25
- wbportfolio-1.44.5.dist-info/RECORD +0 -508
- /wbportfolio/{defaults/portfolio → models/graphs}/__init__.py +0 -0
- {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.1.dist-info}/WHEEL +0 -0
- {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
from django_celery_beat.admin import (
|
|
2
|
-
PeriodicTaskAdmin,
|
|
3
|
-
PeriodicTaskForm,
|
|
4
|
-
_,
|
|
5
|
-
loads,
|
|
6
|
-
messages,
|
|
7
|
-
pluralize,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SynchronizationTaskForm(PeriodicTaskForm):
|
|
12
|
-
def clean(self):
|
|
13
|
-
return self.cleaned_data
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class SynchronizationAdmin(PeriodicTaskAdmin):
|
|
17
|
-
form = SynchronizationTaskForm
|
|
18
|
-
search_fields = ("name",)
|
|
19
|
-
regtask = None
|
|
20
|
-
readonly_fields = (
|
|
21
|
-
"last_run_at",
|
|
22
|
-
# 'regtask',
|
|
23
|
-
"task",
|
|
24
|
-
"args",
|
|
25
|
-
# 'kwargs',
|
|
26
|
-
"expires",
|
|
27
|
-
"expire_seconds",
|
|
28
|
-
"queue",
|
|
29
|
-
"exchange",
|
|
30
|
-
"routing_key",
|
|
31
|
-
"priority",
|
|
32
|
-
"headers",
|
|
33
|
-
)
|
|
34
|
-
fieldsets = (
|
|
35
|
-
(
|
|
36
|
-
None,
|
|
37
|
-
{
|
|
38
|
-
"fields": (
|
|
39
|
-
"name",
|
|
40
|
-
"task",
|
|
41
|
-
"enabled",
|
|
42
|
-
"description",
|
|
43
|
-
),
|
|
44
|
-
"classes": ("extrapretty", "wide"),
|
|
45
|
-
},
|
|
46
|
-
),
|
|
47
|
-
(
|
|
48
|
-
"Schedule",
|
|
49
|
-
{
|
|
50
|
-
"fields": ("crontab", "start_time", "last_run_at", "one_off"),
|
|
51
|
-
"classes": ("extrapretty", "wide"),
|
|
52
|
-
},
|
|
53
|
-
),
|
|
54
|
-
(
|
|
55
|
-
"Arguments",
|
|
56
|
-
{
|
|
57
|
-
"fields": ("args", "kwargs"),
|
|
58
|
-
"classes": ("extrapretty", "wide", "collapse", "in"),
|
|
59
|
-
},
|
|
60
|
-
),
|
|
61
|
-
(
|
|
62
|
-
"Execution Options",
|
|
63
|
-
{
|
|
64
|
-
"fields": ("expires", "expire_seconds", "queue", "exchange", "routing_key", "priority", "headers"),
|
|
65
|
-
"classes": ("extrapretty", "wide", "collapse", "in"),
|
|
66
|
-
},
|
|
67
|
-
),
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
def run_tasks(self, request, queryset):
|
|
71
|
-
def _load_kwargs(task):
|
|
72
|
-
kwargs = loads(task.kwargs)
|
|
73
|
-
kwargs["override_execution_datetime_validity"] = True
|
|
74
|
-
return kwargs
|
|
75
|
-
|
|
76
|
-
self.celery_app.loader.import_default_modules()
|
|
77
|
-
|
|
78
|
-
tasks = [
|
|
79
|
-
(self.celery_app.tasks.get(task.task), loads(task.args), _load_kwargs(task), task.queue)
|
|
80
|
-
for task in queryset
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
if any(t[0] is None for t in tasks):
|
|
84
|
-
for i, t in enumerate(tasks):
|
|
85
|
-
if t[0] is None:
|
|
86
|
-
break
|
|
87
|
-
|
|
88
|
-
# variable "i" will be set because list "tasks" is not empty
|
|
89
|
-
not_found_task_name = queryset[i].task
|
|
90
|
-
|
|
91
|
-
self.message_user(
|
|
92
|
-
request,
|
|
93
|
-
_('task "{0}" not found'.format(not_found_task_name)),
|
|
94
|
-
level=messages.ERROR,
|
|
95
|
-
)
|
|
96
|
-
return
|
|
97
|
-
|
|
98
|
-
task_ids = [
|
|
99
|
-
task.apply_async(args=args, kwargs=kwargs, queue=queue)
|
|
100
|
-
if queue and len(queue)
|
|
101
|
-
else task.apply_async(args=args, kwargs=kwargs)
|
|
102
|
-
for task, args, kwargs, queue in tasks
|
|
103
|
-
]
|
|
104
|
-
tasks_run = len(task_ids)
|
|
105
|
-
self.message_user(
|
|
106
|
-
request,
|
|
107
|
-
_("{0} task{1} {2} successfully run").format(
|
|
108
|
-
tasks_run,
|
|
109
|
-
pluralize(tasks_run),
|
|
110
|
-
pluralize(tasks_run, _("was,were")),
|
|
111
|
-
),
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
run_tasks.short_description = _("Run selected tasks")
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from django.contrib import admin
|
|
2
|
-
from wbportfolio.models import PortfolioSynchronization
|
|
3
|
-
|
|
4
|
-
from .admin import SynchronizationAdmin
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@admin.register(PortfolioSynchronization)
|
|
8
|
-
class PortfolioSynchronizationModelAdmin(SynchronizationAdmin):
|
|
9
|
-
fieldsets = (
|
|
10
|
-
*SynchronizationAdmin.fieldsets,
|
|
11
|
-
(
|
|
12
|
-
"Synchronization",
|
|
13
|
-
{
|
|
14
|
-
"fields": ("import_path", "dependent_task", "is_automatic_validation"),
|
|
15
|
-
"classes": ("extrapretty", "wide"),
|
|
16
|
-
},
|
|
17
|
-
),
|
|
18
|
-
)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from django.contrib import admin
|
|
2
|
-
from wbportfolio.models import PriceComputation
|
|
3
|
-
|
|
4
|
-
from .admin import SynchronizationAdmin
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@admin.register(PriceComputation)
|
|
8
|
-
class PriceComputationModelAdmin(SynchronizationAdmin):
|
|
9
|
-
fieldsets = (
|
|
10
|
-
*SynchronizationAdmin.fieldsets,
|
|
11
|
-
(
|
|
12
|
-
"Synchronization",
|
|
13
|
-
{
|
|
14
|
-
"fields": (
|
|
15
|
-
"import_path",
|
|
16
|
-
"dependent_task",
|
|
17
|
-
),
|
|
18
|
-
"classes": ("extrapretty", "wide"),
|
|
19
|
-
},
|
|
20
|
-
),
|
|
21
|
-
)
|
|
@@ -1,45 +0,0 @@
|
|
|
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)
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
from datetime import date
|
|
2
|
-
|
|
3
|
-
import pandas as pd
|
|
4
|
-
from django.utils import timezone
|
|
5
|
-
from faker import Faker
|
|
6
|
-
from pandas.tseries.offsets import BusinessMonthEnd
|
|
7
|
-
from wbfdm.factories import (
|
|
8
|
-
CashFactory,
|
|
9
|
-
ClassificationFactory,
|
|
10
|
-
ClassificationGroupFactory,
|
|
11
|
-
EquityFactory,
|
|
12
|
-
InstrumentFactory,
|
|
13
|
-
)
|
|
14
|
-
from wbportfolio.factories import (
|
|
15
|
-
AssetPositionFactory,
|
|
16
|
-
InstrumentPriceFactory,
|
|
17
|
-
PortfolioFactory,
|
|
18
|
-
PortfolioSynchronizationFactory,
|
|
19
|
-
PriceComputationFactory,
|
|
20
|
-
ProductFactory,
|
|
21
|
-
)
|
|
22
|
-
from wbportfolio.models import (
|
|
23
|
-
InstrumentPortfolioThroughModel,
|
|
24
|
-
PortfolioPortfolioThroughModel,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
fake = Faker()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def setup_product(today):
|
|
31
|
-
product = ProductFactory.create()
|
|
32
|
-
product.related_instruments.add(InstrumentFactory.create())
|
|
33
|
-
primary_group = ClassificationGroupFactory.create(is_primary=True, max_depth=3)
|
|
34
|
-
classification_parent_1 = ClassificationFactory.create(group=primary_group)
|
|
35
|
-
classification_parent_2 = ClassificationFactory.create(group=primary_group)
|
|
36
|
-
|
|
37
|
-
i1 = EquityFactory.create(classifications=[classification_parent_1.children.first().children.first()])
|
|
38
|
-
i2 = EquityFactory.create(classifications=[classification_parent_2.children.first().children.first()])
|
|
39
|
-
i3 = CashFactory.create()
|
|
40
|
-
for _d in pd.date_range(
|
|
41
|
-
today - BusinessMonthEnd(1), today + BusinessMonthEnd(0), freq="B"
|
|
42
|
-
): # Build a complete factsheet month
|
|
43
|
-
_d = _d.date()
|
|
44
|
-
InstrumentPriceFactory.create(instrument=product, date=_d)
|
|
45
|
-
pos = [
|
|
46
|
-
AssetPositionFactory.create(
|
|
47
|
-
portfolio=product.portfolio,
|
|
48
|
-
date=_d,
|
|
49
|
-
underlying_instrument=i1,
|
|
50
|
-
underlying_instrument_price=InstrumentPriceFactory.create(instrument=i1, date=_d, calculated=False),
|
|
51
|
-
),
|
|
52
|
-
AssetPositionFactory.create(
|
|
53
|
-
portfolio=product.portfolio,
|
|
54
|
-
date=_d,
|
|
55
|
-
underlying_instrument=i2,
|
|
56
|
-
underlying_instrument_price=InstrumentPriceFactory.create(instrument=i2, date=_d, calculated=False),
|
|
57
|
-
),
|
|
58
|
-
AssetPositionFactory.create(
|
|
59
|
-
portfolio=product.portfolio,
|
|
60
|
-
date=_d,
|
|
61
|
-
underlying_instrument=i3,
|
|
62
|
-
underlying_instrument_price=InstrumentPriceFactory.create(instrument=i3, date=_d, calculated=False),
|
|
63
|
-
),
|
|
64
|
-
]
|
|
65
|
-
for p in pos:
|
|
66
|
-
InstrumentPriceFactory.create(instrument=p.underlying_instrument, date=_d)
|
|
67
|
-
return product
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class ValidTodayProductSetupMixin:
|
|
71
|
-
def setup_method(self, method=None):
|
|
72
|
-
product = setup_product(timezone.now().date())
|
|
73
|
-
self.product = product
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class ValidTodayMultiThematicProductSetupMixin:
|
|
77
|
-
def setup_method(self, method=None):
|
|
78
|
-
today = date.today()
|
|
79
|
-
main_product = ProductFactory.create(price_computation=PriceComputationFactory.create())
|
|
80
|
-
main_product.related_instruments.add(InstrumentFactory.create())
|
|
81
|
-
|
|
82
|
-
# Create two valid products that will span our portfolio
|
|
83
|
-
product1 = setup_product(today)
|
|
84
|
-
product2 = setup_product(today)
|
|
85
|
-
|
|
86
|
-
# Create a computed portfolio (model to allow automatic sync), linked throught he primary portfolio with a Thematic relationship
|
|
87
|
-
computed_portfolio = PortfolioFactory.create(
|
|
88
|
-
portfolio_synchronization=PortfolioSynchronizationFactory.create(),
|
|
89
|
-
)
|
|
90
|
-
PortfolioPortfolioThroughModel.objects.create(
|
|
91
|
-
portfolio=computed_portfolio, dependency_portfolio=main_product.primary_portfolio
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
InstrumentPortfolioThroughModel.objects.update_or_create(
|
|
95
|
-
instrument=main_product, defaults={"portfolio": computed_portfolio}
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
# Loop through the date range and create an equally weighted portfolio of products and synchronize the computed portfolio and compute the estimate NAV
|
|
99
|
-
for _d in pd.date_range(today - BusinessMonthEnd(1), today + BusinessMonthEnd(0), freq="B"):
|
|
100
|
-
_d = _d.date()
|
|
101
|
-
AssetPositionFactory.create(
|
|
102
|
-
portfolio=main_product.primary_portfolio,
|
|
103
|
-
underlying_instrument=product1,
|
|
104
|
-
date=_d,
|
|
105
|
-
weighting=0.5,
|
|
106
|
-
initial_price=product1.prices.get(date=_d).net_value,
|
|
107
|
-
)
|
|
108
|
-
AssetPositionFactory.create(
|
|
109
|
-
portfolio=main_product.primary_portfolio,
|
|
110
|
-
underlying_instrument=product2,
|
|
111
|
-
date=_d,
|
|
112
|
-
weighting=0.5,
|
|
113
|
-
initial_price=product2.prices.get(date=_d).net_value,
|
|
114
|
-
)
|
|
115
|
-
computed_portfolio.portfolio_synchronization.synchronize(
|
|
116
|
-
computed_portfolio, _d, override_execution_datetime_validity=True
|
|
117
|
-
)
|
|
118
|
-
main_product.price_computation.compute(main_product, _d, override_execution_datetime_validity=True)
|
|
119
|
-
main_product.prices.update(calculated=False)
|
|
120
|
-
self.product = main_product
|
|
121
|
-
self.theme_portfolio = main_product.primary_portfolio
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import factory
|
|
2
|
-
from django_celery_beat.models import CrontabSchedule, PeriodicTask
|
|
3
|
-
from wbportfolio.models import (
|
|
4
|
-
PortfolioSynchronization,
|
|
5
|
-
PriceComputation,
|
|
6
|
-
SynchronizationTask,
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class CrontabScheduleFactory(factory.django.DjangoModelFactory):
|
|
11
|
-
hour = factory.Iterator(range(0, 24))
|
|
12
|
-
minute = factory.Iterator(range(0, 60))
|
|
13
|
-
|
|
14
|
-
class Meta:
|
|
15
|
-
model = CrontabSchedule
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class PeriodicTaskFactory(factory.django.DjangoModelFactory):
|
|
19
|
-
class Meta:
|
|
20
|
-
model = PeriodicTask
|
|
21
|
-
|
|
22
|
-
name = factory.Sequence(lambda n: f"Synchronization Task {n}")
|
|
23
|
-
crontab = factory.SubFactory(CrontabScheduleFactory)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class SynchronizationTaskFactory(PeriodicTaskFactory):
|
|
27
|
-
class Meta:
|
|
28
|
-
model = SynchronizationTask
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class PortfolioSynchronizationFactory(SynchronizationTaskFactory):
|
|
32
|
-
propagate_history = False
|
|
33
|
-
|
|
34
|
-
class Meta:
|
|
35
|
-
model = PortfolioSynchronization
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class PriceComputationFactory(SynchronizationTaskFactory):
|
|
39
|
-
class Meta:
|
|
40
|
-
model = PriceComputation
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from datetime import date, datetime
|
|
3
|
-
from decimal import Decimal
|
|
4
|
-
from typing import Any, Optional
|
|
5
|
-
|
|
6
|
-
import numpy as np
|
|
7
|
-
import pandas as pd
|
|
8
|
-
from celery import shared_task
|
|
9
|
-
from celery.canvas import Signature
|
|
10
|
-
from django.db import models
|
|
11
|
-
from django.db.models import F
|
|
12
|
-
from django.utils import timezone
|
|
13
|
-
from wbfdm.enums import MarketData
|
|
14
|
-
from wbfdm.models.instruments import Instrument
|
|
15
|
-
from wbportfolio.models import Portfolio
|
|
16
|
-
from wbportfolio.models.transactions.trade_proposals import TradeProposal
|
|
17
|
-
from wbportfolio.pms.typing import Portfolio as PortfolioDTO
|
|
18
|
-
from wbportfolio.pms.typing import Position as PositionDTO
|
|
19
|
-
|
|
20
|
-
from .synchronization import SynchronizationTask
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def convert_currency(x, val_date, other_currency):
|
|
24
|
-
instrument = Instrument.objects.get(id=x)
|
|
25
|
-
try:
|
|
26
|
-
return instrument.currency.convert(val_date, other_currency)
|
|
27
|
-
except Exception:
|
|
28
|
-
return np.nan
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class PortfolioSynchronization(SynchronizationTask):
|
|
32
|
-
is_automatic_validation = models.BooleanField(
|
|
33
|
-
default=True,
|
|
34
|
-
verbose_name="Automatic validation",
|
|
35
|
-
help_text="Set to True if you want to automatically implement proposed positions",
|
|
36
|
-
)
|
|
37
|
-
propagate_history = models.BooleanField(
|
|
38
|
-
default=False,
|
|
39
|
-
verbose_name="Propagate History",
|
|
40
|
-
help_text="If true, when the depends on portfolio changes at a certain date, this method will trigger a synchronization for each date (at the scheduled frequency) from that date to the latest valid date",
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
def synchronize(
|
|
44
|
-
self,
|
|
45
|
-
portfolio: models.Model,
|
|
46
|
-
sync_date: date,
|
|
47
|
-
task_execution_datetime: Optional[datetime] = None,
|
|
48
|
-
override_execution_datetime_validity: Optional[bool] = False,
|
|
49
|
-
post_processing: bool = True,
|
|
50
|
-
**kwargs: Any,
|
|
51
|
-
):
|
|
52
|
-
"""
|
|
53
|
-
This function compute the new portfolio composition after synchronization (returns from `_import_method`) for a
|
|
54
|
-
given date and either update or create the portfolio or create a trade proposal given the new portfolio constituent.
|
|
55
|
-
|
|
56
|
-
:param portfolio: The portfolio to synchronize the positions from
|
|
57
|
-
:param sync_date: The date at which we need to synchronize the given portfolio
|
|
58
|
-
:param task_execution_datetime: An optional datetime specifying at which time this task was initially executed.
|
|
59
|
-
:param override_execution_datetime_validity: If true, we don't valide `task_execution_datetime`
|
|
60
|
-
:param kwargs: keyword arguments
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
initkwargs = {**kwargs, **self.cast_kwargs}
|
|
64
|
-
if not task_execution_datetime:
|
|
65
|
-
task_execution_datetime = timezone.now()
|
|
66
|
-
if portfolio.is_active_at_date(sync_date):
|
|
67
|
-
if self.is_valid_date(task_execution_datetime) or override_execution_datetime_validity:
|
|
68
|
-
if import_res := list(zip(*self._import_method(portfolio, sync_date, **initkwargs))):
|
|
69
|
-
effective_positions = list(filter(lambda x: x, import_res[0]))
|
|
70
|
-
target_positions = list(filter(lambda x: x, import_res[1]))
|
|
71
|
-
if len(target_positions) > 0:
|
|
72
|
-
target_portfolio = PortfolioDTO(target_positions)
|
|
73
|
-
|
|
74
|
-
effective_portfolio = (
|
|
75
|
-
PortfolioDTO(effective_positions) if len(effective_positions) > 0 else None
|
|
76
|
-
)
|
|
77
|
-
if self.is_automatic_validation:
|
|
78
|
-
# We process these positions automatically
|
|
79
|
-
portfolio.import_positions_at_date(
|
|
80
|
-
target_portfolio, sync_date, post_processing=post_processing
|
|
81
|
-
)
|
|
82
|
-
else:
|
|
83
|
-
trade_proposal, created = TradeProposal.objects.get_or_create(
|
|
84
|
-
trade_date=sync_date,
|
|
85
|
-
portfolio=portfolio,
|
|
86
|
-
defaults={"comment": "Automatic rebalancing"},
|
|
87
|
-
)
|
|
88
|
-
trade_proposal.create_or_update_trades(
|
|
89
|
-
target_portfolio=target_portfolio, effective_portfolio=effective_portfolio
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
portfolio.last_synchronization = timezone.now()
|
|
93
|
-
portfolio.save()
|
|
94
|
-
|
|
95
|
-
else:
|
|
96
|
-
logging.info(
|
|
97
|
-
f"Synchronization invalid: {portfolio.name} synchronization with {self.name} was triggered for {sync_date} but date not valid for crontab schedule {str(self.crontab)}"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
def synchronize_as_task_si(self, portfolio: models.Model, sync_date: date, **kwargs: Any) -> Signature:
|
|
101
|
-
"""
|
|
102
|
-
Utility function that returns the signature of the synchronize method
|
|
103
|
-
"""
|
|
104
|
-
return synchronize_portfolio_as_task.si(self.id, portfolio.id, sync_date, **kwargs)
|
|
105
|
-
|
|
106
|
-
def _tasks_signature(self, sync_date: Optional[date] = None, **kwargs: Any) -> Signature:
|
|
107
|
-
"""
|
|
108
|
-
Gather all tasks that needs to run under this synchronization job as a list of celery signatures.
|
|
109
|
-
This method is expected to be implemented at each inheriting class.
|
|
110
|
-
:param args: list
|
|
111
|
-
:param kwargs: dict
|
|
112
|
-
:return: list[signature]
|
|
113
|
-
"""
|
|
114
|
-
for portfolio in self.portfolios.all():
|
|
115
|
-
portfolio_sync_dates = []
|
|
116
|
-
if sync_date:
|
|
117
|
-
portfolio_sync_dates = [sync_date]
|
|
118
|
-
elif not sync_date and portfolio.assets.exists() and (latest_asset := portfolio.assets.latest("date")):
|
|
119
|
-
portfolio_sync_dates = map(
|
|
120
|
-
lambda x: x.date(), pd.date_range(latest_asset.date, date.today(), freq="B", inclusive="left")
|
|
121
|
-
)
|
|
122
|
-
for portfolio_sync_date in portfolio_sync_dates:
|
|
123
|
-
if portfolio.is_active_at_date(portfolio_sync_date):
|
|
124
|
-
yield synchronize_portfolio_as_task.si(self.id, portfolio.id, portfolio_sync_date, **kwargs)
|
|
125
|
-
|
|
126
|
-
@classmethod
|
|
127
|
-
def _default_callback(
|
|
128
|
-
cls,
|
|
129
|
-
portfolio: Portfolio,
|
|
130
|
-
sync_date: date,
|
|
131
|
-
portfolio_created: Optional[Portfolio] = None,
|
|
132
|
-
adjusted_weighting: Optional[Decimal] = Decimal(1.0),
|
|
133
|
-
adjusted_currency_fx_rate: Optional[Decimal] = Decimal(1.0),
|
|
134
|
-
is_estimated: Optional[bool] = False,
|
|
135
|
-
portfolio_total_value: Optional[float] = None,
|
|
136
|
-
**kwargs: Any,
|
|
137
|
-
):
|
|
138
|
-
"""Recursively calculates the position for a portfolio
|
|
139
|
-
|
|
140
|
-
Arguments:
|
|
141
|
-
portfolio {portfolio.Portfolio} -- The Portfolio on which the assets will be computed
|
|
142
|
-
sync_date {datetime.date} -- The date on which the assets will be computed
|
|
143
|
-
|
|
144
|
-
Keyword Arguments:
|
|
145
|
-
portfolio {portfolio.Portfolio} -- The core portfolio from which the computed position are created (default: {None})
|
|
146
|
-
adjusted_weighting {int} -- the adjusted weight of the current level of index (default: {1})
|
|
147
|
-
adjusted_currency_fx_rate {int} -- the adjusted currency exchange rate on the current level of index (default: {1})
|
|
148
|
-
|
|
149
|
-
Yields:
|
|
150
|
-
tuple[dict, dict] -- Two dictionaries: One with filter parameters and one with default values
|
|
151
|
-
"""
|
|
152
|
-
is_root_position_estimated = False
|
|
153
|
-
if not portfolio_created:
|
|
154
|
-
if portfolio_created := portfolio.primary_portfolio:
|
|
155
|
-
is_root_position_estimated = (
|
|
156
|
-
portfolio_created.assets.filter(date=sync_date).count() == 1
|
|
157
|
-
and portfolio_created.assets.filter(date=sync_date, is_estimated=True).count() == 1
|
|
158
|
-
)
|
|
159
|
-
if portfolio_created:
|
|
160
|
-
child_positions = portfolio_created.assets.filter(date=sync_date)
|
|
161
|
-
asset_positions = child_positions.all()
|
|
162
|
-
# Compute the total portfolio value based on the root position child (otherwise the value is passed as
|
|
163
|
-
# parameters in the recursion
|
|
164
|
-
if not portfolio_total_value:
|
|
165
|
-
portfolio_total_value = child_positions.aggregate(tv=models.Sum(F("total_value_fx_portfolio")))["tv"]
|
|
166
|
-
if not portfolio_total_value:
|
|
167
|
-
portfolio_total_value = portfolio_created.get_total_value(sync_date)
|
|
168
|
-
for position in child_positions:
|
|
169
|
-
if child_portfolio := position.underlying_instrument.portfolio:
|
|
170
|
-
if child_portfolio.assets.filter(date=sync_date).exists() and position.weighting is not None:
|
|
171
|
-
asset_positions = asset_positions.exclude(id=position.id)
|
|
172
|
-
yield from cls._default_callback(
|
|
173
|
-
portfolio,
|
|
174
|
-
sync_date,
|
|
175
|
-
portfolio_created=child_portfolio,
|
|
176
|
-
adjusted_weighting=position.weighting * adjusted_weighting,
|
|
177
|
-
portfolio_total_value=portfolio_total_value,
|
|
178
|
-
adjusted_currency_fx_rate=position.currency_fx_rate * adjusted_currency_fx_rate,
|
|
179
|
-
is_estimated=False
|
|
180
|
-
if is_root_position_estimated
|
|
181
|
-
else (is_estimated and position.is_estimated),
|
|
182
|
-
)
|
|
183
|
-
df = pd.DataFrame(
|
|
184
|
-
asset_positions.values_list(
|
|
185
|
-
"currency_fx_rate",
|
|
186
|
-
"price",
|
|
187
|
-
"weighting",
|
|
188
|
-
"shares",
|
|
189
|
-
"is_estimated",
|
|
190
|
-
"underlying_instrument",
|
|
191
|
-
"currency",
|
|
192
|
-
"exchange",
|
|
193
|
-
),
|
|
194
|
-
columns=[
|
|
195
|
-
"currency_fx_rate",
|
|
196
|
-
"price",
|
|
197
|
-
"weighting",
|
|
198
|
-
"shares",
|
|
199
|
-
"is_estimated",
|
|
200
|
-
"underlying_instrument",
|
|
201
|
-
"currency",
|
|
202
|
-
"exchange",
|
|
203
|
-
],
|
|
204
|
-
)
|
|
205
|
-
if not df.empty:
|
|
206
|
-
df.currency_fx_rate = df.currency_fx_rate * adjusted_currency_fx_rate
|
|
207
|
-
df.weighting = df.weighting * adjusted_weighting
|
|
208
|
-
|
|
209
|
-
df = (
|
|
210
|
-
df.groupby(["underlying_instrument", "currency", "exchange"], dropna=False)
|
|
211
|
-
.agg(
|
|
212
|
-
{
|
|
213
|
-
"currency_fx_rate": "first",
|
|
214
|
-
"price": "first",
|
|
215
|
-
"weighting": "sum",
|
|
216
|
-
"shares": "sum",
|
|
217
|
-
"is_estimated": "first",
|
|
218
|
-
}
|
|
219
|
-
)
|
|
220
|
-
.reset_index()
|
|
221
|
-
)
|
|
222
|
-
df[["underlying_instrument", "currency", "exchange"]] = df[
|
|
223
|
-
["underlying_instrument", "currency", "exchange"]
|
|
224
|
-
].astype("object")
|
|
225
|
-
df[["currency_fx_rate", "price", "weighting", "shares"]] = df[
|
|
226
|
-
["currency_fx_rate", "price", "weighting", "shares"]
|
|
227
|
-
].astype("float")
|
|
228
|
-
|
|
229
|
-
df["actual_currency_fx_rate"] = df.underlying_instrument.apply(
|
|
230
|
-
lambda x: convert_currency(x, sync_date, portfolio.currency)
|
|
231
|
-
).astype("float")
|
|
232
|
-
df["actual_currency_fx_rate"] = df["actual_currency_fx_rate"].fillna(df["currency_fx_rate"])
|
|
233
|
-
|
|
234
|
-
df = df.where(pd.notnull(df), None).set_index("underlying_instrument")
|
|
235
|
-
missing_prices = df.loc[df["price"].isnull(), "price"]
|
|
236
|
-
if not missing_prices.empty:
|
|
237
|
-
prices_df = pd.DataFrame(
|
|
238
|
-
Instrument.objects.filter(id__in=missing_prices.index).dl.market_data(
|
|
239
|
-
values=[MarketData.CLOSE], exact_date=sync_date
|
|
240
|
-
)
|
|
241
|
-
)
|
|
242
|
-
if not prices_df.empty:
|
|
243
|
-
prices_df = prices_df[["close", "instrument_id"]].set_index("instrument_id").astype("float")
|
|
244
|
-
df.loc[prices_df.index, "price"] = prices_df
|
|
245
|
-
|
|
246
|
-
if portfolio_total_value is not None:
|
|
247
|
-
df["shares"] = (df["weighting"] * float(portfolio_total_value)) / (
|
|
248
|
-
df["price"] * df["actual_currency_fx_rate"]
|
|
249
|
-
)
|
|
250
|
-
if is_estimated:
|
|
251
|
-
df["is_estimated"] = True
|
|
252
|
-
for underlying_instrument, asset_position in df.to_dict("index").items():
|
|
253
|
-
if (
|
|
254
|
-
asset_position["weighting"] or asset_position["shares"]
|
|
255
|
-
): # We don't yield empty position (pos with shares and weight equal to 0 or None)
|
|
256
|
-
# We return the position as a serialized dictionary
|
|
257
|
-
yield None, PositionDTO(
|
|
258
|
-
date=sync_date,
|
|
259
|
-
asset_valuation_date=sync_date,
|
|
260
|
-
portfolio_created=portfolio_created.id,
|
|
261
|
-
underlying_instrument=underlying_instrument,
|
|
262
|
-
instrument_type=Instrument.objects.get(id=underlying_instrument).security_instrument_type,
|
|
263
|
-
currency=asset_position["currency"],
|
|
264
|
-
exchange=asset_position["exchange"],
|
|
265
|
-
shares=asset_position["shares"],
|
|
266
|
-
price=asset_position["price"],
|
|
267
|
-
currency_fx_rate=asset_position["actual_currency_fx_rate"],
|
|
268
|
-
weighting=asset_position["weighting"],
|
|
269
|
-
is_estimated=asset_position["is_estimated"],
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
def __str__(self) -> str:
|
|
273
|
-
return self.name
|
|
274
|
-
|
|
275
|
-
@classmethod
|
|
276
|
-
def get_representation_endpoint(cls) -> str:
|
|
277
|
-
return "wbportfolio:portfoliosynchronizationrepresentation-list"
|
|
278
|
-
|
|
279
|
-
@classmethod
|
|
280
|
-
def get_representation_value_key(cls) -> str:
|
|
281
|
-
return "id"
|
|
282
|
-
|
|
283
|
-
@classmethod
|
|
284
|
-
def get_representation_label_key(cls) -> str:
|
|
285
|
-
return "{{name}}"
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@shared_task(queue="portfolio")
|
|
289
|
-
def synchronize_portfolio_as_task(synchronization_method_id: int, portfolio_id: int, sync_date: date, **kwargs: Any):
|
|
290
|
-
portfolio = Portfolio.objects.get(id=portfolio_id)
|
|
291
|
-
synchronization_method = PortfolioSynchronization.objects.get(id=synchronization_method_id)
|
|
292
|
-
synchronization_method.synchronize(portfolio, sync_date, **kwargs)
|