wbportfolio 1.52.4__py2.py3-none-any.whl → 1.52.6__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.

@@ -1,3 +1,5 @@
1
+ from decimal import Decimal
2
+
1
3
  from django.contrib.messages import warning
2
4
  from django.core.exceptions import ValidationError
3
5
  from rest_framework.reverse import reverse
@@ -16,6 +18,17 @@ class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
16
18
  queryset=Portfolio.objects.all(), write_only=True, required=False, default=DefaultFromView("portfolio")
17
19
  )
18
20
  _target_portfolio = PortfolioRepresentationSerializer(source="target_portfolio")
21
+ total_cash_weight = wb_serializers.DecimalField(
22
+ default=0,
23
+ decimal_places=4,
24
+ max_digits=5,
25
+ write_only=True,
26
+ required=False,
27
+ precision=4,
28
+ percent=True,
29
+ label="Target Cash",
30
+ help_text="Enter the desired percentage for the cash component. The remaining percentage (100% minus this value) will be allocated to total target weighting. Default is 0%.",
31
+ )
19
32
 
20
33
  trade_date = wb_serializers.DateField(
21
34
  read_only=lambda view: not view.new_mode, default=DefaultFromView("default_trade_date")
@@ -23,6 +36,7 @@ class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
23
36
 
24
37
  def create(self, validated_data):
25
38
  target_portfolio = validated_data.pop("target_portfolio", None)
39
+ total_cash_weight = validated_data.pop("total_cash_weight", Decimal("0.0"))
26
40
  rebalancing_model = validated_data.get("rebalancing_model", None)
27
41
  if request := self.context.get("request"):
28
42
  validated_data["creator"] = request.user.profile
@@ -32,7 +46,9 @@ class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
32
46
  if target_portfolio and not rebalancing_model and (last_effective_date := obj.last_effective_date):
33
47
  target_portfolio_dto = target_portfolio._build_dto(last_effective_date)
34
48
  try:
35
- obj.reset_trades(target_portfolio=target_portfolio_dto)
49
+ obj.reset_trades(
50
+ target_portfolio=target_portfolio_dto, total_target_weight=Decimal("1.0") - total_cash_weight
51
+ )
36
52
  except ValidationError as e:
37
53
  if request := self.context.get("request"):
38
54
  warning(request, str(e), extra_tags="auto_close=0")
@@ -60,6 +76,7 @@ class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
60
76
  fields = (
61
77
  "id",
62
78
  "trade_date",
79
+ "total_cash_weight",
63
80
  "comment",
64
81
  "status",
65
82
  "portfolio",
@@ -320,6 +320,16 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
320
320
  effective_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
321
321
  target_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
322
322
 
323
+ total_value_fx_portfolio = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=2, default=0)
324
+ effective_total_value_fx_portfolio = wb_serializers.DecimalField(
325
+ read_only=True, max_digits=16, decimal_places=2, default=0
326
+ )
327
+ target_total_value_fx_portfolio = wb_serializers.DecimalField(
328
+ read_only=True, max_digits=16, decimal_places=2, default=0
329
+ )
330
+
331
+ portfolio_currency = wb_serializers.CharField(read_only=True)
332
+
323
333
  def validate(self, data):
324
334
  data.pop("company", None)
325
335
  data.pop("security", None)
@@ -331,6 +341,8 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
331
341
  )
332
342
  effective_weight = self.instance._effective_weight if self.instance else Decimal(0.0)
333
343
  weighting = data.get("weighting", self.instance.weighting if self.instance else Decimal(0.0))
344
+ if (target_weight := data.pop("target_weight", None)) is not None:
345
+ weighting = target_weight - effective_weight
334
346
  if (target_weight := data.pop("target_weight", None)) is not None:
335
347
  weighting = target_weight - effective_weight
336
348
  if weighting >= 0:
@@ -343,14 +355,25 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
343
355
  class Meta:
344
356
  model = Trade
345
357
  percent_fields = ["effective_weight", "target_weight", "weighting"]
358
+ decorators = {
359
+ "total_value_fx_portfolio": wb_serializers.decorator(
360
+ decorator_type="text", position="left", value="{{portfolio_currency}}"
361
+ ),
362
+ "effective_total_value_fx_portfolio": wb_serializers.decorator(
363
+ decorator_type="text", position="left", value="{{portfolio_currency}}"
364
+ ),
365
+ "target_total_value_fx_portfolio": wb_serializers.decorator(
366
+ decorator_type="text", position="left", value="{{portfolio_currency}}"
367
+ ),
368
+ }
346
369
  read_only_fields = (
347
370
  "transaction_subtype",
348
371
  "shares",
349
- # "underlying_instrument",
350
- # "_underlying_instrument",
351
- "shares",
352
372
  "effective_shares",
353
373
  "target_shares",
374
+ "total_value_fx_portfolio",
375
+ "effective_total_value_fx_portfolio",
376
+ "target_total_value_fx_portfolio",
354
377
  )
355
378
  fields = (
356
379
  "id",
@@ -375,6 +398,10 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
375
398
  "order",
376
399
  "effective_shares",
377
400
  "target_shares",
401
+ "total_value_fx_portfolio",
402
+ "effective_total_value_fx_portfolio",
403
+ "target_total_value_fx_portfolio",
404
+ "portfolio_currency",
378
405
  )
