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,24 +1,41 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from decimal import Decimal
|
|
2
5
|
|
|
3
|
-
import numpy as np
|
|
4
6
|
import pandas as pd
|
|
5
7
|
import plotly.express as px
|
|
6
8
|
import plotly.graph_objects as go
|
|
7
|
-
from django.db.models import
|
|
8
|
-
from django.shortcuts import get_object_or_404
|
|
9
|
+
from django.db.models import F, Prefetch
|
|
9
10
|
from django.utils.functional import cached_property
|
|
11
|
+
from plotly.subplots import make_subplots
|
|
12
|
+
from rest_framework.request import Request
|
|
10
13
|
from wbcore import viewsets
|
|
14
|
+
from wbcore.contrib.currency.models import Currency
|
|
15
|
+
from wbcore.contrib.geography.models import Geography
|
|
11
16
|
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
17
|
+
from wbcore.contrib.pandas import fields as pf
|
|
12
18
|
from wbcore.filters import DjangoFilterBackend
|
|
13
|
-
from wbcore.
|
|
19
|
+
from wbcore.utils.date import get_date_interval_from_request
|
|
20
|
+
from wbcore.utils.figures import (
|
|
21
|
+
get_default_timeserie_figure,
|
|
22
|
+
get_hovertemplate_timeserie,
|
|
23
|
+
)
|
|
24
|
+
from wbcore.utils.strings import format_number
|
|
14
25
|
from wbfdm.models import (
|
|
26
|
+
Classification,
|
|
15
27
|
ClassificationGroup,
|
|
16
28
|
Instrument,
|
|
17
29
|
InstrumentClassificationThroughModel,
|
|
18
30
|
InstrumentType,
|
|
19
31
|
)
|
|
20
32
|
|
|
21
|
-
from wbportfolio.filters.assets import
|
|
33
|
+
from wbportfolio.filters.assets import (
|
|
34
|
+
AssetPositionUnderlyingInstrumentChartFilter,
|
|
35
|
+
CompositionContributionChartFilter,
|
|
36
|
+
ContributionChartFilter,
|
|
37
|
+
DistributionFilter,
|
|
38
|
+
)
|
|
22
39
|
from wbportfolio.models import (
|
|
23
40
|
AssetPosition,
|
|
24
41
|
AssetPositionGroupBy,
|
|
@@ -32,20 +49,40 @@ from ..configs.buttons.assets import (
|
|
|
32
49
|
)
|
|
33
50
|
from ..configs.display.assets import DistributionTableDisplayConfig
|
|
34
51
|
from ..configs.endpoints.assets import (
|
|
52
|
+
AssetPositionUnderlyingInstrumentChartEndpointConfig,
|
|
53
|
+
ContributorPortfolioChartEndpointConfig,
|
|
35
54
|
DistributionChartEndpointConfig,
|
|
36
55
|
DistributionTableEndpointConfig,
|
|
37
56
|
)
|
|
38
57
|
from ..configs.titles.assets import (
|
|
58
|
+
AssetPositionUnderlyingInstrumentChartTitleConfig,
|
|
59
|
+
ContributorPortfolioChartTitleConfig,
|
|
39
60
|
DistributionChartTitleConfig,
|
|
40
61
|
DistributionTableTitleConfig,
|
|
41
62
|
)
|
|
63
|
+
from ..mixins import UserPortfolioRequestPermissionMixin
|
|
42
64
|
|
|
43
65
|
|
|
44
|
-
class AbstractDistributionMixin:
|
|
45
|
-
AUTORESIZE = False
|
|
66
|
+
class AbstractDistributionMixin(UserPortfolioRequestPermissionMixin):
|
|
46
67
|
queryset = AssetPosition.objects.all()
|
|
47
68
|
filterset_class = DistributionFilter
|
|
48
69
|
filter_backends = (DjangoFilterBackend,)
|
|
70
|
+
request: Request
|
|
71
|
+
|
|
72
|
+
@cached_property
|
|
73
|
+
def group_by(self) -> AssetPositionGroupBy:
|
|
74
|
+
try:
|
|
75
|
+
return AssetPositionGroupBy(self.request.GET.get("group_by", "classification"))
|
|
76
|
+
except ValueError:
|
|
77
|
+
return AssetPositionGroupBy.INDUSTRY
|
|
78
|
+
|
|
79
|
+
@cached_property
|
|
80
|
+
def val_date(self) -> dt.date:
|
|
81
|
+
if validity_date_repr := self.request.GET.get("date"):
|
|
82
|
+
val_date = dt.datetime.strptime(validity_date_repr, "%Y-%m-%d")
|
|
83
|
+
else:
|
|
84
|
+
val_date = dt.date.today()
|
|
85
|
+
return val_date
|
|
49
86
|
|
|
50
87
|
@cached_property
|
|
51
88
|
def classification_group(self):
|
|
@@ -55,71 +92,99 @@ class AbstractDistributionMixin:
|
|
|
55
92
|
return ClassificationGroup.objects.get(is_primary=True)
|
|
56
93
|
|
|
57
94
|
@cached_property
|
|
58
|
-
def
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
@cached_property
|
|
62
|
-
def classification_levels_representation(self):
|
|
63
|
-
return self.classification_group.get_levels_representation()
|
|
95
|
+
def classification_height(self) -> int:
|
|
96
|
+
return int(self.request.GET.get("group_by_classification_height", "0"))
|
|
64
97
|
|
|
65
98
|
@cached_property
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
columns
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.annotate_base_data()
|
|
82
|
-
.values_list("id", "root")
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
df = df.groupby("underlying_instrument").sum()
|
|
86
|
-
classifications = InstrumentClassificationThroughModel.objects.filter(
|
|
87
|
-
classification__group=self.classification_group, instrument__in=df.index
|
|
88
|
-
)
|
|
89
|
-
df_classification = pd.DataFrame(
|
|
90
|
-
classifications.values(
|
|
91
|
-
"instrument",
|
|
92
|
-
"classification__name",
|
|
93
|
-
*self.classification_field_names,
|
|
94
|
-
)
|
|
95
|
-
)
|
|
96
|
-
if df_classification.empty:
|
|
97
|
-
return pd.DataFrame()
|
|
98
|
-
return pd.concat([df, df_classification.groupby("instrument").first()], axis=1).replace(
|
|
99
|
-
[np.inf, -np.inf, np.nan], "N/A"
|
|
100
|
-
)
|
|
99
|
+
def columns_map(self) -> dict:
|
|
100
|
+
if self.group_by == AssetPositionGroupBy.INDUSTRY:
|
|
101
|
+
columns = {}
|
|
102
|
+
level_representations = self.classification_group.get_levels_representation()
|
|
103
|
+
for key, label in zip(
|
|
104
|
+
reversed(self.classification_group.get_fields_names(sep="_")),
|
|
105
|
+
reversed(level_representations[1:]),
|
|
106
|
+
strict=False,
|
|
107
|
+
):
|
|
108
|
+
columns[key] = label
|
|
109
|
+
columns["label"] = "Classification"
|
|
110
|
+
columns["equity"] = "Instrument"
|
|
111
|
+
else:
|
|
112
|
+
columns = {"label": "Label"}
|
|
113
|
+
return columns
|
|
101
114
|
|
|
102
115
|
def get_queryset(self):
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
or self.request.user.profile.is_internal
|
|
107
|
-
):
|
|
108
|
-
return super().get_queryset().filter(portfolio=portfolio)
|
|
116
|
+
profile = self.request.user.profile
|
|
117
|
+
if PortfolioRole.is_analyst(profile, portfolio=self.portfolio) or profile.is_internal:
|
|
118
|
+
return AssetPosition.objects.filter(portfolio=self.portfolio)
|
|
109
119
|
return AssetPosition.objects.none()
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
def get_dataframe(self, request, queryset, **kwargs) -> pd.DataFrame:
|
|
122
|
+
instruments = defaultdict(Decimal)
|
|
123
|
+
for asset in self.portfolio.get_positions(self.val_date):
|
|
124
|
+
if self.group_by != AssetPositionGroupBy.INDUSTRY:
|
|
125
|
+
group_field = getattr(asset.underlying_instrument, self.group_by.value)
|
|
126
|
+
else:
|
|
127
|
+
group_field = asset.underlying_instrument.get_root()
|
|
128
|
+
group_field = getattr(group_field, "id", group_field)
|
|
129
|
+
instruments[group_field] += asset.weighting
|
|
130
|
+
df = pd.DataFrame.from_dict(instruments, orient="index", columns=["weighting"])
|
|
131
|
+
if self.group_by == AssetPositionGroupBy.INDUSTRY:
|
|
132
|
+
classifications = InstrumentClassificationThroughModel.objects.filter(
|
|
133
|
+
instrument__in=instruments.keys(), classification__group=self.classification_group
|
|
134
|
+
)
|
|
135
|
+
field_names = {
|
|
136
|
+
field_name.replace("__", "_"): F(f"classification__{field_name}__name")
|
|
137
|
+
for field_name in self.classification_group.get_fields_names()
|
|
138
|
+
}
|
|
139
|
+
classifications = (
|
|
140
|
+
classifications.annotate(**field_names)
|
|
141
|
+
.select_related(
|
|
142
|
+
*[f"classification__{field_name}" for field_name in self.classification_group.get_fields_names()]
|
|
143
|
+
)
|
|
144
|
+
.prefetch_related(
|
|
145
|
+
"tags",
|
|
146
|
+
Prefetch("instrument", queryset=Instrument.objects.filter(classifications_through__isnull=False)),
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
df_classification = (
|
|
150
|
+
pd.DataFrame(
|
|
151
|
+
classifications.values_list("instrument", "classification", *field_names.keys()),
|
|
152
|
+
columns=["id", "classification", *field_names.keys()],
|
|
153
|
+
)
|
|
154
|
+
.groupby("id")
|
|
155
|
+
.first()
|
|
156
|
+
)
|
|
157
|
+
df = pd.concat([df, df_classification], axis=1)
|
|
158
|
+
if df.weighting.sum(): # normalize
|
|
159
|
+
df.weighting /= df.weighting.sum()
|
|
160
|
+
return df.reset_index(names="id")
|
|
116
161
|
|
|
117
|
-
def
|
|
118
|
-
df =
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
|
|
162
|
+
def manipulate_dataframe(self, df):
|
|
163
|
+
df["id"] = df["id"].fillna(-1)
|
|
164
|
+
if self.group_by == AssetPositionGroupBy.INDUSTRY:
|
|
165
|
+
if not PortfolioRole.is_analyst(self.request.user.profile, portfolio=self.portfolio):
|
|
166
|
+
df["equity"] = ""
|
|
167
|
+
else:
|
|
168
|
+
df["equity"] = df["id"].map(
|
|
169
|
+
dict(Instrument.objects.filter(id__in=df["id"]).values_list("id", "computed_str"))
|
|
170
|
+
)
|
|
171
|
+
df["label"] = df["classification"].map(
|
|
172
|
+
dict(Classification.objects.filter(id__in=df["classification"].dropna()).values_list("id", "name"))
|
|
173
|
+
)
|
|
174
|
+
elif self.group_by == AssetPositionGroupBy.CASH:
|
|
175
|
+
df.loc[df["id"], "label"] = "Cash"
|
|
176
|
+
df.loc[~df["id"], "label"] = "Non-Cash"
|
|
177
|
+
elif self.group_by == AssetPositionGroupBy.COUNTRY:
|
|
178
|
+
df["label"] = df["id"].map(dict(Geography.objects.filter(id__in=df["id"]).values_list("id", "name")))
|
|
179
|
+
elif self.group_by == AssetPositionGroupBy.CURRENCY:
|
|
180
|
+
currencies = dict(map(lambda o: (o.id, str(o)), Currency.objects.filter(id__in=df["id"])))
|
|
181
|
+
df["label"] = df["id"].map(currencies)
|
|
182
|
+
elif self.group_by == AssetPositionGroupBy.INSTRUMENT_TYPE:
|
|
183
|
+
df["label"] = df["id"].map(
|
|
184
|
+
dict(InstrumentType.objects.filter(id__in=df["id"]).values_list("id", "short_name"))
|
|
122
185
|
)
|
|
186
|
+
df.loc[df["id"] == -1, "label"] = "N/A"
|
|
187
|
+
df.sort_values(by="weighting", ascending=False, inplace=True)
|
|
123
188
|
return df
|
|
124
189
|
|
|
125
190
|
|
|
@@ -142,39 +207,17 @@ class DistributionChartViewSet(AbstractDistributionMixin, viewsets.ChartViewSet)
|
|
|
142
207
|
|
|
143
208
|
def get_plotly(self, queryset):
|
|
144
209
|
fig = go.Figure()
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if not df.empty:
|
|
157
|
-
df["weighting"] = df.weighting / df.weighting.sum()
|
|
158
|
-
df.weighting = df.weighting.astype("float")
|
|
159
|
-
df = df.reset_index().rename(
|
|
160
|
-
columns={**self.classification_columns_map, "weighting": "weight", "index": "Equity"}
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
levels = [*self.classification_levels_representation[::-1], "Equity"]
|
|
164
|
-
df["Equity"] = df["Equity"].map(
|
|
165
|
-
dict(Instrument.objects.filter(id__in=df["Equity"]).values_list("id", "name_repr"))
|
|
166
|
-
)
|
|
167
|
-
portfolio = Portfolio.objects.get(id=self.kwargs["portfolio_id"])
|
|
168
|
-
if not PortfolioRole.is_analyst(self.request.user.profile, portfolio=portfolio):
|
|
169
|
-
del df["Equity"]
|
|
170
|
-
levels.remove("Equity")
|
|
171
|
-
fig = px.sunburst(
|
|
172
|
-
df,
|
|
173
|
-
path=levels,
|
|
174
|
-
values="weight",
|
|
175
|
-
hover_data={"weight": ":.2%"},
|
|
176
|
-
)
|
|
177
|
-
fig.update_traces(hovertemplate="<b>%{label}</b><br>Weight = %{customdata:.3p}")
|
|
210
|
+
df = self.manipulate_dataframe(self.get_dataframe(self.request, queryset))
|
|
211
|
+
df = df.dropna(how="any")
|
|
212
|
+
if not df.empty:
|
|
213
|
+
levels = list(self.columns_map.keys())
|
|
214
|
+
fig = px.sunburst(
|
|
215
|
+
df,
|
|
216
|
+
path=levels,
|
|
217
|
+
values="weighting",
|
|
218
|
+
hover_data={"weighting": ":.2%"},
|
|
219
|
+
)
|
|
220
|
+
fig.update_traces(hovertemplate="<b>%{label}</b><br>Weight = %{customdata:.3p}")
|
|
178
221
|
return fig
|
|
179
222
|
|
|
180
223
|
|
|
@@ -185,65 +228,212 @@ class DistributionTableViewSet(AbstractDistributionMixin, ExportPandasAPIViewSet
|
|
|
185
228
|
button_config_class = DistributionTableButtonConfig
|
|
186
229
|
|
|
187
230
|
def get_pandas_fields(self, request):
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
fields =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
231
|
+
fields = [
|
|
232
|
+
pf.PKField(key="id", label="id"),
|
|
233
|
+
pf.FloatField(key="weighting", label="Weight", precision=2, percent=True),
|
|
234
|
+
]
|
|
235
|
+
for key, label in self.columns_map.items():
|
|
236
|
+
fields.append(pf.CharField(key=key, label=label))
|
|
237
|
+
return pf.PandasFields(fields=fields)
|
|
238
|
+
|
|
239
|
+
def get_aggregates(self, request, df):
|
|
240
|
+
return {"weighting": {"Σ": format_number(df["weighting"].sum())}}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ##### CHART VIEWS #####
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class ContributorPortfolioChartView(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
|
|
247
|
+
filterset_class = ContributionChartFilter
|
|
248
|
+
filter_backends = (DjangoFilterBackend,)
|
|
249
|
+
IDENTIFIER = "wbportfolio:portfolio-contributor"
|
|
250
|
+
queryset = AssetPosition.objects.all()
|
|
251
|
+
|
|
252
|
+
title_config_class = ContributorPortfolioChartTitleConfig
|
|
253
|
+
endpoint_config_class = ContributorPortfolioChartEndpointConfig
|
|
254
|
+
|
|
255
|
+
ROW_HEIGHT: int = 20
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def min_height(self):
|
|
259
|
+
if hasattr(self, "nb_rows"):
|
|
260
|
+
return self.nb_rows * self.ROW_HEIGHT
|
|
261
|
+
return "300px"
|
|
262
|
+
|
|
263
|
+
@cached_property
|
|
264
|
+
def hedged_currency(self) -> Currency | None:
|
|
265
|
+
if "hedged_currency" in self.request.GET:
|
|
266
|
+
with suppress(Currency.DoesNotExist):
|
|
267
|
+
return Currency.objects.get(pk=self.request.GET["hedged_currency"])
|
|
268
|
+
|
|
269
|
+
@cached_property
|
|
270
|
+
def show_lookthrough(self) -> bool:
|
|
271
|
+
return self.portfolio.is_composition and self.request.GET.get("show_lookthrough", "false").lower() == "true"
|
|
272
|
+
|
|
273
|
+
def get_filterset_class(self, request):
|
|
274
|
+
if self.portfolio.is_composition:
|
|
275
|
+
return CompositionContributionChartFilter
|
|
276
|
+
return ContributionChartFilter
|
|
277
|
+
|
|
278
|
+
def get_plotly(self, queryset):
|
|
279
|
+
fig = go.Figure()
|
|
280
|
+
data = []
|
|
281
|
+
if self.show_lookthrough:
|
|
282
|
+
d1, d2 = get_date_interval_from_request(self.request)
|
|
283
|
+
for _d in pd.date_range(d1, d2):
|
|
284
|
+
for pos in self.portfolio.get_lookthrough_positions(_d.date()):
|
|
285
|
+
data.append(
|
|
286
|
+
[
|
|
287
|
+
pos.date,
|
|
288
|
+
pos.initial_price,
|
|
289
|
+
pos.initial_currency_fx_rate,
|
|
290
|
+
pos.underlying_instrument_id,
|
|
291
|
+
pos.weighting,
|
|
292
|
+
]
|
|
293
|
+
)
|
|
207
294
|
else:
|
|
208
|
-
|
|
209
|
-
|
|
295
|
+
data = queryset.annotate_hedged_currency_fx_rate(self.hedged_currency).values_list(
|
|
296
|
+
"date", "price", "hedged_currency_fx_rate", "underlying_instrument", "weighting"
|
|
297
|
+
)
|
|
298
|
+
df = Portfolio.get_contribution_df(data).rename(columns={"group_key": "underlying_instrument"})
|
|
299
|
+
if not df.empty:
|
|
300
|
+
df = df[["contribution_total", "contribution_forex", "underlying_instrument"]].sort_values(
|
|
301
|
+
by="contribution_total", ascending=True
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
df["instrument_id"] = df.underlying_instrument.map(
|
|
305
|
+
dict(Instrument.objects.filter(id__in=df["underlying_instrument"]).values_list("id", "name_repr"))
|
|
306
|
+
)
|
|
307
|
+
df_forex = df[["instrument_id", "contribution_forex"]]
|
|
308
|
+
df_forex = df_forex[df_forex.contribution_forex != 0]
|
|
309
|
+
|
|
310
|
+
contribution_equity = df.contribution_total - df.contribution_forex
|
|
311
|
+
|
|
312
|
+
text_forex = df_forex.contribution_forex.apply(lambda x: f"{x:,.2%}")
|
|
313
|
+
text_equity = contribution_equity.apply(lambda x: f"{x:,.2%}")
|
|
314
|
+
self.nb_rows = df.shape[0]
|
|
315
|
+
fig.add_trace(
|
|
316
|
+
go.Bar(
|
|
317
|
+
y=df.instrument_id,
|
|
318
|
+
x=contribution_equity,
|
|
319
|
+
name="Contribution Equity",
|
|
320
|
+
orientation="h",
|
|
321
|
+
marker=dict(
|
|
322
|
+
color="rgba(247,110,91,0.6)",
|
|
323
|
+
line=dict(color="rgb(247,110,91,1.0)", width=2),
|
|
324
|
+
),
|
|
325
|
+
text=text_equity.values,
|
|
326
|
+
textposition="auto",
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
fig.add_trace(
|
|
330
|
+
go.Bar(
|
|
331
|
+
y=df_forex.instrument_id,
|
|
332
|
+
x=df_forex.contribution_forex,
|
|
333
|
+
name="Contribution Forex",
|
|
334
|
+
orientation="h",
|
|
335
|
+
marker=dict(
|
|
336
|
+
color="rgba(58, 71, 80, 0.6)",
|
|
337
|
+
line=dict(color="rgba(58, 71, 80, 1.0)", width=2),
|
|
338
|
+
),
|
|
339
|
+
text=text_forex.values,
|
|
340
|
+
textposition="outside",
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
fig.update_layout(
|
|
344
|
+
barmode="relative",
|
|
345
|
+
xaxis=dict(showgrid=False, showline=False, zeroline=False, tickformat=".2%"),
|
|
346
|
+
yaxis=dict(showgrid=False, showline=False, zeroline=False, tickmode="linear"),
|
|
347
|
+
margin=dict(b=0, r=20, l=20, t=0, pad=20),
|
|
348
|
+
paper_bgcolor="rgba(0,0,0,0)",
|
|
349
|
+
plot_bgcolor="rgba(0,0,0,0)",
|
|
350
|
+
font=dict(family="roboto", size=12, color="black"),
|
|
351
|
+
bargap=0.3,
|
|
352
|
+
)
|
|
353
|
+
# fig = get_horizontal_barplot(df, x_label="contribution_total", y_label="name")
|
|
354
|
+
return fig
|
|
355
|
+
|
|
356
|
+
def parse_figure_dict(self, figure_dict: dict[str, any]) -> dict[str, any]:
|
|
357
|
+
figure_dict = super().parse_figure_dict(figure_dict)
|
|
358
|
+
figure_dict["style"]["minHeight"] = self.min_height
|
|
359
|
+
return figure_dict
|
|
360
|
+
|
|
361
|
+
def get_queryset(self):
|
|
362
|
+
if self.has_portfolio_access:
|
|
363
|
+
return super().get_queryset().filter(portfolio=self.portfolio)
|
|
364
|
+
return AssetPosition.objects.none()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class AssetPositionUnderlyingInstrumentChartViewSet(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
|
|
368
|
+
IDENTIFIER = "wbportfolio:assetpositionchart"
|
|
369
|
+
|
|
370
|
+
queryset = AssetPosition.objects.all()
|
|
371
|
+
|
|
372
|
+
title_config_class = AssetPositionUnderlyingInstrumentChartTitleConfig
|
|
373
|
+
endpoint_config_class = AssetPositionUnderlyingInstrumentChartEndpointConfig
|
|
374
|
+
filterset_class = AssetPositionUnderlyingInstrumentChartFilter
|
|
210
375
|
|
|
211
376
|
def get_queryset(self):
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
dict(Instrument.objects.filter(id__in=df["equity"]).values_list("id", "name_repr"))
|
|
377
|
+
return AssetPosition.objects.filter(underlying_quote__in=self.instrument.get_descendants(include_self=True))
|
|
378
|
+
|
|
379
|
+
def get_plotly(self, queryset):
|
|
380
|
+
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
|
381
|
+
fig = get_default_timeserie_figure(fig)
|
|
382
|
+
if queryset.exists():
|
|
383
|
+
df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
|
|
384
|
+
df_weight = df_weight.where(pd.notnull(df_weight), 0)
|
|
385
|
+
df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
|
|
386
|
+
min_date = df_weight["date"].min()
|
|
387
|
+
max_date = df_weight["date"].max()
|
|
388
|
+
|
|
389
|
+
df_price = (
|
|
390
|
+
pd.DataFrame(
|
|
391
|
+
self.instrument.prices.filter_only_valid_prices()
|
|
392
|
+
.annotate_base_data()
|
|
393
|
+
.filter(date__gte=min_date, date__lte=max_date)
|
|
394
|
+
.values_list("date", "net_value_usd"),
|
|
395
|
+
columns=["date", "price_fx_usd"],
|
|
232
396
|
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
df[level] += " (" + df[f"weighting_{level}"].astype(str) + "%)"
|
|
237
|
-
portfolio = Portfolio.objects.get(id=self.request.GET.get("portfolio"))
|
|
238
|
-
if not PortfolioRole.is_analyst(self.request.user.profile, portfolio=portfolio):
|
|
239
|
-
df[["weighting", "equity"]] = None
|
|
240
|
-
df.drop_duplicates(inplace=True)
|
|
241
|
-
return df
|
|
397
|
+
.set_index("date")
|
|
398
|
+
.sort_index()
|
|
399
|
+
)
|
|
242
400
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
401
|
+
fig.add_trace(
|
|
402
|
+
go.Scatter(
|
|
403
|
+
x=df_price.index, y=df_price.price_fx_usd, mode="lines", marker_color="green", name="Price"
|
|
404
|
+
),
|
|
405
|
+
secondary_y=False,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
|
|
409
|
+
df_weight = df_weight.where(pd.notnull(df_weight), 0)
|
|
410
|
+
df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
|
|
411
|
+
for portfolio_name, df_tmp in df_weight.groupby("portfolio__name"):
|
|
412
|
+
fig.add_trace(
|
|
413
|
+
go.Scatter(
|
|
414
|
+
x=df_tmp.date,
|
|
415
|
+
y=df_tmp.weighting,
|
|
416
|
+
hovertemplate=get_hovertemplate_timeserie(is_percent=True),
|
|
417
|
+
mode="lines",
|
|
418
|
+
name=f"Allocation: {portfolio_name}",
|
|
419
|
+
),
|
|
420
|
+
secondary_y=True,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Set x-axis title
|
|
424
|
+
fig.update_xaxes(title_text="Date")
|
|
425
|
+
# Set y-axes titles
|
|
426
|
+
fig.update_yaxes(
|
|
427
|
+
title_text="<b>Price</b>",
|
|
428
|
+
secondary_y=False,
|
|
429
|
+
titlefont=dict(color="green"),
|
|
430
|
+
tickfont=dict(color="green"),
|
|
431
|
+
)
|
|
432
|
+
fig.update_yaxes(
|
|
433
|
+
title_text="<b>Portfolio Allocation (%)</b>",
|
|
434
|
+
secondary_y=True,
|
|
435
|
+
titlefont=dict(color="blue"),
|
|
436
|
+
tickfont=dict(color="blue"),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
return fig
|
|
@@ -15,8 +15,8 @@ from .products import ProductButtonConfig, ProductCustomerButtonConfig
|
|
|
15
15
|
from .registers import RegisterButtonConfig
|
|
16
16
|
from .trades import (
|
|
17
17
|
TradeButtonConfig,
|
|
18
|
-
TradeInstrumentButtonConfig
|
|
18
|
+
TradeInstrumentButtonConfig
|
|
19
19
|
)
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
from .reconciliations import AccountReconciliationButtonViewConfig, AccountReconciliationLineButtonViewConfig
|
|
22
22
|
from .signals import *
|
|
@@ -96,7 +96,7 @@ class AssetPositionPortfolioButtonConfig(AssetPositionButtonConfig):
|
|
|
96
96
|
request=self.request,
|
|
97
97
|
)
|
|
98
98
|
),
|
|
99
|
-
label=f"{PortfolioPortfolioThroughModel.Type[rel.type].label}
|
|
99
|
+
label=f"Dependency Portfolio ({PortfolioPortfolioThroughModel.Type[rel.type].label})",
|
|
100
100
|
)
|
|
101
101
|
)
|
|
102
102
|
return set(btns)
|
|
@@ -7,7 +7,7 @@ from wbfdm.models.instruments import Instrument
|
|
|
7
7
|
|
|
8
8
|
class InstrumentButtonMixin:
|
|
9
9
|
@classmethod
|
|
10
|
-
def add_instrument_request_button(
|
|
10
|
+
def add_instrument_request_button(cls, request=None, view=None, pk=None, **kwargs):
|
|
11
11
|
buttons = [
|
|
12
12
|
bt.WidgetButton(key="assets", label="Implemented Portfolios (Assets)"),
|
|
13
13
|
# bt.WidgetButton(
|
|
@@ -44,7 +44,7 @@ class InstrumentButtonMixin:
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
@classmethod
|
|
47
|
-
def add_transactions_request_button(
|
|
47
|
+
def add_transactions_request_button(cls, request=None, view=None, pk=None, **kwargs):
|
|
48
48
|
return bt.DropDownButton(
|
|
49
49
|
label="Transactions",
|
|
50
50
|
icon=WBIcon.UNFOLD.icon,
|
|
@@ -53,8 +53,8 @@ class InstrumentButtonMixin:
|
|
|
53
53
|
bt.WidgetButton(key="portfolio_trades", label="Trades"),
|
|
54
54
|
bt.WidgetButton(key="instrument_subscriptionsredemptions", label="Subscriptions/Redemptions"),
|
|
55
55
|
bt.WidgetButton(key="instrument_trades", label="Trades (Implemented)"),
|
|
56
|
-
bt.WidgetButton(key="
|
|
57
|
-
bt.WidgetButton(key="
|
|
56
|
+
bt.WidgetButton(key="product_fees", label="Fees"),
|
|
57
|
+
bt.WidgetButton(key="product_aggregatedfees", label="Aggregated Fees"),
|
|
58
58
|
bt.DropDownButton(
|
|
59
59
|
label="Charts",
|
|
60
60
|
icon=WBIcon.UNFOLD.icon,
|