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
wbportfolio/viewsets/assets.py
CHANGED
|
@@ -2,23 +2,15 @@ from contextlib import suppress
|
|
|
2
2
|
from datetime import date, datetime
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
|
-
import plotly.graph_objects as go
|
|
6
5
|
from django.db.models import Exists, F, OuterRef, Q, Sum
|
|
7
6
|
from django.utils.dateparse import parse_date
|
|
8
7
|
from django.utils.functional import cached_property
|
|
9
|
-
from plotly.subplots import make_subplots
|
|
10
8
|
from wbcore import viewsets
|
|
11
|
-
from wbcore.contrib.currency.models import
|
|
9
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
12
10
|
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
13
|
-
from wbcore.filters import DjangoFilterBackend
|
|
14
11
|
from wbcore.pandas import fields as pf
|
|
15
12
|
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
16
13
|
from wbcore.serializers import decorator
|
|
17
|
-
from wbcore.utils.date import get_date_interval_from_request
|
|
18
|
-
from wbcore.utils.figures import (
|
|
19
|
-
get_default_timeserie_figure,
|
|
20
|
-
get_hovertemplate_timeserie,
|
|
21
|
-
)
|
|
22
14
|
from wbcore.utils.strings import format_number
|
|
23
15
|
from wbfdm.contrib.metric.viewsets.mixins import InstrumentMetricMixin
|
|
24
16
|
from wbfdm.models import Instrument
|
|
@@ -27,11 +19,8 @@ from wbportfolio.filters import (
|
|
|
27
19
|
AssetPositionFilter,
|
|
28
20
|
AssetPositionInstrumentFilter,
|
|
29
21
|
AssetPositionPortfolioFilter,
|
|
30
|
-
AssetPositionUnderlyingInstrumentChartFilter,
|
|
31
22
|
CashPositionPortfolioFilterSet,
|
|
32
|
-
CompositionContributionChartFilter,
|
|
33
23
|
CompositionModelPortfolioPandasFilter,
|
|
34
|
-
ContributionChartFilter,
|
|
35
24
|
)
|
|
36
25
|
from wbportfolio.import_export.resources.assets import AssetPositionResource
|
|
37
26
|
from wbportfolio.metric.backends.portfolio_base import (
|
|
@@ -69,16 +58,12 @@ from .configs import (
|
|
|
69
58
|
AssetPositionPortfolioEndpointConfig,
|
|
70
59
|
AssetPositionPortfolioTitleConfig,
|
|
71
60
|
AssetPositionTitleConfig,
|
|
72
|
-
AssetPositionUnderlyingInstrumentChartEndpointConfig,
|
|
73
|
-
AssetPositionUnderlyingInstrumentChartTitleConfig,
|
|
74
61
|
CashPositionPortfolioDisplayConfig,
|
|
75
62
|
CashPositionPortfolioEndpointConfig,
|
|
76
63
|
CashPositionPortfolioTitleConfig,
|
|
77
64
|
CompositionModelPortfolioPandasDisplayConfig,
|
|
78
65
|
CompositionModelPortfolioPandasEndpointConfig,
|
|
79
66
|
CompositionModelPortfolioPandasTitleConfig,
|
|
80
|
-
ContributorPortfolioChartEndpointConfig,
|
|
81
|
-
ContributorPortfolioChartTitleConfig,
|
|
82
67
|
)
|
|
83
68
|
from .mixins import UserPortfolioRequestPermissionMixin
|
|
84
69
|
|
|
@@ -133,7 +118,7 @@ class AssetPositionModelViewSet(
|
|
|
133
118
|
weighting = queryset.aggregate(s=Sum(F("weighting")))["s"]
|
|
134
119
|
aggregates.update(
|
|
135
120
|
{
|
|
136
|
-
"weighting": {"Σ": format_number(weighting, decimal=
|
|
121
|
+
"weighting": {"Σ": format_number(weighting, decimal=8)},
|
|
137
122
|
"total_value_fx_usd": {"Σ": format_number(total_value_fx_usd)},
|
|
138
123
|
}
|
|
139
124
|
)
|
|
@@ -204,7 +189,7 @@ class AssetPositionPortfolioModelViewSet(InstrumentMetricMixin, AssetPositionMod
|
|
|
204
189
|
total_value_fx_portfolio = queryset.aggregate(s=Sum(F("total_value_fx_portfolio")))["s"]
|
|
205
190
|
aggregates = super().get_aggregates(queryset, paginated_queryset)
|
|
206
191
|
aggregates["total_value_fx_portfolio"] = {"Σ": format_number(total_value_fx_portfolio)}
|
|
207
|
-
aggregates["weighting"] = {"Σ": format_number(weighting, decimal=
|
|
192
|
+
aggregates["weighting"] = {"Σ": format_number(weighting, decimal=8)}
|
|
208
193
|
return aggregates
|
|
209
194
|
|
|
210
195
|
def get_queryset(self):
|
|
@@ -344,192 +329,6 @@ class CashPositionPortfolioPandasAPIView(
|
|
|
344
329
|
return AssetPosition.objects.none()
|
|
345
330
|
|
|
346
331
|
|
|
347
|
-
# ##### CHART VIEWS #####
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
class ContributorPortfolioChartView(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
|
|
351
|
-
filterset_class = ContributionChartFilter
|
|
352
|
-
filter_backends = (DjangoFilterBackend,)
|
|
353
|
-
IDENTIFIER = "wbportfolio:portfolio-contributor"
|
|
354
|
-
queryset = AssetPosition.objects.all()
|
|
355
|
-
|
|
356
|
-
title_config_class = ContributorPortfolioChartTitleConfig
|
|
357
|
-
endpoint_config_class = ContributorPortfolioChartEndpointConfig
|
|
358
|
-
|
|
359
|
-
ROW_HEIGHT: int = 20
|
|
360
|
-
|
|
361
|
-
@property
|
|
362
|
-
def min_height(self):
|
|
363
|
-
if hasattr(self, "nb_rows"):
|
|
364
|
-
return self.nb_rows * self.ROW_HEIGHT
|
|
365
|
-
return "300px"
|
|
366
|
-
|
|
367
|
-
@cached_property
|
|
368
|
-
def hedged_currency(self) -> Currency | None:
|
|
369
|
-
if "hedged_currency" in self.request.GET:
|
|
370
|
-
with suppress(Currency.DoesNotExist):
|
|
371
|
-
return Currency.objects.get(pk=self.request.GET["hedged_currency"])
|
|
372
|
-
|
|
373
|
-
@cached_property
|
|
374
|
-
def show_lookthrough(self) -> bool:
|
|
375
|
-
return self.portfolio.is_composition and self.request.GET.get("show_lookthrough", "false").lower() == "true"
|
|
376
|
-
|
|
377
|
-
def get_filterset_class(self, request):
|
|
378
|
-
if self.portfolio.is_composition:
|
|
379
|
-
return CompositionContributionChartFilter
|
|
380
|
-
return ContributionChartFilter
|
|
381
|
-
|
|
382
|
-
def get_plotly(self, queryset):
|
|
383
|
-
fig = go.Figure()
|
|
384
|
-
data = []
|
|
385
|
-
if self.show_lookthrough:
|
|
386
|
-
d1, d2 = get_date_interval_from_request(self.request)
|
|
387
|
-
for _d in pd.date_range(d1, d2):
|
|
388
|
-
for pos in self.portfolio.get_lookthrough_positions(_d.date()):
|
|
389
|
-
data.append(
|
|
390
|
-
[
|
|
391
|
-
pos.date,
|
|
392
|
-
pos.initial_price,
|
|
393
|
-
pos.initial_currency_fx_rate,
|
|
394
|
-
pos.underlying_instrument_id,
|
|
395
|
-
pos.weighting,
|
|
396
|
-
]
|
|
397
|
-
)
|
|
398
|
-
else:
|
|
399
|
-
data = queryset.annotate_hedged_currency_fx_rate(self.hedged_currency).values_list(
|
|
400
|
-
"date", "price", "hedged_currency_fx_rate", "underlying_instrument", "weighting"
|
|
401
|
-
)
|
|
402
|
-
df = Portfolio.get_contribution_df(data).rename(columns={"group_key": "underlying_instrument"})
|
|
403
|
-
if not df.empty:
|
|
404
|
-
df = df[["contribution_total", "contribution_forex", "underlying_instrument"]].sort_values(
|
|
405
|
-
by="contribution_total", ascending=True
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
df["instrument_id"] = df.underlying_instrument.map(
|
|
409
|
-
dict(Instrument.objects.filter(id__in=df["underlying_instrument"]).values_list("id", "name_repr"))
|
|
410
|
-
)
|
|
411
|
-
df_forex = df[["instrument_id", "contribution_forex"]]
|
|
412
|
-
df_forex = df_forex[df_forex.contribution_forex != 0]
|
|
413
|
-
|
|
414
|
-
contribution_equity = df.contribution_total - df.contribution_forex
|
|
415
|
-
|
|
416
|
-
text_forex = df_forex.contribution_forex.apply(lambda x: f"{x:,.2%}")
|
|
417
|
-
text_equity = contribution_equity.apply(lambda x: f"{x:,.2%}")
|
|
418
|
-
setattr(self, "nb_rows", df.shape[0])
|
|
419
|
-
fig.add_trace(
|
|
420
|
-
go.Bar(
|
|
421
|
-
y=df.instrument_id,
|
|
422
|
-
x=contribution_equity,
|
|
423
|
-
name="Contribution Equity",
|
|
424
|
-
orientation="h",
|
|
425
|
-
marker=dict(
|
|
426
|
-
color="rgba(247,110,91,0.6)",
|
|
427
|
-
line=dict(color="rgb(247,110,91,1.0)", width=2),
|
|
428
|
-
),
|
|
429
|
-
text=text_equity.values,
|
|
430
|
-
textposition="auto",
|
|
431
|
-
)
|
|
432
|
-
)
|
|
433
|
-
fig.add_trace(
|
|
434
|
-
go.Bar(
|
|
435
|
-
y=df_forex.instrument_id,
|
|
436
|
-
x=df_forex.contribution_forex,
|
|
437
|
-
name="Contribution Forex",
|
|
438
|
-
orientation="h",
|
|
439
|
-
marker=dict(
|
|
440
|
-
color="rgba(58, 71, 80, 0.6)",
|
|
441
|
-
line=dict(color="rgba(58, 71, 80, 1.0)", width=2),
|
|
442
|
-
),
|
|
443
|
-
text=text_forex.values,
|
|
444
|
-
textposition="outside",
|
|
445
|
-
)
|
|
446
|
-
)
|
|
447
|
-
fig.update_layout(
|
|
448
|
-
barmode="relative",
|
|
449
|
-
xaxis=dict(showgrid=False, showline=False, zeroline=False, tickformat=".2%"),
|
|
450
|
-
yaxis=dict(showgrid=False, showline=False, zeroline=False, tickmode="linear"),
|
|
451
|
-
margin=dict(b=0, r=20, l=20, t=0, pad=20),
|
|
452
|
-
paper_bgcolor="rgba(0,0,0,0)",
|
|
453
|
-
plot_bgcolor="rgba(0,0,0,0)",
|
|
454
|
-
font=dict(family="roboto", size=12, color="black"),
|
|
455
|
-
bargap=0.3,
|
|
456
|
-
)
|
|
457
|
-
# fig = get_horizontal_barplot(df, x_label="contribution_total", y_label="name")
|
|
458
|
-
return fig
|
|
459
|
-
|
|
460
|
-
def parse_figure_dict(self, figure_dict: dict[str, any]) -> dict[str, any]:
|
|
461
|
-
figure_dict = super().parse_figure_dict(figure_dict)
|
|
462
|
-
figure_dict["style"]["minHeight"] = self.min_height
|
|
463
|
-
return figure_dict
|
|
464
|
-
|
|
465
|
-
def get_queryset(self):
|
|
466
|
-
if self.has_portfolio_access:
|
|
467
|
-
return super().get_queryset().filter(portfolio=self.portfolio)
|
|
468
|
-
return AssetPosition.objects.none()
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
class AssetPositionUnderlyingInstrumentChartViewSet(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
|
|
472
|
-
IDENTIFIER = "wbportfolio:assetpositionchart"
|
|
473
|
-
|
|
474
|
-
queryset = AssetPosition.objects.all()
|
|
475
|
-
|
|
476
|
-
title_config_class = AssetPositionUnderlyingInstrumentChartTitleConfig
|
|
477
|
-
endpoint_config_class = AssetPositionUnderlyingInstrumentChartEndpointConfig
|
|
478
|
-
filterset_class = AssetPositionUnderlyingInstrumentChartFilter
|
|
479
|
-
|
|
480
|
-
def get_queryset(self):
|
|
481
|
-
return AssetPosition.objects.filter(
|
|
482
|
-
underlying_instrument__in=self.instrument.get_descendants(include_self=True)
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
def get_plotly(self, queryset):
|
|
486
|
-
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
|
487
|
-
fig = get_default_timeserie_figure(fig)
|
|
488
|
-
if queryset.exists():
|
|
489
|
-
df_price = (pd.DataFrame(queryset.values("date", "price_fx_usd"))).groupby("date").first()
|
|
490
|
-
|
|
491
|
-
df_price = df_price.where(pd.notnull(df_price), 0)
|
|
492
|
-
fig.add_trace(
|
|
493
|
-
go.Scatter(
|
|
494
|
-
x=df_price.index, y=df_price.price_fx_usd, mode="lines", marker_color="green", name="Price"
|
|
495
|
-
),
|
|
496
|
-
secondary_y=False,
|
|
497
|
-
)
|
|
498
|
-
|
|
499
|
-
df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
|
|
500
|
-
df_weight = df_weight.where(pd.notnull(df_weight), 0)
|
|
501
|
-
df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
|
|
502
|
-
for portfolio_name, df_tmp in df_weight.groupby("portfolio__name"):
|
|
503
|
-
fig.add_trace(
|
|
504
|
-
go.Scatter(
|
|
505
|
-
x=df_tmp.date,
|
|
506
|
-
y=df_tmp.weighting,
|
|
507
|
-
hovertemplate=get_hovertemplate_timeserie(is_percent=True),
|
|
508
|
-
mode="lines",
|
|
509
|
-
name=f"Allocation: {portfolio_name}",
|
|
510
|
-
),
|
|
511
|
-
secondary_y=True,
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
# Set x-axis title
|
|
515
|
-
fig.update_xaxes(title_text="Date")
|
|
516
|
-
# Set y-axes titles
|
|
517
|
-
fig.update_yaxes(
|
|
518
|
-
title_text="<b>Price</b>",
|
|
519
|
-
secondary_y=False,
|
|
520
|
-
titlefont=dict(color="green"),
|
|
521
|
-
tickfont=dict(color="green"),
|
|
522
|
-
)
|
|
523
|
-
fig.update_yaxes(
|
|
524
|
-
title_text="<b>Portfolio Allocation (%)</b>",
|
|
525
|
-
secondary_y=True,
|
|
526
|
-
titlefont=dict(color="blue"),
|
|
527
|
-
tickfont=dict(color="blue"),
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
return fig
|
|
531
|
-
|
|
532
|
-
|
|
533
332
|
class CompositionModelPortfolioPandasView(
|
|
534
333
|
UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
|
|
535
334
|
):
|