wbportfolio 1.54.14__py2.py3-none-any.whl → 1.54.15__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 +2 -0
- wbportfolio/admin/orders/__init__.py +2 -0
- wbportfolio/admin/orders/order_proposals.py +14 -0
- wbportfolio/admin/orders/orders.py +30 -0
- wbportfolio/admin/{transactions/rebalancing.py → rebalancing.py} +1 -1
- wbportfolio/admin/transactions/__init__.py +0 -1
- wbportfolio/admin/transactions/trades.py +2 -17
- wbportfolio/contrib/company_portfolio/tests/conftest.py +2 -2
- wbportfolio/factories/__init__.py +2 -1
- wbportfolio/factories/orders/__init__.py +2 -0
- wbportfolio/factories/orders/order_proposals.py +17 -0
- wbportfolio/factories/orders/orders.py +21 -0
- wbportfolio/factories/rebalancing.py +1 -1
- wbportfolio/factories/trades.py +2 -13
- wbportfolio/filters/orders/__init__.py +1 -0
- wbportfolio/filters/orders/orders.py +11 -0
- wbportfolio/import_export/handlers/trade.py +20 -20
- wbportfolio/import_export/resources/trades.py +2 -2
- wbportfolio/migrations/0082_remove_tradeproposal_creator_and_more.py +93 -0
- wbportfolio/migrations/0083_order_alter_trade_options_and_more.py +181 -0
- wbportfolio/models/__init__.py +2 -0
- wbportfolio/models/orders/__init__.py +2 -0
- wbportfolio/models/{transactions/trade_proposals.py → orders/order_proposals.py} +288 -244
- wbportfolio/models/orders/orders.py +243 -0
- wbportfolio/models/portfolio.py +16 -19
- wbportfolio/models/{transactions/rebalancing.py → rebalancing.py} +18 -18
- wbportfolio/models/transactions/__init__.py +0 -2
- wbportfolio/models/transactions/trades.py +10 -450
- wbportfolio/pms/analytics/portfolio.py +10 -6
- wbportfolio/pms/analytics/utils.py +9 -0
- wbportfolio/pms/trading/handler.py +6 -4
- wbportfolio/pms/typing.py +18 -7
- wbportfolio/rebalancing/decorators.py +1 -1
- wbportfolio/rebalancing/models/composite.py +3 -7
- wbportfolio/rebalancing/models/market_capitalization_weighted.py +3 -1
- wbportfolio/serializers/__init__.py +1 -0
- wbportfolio/serializers/orders/__init__.py +2 -0
- wbportfolio/serializers/{transactions/trade_proposals.py → orders/order_proposals.py} +23 -15
- wbportfolio/serializers/orders/orders.py +187 -0
- wbportfolio/serializers/portfolios.py +7 -7
- wbportfolio/serializers/rebalancing.py +1 -1
- wbportfolio/serializers/transactions/__init__.py +1 -5
- wbportfolio/serializers/transactions/trades.py +1 -182
- wbportfolio/tests/conftest.py +4 -2
- wbportfolio/tests/models/orders/__init__.py +0 -0
- wbportfolio/tests/models/{transactions/test_trade_proposals.py → orders/test_order_proposals.py} +218 -246
- wbportfolio/tests/models/test_portfolios.py +11 -10
- wbportfolio/tests/models/transactions/test_rebalancing.py +5 -5
- wbportfolio/tests/models/transactions/test_trades.py +0 -20
- wbportfolio/tests/rebalancing/test_models.py +24 -28
- wbportfolio/tests/signals.py +10 -10
- wbportfolio/tests/tests.py +1 -1
- wbportfolio/urls.py +7 -7
- wbportfolio/viewsets/__init__.py +2 -0
- wbportfolio/viewsets/configs/buttons/__init__.py +2 -3
- wbportfolio/viewsets/configs/buttons/trades.py +0 -8
- wbportfolio/viewsets/configs/display/__init__.py +0 -2
- wbportfolio/viewsets/configs/display/portfolios.py +5 -5
- wbportfolio/viewsets/configs/display/rebalancing.py +2 -2
- wbportfolio/viewsets/configs/display/trades.py +1 -225
- wbportfolio/viewsets/configs/endpoints/__init__.py +0 -3
- wbportfolio/viewsets/configs/endpoints/trades.py +0 -41
- 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/{configs/buttons/trade_proposals.py → orders/configs/buttons/order_proposals.py} +21 -21
- wbportfolio/viewsets/orders/configs/buttons/orders.py +9 -0
- wbportfolio/viewsets/orders/configs/displays/__init__.py +2 -0
- wbportfolio/viewsets/{configs/display/trade_proposals.py → orders/configs/displays/order_proposals.py} +21 -21
- wbportfolio/viewsets/orders/configs/displays/orders.py +180 -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 +26 -0
- wbportfolio/viewsets/orders/configs/titles/__init__.py +0 -0
- wbportfolio/viewsets/orders/configs/titles/orders.py +0 -0
- wbportfolio/viewsets/{transactions/trade_proposals.py → orders/order_proposals.py} +46 -46
- wbportfolio/viewsets/orders/orders.py +219 -0
- wbportfolio/viewsets/portfolios.py +12 -12
- wbportfolio/viewsets/{transactions/rebalancing.py → rebalancing.py} +2 -2
- wbportfolio/viewsets/transactions/__init__.py +1 -7
- wbportfolio/viewsets/transactions/trades.py +1 -199
- {wbportfolio-1.54.14.dist-info → wbportfolio-1.54.15.dist-info}/METADATA +1 -1
- {wbportfolio-1.54.14.dist-info → wbportfolio-1.54.15.dist-info}/RECORD +85 -58
- wbportfolio/viewsets/configs/endpoints/trade_proposals.py +0 -18
- {wbportfolio-1.54.14.dist-info → wbportfolio-1.54.15.dist-info}/WHEEL +0 -0
- {wbportfolio-1.54.14.dist-info → wbportfolio-1.54.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -21,11 +21,11 @@ from wbfdm.models import Instrument
|
|
|
21
21
|
from wbportfolio.filters import PortfolioFilterSet, PortfolioTreeGraphChartFilterSet
|
|
22
22
|
from wbportfolio.models import (
|
|
23
23
|
AssetPosition,
|
|
24
|
+
OrderProposal,
|
|
24
25
|
Portfolio,
|
|
25
26
|
PortfolioPortfolioThroughModel,
|
|
26
27
|
Rebalancer,
|
|
27
28
|
RebalancingModel,
|
|
28
|
-
TradeProposal,
|
|
29
29
|
)
|
|
30
30
|
from wbportfolio.models.portfolio import compute_lookthrough_as_task
|
|
31
31
|
from wbportfolio.serializers import (
|
|
@@ -77,7 +77,7 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
|
|
|
77
77
|
"last_asset_under_management_usd",
|
|
78
78
|
"last_positions",
|
|
79
79
|
"automatic_rebalancer",
|
|
80
|
-
"
|
|
80
|
+
"last_order_proposal_date",
|
|
81
81
|
"is_manageable",
|
|
82
82
|
"is_tracked",
|
|
83
83
|
"only_weighting",
|
|
@@ -111,8 +111,8 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
|
|
|
111
111
|
.annotate(s=Sum("shares"))
|
|
112
112
|
.values("s")[:1]
|
|
113
113
|
),
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
last_order_proposal_date=Subquery(
|
|
115
|
+
OrderProposal.objects.filter(portfolio=OuterRef("pk"))
|
|
116
116
|
.order_by("-trade_date")
|
|
117
117
|
.values("trade_date")[:1]
|
|
118
118
|
),
|
|
@@ -123,10 +123,10 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
|
|
|
123
123
|
def rebalance(self, request, pk=None):
|
|
124
124
|
if date_str := request.POST.get("trade_date", None):
|
|
125
125
|
trade_date = datetime.strptime(date_str, "%Y-%m-%d")
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
order_proposal, _ = OrderProposal.objects.get_or_create(portfolio_id=pk, trade_date=trade_date)
|
|
127
|
+
order_proposal.reset_orders()
|
|
128
128
|
return Response(
|
|
129
|
-
{"endpoint": reverse("wbportfolio:
|
|
129
|
+
{"endpoint": reverse("wbportfolio:orderproposal-detail", args=[order_proposal.id], request=request)}
|
|
130
130
|
)
|
|
131
131
|
raise HttpResponse("Bad Request", status=400)
|
|
132
132
|
|
|
@@ -147,8 +147,8 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
|
|
|
147
147
|
activation_date = datetime.strptime(request.POST["activation_date"], "%Y-%m-%d")
|
|
148
148
|
rebalancing_model = get_object_or_404(RebalancingModel, pk=request.POST["rebalancing_model"])
|
|
149
149
|
frequency = request.POST["frequency"]
|
|
150
|
-
|
|
151
|
-
request.POST.get("
|
|
150
|
+
approve_order_proposal_automatically = (
|
|
151
|
+
request.POST.get("approve_order_proposal_automatically", "false") == "true"
|
|
152
152
|
)
|
|
153
153
|
|
|
154
154
|
rebalancer, _ = Rebalancer.objects.update_or_create(
|
|
@@ -157,7 +157,7 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
|
|
|
157
157
|
"rebalancing_model": rebalancing_model,
|
|
158
158
|
"frequency": frequency,
|
|
159
159
|
"activation_date": activation_date,
|
|
160
|
-
"
|
|
160
|
+
"approve_order_proposal_automatically": approve_order_proposal_automatically,
|
|
161
161
|
},
|
|
162
162
|
)
|
|
163
163
|
return Response(
|
|
@@ -242,9 +242,9 @@ class TopDownPortfolioCompositionPandasAPIView(UserPortfolioRequestPermissionMix
|
|
|
242
242
|
@cached_property
|
|
243
243
|
def last_rebalancing_date(self) -> date | None:
|
|
244
244
|
if self.composition_portfolio and self.last_effective_date:
|
|
245
|
-
with suppress(
|
|
245
|
+
with suppress(OrderProposal.DoesNotExist):
|
|
246
246
|
return (
|
|
247
|
-
self.composition_portfolio.
|
|
247
|
+
self.composition_portfolio.order_proposals.filter(trade_date__lte=self.last_effective_date)
|
|
248
248
|
.latest("trade_date")
|
|
249
249
|
.trade_date
|
|
250
250
|
)
|
|
@@ -8,8 +8,8 @@ from wbportfolio.serializers import (
|
|
|
8
8
|
RebalancingModelRepresentationSerializer,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
11
|
+
from .configs.display import RebalancerDisplayConfig
|
|
12
|
+
from .configs.endpoints import RebalancerEndpointConfig
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class RebalancingModelRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
@@ -17,12 +17,7 @@ from .fees import (
|
|
|
17
17
|
FeesModelViewSet,
|
|
18
18
|
FeesProductModelViewSet,
|
|
19
19
|
)
|
|
20
|
-
|
|
21
|
-
from .trade_proposals import (
|
|
22
|
-
TradeProposalModelViewSet,
|
|
23
|
-
TradeProposalPortfolioModelViewSet,
|
|
24
|
-
TradeProposalRepresentationViewSet,
|
|
25
|
-
)
|
|
20
|
+
|
|
26
21
|
from .trades import (
|
|
27
22
|
CustodianDistributionInstrumentChartViewSet,
|
|
28
23
|
CustomerDistributionInstrumentChartViewSet,
|
|
@@ -32,5 +27,4 @@ from .trades import (
|
|
|
32
27
|
TradeModelViewSet,
|
|
33
28
|
TradePortfolioModelViewSet,
|
|
34
29
|
TradeRepresentationViewSet,
|
|
35
|
-
TradeTradeProposalModelViewSet,
|
|
36
30
|
)
|
|
@@ -2,7 +2,6 @@ from decimal import Decimal
|
|
|
2
2
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import plotly.graph_objects as go
|
|
5
|
-
from django.contrib.messages import error, warning
|
|
6
5
|
from django.db.models import (
|
|
7
6
|
BooleanField,
|
|
8
7
|
Case,
|
|
@@ -16,14 +15,11 @@ from django.db.models import (
|
|
|
16
15
|
When,
|
|
17
16
|
)
|
|
18
17
|
from django.db.models.functions import Coalesce
|
|
19
|
-
from django.shortcuts import get_object_or_404
|
|
20
|
-
from django.utils.functional import cached_property
|
|
21
18
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
22
19
|
from wbcore import viewsets
|
|
23
20
|
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
24
21
|
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
25
22
|
from wbcore.utils.strings import format_number
|
|
26
|
-
from wbcore.viewsets.mixins import OrderableMixin
|
|
27
23
|
from wbcrm.models import Account
|
|
28
24
|
|
|
29
25
|
from wbportfolio.filters import (
|
|
@@ -33,15 +29,11 @@ from wbportfolio.filters import (
|
|
|
33
29
|
TradeInstrumentFilterSet,
|
|
34
30
|
TradePortfolioFilter,
|
|
35
31
|
)
|
|
36
|
-
from wbportfolio.
|
|
37
|
-
from wbportfolio.models import Trade, TradeProposal
|
|
32
|
+
from wbportfolio.models import Trade
|
|
38
33
|
from wbportfolio.models.transactions.claim import Claim
|
|
39
34
|
from wbportfolio.serializers import (
|
|
40
|
-
ReadOnlyTradeTradeProposalModelSerializer,
|
|
41
35
|
TradeModelSerializer,
|
|
42
|
-
TradeProposalRepresentationSerializer,
|
|
43
36
|
TradeRepresentationSerializer,
|
|
44
|
-
TradeTradeProposalModelSerializer,
|
|
45
37
|
)
|
|
46
38
|
from wbportfolio.viewsets.configs.titles.trades import (
|
|
47
39
|
CustomerDistributionInstrumentTitleConfig,
|
|
@@ -63,19 +55,10 @@ from ..configs import (
|
|
|
63
55
|
TradePortfolioEndpointConfig,
|
|
64
56
|
TradePortfolioTitleConfig,
|
|
65
57
|
TradeTitleConfig,
|
|
66
|
-
TradeTradeProposalButtonConfig,
|
|
67
|
-
TradeTradeProposalDisplayConfig,
|
|
68
|
-
TradeTradeProposalEndpointConfig,
|
|
69
58
|
)
|
|
70
59
|
from ..mixins import UserPortfolioRequestPermissionMixin
|
|
71
60
|
|
|
72
61
|
|
|
73
|
-
class TradeProposalRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
74
|
-
IDENTIFIER = "wbportfolio:trade"
|
|
75
|
-
queryset = TradeProposal.objects.all()
|
|
76
|
-
serializer_class = TradeProposalRepresentationSerializer
|
|
77
|
-
|
|
78
|
-
|
|
79
62
|
class TradeRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
80
63
|
filterset_class = TradeFilter
|
|
81
64
|
|
|
@@ -357,184 +340,3 @@ class CustomerDistributionInstrumentChartViewSet(UserPortfolioRequestPermissionM
|
|
|
357
340
|
)
|
|
358
341
|
.filter(account_name__isnull=False)
|
|
359
342
|
)
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
class TradeTradeProposalModelViewSet(
|
|
363
|
-
UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, OrderableMixin, viewsets.ModelViewSet
|
|
364
|
-
):
|
|
365
|
-
IMPORT_ALLOWED = True
|
|
366
|
-
ordering = (
|
|
367
|
-
"trade_proposal",
|
|
368
|
-
"order",
|
|
369
|
-
)
|
|
370
|
-
ordering_fields = (
|
|
371
|
-
"underlying_instrument__name",
|
|
372
|
-
"underlying_instrument_isin",
|
|
373
|
-
"underlying_instrument_ticker",
|
|
374
|
-
"underlying_instrument_refinitiv_identifier_code",
|
|
375
|
-
"underlying_instrument_instrument_type",
|
|
376
|
-
"target_weight",
|
|
377
|
-
"effective_weight",
|
|
378
|
-
"effective_shares",
|
|
379
|
-
"target_shares",
|
|
380
|
-
"shares",
|
|
381
|
-
"weighting",
|
|
382
|
-
)
|
|
383
|
-
IDENTIFIER = "wbportfolio:tradeproposal"
|
|
384
|
-
search_fields = ("underlying_instrument__name",)
|
|
385
|
-
filterset_fields = {"status": ["exact"]}
|
|
386
|
-
queryset = Trade.objects.all()
|
|
387
|
-
|
|
388
|
-
display_config_class = TradeTradeProposalDisplayConfig
|
|
389
|
-
endpoint_config_class = TradeTradeProposalEndpointConfig
|
|
390
|
-
serializer_class = TradeTradeProposalModelSerializer
|
|
391
|
-
button_config_class = TradeTradeProposalButtonConfig
|
|
392
|
-
|
|
393
|
-
@cached_property
|
|
394
|
-
def trade_proposal(self):
|
|
395
|
-
return get_object_or_404(TradeProposal, pk=self.kwargs["trade_proposal_id"])
|
|
396
|
-
|
|
397
|
-
@cached_property
|
|
398
|
-
def portfolio_total_asset_value(self):
|
|
399
|
-
return self.trade_proposal.portfolio_total_asset_value
|
|
400
|
-
|
|
401
|
-
def has_import_permission(self, request) -> bool: # allow import only on draft trade proposal
|
|
402
|
-
return super().has_import_permission(request) and self.trade_proposal.status == TradeProposal.Status.DRAFT
|
|
403
|
-
|
|
404
|
-
def get_import_resource_kwargs(self):
|
|
405
|
-
resource_kwargs = super().get_import_resource_kwargs()
|
|
406
|
-
resource_kwargs["columns_mapping"] = {"underlying_instrument": "underlying_instrument__isin"}
|
|
407
|
-
return resource_kwargs
|
|
408
|
-
|
|
409
|
-
def get_resource_class(self):
|
|
410
|
-
return TradeProposalTradeResource
|
|
411
|
-
|
|
412
|
-
def get_aggregates(self, queryset, *args, **kwargs):
|
|
413
|
-
agg = {}
|
|
414
|
-
if queryset.exists():
|
|
415
|
-
noncash_aggregates = queryset.filter(underlying_instrument__is_cash=False).aggregate(
|
|
416
|
-
sum_target_weight=Sum(F("target_weight")),
|
|
417
|
-
sum_effective_weight=Sum(F("effective_weight")),
|
|
418
|
-
sum_target_total_value_fx_portfolio=Sum(F("target_total_value_fx_portfolio")),
|
|
419
|
-
sum_effective_total_value_fx_portfolio=Sum(F("effective_total_value_fx_portfolio")),
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
# weights aggregates
|
|
423
|
-
cash_sum_effective_weight = Decimal("1.0") - noncash_aggregates["sum_effective_weight"]
|
|
424
|
-
cash_sum_target_cash_weight = Decimal("1.0") - noncash_aggregates["sum_target_weight"]
|
|
425
|
-
noncash_sum_effective_weight = noncash_aggregates["sum_effective_weight"] or Decimal(0)
|
|
426
|
-
noncash_sum_target_weight = noncash_aggregates["sum_target_weight"] or Decimal(0)
|
|
427
|
-
sum_buy_weight = queryset.filter(weighting__gte=0).aggregate(s=Sum(F("weighting")))["s"] or Decimal(0)
|
|
428
|
-
sum_sell_weight = queryset.filter(weighting__lt=0).aggregate(s=Sum(F("weighting")))["s"] or Decimal(0)
|
|
429
|
-
|
|
430
|
-
# shares aggregates
|
|
431
|
-
cash_sum_effective_total_value_fx_portfolio = cash_sum_effective_weight * self.portfolio_total_asset_value
|
|
432
|
-
cash_sum_target_total_value_fx_portfolio = cash_sum_target_cash_weight * self.portfolio_total_asset_value
|
|
433
|
-
noncash_sum_effective_total_value_fx_portfolio = noncash_aggregates[
|
|
434
|
-
"sum_effective_total_value_fx_portfolio"
|
|
435
|
-
] or Decimal(0)
|
|
436
|
-
noncash_sum_target_total_value_fx_portfolio = noncash_aggregates[
|
|
437
|
-
"sum_target_total_value_fx_portfolio"
|
|
438
|
-
] or Decimal(0)
|
|
439
|
-
sum_buy_total_value_fx_portfolio = queryset.filter(total_value_fx_portfolio__gte=0).aggregate(
|
|
440
|
-
s=Sum(F("total_value_fx_portfolio"))
|
|
441
|
-
)["s"] or Decimal(0)
|
|
442
|
-
sum_sell_total_value_fx_portfolio = queryset.filter(total_value_fx_portfolio__lt=0).aggregate(
|
|
443
|
-
s=Sum(F("total_value_fx_portfolio"))
|
|
444
|
-
)["s"] or Decimal(0)
|
|
445
|
-
|
|
446
|
-
agg = {
|
|
447
|
-
"effective_weight": {
|
|
448
|
-
"Cash": format_number(cash_sum_effective_weight, decimal=Trade.TRADE_WEIGHTING_PRECISION),
|
|
449
|
-
"Non-Cash": format_number(noncash_sum_effective_weight, decimal=Trade.TRADE_WEIGHTING_PRECISION),
|
|
450
|
-
"Total": format_number(
|
|
451
|
-
noncash_sum_effective_weight + cash_sum_effective_weight,
|
|
452
|
-
decimal=Trade.TRADE_WEIGHTING_PRECISION,
|
|
453
|
-
),
|
|
454
|
-
},
|
|
455
|
-
"target_weight": {
|
|
456
|
-
"Cash": format_number(cash_sum_target_cash_weight, decimal=Trade.TRADE_WEIGHTING_PRECISION),
|
|
457
|
-
"Non-Cash": format_number(noncash_sum_target_weight, decimal=Trade.TRADE_WEIGHTING_PRECISION),
|
|
458
|
-
"Total": format_number(
|
|
459
|
-
cash_sum_target_cash_weight + noncash_sum_target_weight,
|
|
460
|
-
decimal=Trade.TRADE_WEIGHTING_PRECISION,
|
|
461
|
-
),
|
|
462
|
-
},
|
|
463
|
-
"effective_total_value_fx_portfolio": {
|
|
464
|
-
"Cash": format_number(cash_sum_effective_total_value_fx_portfolio, decimal=6),
|
|
465
|
-
"Non-Cash": format_number(noncash_sum_effective_total_value_fx_portfolio, decimal=6),
|
|
466
|
-
"Total": format_number(
|
|
467
|
-
cash_sum_effective_total_value_fx_portfolio + noncash_sum_effective_total_value_fx_portfolio,
|
|
468
|
-
decimal=6,
|
|
469
|
-
),
|
|
470
|
-
},
|
|
471
|
-
"target_total_value_fx_portfolio": {
|
|
472
|
-
"Cash": format_number(cash_sum_target_total_value_fx_portfolio, decimal=6),
|
|
473
|
-
"Non-Cash": format_number(noncash_sum_target_total_value_fx_portfolio, decimal=6),
|
|
474
|
-
"Total": format_number(
|
|
475
|
-
cash_sum_target_total_value_fx_portfolio + noncash_sum_target_total_value_fx_portfolio,
|
|
476
|
-
decimal=6,
|
|
477
|
-
),
|
|
478
|
-
},
|
|
479
|
-
"weighting": {
|
|
480
|
-
"Cash Flow": format_number(
|
|
481
|
-
cash_sum_target_cash_weight - cash_sum_effective_weight,
|
|
482
|
-
decimal=Trade.TRADE_WEIGHTING_PRECISION,
|
|
483
|
-
),
|
|
484
|
-
"Buy": format_number(sum_buy_weight, decimal=Trade.TRADE_WEIGHTING_PRECISION),
|
|
485
|
-
"Sell": format_number(sum_sell_weight, decimal=Trade.TRADE_WEIGHTING_PRECISION),
|
|
486
|
-
},
|
|
487
|
-
"total_value_fx_portfolio": {
|
|
488
|
-
"Cash Flow": format_number(
|
|
489
|
-
cash_sum_target_total_value_fx_portfolio - cash_sum_effective_total_value_fx_portfolio,
|
|
490
|
-
decimal=6,
|
|
491
|
-
),
|
|
492
|
-
"Buy": format_number(sum_buy_total_value_fx_portfolio, decimal=6),
|
|
493
|
-
"Sell": format_number(sum_sell_total_value_fx_portfolio, decimal=6),
|
|
494
|
-
},
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return agg
|
|
498
|
-
|
|
499
|
-
def get_serializer_class(self):
|
|
500
|
-
if self.trade_proposal.status != TradeProposal.Status.DRAFT:
|
|
501
|
-
return ReadOnlyTradeTradeProposalModelSerializer
|
|
502
|
-
return TradeTradeProposalModelSerializer
|
|
503
|
-
|
|
504
|
-
def add_messages(self, request, queryset=None, paginated_queryset=None, instance=None, initial=False):
|
|
505
|
-
if queryset is not None and queryset.exists() and self.trade_proposal.status != TradeProposal.Status.APPROVED:
|
|
506
|
-
total_target_weight = queryset.aggregate(c=Sum(F("target_weight")))["c"] or Decimal(0)
|
|
507
|
-
if round(total_target_weight, 3) != 1:
|
|
508
|
-
warning(
|
|
509
|
-
request,
|
|
510
|
-
"The total target weight does not equal 1. To avoid automatic cash allocation, please adjust the trade weights to sum up to 1. Otherwise, a cash component will be added when this trade proposal is submitted.",
|
|
511
|
-
)
|
|
512
|
-
if queryset.filter(status=Trade.Status.FAILED):
|
|
513
|
-
error(
|
|
514
|
-
request,
|
|
515
|
-
"Some trades failed preparation. To resolve this, please revert the trade proposal to draft, review and correct the trades, and then resubmit.",
|
|
516
|
-
)
|
|
517
|
-
|
|
518
|
-
def get_queryset(self):
|
|
519
|
-
if self.is_portfolio_manager:
|
|
520
|
-
qs = super().get_queryset().filter(trade_proposal=self.kwargs["trade_proposal_id"]).annotate_base_info()
|
|
521
|
-
else:
|
|
522
|
-
qs = TradeProposal.objects.none()
|
|
523
|
-
|
|
524
|
-
return qs.annotate(
|
|
525
|
-
underlying_instrument_isin=F("underlying_instrument__isin"),
|
|
526
|
-
underlying_instrument_ticker=F("underlying_instrument__ticker"),
|
|
527
|
-
underlying_instrument_refinitiv_identifier_code=F("underlying_instrument__refinitiv_identifier_code"),
|
|
528
|
-
underlying_instrument_instrument_type=Case(
|
|
529
|
-
When(
|
|
530
|
-
underlying_instrument__parent__is_security=True,
|
|
531
|
-
then=F("underlying_instrument__parent__instrument_type__short_name"),
|
|
532
|
-
),
|
|
533
|
-
default=F("underlying_instrument__instrument_type__short_name"),
|
|
534
|
-
),
|
|
535
|
-
effective_total_value_fx_portfolio=F("effective_weight") * Value(self.portfolio_total_asset_value),
|
|
536
|
-
target_total_value_fx_portfolio=F("target_weight") * Value(self.portfolio_total_asset_value),
|
|
537
|
-
portfolio_currency=F("portfolio__currency__symbol"),
|
|
538
|
-
security=F("underlying_instrument__parent"),
|
|
539
|
-
company=F("underlying_instrument__parent__parent"),
|
|
540
|
-
)
|