379
406
 
380
407
 
@@ -66,11 +66,11 @@ class TestTradeProposal:
66
66
  validated_trading_service = trade_proposal.validated_trading_service
67
67
 
68
68
  # Assert effective and target portfolios are as expected
69
- assert validated_trading_service.effective_portfolio.to_dict() == {
69
+ assert validated_trading_service._effective_portfolio.to_dict() == {
70
70
  a1.underlying_quote.id: a1.weighting,
71
71
  a2.underlying_quote.id: a2.weighting,
72
72
  }
73
- assert validated_trading_service.target_portfolio.to_dict() == {
73
+ assert validated_trading_service._target_portfolio.to_dict() == {
74
74
  a1.underlying_quote.id: a1.weighting + t1.weighting,
75
75
  a2.underlying_quote.id: a2.weighting + t2.weighting,
76
76
  }
@@ -361,6 +361,8 @@ class TestTradeProposal:
361
361
  """
362
362
  portfolio = trade_proposal.portfolio
363
363
  instrument = instrument_factory.create(currency=portfolio.currency)
364
+ underlying_quote_price = instrument_price_factory.create(instrument=instrument, date=trade_proposal.trade_date)
365
+ mock_fct.return_value = Decimal(1_000_000) # 1 million cash
364
366
  trade = trade_factory.create(
365
367
  trade_proposal=trade_proposal,
366
368
  transaction_date=trade_proposal.trade_date,
@@ -368,12 +370,12 @@ class TestTradeProposal:
368
370
  underlying_instrument=instrument,
369
371
  )
370
372
  trade.refresh_from_db()
371
- underlying_quote_price = instrument_price_factory.create(instrument=instrument, date=trade.transaction_date)
372
- mock_fct.return_value = Decimal(1_000_000) # 1 million cash
373
373
 
374
374
  # Assert estimated shares are correctly calculated
375
375
  assert (
376
- trade_proposal.get_estimated_shares(trade.weighting, trade.underlying_instrument)
376
+ trade_proposal.get_estimated_shares(
377
+ trade.weighting, trade.underlying_instrument, underlying_quote_price.net_value
378
+ )
377
379
  == Decimal(1_000_000) * trade.weighting / underlying_quote_price.net_value
378
380
  )
379
381
 
@@ -38,30 +38,24 @@ class TestModelPortfolioRebalancing:
38
38
  def model(self, portfolio, weekday):
39
39
  from wbportfolio.rebalancing.models import ModelPortfolioRebalancing
40
40
 
41
+ PortfolioPortfolioThroughModel.objects.create(
42
+ portfolio=portfolio,
43
+ dependency_portfolio=PortfolioFactory.create(),
44
+ type=PortfolioPortfolioThroughModel.Type.MODEL,
45
+ )
41
46
  return ModelPortfolioRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
42
47
 
43
48
  def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
44
49
  assert not model.is_valid()
45
50
  asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
46
51
  assert not model.is_valid()
47
- model_portfolio = PortfolioFactory.create()
48
- PortfolioPortfolioThroughModel.objects.create(
49
- portfolio=model.portfolio,
50
- dependency_portfolio=model_portfolio,
51
- type=PortfolioPortfolioThroughModel.Type.MODEL,
52
- )
52
+
53
53
  a = asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
54
54
  assert not model.is_valid()
55
55
  instrument_price_factory.create(instrument=a.underlying_quote, date=model.trade_date)
56
56
  assert model.is_valid()
57
57
 
58
58
  def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
59
- model_portfolio = PortfolioFactory.create()
60
- PortfolioPortfolioThroughModel.objects.create(
61
- portfolio=model.portfolio,
62
- dependency_portfolio=model_portfolio,
63
- type=PortfolioPortfolioThroughModel.Type.MODEL,
64
- )
65
59
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
66
60
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
67
61
  a1 = asset_position_factory(weighting=0.8, portfolio=portfolio.model_portfolio, date=model.last_effective_date)
@@ -91,7 +85,7 @@ class TestCompositeRebalancing:
91
85
  transaction_date=model.last_effective_date,
92
86
  transaction_subtype=Trade.Type.BUY,
93
87
  trade_proposal=trade_proposal,
94
- weighting=0.7,
88
+ weighting=Decimal(0.7),
95
89
  status=Trade.Status.EXECUTED,
96
90
  )
97
91
  TradeFactory.create(
@@ -99,7 +93,7 @@ class TestCompositeRebalancing:
99
93
  transaction_date=model.last_effective_date,
100
94
  transaction_subtype=Trade.Type.BUY,
101
95
  trade_proposal=trade_proposal,
102
- weighting=0.3,
96
+ weighting=Decimal(0.3),
103
97
  status=Trade.Status.EXECUTED,
104
98
  )
105
99
  assert not model.is_valid()
@@ -117,7 +111,7 @@ class TestCompositeRebalancing:
117
111
  transaction_date=model.last_effective_date,
118
112
  transaction_subtype=Trade.Type.BUY,
119
113
  trade_proposal=trade_proposal,
120
- weighting=0.8,
114
+ weighting=Decimal(0.8),
121
115
  status=Trade.Status.EXECUTED,
122
116
  )
123
117
  t2 = TradeFactory.create(
@@ -125,7 +119,7 @@ class TestCompositeRebalancing:
125
119
  transaction_date=model.last_effective_date,
126
120
  transaction_subtype=Trade.Type.BUY,
127
121
  trade_proposal=trade_proposal,
128
- weighting=0.2,
122
+ weighting=Decimal(0.2),
129
123
  status=Trade.Status.EXECUTED,
130
124
  )
131
125
  target_portfolio = model.get_target_portfolio()
@@ -478,9 +478,7 @@ class AssetPositionUnderlyingInstrumentChartViewSet(UserPortfolioRequestPermissi
478
478
  filterset_class = AssetPositionUnderlyingInstrumentChartFilter
479
479
 
480
480
  def get_queryset(self):
481
- return AssetPosition.objects.filter(
482
- underlying_instrument__in=self.instrument.get_descendants(include_self=True)
483
- )
481
+ return AssetPosition.objects.filter(underlying_quote__in=self.instrument.get_descendants(include_self=True))
484
482
 
485
483
  def get_plotly(self, queryset):
486
484
  fig = make_subplots(specs=[[{"secondary_y": True}]])
@@ -1,7 +1,13 @@
1
+ from wbcore import serializers as wb_serializers
1
2
  from wbcore.contrib.icons import WBIcon
2
3
  from wbcore.enums import RequestType
3
4
  from wbcore.metadata.configs import buttons as bt
4
5
  from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
6
+ from wbcore.metadata.configs.display import create_simple_display
7
+
8
+
9
+ class NormalizeSerializer(wb_serializers.Serializer):
10
+ total_cash_weight = wb_serializers.FloatField(default=0, precision=4, percent=True)
5
11
 
6
12
 
7
13
  class TradeProposalButtonConfig(ButtonViewConfig):
@@ -41,10 +47,12 @@ class TradeProposalButtonConfig(ButtonViewConfig):
41
47
  icon=WBIcon.EDIT.icon,
42
48
  label="Normalize Trades",
43
49
  description_fields="""
44
- <p>Make sure all trades normalize to a total target weight of 100%</p>
50
+ <p>Make sure all trades normalize to a total target weight of (100 - {{total_cash_weight}})%</p>
45
51
  """,
46
52
  action_label="Normalize Trades",
47
53
  title="Normalize Trades",
54
+ serializer=NormalizeSerializer,
55
+ instance_display=create_simple_display([["total_cash_weight"]]),
48
56
  ),
49
57
  bt.ActionButton(
50
58
  method=RequestType.PATCH,
@@ -77,11 +77,12 @@ class TradeProposalDisplayConfig(DisplayViewConfig):
77
77
  layouts={
78
78
  default(): Layout(
79
79
  grid_template_areas=[
80
- ["status", "status", "status"],
81
- ["trade_date", "rebalancing_model", "target_portfolio"]
80
+ ["status", "status"],
81
+ ["trade_date", "total_cash_weight"],
82
+ ["rebalancing_model", "target_portfolio"]
82
83
  if self.view.new_mode
83
- else ["trade_date", "rebalancing_model", "rebalancing_model"],
84
- ["comment", "comment", "comment"],
84
+ else ["rebalancing_model", "rebalancing_model"],
85
+ ["comment", "comment"],
85
86
  ],
86
87
  ),
87
88
  },
@@ -357,6 +357,42 @@ class TradeTradeProposalDisplayConfig(DisplayViewConfig):
357
357
  ],
358
358
  )
359
359
  )
360
+ fields.append(
361
+ dp.Field(
362
+ label="Total Value",
363
+ open_by_default=False,
364
+ key=None,
365
+ children=[
366
+ dp.Field(
367
+ key="effective_total_value_fx_portfolio",
368
+ label="Effective Total Value",
369
+ show="open",
370
+ width=Unit.PIXEL(150),
371
+ ),
372
+ dp.Field(
373
+ key="target_total_value_fx_portfolio",
374
+ label="Target Total Value",
375
+ show="open",
376
+ width=Unit.PIXEL(150),
377
+ ),
378
+ dp.Field(
379
+ key="total_value_fx_portfolio",
380
+ label="Total Value",
381
+ formatting_rules=[
382
+ dp.FormattingRule(
383
+ style={"color": WBColor.RED_DARK.value, "fontWeight": "bold"},
384
+ condition=("<", 0),
385
+ ),
386
+ dp.FormattingRule(
387
+ style={"color": WBColor.GREEN_DARK.value, "fontWeight": "bold"},
388
+ condition=(">", 0),
389
+ ),
390
+ ],
391
+ width=Unit.PIXEL(150),
392
+ ),
393
+ ],
394
+ )
395
+ )
360
396
  fields.append(
361
397
  dp.Field(
362
398
  label="Information",
@@ -1,5 +1,6 @@
1
1
  from contextlib import suppress
2
2
  from datetime import date
3
+ from decimal import Decimal
3
4
 
4
5
  from django.contrib.messages import info, warning
5
6
  from django.shortcuts import get_object_or_404
@@ -106,8 +107,9 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
106
107
  @action(detail=True, methods=["PATCH"])
107
108
  def normalize(self, request, pk=None):
108
109
  trade_proposal = get_object_or_404(TradeProposal, pk=pk)
110
+ total_cash_weight = Decimal(request.data.get("total_cash_weight", Decimal("0.0")))
109
111
  if trade_proposal.status == TradeProposal.Status.DRAFT:
110
- trade_proposal.normalize_trades()
112
+ trade_proposal.normalize_trades(total_target_weight=Decimal("1.0") - total_cash_weight)
111
113
  return Response({"send": True})
112
114
  return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
113
115
 
@@ -25,7 +25,6 @@ from wbcore.permissions.permissions import InternalUserPermissionMixin
25
25
  from wbcore.utils.strings import format_number
26
26
  from wbcore.viewsets.mixins import OrderableMixin
27
27
  from wbcrm.models import Account
28
- from wbfdm.models import Cash
29
28
 
30
29
  from wbportfolio.filters import (
31
30
  SubscriptionRedemptionFilterSet,
@@ -395,6 +394,10 @@ class TradeTradeProposalModelViewSet(
395
394
  def trade_proposal(self):
396
395
  return get_object_or_404(TradeProposal, pk=self.kwargs["trade_proposal_id"])
397
396
 
397
+ @cached_property
398
+ def portfolio_total_asset_value(self):
399
+ return self.trade_proposal.portfolio_total_asset_value
400
+
398
401
  def has_import_permission(self, request) -> bool: # allow import only on draft trade proposal
399
402
  return super().has_import_permission(request) and self.trade_proposal.status == TradeProposal.Status.DRAFT
400
403
 
@@ -409,46 +412,36 @@ class TradeTradeProposalModelViewSet(
409
412
  def get_aggregates(self, queryset, *args, **kwargs):
410
413
  agg = {}
411
414
  if queryset.exists():
412
- cash_target_position = self.trade_proposal.get_estimated_target_cash(
413
- self.trade_proposal.portfolio.currency
414
- )
415
- cash_target_cash_weight, cash_target_cash_shares = (
416
- cash_target_position.weighting,
417
- cash_target_position.initial_shares,
418
- )
419
- extra_existing_cash_components = Cash.objects.filter(
420
- id__in=self.trade_proposal.trades.filter(underlying_instrument__is_cash=True).values(
421
- "underlying_instrument"
422
- )
423
- ).exclude(currency=self.trade_proposal.portfolio.currency)
424
-
425
- for cash_component in extra_existing_cash_components:
426
- extra_cash_position = self.trade_proposal.get_estimated_target_cash(cash_component.currency)
427
- cash_target_cash_weight += extra_cash_position.weighting
428
- cash_target_cash_shares += extra_cash_position.initial_shares
429
415
  noncash_aggregates = queryset.filter(underlying_instrument__is_cash=False).aggregate(
430
416
  sum_target_weight=Sum(F("target_weight")),
431
417
  sum_effective_weight=Sum(F("effective_weight")),
432
- sum_target_shares=Sum(F("target_shares")),
433
- sum_effective_shares=Sum(F("effective_shares")),
434
- )
435
- cash_aggregates = queryset.filter(underlying_instrument__is_cash=True).aggregate(
436
- sum_effective_weight=Sum(F("effective_weight")),
437
- sum_effective_shares=Sum(F("effective_shares")),
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")),
438
420
  )
421
+
439
422
  # weights aggregates
440
- cash_sum_effective_weight = cash_aggregates["sum_effective_weight"] or Decimal(0)
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"]
441
425
  noncash_sum_effective_weight = noncash_aggregates["sum_effective_weight"] or Decimal(0)
442
426
  noncash_sum_target_weight = noncash_aggregates["sum_target_weight"] or Decimal(0)
443
427
  sum_buy_weight = queryset.filter(weighting__gte=0).aggregate(s=Sum(F("weighting")))["s"] or Decimal(0)
444
428
  sum_sell_weight = queryset.filter(weighting__lt=0).aggregate(s=Sum(F("weighting")))["s"] or Decimal(0)
445
429
 
446
430
  # shares aggregates
447
- cash_sum_effective_shares = cash_aggregates["sum_effective_shares"] or Decimal(0)
448
- noncash_sum_effective_shares = noncash_aggregates["sum_effective_shares"] or Decimal(0)
449
- noncash_sum_target_shares = noncash_aggregates["sum_target_shares"] or Decimal(0)
450
- sum_buy_shares = queryset.filter(shares__gte=0).aggregate(s=Sum(F("shares")))["s"] or Decimal(0)
451
- sum_sell_shares = queryset.filter(shares__lt=0).aggregate(s=Sum(F("shares")))["s"] or Decimal(0)
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)
452
445
 
453
446
  agg = {
454
447
  "effective_weight": {
@@ -457,29 +450,38 @@ class TradeTradeProposalModelViewSet(
457
450
  "Total": format_number(noncash_sum_effective_weight + cash_sum_effective_weight, decimal=6),
458
451
  },
459
452
  "target_weight": {
460
- "Cash": format_number(cash_target_cash_weight, decimal=6),
453
+ "Cash": format_number(cash_sum_target_cash_weight, decimal=6),
461
454
  "Non-Cash": format_number(noncash_sum_target_weight, decimal=6),
462
- "Total": format_number(cash_target_cash_weight + noncash_sum_target_weight, decimal=6),
455
+ "Total": format_number(cash_sum_target_cash_weight + noncash_sum_target_weight, decimal=6),
463
456
  },
464
- "effective_shares": {
465
- "Cash": format_number(cash_sum_effective_shares, decimal=6),
466
- "Non-Cash": format_number(noncash_sum_effective_shares, decimal=6),
467
- "Total": format_number(cash_sum_effective_shares + noncash_sum_effective_shares, decimal=6),
457
+ "effective_total_value_fx_portfolio": {
458
+ "Cash": format_number(cash_sum_effective_total_value_fx_portfolio, decimal=6),
459
+ "Non-Cash": format_number(noncash_sum_effective_total_value_fx_portfolio, decimal=6),
460
+ "Total": format_number(
461
+ cash_sum_effective_total_value_fx_portfolio + noncash_sum_effective_total_value_fx_portfolio,
462
+ decimal=6,
463
+ ),
468
464
  },
469
- "target_shares": {
470
- "Cash": format_number(cash_target_cash_shares, decimal=6),
471
- "Non-Cash": format_number(noncash_sum_target_shares, decimal=6),
472
- "Total": format_number(cash_target_cash_shares + noncash_sum_target_shares, decimal=6),
465
+ "target_total_value_fx_portfolio": {
466
+ "Cash": format_number(cash_sum_target_total_value_fx_portfolio, decimal=6),
467
+ "Non-Cash": format_number(noncash_sum_target_total_value_fx_portfolio, decimal=6),
468
+ "Total": format_number(
469
+ cash_sum_target_total_value_fx_portfolio + noncash_sum_target_total_value_fx_portfolio,
470
+ decimal=6,
471
+ ),
473
472
  },
474
473
  "weighting": {
475
- "Cash Flow": format_number(cash_sum_effective_weight - cash_target_cash_weight, decimal=6),
474
+ "Cash Flow": format_number(cash_sum_target_cash_weight - cash_sum_effective_weight, decimal=6),
476
475
  "Buy": format_number(sum_buy_weight, decimal=6),
477
476
  "Sell": format_number(sum_sell_weight, decimal=6),
478
477
  },
479
- "shares": {
480
- "Cash Flow": format_number(cash_sum_effective_shares - cash_target_cash_shares, decimal=6),
481
- "Buy": format_number(sum_buy_shares, decimal=6),
482
- "Sell": format_number(sum_sell_shares, decimal=6),
478
+ "total_value_fx_portfolio": {
479
+ "Cash Flow": format_number(
480
+ cash_sum_target_total_value_fx_portfolio - cash_sum_effective_total_value_fx_portfolio,
481
+ decimal=6,
482
+ ),
483
+ "Buy": format_number(sum_buy_total_value_fx_portfolio, decimal=6),
484
+ "Sell": format_number(sum_sell_total_value_fx_portfolio, decimal=6),
483
485
  },
484
486
  }
485
487
 
@@ -509,6 +511,7 @@ class TradeTradeProposalModelViewSet(
509
511
  qs = super().get_queryset().filter(trade_proposal=self.kwargs["trade_proposal_id"]).annotate_base_info()
510
512
  else:
511
513
  qs = TradeProposal.objects.none()
514
+
512
515
  return qs.annotate(
513
516
  underlying_instrument_isin=F("underlying_instrument__isin"),
514
517
  underlying_instrument_ticker=F("underlying_instrument__ticker"),
@@ -520,4 +523,7 @@ class TradeTradeProposalModelViewSet(
520
523
  ),
521
524
  default=F("underlying_instrument__instrument_type__short_name"),
522
525
  ),
526
+ effective_total_value_fx_portfolio=F("effective_weight") * Value(self.portfolio_total_asset_value),
527
+ target_total_value_fx_portfolio=F("target_weight") * Value(self.portfolio_total_asset_value),
528
+ portfolio_currency=F("portfolio__currency__symbol"),
523
529
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.52.4
3
+ Version: 1.52.6
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -250,11 +250,11 @@ wbportfolio/migrations/0077_remove_transaction_currency_and_more.py,sha256=Yf4a3
250
250
  wbportfolio/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
251
251
  wbportfolio/models/__init__.py,sha256=HSpa5xwh_MHQaBpNrq9E0CbdEE5Iq-pDLIsPzZ-TRTg,904
252
252
  wbportfolio/models/adjustments.py,sha256=osXWkJZOiansPWYPyHtl7Z121zDWi7u1YMtrBQtbHVo,10272
253
- wbportfolio/models/asset.py,sha256=MgEdE1AZQAxHDQX6iVCr1zD9QbsIn95OQzOHoTKjAEg,45446
253
+ wbportfolio/models/asset.py,sha256=WwbLjpGHzz17yJxrkuTP4MbErqcTJMlKkDrQI92p5II,45490
254
254
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
255
255
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
256
256
  wbportfolio/models/indexes.py,sha256=gvW4K9U9Bj8BmVCqFYdWiXvDWhjHINRON8XhNsZUiQY,639
257
- wbportfolio/models/portfolio.py,sha256=afk2XnVxMIiAmLPrDIGDbeOyhvdDzXs7K-MCaUKKKyc,56275
257
+ wbportfolio/models/portfolio.py,sha256=yQ8cqAZLOMwsiejC9RzOLoqLDV5q5ZWYWMOc2BlcHdw,56281
258
258
  wbportfolio/models/portfolio_cash_flow.py,sha256=uElG7IJUBY8qvtrXftOoskX6EA-dKgEG1JJdvHeWV7g,7336
259
259
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
260
260
  wbportfolio/models/portfolio_relationship.py,sha256=ZGECiPZiLdlk4uSamOrEfuzO0hduK6OMKJLUSnh5_kc,5190
@@ -281,16 +281,16 @@ wbportfolio/models/transactions/claim.py,sha256=SF2FlwG6SRVmA_hT0NbXah5-fYejccWK
281
281
  wbportfolio/models/transactions/dividends.py,sha256=mmOdGWR35yndUMoCuG24Y6BdtxDhSk2gMQ-8LVguqzg,1890
282
282
  wbportfolio/models/transactions/fees.py,sha256=wJtlzbBCAq1UHvv0wqWTE2BEjCF5RMtoaSDS3kODFRo,7112
283
283
  wbportfolio/models/transactions/rebalancing.py,sha256=obzgewWKOD4kJbCoF5fhtfDk502QkbrjPKh8T9KDGew,7355
284
- wbportfolio/models/transactions/trade_proposals.py,sha256=b1VbgR91-3LFq08iNnafcTjvsDboimU7ZRpf5jtQk7w,32234
285
- wbportfolio/models/transactions/trades.py,sha256=wTOa0Wu4OejO3CeeJnwvPzoDUVWMsHRbyEA7QllaLRs,31231
284
+ wbportfolio/models/transactions/trade_proposals.py,sha256=Jd7rfiE42Qtqp9VXQ448OF9m_DKoFR8ISjSh-1TDi8o,31592
285
+ wbportfolio/models/transactions/trades.py,sha256=og8R4Sg7LCRQsuX09uYjZXq9q4VsUb0j8W8iCTyx0pQ,31571
286
286
  wbportfolio/models/transactions/transactions.py,sha256=XTcUeMUfkf5XTSZaR2UAyGqCVkOhQYk03_vzHLIgf8Q,3807
287
287
  wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
288
- wbportfolio/pms/typing.py,sha256=b2pBWYt1E8ok-Kqm0lEFIakSnWJ6Ib57z-VX3C3gkQc,6081
288
+ wbportfolio/pms/typing.py,sha256=qcqEs94HA7qNB631LErV7FVQ4ytYBLetuPDcilrnlW0,6798
289
289
  wbportfolio/pms/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
290
290
  wbportfolio/pms/analytics/portfolio.py,sha256=vE0KA6Z037bUdmBTkYuBqXElt80nuYObNzY_kWvxEZY,1360
291
291
  wbportfolio/pms/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
292
292
  wbportfolio/pms/trading/__init__.py,sha256=R_yLKc54sCak8A1cW0O1Aszrcv5KV8mC_3h17Hr20e4,36
293
- wbportfolio/pms/trading/handler.py,sha256=EzvCP8NNzd0jjE3ilCupbmFBCaojKvlrbq7n_JirahU,7151
293
+ wbportfolio/pms/trading/handler.py,sha256=ORvtwAG2VX1GvNvNE2HnDvS-4UNrgBwCMPydnfphtY0,5992
294
294
  wbportfolio/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
295
295
  wbportfolio/rebalancing/base.py,sha256=NwTGZtBm1f35gj5Jp6iTyyFvDT1GSIztN990cKBvYzQ,637
296
296
  wbportfolio/rebalancing/decorators.py,sha256=JhQ2vkcIuGBTGvNmpkQerdw2-vLq-RAb0KAPjOrETkw,515
@@ -298,7 +298,7 @@ wbportfolio/rebalancing/models/__init__.py,sha256=AQjG7Tu5vlmhqncVoYOjpBKU2UIvgo
298
298
  wbportfolio/rebalancing/models/composite.py,sha256=XEgK3oMurrE_d_l5uN0stBKRrtvnKQzRWyXNXuBYfmc,1818
299
299
  wbportfolio/rebalancing/models/equally_weighted.py,sha256=FCpSKOs49ckNYVgoYIiHB0BqPT9OeCMuFoet4Ixbp-Y,1210
300
300
  wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=6ZsR8iJg6l89CsxHqoxJSXlTaj8Pmb8_bFPrXhnxaRs,5295
301
- wbportfolio/rebalancing/models/model_portfolio.py,sha256=DNg9vEDYDUwXTOnIpk26FQSPHC0qxkuvW2sJWX0VodQ,1489
301
+ wbportfolio/rebalancing/models/model_portfolio.py,sha256=Dk3tw9u3WG1jCP3V2-R05GS4-DmDBBtxH4h6p7pRe4g,1393
302
302
  wbportfolio/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
303
303
  wbportfolio/reports/monthly_position_report.py,sha256=e7BzjDd6eseUOwLwQJXKvWErQ58YnCsznHU2VtR6izM,2981
304
304
  wbportfolio/risk_management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -346,8 +346,8 @@ wbportfolio/serializers/transactions/__init__.py,sha256=oAfidhjjCKP0exeHbzJgGuBd
346
346
  wbportfolio/serializers/transactions/claim.py,sha256=kC4E2RZRrpd9i8tGfoiV-gpWDk3ikR5F1Wf0v_IGIvw,11599
347
347
  wbportfolio/serializers/transactions/dividends.py,sha256=ADXf9cXe8rq55lC_a8vIzViGLmQ-yDXkgR54k2m-N0w,1814
348
348
  wbportfolio/serializers/transactions/fees.py,sha256=3mBzs6vdfST9roeQB-bmLJhipY7i5jBtAXjoTTE-GOg,2388
349
- wbportfolio/serializers/transactions/trade_proposals.py,sha256=fiGpL6za5ERLtdbud5wrciCVXHX6-3SjeF8Zaa6Zhzg,3410
350
- wbportfolio/serializers/transactions/trades.py,sha256=Rhv0UezIpnDxWmj5v9dQECfS0un7yQUIpnkOtD6z6YY,15452
349
+ wbportfolio/serializers/transactions/trade_proposals.py,sha256=ufYBYiSttz5KAlAaaXbnf98oUFT_qNfxF_VUM4ClXE8,4072
350
+ wbportfolio/serializers/transactions/trades.py,sha256=JtL2jrvIjBVsmB2N5ngw_1UyzK7BbxGK5yuNTCRSfkU,16815
351
351
  wbportfolio/static/wbportfolio/css/macro_review.css,sha256=FAVVO8nModxwPXcTKpcfzVxBGPZGJVK1Xn-0dkSfGyc,233
352
352
  wbportfolio/static/wbportfolio/markdown/documentation/account_holding_reconciliation.md,sha256=MabxOvOne8s5gl6osDoow6-3ghaXLAYg9THWpvy6G5I,921
353
353
  wbportfolio/static/wbportfolio/markdown/documentation/aggregate_asset_position_liquidity.md,sha256=HEgXB7uqmqfty-GBCCXYxrAN-teqmxWuqDLK_liKWVc,1090
@@ -387,12 +387,12 @@ wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
387
387
  wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
388
388
  wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
389
389
  wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=fZ5tx6kByEGXD6nhapYdvk9HOjYlmjhU2w6KlQJ6QE4,4061
390
- wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=UB8Dvs7nT42D_uznuLyzni7PYiY-dabLCDXmV2W_yb4,18928
390
+ wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=ERGDbniHctEnIG5yFLkrZX2Jw1jEew5rWceVKuKskto,18997
391
391
  wbportfolio/tests/models/transactions/test_trades.py,sha256=vqvOqUY_uXvBp8YOKR0Wq9ycA2oeeEBhO3dzV7sbXEU,9863
392
392
  wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
393
393
  wbportfolio/tests/pms/test_analytics.py,sha256=fAuY1zcXibttFpBh2GhKVyzdYfi1kz_b7SPa9xZQXY0,1086
394
394
  wbportfolio/tests/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
395
- wbportfolio/tests/rebalancing/test_models.py,sha256=_gT_7UtpOWceDwT7FbTUW6P6ZpCVLBpgXWM0goIljWc,8090
395
+ wbportfolio/tests/rebalancing/test_models.py,sha256=4rSb05xKH881ft12G0B8ZSeFgJvutKcW_7vFgVQa0DI,7813
396
396
  wbportfolio/tests/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
397
397
  wbportfolio/tests/serializers/test_claims.py,sha256=vQrg73xQXRFEgvx3KI9ivFre_wpBFzdO0p0J13PkvdY,582
398
398
  wbportfolio/tests/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -403,7 +403,7 @@ wbportfolio/tests/viewsets/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
403
403
  wbportfolio/tests/viewsets/transactions/test_claims.py,sha256=QEZfMAW07dyoZ63t2umSwGOqvaTULfYfbN_F4ZoSAcw,6368
404
404
  wbportfolio/viewsets/__init__.py,sha256=3kUaQ66ybvROwejd3bEcSt4XKzfOlPDaeoStMvlz7qY,2294
405
405
  wbportfolio/viewsets/adjustments.py,sha256=ugbX4aFRCaD4Yj1hxL-VIPaNI7GF_wt0FrkN6mq1YjU,1524
406
- wbportfolio/viewsets/assets.py,sha256=Gf5QKDM7QR9MC8GJhMNzaY7NzeKV_yOQw0wktKPEc9o,24661
406
+ wbportfolio/viewsets/assets.py,sha256=nMMVh7P531KZ-zpFogIMFKbfvRD3KdG2oIAAHQ42lpc,24634
407
407
  wbportfolio/viewsets/assets_and_net_new_money_progression.py,sha256=Jl4vEQP4N2OFL5IGBXoKcj-0qaPviU0I8npvQLw4Io0,4464
408
408
  wbportfolio/viewsets/custodians.py,sha256=CTFqkqVP1R3AV7lhdvcdICxB5DfwDYCyikNSI5kbYEo,2322
409
409
  wbportfolio/viewsets/esg.py,sha256=27MxxdXQH3Cq_1UEYmcrF7htUOg6i81fUpbVQXAAKJI,6985
@@ -436,7 +436,7 @@ wbportfolio/viewsets/configs/buttons/products.py,sha256=bHOs2ftfaLbIbJ7E8yFqQqSu
436
436
  wbportfolio/viewsets/configs/buttons/reconciliations.py,sha256=lw4r22GHpqKPUF1MrB6P9dOkL-FHe5iiBJ0--f8D74E,3145
437
437
  wbportfolio/viewsets/configs/buttons/registers.py,sha256=aS89TsYHql83k-NHojOrLDqtBpnpsUUO8x63PiMfrXM,445
438
438
  wbportfolio/viewsets/configs/buttons/signals.py,sha256=6sKBQI_eDvZuZR5bUUwvur5R67A3oChAGxPfayWUelE,2739
439
- wbportfolio/viewsets/configs/buttons/trade_proposals.py,sha256=eZBfYk5ZhancCVcu7bvRKPGBKTl_tGlgz6BZrGPpPxQ,2953
439
+ wbportfolio/viewsets/configs/buttons/trade_proposals.py,sha256=Os5jT-aL0jMNkdm2sJpGRZvY4OzreR6Ah7eHex_6zB4,3383
440
440
  wbportfolio/viewsets/configs/buttons/trades.py,sha256=bPOqqwdgadSUbU9l5aSirqr5UAMFe2AiFNsXLRsKwn8,2535
441
441
  wbportfolio/viewsets/configs/display/__init__.py,sha256=jJqSCCAfw_vjbcsIkzKr39LdAZA810LUBfSH1EA7MAI,2086
442
442
  wbportfolio/viewsets/configs/display/adjustments.py,sha256=jIOEc23OCYBguLaZRlZxC916kocYT35ZV9Jsiocs9nk,3334
@@ -456,8 +456,8 @@ wbportfolio/viewsets/configs/display/rebalancing.py,sha256=yw9X1Nf2-V_KP_mCX4pVK
456
456
  wbportfolio/viewsets/configs/display/reconciliations.py,sha256=YvMAuwmpX0HExvGsuf5UvcRQxe4eMo1iyNJX68GGC_k,6021
457
457
  wbportfolio/viewsets/configs/display/registers.py,sha256=1np75exIk5rfct6UkVN_RnfJ9ozvIkcWJgFV4_4rJns,3182
458
458
  wbportfolio/viewsets/configs/display/roles.py,sha256=SFUyCdxSlHZ3NsMrJmpVBSlg-XKGaEFteV89nyLMMAQ,1815
459
- wbportfolio/viewsets/configs/display/trade_proposals.py,sha256=sRLSUjKlarBhnTwg7tX_Juldor3beswJlyvZfFPvNEk,4315
460
- wbportfolio/viewsets/configs/display/trades.py,sha256=PbGI8Sjs8YMBLfeYdgXRbv_tvel2aV3dB-7oZXnLwz4,17587
459
+ wbportfolio/viewsets/configs/display/trade_proposals.py,sha256=gM5CuUmNcVtwJHYE9Myg-RR_WmAc6VDJztwUqQ1uZ7g,4335
460
+ wbportfolio/viewsets/configs/display/trades.py,sha256=ZYZ2ceE4Hyw2SpQjW00VDnzGz7Wlu3jjPoK_J2GZLyk,19181
461
461
  wbportfolio/viewsets/configs/endpoints/__init__.py,sha256=KR3AsSxl71VAVkYBRVowgs3PZB8vaKa34WHHUvC-2MY,2807
462
462
  wbportfolio/viewsets/configs/endpoints/adjustments.py,sha256=KRZLqv4ZeB27d-_s7Qlqj9LI3I9WFJkjc6Mw8agDueE,606
463
463
  wbportfolio/viewsets/configs/endpoints/assets.py,sha256=B8W6ATQQfT292TllE4sUDYv5wHUCo99HEqymJuMtFAc,1909
@@ -517,9 +517,9 @@ wbportfolio/viewsets/transactions/claim.py,sha256=Pb1WftoO-w-ZSTbLRhmQubhy7hgd68
517
517
  wbportfolio/viewsets/transactions/fees.py,sha256=WT2bWWfgozz4_rpyTKX7dgBBTXD-gu0nlsd2Nk2Zh1Q,7028
518
518
  wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
519
519
  wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQVTN2MH1kG_yCeVkmyK8k,1263
520
- wbportfolio/viewsets/transactions/trade_proposals.py,sha256=rCJoJSL11qpjxciX25Q5ZU3ewELyyDBUPXcj4Z88Vdg,6037
521
- wbportfolio/viewsets/transactions/trades.py,sha256=wCyTTyVKjYuMRsrFcmwD3_yggWxCNm-mdoYzVnhGP0U,21727
522
- wbportfolio-1.52.4.dist-info/METADATA,sha256=D1NdVWFeugjXwTxXubD32oNp_T4AVzsAAXfkTXgUiR4,702
523
- wbportfolio-1.52.4.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
524
- wbportfolio-1.52.4.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
525
- wbportfolio-1.52.4.dist-info/RECORD,,
520
+ wbportfolio/viewsets/transactions/trade_proposals.py,sha256=zdb9yqyAE1-hcMmxxN3CYePWoPVWIW3WP3v8agErDeY,6210
521
+ wbportfolio/viewsets/transactions/trades.py,sha256=DcfiJReBnnR6oBgNKZyBYWhXurtxJG_DCzxq5WCionA,22025
522
+ wbportfolio-1.52.6.dist-info/METADATA,sha256=9YI1D7f4Omg3dI2yJs8v_cUOHJ4cdzujpm3l7QfGh38,702
523
+ wbportfolio-1.52.6.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
524
+ wbportfolio-1.52.6.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
525
+ wbportfolio-1.52.6.dist-info/RECORD,,