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

Files changed (31) hide show
  1. wbportfolio/factories/dividends.py +1 -0
  2. wbportfolio/import_export/handlers/dividend.py +1 -1
  3. wbportfolio/import_export/handlers/fees.py +1 -1
  4. wbportfolio/import_export/handlers/portfolio_cash_flow.py +1 -1
  5. wbportfolio/import_export/handlers/trade.py +5 -2
  6. wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +1 -1
  7. wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +1 -1
  8. wbportfolio/migrations/0076_alter_dividendtransaction_price_and_more.py +126 -0
  9. wbportfolio/models/portfolio.py +19 -6
  10. wbportfolio/models/transactions/claim.py +8 -7
  11. wbportfolio/models/transactions/dividends.py +3 -20
  12. wbportfolio/models/transactions/trade_proposals.py +23 -20
  13. wbportfolio/models/transactions/trades.py +9 -12
  14. wbportfolio/models/transactions/transactions.py +37 -37
  15. wbportfolio/rebalancing/models/market_capitalization_weighted.py +13 -16
  16. wbportfolio/serializers/portfolios.py +1 -1
  17. wbportfolio/tests/models/test_portfolios.py +1 -1
  18. wbportfolio/tests/rebalancing/test_models.py +2 -2
  19. wbportfolio/viewsets/assets.py +1 -1
  20. wbportfolio/viewsets/configs/display/assets.py +0 -11
  21. wbportfolio/viewsets/configs/display/portfolios.py +2 -2
  22. wbportfolio/viewsets/configs/display/products.py +0 -13
  23. wbportfolio/viewsets/configs/display/trades.py +1 -9
  24. wbportfolio/viewsets/configs/endpoints/transactions.py +6 -0
  25. wbportfolio/viewsets/portfolios.py +17 -1
  26. wbportfolio/viewsets/transactions/trade_proposals.py +6 -4
  27. wbportfolio/viewsets/transactions/trades.py +21 -3
  28. {wbportfolio-1.49.0.dist-info → wbportfolio-1.49.2.dist-info}/METADATA +1 -1
  29. {wbportfolio-1.49.0.dist-info → wbportfolio-1.49.2.dist-info}/RECORD +31 -30
  30. {wbportfolio-1.49.0.dist-info → wbportfolio-1.49.2.dist-info}/WHEEL +0 -0
  31. {wbportfolio-1.49.0.dist-info → wbportfolio-1.49.2.dist-info}/licenses/LICENSE +0 -0
@@ -11,5 +11,6 @@ class DividendTransactionsFactory(TransactionFactory):
11
11
  class Meta:
12
12
  model = DividendTransaction
13
13
 
14
+ retrocession = 1.0
14
15
  shares = factory.LazyAttribute(lambda o: random.randint(10, 10000))
15
16
  price = factory.LazyAttribute(lambda o: random.randint(10, 10000))
@@ -23,7 +23,7 @@ class DividendImportHandler(ImportExportHandler):
23
23
  data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
24
24
  from wbportfolio.models import Portfolio
25
25
 
26
- data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
26
+ data["portfolio"] = Portfolio.all_objects.get(id=data["portfolio"])
27
27
  instrument = self.instrument_handler.process_object(
28
28
  data["underlying_instrument"], only_security=False, read_only=True
29
29
  )[0]
@@ -26,7 +26,7 @@ class FeesImportHandler(ImportExportHandler):
26
26
 
27
27
  data["linked_product"] = Product.objects.get(id=data["linked_product"])
28
28
  if "porfolio" in data:
29
- data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
29
+ data["portfolio"] = Portfolio.all_objects.get(id=data["portfolio"])
30
30
  else:
31
31
  data["portfolio"] = data["linked_product"].primary_portfolio
32
32
  data["underlying_instrument"] = Cash.objects.filter(currency=data["portfolio"].currency).first()
@@ -19,7 +19,7 @@ class DailyPortfolioCashFlowImportHandler(ImportExportHandler):
19
19
 
20
20
  def _deserialize(self, data):
21
21
  data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
22
- data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
22
+ data["portfolio"] = Portfolio.all_objects.get(id=data["portfolio"])
23
23
  if "cash" in data:
24
24
  data["cash"] = Decimal(data["cash"])
25
25
 
@@ -1,3 +1,4 @@
1
+ import math
1
2
  from datetime import datetime
2
3
  from decimal import Decimal
3
4
  from typing import Any, Dict, Optional
@@ -71,7 +72,10 @@ class TradeImportHandler(ImportExportHandler):
71
72
 
72
73
  for field in self.model._meta.get_fields():
73
74
  if not (value := data.get(field.name, None)) is None and isinstance(field, models.DecimalField):
74
- data[field.name] = Decimal(value)
75
+ q = (
76
+ 1 / (math.pow(10, 4))
77
+ ) # we need that convertion mechanism otherwise there is floating point approximation error while casting to decimal and get_instance does not work as expected
78
+ data[field.name] = Decimal(value).quantize(Decimal(str(q)))
75
79
 
76
80
  def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
77
81
  if "transaction_date" not in data: # we might get only book date and not transaction date
@@ -81,7 +85,6 @@ class TradeImportHandler(ImportExportHandler):
81
85
 
82
86
  def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
83
87
  self.import_source.log += "\nGet Trade Instance."
84
-
85
88
  if transaction_date := data.get("transaction_date"):
86
89
  dates_lookup = {"transaction_date": transaction_date}
87
90
  elif book_date := data.get("book_date"):
@@ -29,7 +29,7 @@ def get_portfolio_id(row: pd.Series) -> int:
29
29
  Raises: Portfolio.DoesNotExist: We raise an error intentionally if the portfolio does not exist to make the import fail
30
30
  """
31
31
  iban = str(IBAN.generate(BANK_COUNTRY_CODE, bank_code=BANK_CODE, account_code=str(row["account_number"])))
32
- return Portfolio.objects.get(bank_accounts__iban=iban).id
32
+ return Portfolio.all_objects.get(bank_accounts__iban=iban).id
33
33
 
34
34
 
35
35
  def parse(import_source: "ImportSource") -> dict:
@@ -13,7 +13,7 @@ def parse(import_source):
13
13
 
14
14
  def get_portfolio_id(row) -> int | None:
15
15
  with suppress(Portfolio.DoesNotExist):
16
- return Portfolio.objects.get(instruments__children__isin__in=[row["Isin"]]).pk
16
+ return Portfolio.all_objects.get(instruments__children__isin__in=[row["Isin"]]).pk
17
17
 
18
18
  df = df[~df["Isin"].isnull()]
19
19
  df["Trade date"] = df["Trade date"].ffill()
@@ -0,0 +1,126 @@
1
+ # Generated by Django 5.0.13 on 2025-03-21 12:30
2
+
3
+ import django.db.models.expressions
4
+ from decimal import Decimal
5
+ from django.db import migrations, models
6
+
7
+ def migrate_default_values(apps, schema_editor):
8
+ Transaction = apps.get_model("wbportfolio", "Transaction")
9
+ Trade = apps.get_model("wbportfolio", "Trade")
10
+ Expiry = apps.get_model("wbportfolio", "Expiry")
11
+ DividendTransaction = apps.get_model("wbportfolio", "DividendTransaction")
12
+ Transaction.objects.filter(book_date__isnull=True).update(book_date=models.F("transaction_date"))
13
+ Transaction.objects.filter(value_date__isnull=True).update(value_date=models.F("transaction_date"))
14
+ Trade.objects.filter(price_gross__isnull=True).update(price_gross=models.F("price"))
15
+ Expiry.objects.filter(price_gross__isnull=True).update(price_gross=models.F("price"))
16
+ DividendTransaction.objects.filter(price_gross__isnull=True).update(price_gross=models.F("price"))
17
+ Transaction.objects.filter(total_value_gross__isnull=True).update(total_value_gross=models.F("total_value"))
18
+
19
+
20
+ class Migration(migrations.Migration):
21
+
22
+ dependencies = [
23
+ ('wbportfolio', '0075_portfolio_initial_position_date_and_more'),
24
+ ]
25
+
26
+ operations = [
27
+ migrations.RunPython(migrate_default_values),
28
+ migrations.AlterField(
29
+ model_name='transaction',
30
+ name='book_date',
31
+ field=models.DateField(help_text='The date that this transaction was booked.',
32
+ verbose_name='Trade Date'),
33
+ preserve_default=False,
34
+ ),
35
+ migrations.AlterField(
36
+ model_name='transaction',
37
+ name='value_date',
38
+ field=models.DateField(help_text='The date that this transaction was valuated.',
39
+ verbose_name='Value Date'),
40
+ preserve_default=False,
41
+ ),
42
+ migrations.AlterField(
43
+ model_name='dividendtransaction',
44
+ name='price',
45
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The price per share.', max_digits=16, verbose_name='Price'),
46
+ ),
47
+ migrations.AlterField(
48
+ model_name='dividendtransaction',
49
+ name='price_gross',
50
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The gross price per share.', max_digits=16, verbose_name='Gross Price'),
51
+ preserve_default=False,
52
+ ),
53
+ migrations.AlterField(
54
+ model_name='dividendtransaction',
55
+ name='shares',
56
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The number of shares that were traded.', max_digits=15, verbose_name='Shares'),
57
+ ),
58
+ migrations.AlterField(
59
+ model_name='expiry',
60
+ name='price',
61
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The price per share.', max_digits=16, verbose_name='Price'),
62
+ ),
63
+ migrations.AlterField(
64
+ model_name='expiry',
65
+ name='price_gross',
66
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The gross price per share.', max_digits=16, verbose_name='Gross Price'),
67
+ preserve_default=False,
68
+ ),
69
+ migrations.AlterField(
70
+ model_name='expiry',
71
+ name='shares',
72
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The number of shares that were traded.', max_digits=15, verbose_name='Shares'),
73
+ ),
74
+ migrations.AlterField(
75
+ model_name='trade',
76
+ name='price',
77
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The price per share.', max_digits=16, verbose_name='Price'),
78
+ ),
79
+ migrations.AlterField(
80
+ model_name='trade',
81
+ name='price_gross',
82
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The gross price per share.', max_digits=16, verbose_name='Gross Price'),
83
+ preserve_default=False,
84
+ ),
85
+ migrations.AlterField(
86
+ model_name='trade',
87
+ name='shares',
88
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), help_text='The number of shares that were traded.', max_digits=15, verbose_name='Shares'),
89
+ ),
90
+
91
+ migrations.AlterField(
92
+ model_name='transaction',
93
+ name='total_value',
94
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), max_digits=20, verbose_name='Total Value'),
95
+ preserve_default=False,
96
+ ),
97
+ migrations.AlterField(
98
+ model_name='transaction',
99
+ name='total_value_gross',
100
+ field=models.DecimalField(decimal_places=4, default=Decimal('0.0'), max_digits=20, verbose_name='Total Value Gross'),
101
+ preserve_default=False,
102
+ ),
103
+ migrations.RemoveField(
104
+ model_name='transaction',
105
+ name='total_value_fx_portfolio',
106
+ ),
107
+ migrations.RemoveField(
108
+ model_name='transaction',
109
+ name='total_value_gross_fx_portfolio',
110
+ ),
111
+ migrations.AddField(
112
+ model_name='transaction',
113
+ name='total_value_fx_portfolio',
114
+ field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(
115
+ models.F('currency_fx_rate'), '*', models.F('total_value')),
116
+ output_field=models.DecimalField(decimal_places=4, max_digits=20)),
117
+ preserve_default=False,
118
+ ),
119
+ migrations.AddField(
120
+ model_name='transaction',
121
+ name='total_value_gross_fx_portfolio',
122
+ field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(models.F('currency_fx_rate'), '*', models.F('total_value_gross')), output_field=models.DecimalField(decimal_places=4, max_digits=20)),
123
+ preserve_default=False,
124
+ ),
125
+
126
+ ]
@@ -411,7 +411,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
411
411
  )
412
412
 
413
413
  def __str__(self):
414
- return f"{self.id:06} ({self.name})"
414
+ return f"{self.id:06}: {self.name}"
415
415
 
416
416
  class Meta:
417
417
  verbose_name = "Portfolio"
@@ -853,7 +853,6 @@ class Portfolio(DeleteToDisableMixin, WBModel):
853
853
  adjusted_weighting,
854
854
  adjusted_currency_fx_rate,
855
855
  adjusted_is_estimated,
856
- portfolio_created=None,
857
856
  path=None,
858
857
  ):
859
858
  if not path:
@@ -866,7 +865,18 @@ class Portfolio(DeleteToDisableMixin, WBModel):
866
865
  position.is_estimated = (adjusted_is_estimated or position.is_estimated) and not (
867
866
  position.weighting == 1.0
868
867
  )
869
- position.portfolio_created = portfolio_created
868
+ # to get from which portfolio this position is created, we need to differantiate between:
869
+ # * Composition portfolio: where the portfolio created is the second encountered portfolio
870
+ # * Other: portfolio created is the last encountered portfolio
871
+ # If `path` is empty, we use None as portfolio_created
872
+ try:
873
+ if self.is_composition:
874
+ position.portfolio_created = path[1]
875
+ else:
876
+ position.portfolio_created = path[-1]
877
+ except IndexError:
878
+ position.portfolio_created = None
879
+
870
880
  setattr(position, "path", path)
871
881
  position.initial_shares = None
872
882
  if portfolio_total_asset_value:
@@ -881,7 +891,6 @@ class Portfolio(DeleteToDisableMixin, WBModel):
881
891
  position.weighting,
882
892
  position.currency_fx_rate,
883
893
  position.is_estimated,
884
- portfolio_created=child_portfolio,
885
894
  path=path.copy(),
886
895
  )
887
896
  elif position.weighting: # we do not yield position with weight 0 because of issue with certain multi-thematic portfolios which contain duplicates
@@ -979,7 +988,11 @@ class Portfolio(DeleteToDisableMixin, WBModel):
979
988
  for position in positions:
980
989
  position.portfolio = self
981
990
  update_dates.add(position.date)
982
- # self.assets.filter(date__in=update_dates, is_estimated=True).delete()
991
+
992
+ # we need to delete the existing estimated portfolio because otherwise we risk to have existing and not
993
+ # overlapping positions remaining (as they will not be updating by the bulk create). E.g. when someone
994
+ # change completely the trades of a portfolio model and drift it.
995
+ self.assets.filter(date__in=update_dates, is_estimated=True).delete()
983
996
  leftover_positions_ids = list(
984
997
  self.assets.filter(date__in=update_dates).values_list("id", flat=True)
985
998
  ) # we need to get the ids otherwise the queryset is reevaluated later
@@ -1014,7 +1027,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
1014
1027
  @classmethod
1015
1028
  def _get_or_create_portfolio(cls, instrument_handler, portfolio_data):
1016
1029
  if isinstance(portfolio_data, int):
1017
- return Portfolio.objects.get(id=portfolio_data)
1030
+ return Portfolio.all_objects.get(id=portfolio_data)
1018
1031
  instrument = portfolio_data
1019
1032
  if isinstance(portfolio_data, dict):
1020
1033
  instrument = instrument_handler.process_object(instrument, only_security=False, read_only=True)[0]
@@ -14,6 +14,7 @@ from django.db.models import (
14
14
  )
15
15
  from django.db.models.functions import Greatest
16
16
  from django.dispatch import receiver
17
+ from django.utils.translation import gettext_lazy as _
17
18
  from django_fsm import FSMField, transition
18
19
  from wbcore.contrib.ai.llm.config import add_llm_prompt
19
20
  from wbcore.contrib.authentication.models import User
@@ -335,13 +336,13 @@ class Claim(ReferenceIDMixin, WBModel):
335
336
  errors = dict()
336
337
 
337
338
  if not self.trade:
338
- errors["trade"] = ["With this status, this has to be provided."]
339
+ errors["trade"] = [_("With this status, this has to be provided.")]
339
340
 
340
341
  if not self.product:
341
- errors["product"] = ["With this status, this has to be provided."]
342
+ errors["product"] = [_("With this status, this has to be provided.")]
342
343
 
343
344
  if not self.account:
344
- errors["account"] = ["With this status, this has to be provided."]
345
+ errors["account"] = [_("With this status, this has to be provided.")]
345
346
 
346
347
  # check if the specified product have a valid nav at the specified date
347
348
  if (
@@ -350,13 +351,13 @@ class Claim(ReferenceIDMixin, WBModel):
350
351
  and not product.valuations.filter(date=claim_date).exists()
351
352
  ):
352
353
  if (prices_qs := product.valuations.filter(date__lt=claim_date)).exists():
353
- errors["date"] = (
354
+ errors["date"] = [
354
355
  f"For product {product.name}, the latest valid valuation date before {claim_date:%Y-%m-%d} is {prices_qs.latest('date').date:%Y-%m-%d}: Please select a valid date."
355
- )
356
+ ]
356
357
  else:
357
- errors["date"] = (
358
+ errors["date"] = [
358
359
  f"There is no valuation before {claim_date:%Y-%m-%d} for product {product.name}: Please select a valid date."
359
- )
360
+ ]
360
361
  return errors
361
362
 
362
363
  @transition(
@@ -1,3 +1,5 @@
1
+ from decimal import Decimal
2
+
1
3
  from django.db import models
2
4
 
3
5
  from wbportfolio.import_export.handlers.dividend import DividendImportHandler
@@ -10,23 +12,4 @@ class DividendTransaction(Transaction, ShareMixin, models.Model):
10
12
  retrocession = models.FloatField(default=1)
11
13
 
12
14
  def save(self, *args, **kwargs):
13
- if (
14
- self.shares is not None
15
- and self.price is not None
16
- and self.retrocession is not None
17
- and self.total_value is None
18
- ):
19
- self.total_value = self.shares * self.price * self.retrocession
20
-
21
- if self.price is not None and self.price_gross is None:
22
- self.price_gross = self.price
23
-
24
- if (
25
- self.price_gross is not None
26
- and self.retrocession is not None
27
- and self.shares is not None
28
- and self.total_value_gross is None
29
- ):
30
- self.total_value_gross = self.shares * self.price_gross * self.retrocession
31
-
32
- super().save(*args, **kwargs)
15
+ super().save(*args, factor=Decimal(self.retrocession), **kwargs)
@@ -8,6 +8,7 @@ from celery import shared_task
8
8
  from django.core.exceptions import ValidationError
9
9
  from django.db import models
10
10
  from django.utils.functional import cached_property
11
+ from django.utils.translation import gettext_lazy as _
11
12
  from django_fsm import FSMField, transition
12
13
  from pandas._libs.tslibs.offsets import BDay
13
14
  from wbcompliance.models.risk_management.mixins import RiskCheckMixin
@@ -166,7 +167,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
166
167
  Returns:
167
168
  The cloned trade proposal
168
169
  """
169
- trade_date = kwargs.get("trade_date", self.trade_date)
170
+ trade_date = kwargs.get("clone_date", self.trade_date)
170
171
 
171
172
  # Find the next valid trade date
172
173
  while TradeProposal.objects.filter(portfolio=self.portfolio, trade_date=trade_date).exists():
@@ -174,7 +175,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
174
175
 
175
176
  trade_proposal_clone = TradeProposal.objects.create(
176
177
  trade_date=trade_date,
177
- comment=kwargs.get("comment", self.comment),
178
+ comment=kwargs.get("clone_comment", self.comment),
178
179
  status=TradeProposal.Status.DRAFT,
179
180
  rebalancing_model=self.rebalancing_model,
180
181
  portfolio=self.portfolio,
@@ -228,8 +229,6 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
228
229
  """
229
230
  Will delete all existing trades and recreate them from the method `create_or_update_trades`
230
231
  """
231
- if self.status != TradeProposal.Status.DRAFT:
232
- raise ValueError("Cannot reset non-draft trade proposal. Revert this trade proposal first.")
233
232
  # delete all existing trades
234
233
  last_effective_date = self.last_effective_date
235
234
  # Get effective and target portfolio
@@ -303,7 +302,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
303
302
  )
304
303
  last_trade_proposal = overriding_trade_proposal or next_trade_proposal
305
304
 
306
- def get_estimated_shares(self, weight: Decimal, underlying_quote: Instrument) -> Decimal | None:
305
+ def get_estimated_shares(self, weight: Decimal, underlying_quote: Instrument) -> Decimal:
307
306
  """
308
307
  Estimates the number of shares for a trade based on the given weight and underlying quote.
309
308
 
@@ -332,8 +331,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
332
331
  if price_fx_portfolio:
333
332
  return trade_total_value_fx_portfolio / price_fx_portfolio
334
333
  except Exception:
335
- # Suppress any ValueError and return None if the calculation fails
336
- return None
334
+ raise ValueError("We couldn't estimate the number of shares")
337
335
 
338
336
  def get_estimated_target_cash(self, currency: Currency) -> tuple[Decimal, Decimal]:
339
337
  """
@@ -373,7 +371,8 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
373
371
  )[0]
374
372
 
375
373
  # Estimate the target shares for the cash component
376
- total_target_shares = self.get_estimated_shares(target_cash_weight, cash_component)
374
+ with suppress(ValueError):
375
+ total_target_shares = self.get_estimated_shares(target_cash_weight, cash_component)
377
376
 
378
377
  return target_cash_weight, total_target_shares
379
378
 
@@ -428,7 +427,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
428
427
  errors = dict()
429
428
  errors_list = []
430
429
  if self.trades.exists() and self.trades.exclude(status=Trade.Status.DRAFT).exists():
431
- errors_list.append("All trades need to be draft before submitting")
430
+ errors_list.append(_("All trades need to be draft before submitting"))
432
431
  service = self.validated_trading_service
433
432
  try:
434
433
  service.is_valid(ignore_error=True)
@@ -437,7 +436,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
437
436
  # "There is no change detected in this trade proposal. Please submit at last one valid trade"
438
437
  # )
439
438
  if len(service.validated_trades) == 0:
440
- errors_list.append("There is no valid trade on this proposal")
439
+ errors_list.append(_("There is no valid trade on this proposal"))
441
440
  if service.errors:
442
441
  errors_list.extend(service.errors)
443
442
  if errors_list:
@@ -487,15 +486,17 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
487
486
  def can_approve(self):
488
487
  errors = dict()
489
488
  if not self.portfolio.can_be_rebalanced:
490
- errors["non_field_errors"] = "The portfolio does not allow manual rebalanced"
489
+ errors["non_field_errors"] = [_("The portfolio does not allow manual rebalanced")]
491
490
  if self.trades.exclude(status=Trade.Status.SUBMIT).exists():
492
- errors["non_field_errors"] = "At least one trade needs to be submitted to be able to approve this proposal"
491
+ errors["non_field_errors"] = [
492
+ _("At least one trade needs to be submitted to be able to approve this proposal")
493
+ ]
493
494
  if not self.portfolio.can_be_rebalanced:
494
- errors["portfolio"] = (
495
- "The portfolio needs to be a model portfolio in order to approve this trade proposal manually"
496
- )
495
+ errors["portfolio"] = [
496
+ [_("The portfolio needs to be a model portfolio in order to approve this trade proposal manually")]
497
+ ]
497
498
  if self.has_non_successful_checks:
498
- errors["non_field_errors"] = "The pre trades rules did not passed successfully"
499
+ errors["non_field_errors"] = [_("The pre trades rules did not passed successfully")]
499
500
  return errors
500
501
 
501
502
  @transition(
@@ -526,7 +527,9 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
526
527
  def can_deny(self):
527
528
  errors = dict()
528
529
  if self.trades.exclude(status=Trade.Status.SUBMIT).exists():
529
- errors["non_field_errors"] = "At least one trade needs to be submitted to be able to deny this proposal"
530
+ errors["non_field_errors"] = [
531
+ _("At least one trade needs to be submitted to be able to deny this proposal")
532
+ ]
530
533
  return errors
531
534
 
532
535
  @transition(
@@ -589,9 +592,9 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
589
592
  def can_revert(self):
590
593
  errors = dict()
591
594
  if not self.portfolio.can_be_rebalanced:
592
- errors["portfolio"] = (
593
- "The portfolio needs to be a model portfolio in order to revert this trade proposal manually"
594
- )
595
+ errors["portfolio"] = [
596
+ _("The portfolio needs to be a model portfolio in order to revert this trade proposal manually")
597
+ ]
595
598
  return errors
596
599
 
597
600
  # End FSM logics
@@ -20,6 +20,7 @@ from django.db.models.functions import Coalesce
20
20
  from django.db.models.signals import post_save
21
21
  from django.dispatch import receiver
22
22
  from django.utils.functional import cached_property
23
+ from django.utils.translation import gettext_lazy as _
23
24
  from django_fsm import GET_STATE, FSMField, transition
24
25
  from ordered_model.models import OrderedModel, OrderedModelManager, OrderedModelQuerySet
25
26
  from wbcore.contrib.icons import WBIcon
@@ -320,9 +321,11 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
320
321
 
321
322
  def can_execute(self):
322
323
  if not self.last_underlying_quote_price:
323
- return {"underlying_instrument": "Cannot execute a trade without a valid quote price"}
324
+ return {"underlying_instrument": [_("Cannot execute a trade without a valid quote price")]}
324
325
  if not self.portfolio.is_manageable:
325
- return {"portfolio": "The portfolio needs to be a model portfolio in order to execute this trade manually"}
326
+ return {
327
+ "portfolio": [_("The portfolio needs to be a model portfolio in order to execute this trade manually")]
328
+ }
326
329
 
327
330
  @transition(
328
331
  field=status,
@@ -498,22 +501,16 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
498
501
  self.transaction_date = self.trade_proposal.trade_date
499
502
  self.value_date = self.trade_proposal.last_effective_date
500
503
  if not self.portfolio.only_weighting:
501
- self.shares = self.trade_proposal.get_estimated_shares(self.weighting, self.underlying_instrument)
504
+ with suppress(ValueError):
505
+ self.shares = self.trade_proposal.get_estimated_shares(self.weighting, self.underlying_instrument)
502
506
 
503
507
  if not self.custodian and self.bank:
504
508
  self.custodian = Custodian.get_by_mapping(self.bank)
505
509
  if self.price is None:
506
510
  # we try to get the price if not provided directly from the underlying instrument
507
- with suppress(InstrumentPrice.DoesNotExist):
508
- self.price = self.underlying_instrument.valuations.get(date=self.value_date).net_value
509
- if self.price is not None and self.price_gross is None:
510
- self.price_gross = self.price
511
-
512
- if self.price is not None and self.shares is not None and self.total_value is None:
513
- self.total_value = self.price * self.shares
511
+ with suppress(Exception):
512
+ self.price = self.underlying_instrument.get_price(self.value_date)
514
513
 
515
- if self.price_gross is not None and self.shares is not None and self.total_value_gross is None:
516
- self.total_value_gross = self.price_gross * self.shares
517
514
  self.transaction_type = Transaction.Type.TRADE
518
515
 
519
516
  if self.transaction_subtype is None or self.trade_proposal:
@@ -13,16 +13,16 @@ class ShareMixin(models.Model):
13
13
  shares = models.DecimalField(
14
14
  max_digits=15,
15
15
  decimal_places=4,
16
- null=True,
17
- blank=True,
16
+ default=Decimal("0.0"),
18
17
  help_text="The number of shares that were traded.",
19
18
  verbose_name="Shares",
20
19
  )
21
20
  price = models.DecimalField(
22
21
  max_digits=16,
23
22
  decimal_places=4,
24
- null=True,
25
- blank=True,
23
+ default=Decimal(
24
+ "0.0"
25
+ ), # we shouldn't default to anything but we have trade with price=None. Needs to be handled carefully
26
26
  help_text="The price per share.",
27
27
  verbose_name="Price",
28
28
  )
@@ -30,12 +30,23 @@ class ShareMixin(models.Model):
30
30
  price_gross = models.DecimalField(
31
31
  max_digits=16,
32
32
  decimal_places=4,
33
- null=True,
34
- blank=True,
35
33
  help_text="The gross price per share.",
36
34
  verbose_name="Gross Price",
37
35
  )
38
36
 
37
+ def save(
38
+ self,
39
+ *args,
40
+ factor: Decimal = Decimal("1.0"),
41
+ **kwargs,
42
+ ):
43
+ if self.price_gross is None:
44
+ self.price_gross = self.price
45
+
46
+ self.total_value = self.price * self.shares * factor
47
+ self.total_value_gross = self.price_gross * self.shares * factor
48
+ super().save(*args, **kwargs)
49
+
39
50
  class Meta:
40
51
  abstract = True
41
52
 
@@ -68,14 +79,10 @@ class Transaction(ImportMixin, models.Model):
68
79
  help_text="The date that this transaction was traded.",
69
80
  )
70
81
  book_date = models.DateField(
71
- null=True,
72
- blank=True,
73
82
  verbose_name="Trade Date",
74
83
  help_text="The date that this transaction was booked.",
75
84
  )
76
85
  value_date = models.DateField(
77
- null=True,
78
- blank=True,
79
86
  verbose_name="Value Date",
80
87
  help_text="The date that this transaction was valuated.",
81
88
  )
@@ -89,17 +96,23 @@ class Transaction(ImportMixin, models.Model):
89
96
  currency_fx_rate = models.DecimalField(
90
97
  max_digits=14, decimal_places=8, default=Decimal(1.0), verbose_name="FOREX rate"
91
98
  )
92
- total_value = models.DecimalField(
93
- max_digits=20, decimal_places=4, null=True, blank=True, verbose_name="Total Value"
94
- )
95
- total_value_fx_portfolio = models.DecimalField(
96
- max_digits=20, decimal_places=4, null=True, blank=True, verbose_name="Total Value Fx Portfolio"
97
- )
98
- total_value_gross = models.DecimalField(
99
- max_digits=20, decimal_places=4, null=True, blank=True, verbose_name="Total Value Gross"
100
- )
101
- total_value_gross_fx_portfolio = models.DecimalField(
102
- max_digits=20, decimal_places=4, null=True, blank=True, verbose_name="Total Value Gross Fx Portfolio"
99
+ total_value = models.DecimalField(max_digits=20, decimal_places=4, verbose_name="Total Value")
100
+ total_value_gross = models.DecimalField(max_digits=20, decimal_places=4, verbose_name="Total Value Gross")
101
+ total_value_fx_portfolio = models.GeneratedField(
102
+ expression=models.F("currency_fx_rate") * models.F("total_value"),
103
+ output_field=models.DecimalField(
104
+ max_digits=20,
105
+ decimal_places=4,
106
+ ),
107
+ db_persist=True,
108
+ )
109
+ total_value_gross_fx_portfolio = models.GeneratedField(
110
+ expression=models.F("currency_fx_rate") * models.F("total_value_gross"),
111
+ output_field=models.DecimalField(
112
+ max_digits=20,
113
+ decimal_places=4,
114
+ ),
115
+ db_persist=True,
103
116
  )
104
117
  external_id = models.CharField(
105
118
  max_length=255,
@@ -118,28 +131,15 @@ class Transaction(ImportMixin, models.Model):
118
131
 
119
132
  if not getattr(self, "currency", None) and self.underlying_instrument:
120
133
  self.currency = self.underlying_instrument.currency
121
- if not self.currency_fx_rate:
134
+ if self.currency_fx_rate is None:
122
135
  self.currency_fx_rate = self.underlying_instrument.currency.convert(
123
136
  self.value_date, self.portfolio.currency, exact_lookup=True
124
137
  )
125
138
  if not self.transaction_type:
126
139
  self.transaction_type = self.__class__.__name__
127
- if (
128
- self.total_value is not None
129
- and self.currency_fx_rate is not None
130
- and self.total_value_fx_portfolio is None
131
- ):
132
- self.total_value_fx_portfolio = self.total_value * self.currency_fx_rate
133
-
134
- if self.total_value is not None and self.total_value_gross is None and self.total_value_gross is None:
135
- self.total_value_gross = self.total_value
136
140
 
137
- if (
138
- self.currency_fx_rate is not None
139
- and self.total_value_gross is not None
140
- and self.total_value_gross_fx_portfolio is None
141
- ):
142
- self.total_value_gross_fx_portfolio = self.total_value_gross * self.currency_fx_rate
141
+ if self.total_value_gross is None:
142
+ self.total_value_gross = self.total_value
143
143
 
144
144
  super().save(*args, **kwargs)
145
145
 
@@ -2,13 +2,12 @@ from decimal import Decimal
2
2
 
3
3
  import numpy as np
4
4
  import pandas as pd
5
- from django.db.models import Q, QuerySet
5
+ from django.db.models import QuerySet
6
6
  from wbfdm.enums import MarketData
7
7
  from wbfdm.models import (
8
8
  Classification,
9
9
  Exchange,
10
10
  Instrument,
11
- InstrumentClassificationThroughModel,
12
11
  InstrumentListThroughModel,
13
12
  )
14
13
 
@@ -31,9 +30,11 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
31
30
  target_currency=self.TARGET_CURRENCY,
32
31
  )
33
32
  )
34
- self.exchange_df = pd.DataFrame(
35
- instruments.values_list("id", "exchange"), columns=["id", "exchange"]
36
- ).set_index("id")
33
+ self.exchange_df = (
34
+ pd.DataFrame(instruments.values_list("id", "exchange"), columns=["id", "exchange"])
35
+ .set_index("id")
36
+ .replace({np.nan: None})
37
+ )
37
38
  instrument_ids = list(instruments.values_list("id", flat=True))
38
39
  try:
39
40
  self.market_cap_df = (
@@ -41,6 +42,7 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
41
42
  .set_index("instrument_id")["market_capitalization"]
42
43
  .reindex(instrument_ids)
43
44
  )
45
+ self.market_cap_df = self.market_cap_df
44
46
  except (IndexError, KeyError):
45
47
  self.market_cap_df = pd.Series(dtype="float64", index=instrument_ids)
46
48
 
@@ -59,17 +61,10 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
59
61
  if not instrument_ids:
60
62
  instrument_ids = []
61
63
  if classification_ids:
62
- classifications = set()
63
64
  for classification in Classification.objects.filter(id__in=classification_ids):
64
65
  for children in classification.get_descendants(include_self=True):
65
- classifications.add(children)
66
- instrument_ids.extend(
67
- list(
68
- InstrumentClassificationThroughModel.objects.filter(
69
- classification__in=classifications
70
- ).values_list("id", flat=True)
71
- )
72
- )
66
+ for company in children.instruments.all():
67
+ instrument_ids.append(company.get_primary_quote().id)
73
68
  if instrument_list_id:
74
69
  instrument_ids.extend(
75
70
  list(
@@ -84,8 +79,10 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
84
79
  self.portfolio.assets.filter(date=self.trade_date).values_list("underlying_instrument", flat=True)
85
80
  )
86
81
 
87
- return Instrument.objects.filter(id__in=instrument_ids).filter(
88
- (Q(delisted_date__isnull=True) | Q(delisted_date__gt=self.trade_date))
82
+ return (
83
+ Instrument.objects.filter(id__in=instrument_ids)
84
+ .filter(exchange__isnull=False)
85
+ .filter_active_at_date(self.trade_date)
89
86
  )
90
87
 
91
88
  def is_valid(self) -> bool:
@@ -85,7 +85,7 @@ class PortfolioModelSerializer(wb_serializers.ModelSerializer):
85
85
  request=request,
86
86
  )
87
87
  if user.profile.is_internal:
88
- additional_resources["instruments"] = reverse(
88
+ additional_resources["instruments_list"] = reverse(
89
89
  "wbportfolio:portfolio-instrument-list",
90
90
  args=[instance.id],
91
91
  request=request,
@@ -33,7 +33,7 @@ class TestPortfolioModel(PortfolioTestMixin):
33
33
  assert portfolio.id is not None
34
34
 
35
35
  def test_str(self, portfolio):
36
- assert str(portfolio) == f"{portfolio.id:06} ({portfolio.name})"
36
+ assert str(portfolio) == f"{portfolio.id:06}: {portfolio.name}"
37
37
 
38
38
  def test_get_assets(self, portfolio, product, cash, asset_position_factory):
39
39
  asset_position_factory.create_batch(4, portfolio=portfolio, underlying_instrument=product)
@@ -136,8 +136,8 @@ class TestMarketCapitalizationRebalancing:
136
136
 
137
137
  last_effective_date = (weekday - BDay(1)).date()
138
138
 
139
- i1 = instrument_factory()
140
- i2 = instrument_factory()
139
+ i1 = instrument_factory(inception_date=weekday)
140
+ i2 = instrument_factory(inception_date=weekday)
141
141
  instrument_price_factory.create(instrument=i1, date=weekday)
142
142
  instrument_price_factory.create(instrument=i2, date=weekday)
143
143
  return MarketCapitalizationRebalancing(portfolio, weekday, last_effective_date, instrument_ids=[i1.id, i2.id])
@@ -323,7 +323,7 @@ class CashPositionPortfolioPandasAPIView(
323
323
  val_date = datetime.strptime(date_str, "%Y-%m-%d").date()
324
324
  else:
325
325
  val_date = date.today()
326
- active_products = Product.active_objects.get_queryset_at_date(val_date)
326
+ active_products = Product.active_objects.filter_active_at_date(val_date)
327
327
  return (
328
328
  super()
329
329
  .get_queryset()
@@ -148,17 +148,6 @@ class AssetPositionInstrumentDisplayConfig(DisplayViewConfig):
148
148
  items=[dp.LegendItem(icon=WBIcon.UNFILTER.icon, label="Not Invested", value=True)],
149
149
  )
150
150
  ],
151
- formatting=[
152
- dp.Formatting(
153
- column="is_invested",
154
- formatting_rules=[
155
- dp.FormattingRule(
156
- icon=WBIcon.UNFILTER.icon,
157
- condition=("==", False),
158
- )
159
- ],
160
- )
161
- ],
162
151
  )
163
152
 
164
153
 
@@ -103,8 +103,8 @@ class PortfolioDisplayConfig(DisplayViewConfig):
103
103
  create_simple_section(
104
104
  "instruments_section",
105
105
  _("Linked Instruments"),
106
- [["instruments"]],
107
- "instruments",
106
+ [["instruments_list"]],
107
+ "instruments_list",
108
108
  collapsed=True,
109
109
  ),
110
110
  create_simple_section(
@@ -1,7 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
3
  from django.utils.translation import gettext_lazy as _
4
- from wbcore.contrib.icons import WBIcon
5
4
  from wbcore.enums import Unit
6
5
  from wbcore.metadata.configs import display as dp
7
6
  from wbcore.metadata.configs.display.formatting import Condition, Operator
@@ -27,18 +26,6 @@ class ProductDisplayConfig(DisplayViewConfig):
27
26
  label=_("Information"),
28
27
  open_by_default=False,
29
28
  children=[
30
- dp.Field(
31
- key="is_invested",
32
- label="",
33
- formatting_rules=[
34
- dp.FormattingRule(
35
- icon=WBIcon.UNFILTER.icon,
36
- condition=Condition(Operator("=="), False),
37
- ),
38
- ],
39
- width=30,
40
- show="open",
41
- ),
42
29
  dp.Field(key="name_repr", label="Name", width=250),
43
30
  dp.Field(key="parent", label="Parent"),
44
31
  dp.Field(key="isin", label="ISIN"),
@@ -267,15 +267,6 @@ class SubscriptionRedemptionDisplayConfig(TradeDisplayConfig):
267
267
  )
268
268
  ],
269
269
  ),
270
- dp.Formatting(
271
- column="pending",
272
- formatting_rules=[
273
- dp.FormattingRule(
274
- icon=WBIcon.FOLDERS_ADD.icon,
275
- condition=("==", True),
276
- )
277
- ],
278
- ),
279
270
  ],
280
271
  )
281
272
 
@@ -311,6 +302,7 @@ class TradeTradeProposalDisplayConfig(DisplayViewConfig):
311
302
  dp.Field(
312
303
  key="underlying_instrument_refinitiv_identifier_code", label="RIC", width=Unit.PIXEL(100)
313
304
  ),
305
+ dp.Field(key="underlying_instrument_instrument_type", label="Asset Class", width=Unit.PIXEL(125)),
314
306
  ],
315
307
  ),
316
308
  dp.Field(
@@ -3,6 +3,12 @@ from wbcore.metadata.configs.endpoints import EndpointViewConfig
3
3
 
4
4
 
5
5
  class TransactionEndpointConfig(EndpointViewConfig):
6
+ def get_endpoint(self, **kwargs):
7
+ return None
8
+
9
+ def get_list_endpoint(self, **kwargs):
10
+ return reverse("wbportfolio:transaction-list", request=self.request)
11
+
6
12
  def get_instance_endpoint(self, **kwargs):
7
13
  model = "{{transaction_url_type}}"
8
14
  return f"{self.request.scheme}://{self.request.get_host()}/api/portfolio/{model}/"
@@ -67,7 +67,23 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
67
67
  queryset = Portfolio.objects.all()
68
68
 
69
69
  search_fields = ("currency__key", "name")
70
- ordering_fields = search_fields
70
+ ordering_fields = (
71
+ "name",
72
+ "currency",
73
+ "hedged_currency",
74
+ "updated_at",
75
+ "initial_position_date",
76
+ "last_position_date",
77
+ "last_asset_under_management_usd",
78
+ "last_positions",
79
+ "automatic_rebalancer",
80
+ "last_trade_proposal_date",
81
+ "is_manageable",
82
+ "is_tracked",
83
+ "only_weighting",
84
+ "is_lookthrough",
85
+ "is_composition",
86
+ )
71
87
  ordering = ["name"]
72
88
 
73
89
  display_config_class = PortfolioDisplayConfig
@@ -65,16 +65,18 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
65
65
  # 2 methods to parametrize the clone button functionality
66
66
  def get_clone_button_serializer_class(self, instance):
67
67
  class CloneSerializer(wb_serializers.Serializer):
68
- trade_date = wb_serializers.DateField(default=(instance.trade_date + BDay(1)).date(), label="Trade Date")
69
- comment = wb_serializers.TextField(label="Comment")
68
+ clone_date = wb_serializers.DateField(
69
+ default=(instance.trade_date + BDay(1)).date(), label="Trade Date"
70
+ ) # we need to change the field name from the trade proposa fields, otherwise fontend conflicts
71
+ clone_comment = wb_serializers.TextField(label="Comment")
70
72
 
71
73
  return CloneSerializer
72
74
 
73
75
  def get_clone_button_instance_display(self) -> Display:
74
76
  return create_simple_display(
75
77
  [
76
- ["comment"],
77
- ["trade_date"],
78
+ ["clone_comment"],
79
+ ["clone_date"],
78
80
  ]
79
81
  )
80
82
 
@@ -367,10 +367,22 @@ class TradeTradeProposalModelViewSet(
367
367
  "trade_proposal",
368
368
  "order",
369
369
  )
370
- ordering_fields = ("target_weight", "effective_weight", "effective_shares", "target_shares", "shares", "weighting")
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
+ )
371
383
  IDENTIFIER = "wbportfolio:tradeproposal"
372
384
  search_fields = ("underlying_instrument__name",)
373
- filterset_fields = {}
385
+ filterset_fields = {"status": ["exact"]}
374
386
  queryset = Trade.objects.all()
375
387
 
376
388
  display_config_class = TradeTradeProposalDisplayConfig
@@ -497,5 +509,11 @@ class TradeTradeProposalModelViewSet(
497
509
  underlying_instrument_isin=F("underlying_instrument__isin"),
498
510
  underlying_instrument_ticker=F("underlying_instrument__ticker"),
499
511
  underlying_instrument_refinitiv_identifier_code=F("underlying_instrument__refinitiv_identifier_code"),
500
- underlying_instrument_instrument_type=F("underlying_instrument__instrument_type__short_name"),
512
+ underlying_instrument_instrument_type=Case(
513
+ When(
514
+ underlying_instrument__parent__is_security=True,
515
+ then=F("underlying_instrument__parent__instrument_type__short_name"),
516
+ ),
517
+ default=F("underlying_instrument__instrument_type__short_name"),
518
+ ),
501
519
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.49.0
3
+ Version: 1.49.2
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -58,7 +58,7 @@ wbportfolio/factories/adjustments.py,sha256=aOHXJLy7DW4k1hcRBZteEe2mKe_wQg7z0bVn
58
58
  wbportfolio/factories/assets.py,sha256=1i7xDT6bpvnYQiEJpmukWadSvS1asQC-QuUJ1PCll0U,3173
59
59
  wbportfolio/factories/claim.py,sha256=LQzz8EbIveOYpMY-JOMR8ba-taoX3u53zjBwxiydtrM,1629
60
60
  wbportfolio/factories/custodians.py,sha256=XrNcpNhE4Rbv0y159E_MbP1TAQwe9POab-_RHOHy1Uk,311
61
- wbportfolio/factories/dividends.py,sha256=QqZy-Spdjv9I-dnFAuJUaWFZ5uEuWP-bkZMZ_RMuy64,381
61
+ wbportfolio/factories/dividends.py,sha256=ET2kp0A7U1x8WhNVUn6FM1GF4jfr5NwdULBiIQkSLh0,404
62
62
  wbportfolio/factories/fees.py,sha256=2B2ebj06a0ZI1ra2-TXtW5e0aQ9sXT-Plw6CzV-3_1c,400
63
63
  wbportfolio/factories/indexes.py,sha256=sInyNARyfUNbScxr_ng1HhxtJXIsK3awFf9BF1gr9zU,440
64
64
  wbportfolio/factories/portfolio_cash_flow.py,sha256=e8LWkk5Gy0FU4HoCL9xPseCtLlVIkMrvAmey5swqcfc,727
@@ -111,11 +111,11 @@ wbportfolio/import_export/backends/wbfdm/mixin.py,sha256=JNtjgqGLson1nu_Chqb8MWy
111
111
  wbportfolio/import_export/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  wbportfolio/import_export/handlers/adjustment.py,sha256=6bdTIYFmc8_HFxcdwtnYwglMyCfAD8XrTIrEb2zWY0g,1757
113
113
  wbportfolio/import_export/handlers/asset_position.py,sha256=5wFnHcbq_zGp9rBUec_JEpzjCA0_v17VrV9F8Ps1ETs,8645
114
- wbportfolio/import_export/handlers/dividend.py,sha256=tftdVdAzNpKSSvouOtvJfzWL362HUPIC94F6Noha8CE,3998
115
- wbportfolio/import_export/handlers/fees.py,sha256=XYH752IkNGYhhhwatp8nYa1zG1-YZFDkYW15dyQgOIg,2824
116
- wbportfolio/import_export/handlers/portfolio_cash_flow.py,sha256=2ODaquuC83RfmNwmQ-8TdhiASObfIems_B1g0yqaYTs,2733
114
+ wbportfolio/import_export/handlers/dividend.py,sha256=aIayzCn0n3h17_uHcNMLQK_QndRJSU5FHaEewD8jH9U,4002
115
+ wbportfolio/import_export/handlers/fees.py,sha256=phNZ3zc_RKxryj0IE81HUdiFJ8AFiWnVyZ_xtB_x3xY,2828
116
+ wbportfolio/import_export/handlers/portfolio_cash_flow.py,sha256=W7QPNqEvvsq0RS016EAFBp1ezvc6G9Rk-hviRZh8o6Y,2737
117
117
  wbportfolio/import_export/handlers/register.py,sha256=sYyXkE8b1DPZ5monxylZn0kjxLVdNYYZR-p61dwEoDM,2271
118
- wbportfolio/import_export/handlers/trade.py,sha256=KMYnaEUGWyrft0l7OuPKHcy6WKdIr8I_wefoA8pzgjY,11297
118
+ wbportfolio/import_export/handlers/trade.py,sha256=Vp3nkeg5VvK3oTOiI0V8e0aBg5B2MySypuRx_fMd1VM,11575
119
119
  wbportfolio/import_export/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
120
  wbportfolio/import_export/parsers/default_mapping.py,sha256=KrO-X5CvQCeQoBYzFDxavoQGriyUSeI2QDx5ar_zo7A,1405
121
121
  wbportfolio/import_export/parsers/jpmorgan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -145,7 +145,7 @@ wbportfolio/import_export/parsers/natixis/valuation.py,sha256=mLjIw1GBlPPlzHJkxg
145
145
  wbportfolio/import_export/parsers/refinitiv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
146
  wbportfolio/import_export/parsers/refinitiv/adjustment.py,sha256=64AQoLOZQWn5HCLpflJ4OgQB3gCB3w_6Y4YLhcmuClg,819
147
147
  wbportfolio/import_export/parsers/sg_lux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
148
- wbportfolio/import_export/parsers/sg_lux/custodian_positions.py,sha256=pdrVQcehF1xF5fJCom78Y5kjtjYUCTM_qQ0Cjanl83Y,2479
148
+ wbportfolio/import_export/parsers/sg_lux/custodian_positions.py,sha256=hl-LqJbwojQOe0zGEH7W31i4hNTVme5L7nwXwyk6XBY,2483
149
149
  wbportfolio/import_export/parsers/sg_lux/customer_trade.py,sha256=gTEUIaxlZvXXCgYyg9FD3D3aQhffhruYmeZlaAghrK8,2617
150
150
  wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py,sha256=4KSxUGyflf3kY5BVJqMAF1aFkeSWEJegfDtLiA1BM6c,5065
151
151
  wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py,sha256=24e-vonaBAkvX-F_Gw4hLg4FBjgbpCTUbiFC_dZ-czw,2958
@@ -154,7 +154,7 @@ wbportfolio/import_export/parsers/sg_lux/equity.py,sha256=w13LAMNWReZzqmdFyXdazV
154
154
  wbportfolio/import_export/parsers/sg_lux/fees.py,sha256=xyPO9sYQyJW5w-1XhUMizY2RbH1Pr4n7LAUE-E67Yj4,1911
155
155
  wbportfolio/import_export/parsers/sg_lux/perf_fees.py,sha256=lH1gyswfefwo3Lxi0syydhJYl9EDGsrL_a6fLmpHh8w,1691
156
156
  wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py,sha256=ftLKWukUl9AcGWHQmJZnShVZpkO4rOl04PGlLcalDdQ,889
157
- wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py,sha256=wm0yTgCVYRw738y9DPRUP0ygIwCLHa29pbNzd6EUNjg,1070
157
+ wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py,sha256=2kkEOwZz1hwiPB9rlMp_vkv5azMrJUfyWl7GMaAwGUc,1074
158
158
  wbportfolio/import_export/parsers/sg_lux/registers.py,sha256=5Zaum-6XuQL8-p1MkwA0sN3z8MCymSz4nNzB8MPQDCA,6112
159
159
  wbportfolio/import_export/parsers/sg_lux/sylk.py,sha256=1ThyjneFFe_gNLDHeXQFdWIyanyktxm62P-Ta5Sm1v4,7656
160
160
  wbportfolio/import_export/parsers/sg_lux/utils.py,sha256=YzYIEDQ3CpkOuRWh2eEFRm-0SERu3SRAmx0nnqQS_og,1301
@@ -241,6 +241,7 @@ wbportfolio/migrations/0072_trade_diff_shares.py,sha256=aTKa1SbIiwmlXaFtBg-ENrSx
241
241
  wbportfolio/migrations/0073_remove_product_price_computation_and_more.py,sha256=J4puisDFwnbnfv2VLWaiCQ7ost6PCOkin9qKVQoLIWM,18725
242
242
  wbportfolio/migrations/0074_alter_rebalancer_frequency_and_more.py,sha256=o01rBj-ADgwCRtAai3e5z27alPGEzaiNxUqCwWm6peY,918
243
243
  wbportfolio/migrations/0075_portfolio_initial_position_date_and_more.py,sha256=rXRpsraVlmueAlO2UpBZV4qMf7dtPuptrhfLblZcJDo,1099
244
+ wbportfolio/migrations/0076_alter_dividendtransaction_price_and_more.py,sha256=4g3ok79nw8mTAxHFAqBAdpGKetaPAjv06YSywywt4aU,6106
244
245
  wbportfolio/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
245
246
  wbportfolio/models/__init__.py,sha256=IIS_PNRxyX2Dcvyk1bcQOUzFt0B9SPC0WlM88CXqj04,881
246
247
  wbportfolio/models/adjustments.py,sha256=osXWkJZOiansPWYPyHtl7Z121zDWi7u1YMtrBQtbHVo,10272
@@ -248,7 +249,7 @@ wbportfolio/models/asset.py,sha256=yf4vBPfN1eZlWXG9SowQwr5-goG8rO1yYDitHDLZCBs,3
248
249
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
249
250
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
250
251
  wbportfolio/models/indexes.py,sha256=iLYF2gzNzX4GLj_Nh3fybUcAQ1TslnT0wgQ6mN164QI,728
251
- wbportfolio/models/portfolio.py,sha256=dCLrGVIKqpIdQi78CF58S0GD7sSuzuHjpavKBZgTCm4,58775
252
+ wbportfolio/models/portfolio.py,sha256=KtuPMWtYoKrIOwp9T_REnOjT9CI2kjtEijF3uA0Twsc,59595
252
253
  wbportfolio/models/portfolio_cash_flow.py,sha256=2blPiXSw7dbhUVd-7LcxDBb4v0SheNOdvRK3MFYiChA,7273
253
254
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
254
255
  wbportfolio/models/portfolio_relationship.py,sha256=mMb18UMRWg9kx_9uIPkMktwORuXXLjKdgRPQQvB6fVE,5486
@@ -270,14 +271,14 @@ wbportfolio/models/reconciliations/account_reconciliation_lines.py,sha256=QP6M7h
270
271
  wbportfolio/models/reconciliations/account_reconciliations.py,sha256=_teOTNePxua9C894auy8CvdOFsmUnQiOM2_TZAHvzY4,3955
271
272
  wbportfolio/models/reconciliations/reconciliations.py,sha256=kF-BNhUoT4TCn1RIgPSkdEk1iX4NQeZlGGFd_ZulAZU,686
272
273
  wbportfolio/models/transactions/__init__.py,sha256=R-4fHylrUf3773kGSEt09vtYj3LUlia5yf7rxHXjvHA,295
273
- wbportfolio/models/transactions/claim.py,sha256=agdpGqxpO0FSzYDWV-Gv1tQY46k0LN9CY6hpqvnhPH8,25826
274
- wbportfolio/models/transactions/dividends.py,sha256=naL5xeDQfUBf5KyGt7y-tTcHL22nzZumT8DV6AaG8Bg,1064
274
+ wbportfolio/models/transactions/claim.py,sha256=hcx_tJ9luf2-s1qqsUZXtoDEuxFyty2A1rXSDkljoQo,25890
275
+ wbportfolio/models/transactions/dividends.py,sha256=92-jG8bZN9nU9oDubpu-UDH43Ri7kVjhqE_esOSmOzo,471
275
276
  wbportfolio/models/transactions/expiry.py,sha256=vnNHdcC1hf2HP4rAbmoGgOfagBYKNFytqOwzOI0MlVI,144
276
277
  wbportfolio/models/transactions/fees.py,sha256=ffvqo8I4A0l5rLi00jJ6sGot0jmnkoxaNsbDzdPLwCg,5712
277
278
  wbportfolio/models/transactions/rebalancing.py,sha256=nrBi6x6PssCtkLtOpV-2OoAHDUKnnYyrM6xH1PmnjSo,7240
278
- wbportfolio/models/transactions/trade_proposals.py,sha256=iZb0LHqWSqbWf3Rmyhb5igGGKoT3JN52O2Db8j3J_1g,26727
279
- wbportfolio/models/transactions/trades.py,sha256=zoqWsDpbUKbvdW60ytNg27kRFlH1N0rUz6mf0o6DdR4,28337
280
- wbportfolio/models/transactions/transactions.py,sha256=4THsE4xqdigZAwWKYfTNRLPJlkmAmsgE70Ribp9Lnrk,7127
279
+ wbportfolio/models/transactions/trade_proposals.py,sha256=9ggUkwzDgMKa8cC8dNZ4IHlleiGpRN0WU-oXGDSiuaw,26730
280
+ wbportfolio/models/transactions/trades.py,sha256=P9cSTEpZBsqKBvw25XY_RNctTiOBEzCvcuy0r8IkedE,28013
281
+ wbportfolio/models/transactions/transactions.py,sha256=fWoDf0TSV0L0gLUDOQpCRLzjMt1H4MUvUHGEaMsilCc,7027
281
282
  wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
282
283
  wbportfolio/pms/typing.py,sha256=b2pBWYt1E8ok-Kqm0lEFIakSnWJ6Ib57z-VX3C3gkQc,6081
283
284
  wbportfolio/pms/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -291,7 +292,7 @@ wbportfolio/rebalancing/decorators.py,sha256=JhQ2vkcIuGBTGvNmpkQerdw2-vLq-RAb0KA
291
292
  wbportfolio/rebalancing/models/__init__.py,sha256=AQjG7Tu5vlmhqncVoYOjpBKU2UIvgo9FuP2_jD2w-UI,232
292
293
  wbportfolio/rebalancing/models/composite.py,sha256=uyF1n3NAFprnAZ3Gl5pNJl0GajelqJspC2NufZ7Tf0s,1636
293
294
  wbportfolio/rebalancing/models/equally_weighted.py,sha256=U29MOHJMQMIg7Y7W_8t5K3nXjaznzt4ArIxQSiv0Xok,863
294
- wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=LvzV7a8fNbpsRahysvKbE6oNSQccWpnnne-rlDj3uzU,4928
295
+ wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=7vdr7SMNswi3Emc8RpLPJQqQw39xrznp3Ps9Pf9YEZ4,4789
295
296
  wbportfolio/rebalancing/models/model_portfolio.py,sha256=XQdvs03-0M9YUnL4DidwZC4E6k-ANCNcZ--T_aaOXTQ,1233
296
297
  wbportfolio/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
297
298
  wbportfolio/reports/monthly_position_report.py,sha256=e7BzjDd6eseUOwLwQJXKvWErQ58YnCsznHU2VtR6izM,2981
@@ -327,7 +328,7 @@ wbportfolio/serializers/portfolio_cash_flow.py,sha256=vBRZnUKq4XAqcvjyk-xMsw5v4x
327
328
  wbportfolio/serializers/portfolio_cash_targets.py,sha256=_GdsFXoBda1JHJnfZbaVlNFdnXlvGuy_FEcEZH-UThY,605
328
329
  wbportfolio/serializers/portfolio_relationship.py,sha256=UR_GIF2LHw82a8mv6L8OXYvoCp5-QWtubt33Y4mqFow,1933
329
330
  wbportfolio/serializers/portfolio_swing_pricing.py,sha256=a21st7MmcugbAO_xc90QWSeXwU1ivk-y2PWlMGKySJY,655
330
- wbportfolio/serializers/portfolios.py,sha256=ZowvB0T38tuXVQI-O9pEsPdLfG1hrW6aMZ1DBVnPbuE,7679
331
+ wbportfolio/serializers/portfolios.py,sha256=qEbrTAVepeVIPcs9XaWCDJ0ZxSQX8-M9HmRh6ky4KuY,7684
331
332
  wbportfolio/serializers/positions.py,sha256=UK9dvE1wiILw2E209InrsUhme52vfUYDDERBeKCcn_k,2415
332
333
  wbportfolio/serializers/product_group.py,sha256=klPGmAb1MvJpyiKKMRb8uYFIMm6SCZkMFQDu5EH-mkY,3680
333
334
  wbportfolio/serializers/products.py,sha256=BDMizM77Iohe7JEUncll2ET4GtU8hicQ6c4Vm0srqd4,12374
@@ -373,7 +374,7 @@ wbportfolio/tests/models/test_merge.py,sha256=sdsjiZsmR6vsUKwTa5kkvL6QTeAZqtd_EP
373
374
  wbportfolio/tests/models/test_portfolio_cash_flow.py,sha256=X8dsXexsb1b0lBiuGzu40ps_Az_1UmmKT0eo1vbXH94,5792
374
375
  wbportfolio/tests/models/test_portfolio_cash_targets.py,sha256=q8QWAwt-kKRkLC0E05GyRhF_TTQXIi8bdHjXVU0fCV0,965
375
376
  wbportfolio/tests/models/test_portfolio_swing_pricings.py,sha256=kr2AOcQkyg2pX3ULjU-o9ye-NVpjMrrfoe-DVbYCbjs,1656
376
- wbportfolio/tests/models/test_portfolios.py,sha256=DbOanYdyLEfnHqgmz0jckxnJRo4msiaaDjKWD4dQy4o,52208
377
+ wbportfolio/tests/models/test_portfolios.py,sha256=IxJy0fLkUx3m4qKzvkqixYl_0lR7TU-cQJ_DhTkh8V8,52207
377
378
  wbportfolio/tests/models/test_product_groups.py,sha256=AcdxhurV-n_bBuUsfD1GqVtwLFcs7VI2CRrwzsIUWbU,3337
378
379
  wbportfolio/tests/models/test_products.py,sha256=nBEgyUoY-4F_pfHYnAr7KXdNYvdIkSu-PWJrqp5tPHg,9482
379
380
  wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo1O-9JYxeA,3323
@@ -388,7 +389,7 @@ wbportfolio/tests/models/transactions/test_trades.py,sha256=vqvOqUY_uXvBp8YOKR0W
388
389
  wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
389
390
  wbportfolio/tests/pms/test_analytics.py,sha256=fAuY1zcXibttFpBh2GhKVyzdYfi1kz_b7SPa9xZQXY0,1086
390
391
  wbportfolio/tests/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
391
- wbportfolio/tests/rebalancing/test_models.py,sha256=mhhuIWPZF2SQHzVdotp90eIrfmnMGQNu5YTGcMDjlIY,7505
392
+ wbportfolio/tests/rebalancing/test_models.py,sha256=qyZkNG1WF1lZdYwvjUElkkNey8b5ZMGSlWpzxujCuNg,7549
392
393
  wbportfolio/tests/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
393
394
  wbportfolio/tests/serializers/test_claims.py,sha256=vQrg73xQXRFEgvx3KI9ivFre_wpBFzdO0p0J13PkvdY,582
394
395
  wbportfolio/tests/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -399,7 +400,7 @@ wbportfolio/tests/viewsets/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
399
400
  wbportfolio/tests/viewsets/transactions/test_claims.py,sha256=QEZfMAW07dyoZ63t2umSwGOqvaTULfYfbN_F4ZoSAcw,6368
400
401
  wbportfolio/viewsets/__init__.py,sha256=3kUaQ66ybvROwejd3bEcSt4XKzfOlPDaeoStMvlz7qY,2294
401
402
  wbportfolio/viewsets/adjustments.py,sha256=5hWCjxSgUIsrPOmJKoDYK3gywdMTI0aYDorEj1FXRVc,1429
402
- wbportfolio/viewsets/assets.py,sha256=vwfrJaws7aVOUd-4FTlZVahpiMGgIEt1rcUumvWIKss,22734
403
+ wbportfolio/viewsets/assets.py,sha256=CrAMuyJDyUcSZlckrhy5D6-vVRXD7I4EG9C3ZKA5Z08,22735
403
404
  wbportfolio/viewsets/assets_and_net_new_money_progression.py,sha256=Jl4vEQP4N2OFL5IGBXoKcj-0qaPviU0I8npvQLw4Io0,4464
404
405
  wbportfolio/viewsets/custodians.py,sha256=CTFqkqVP1R3AV7lhdvcdICxB5DfwDYCyikNSI5kbYEo,2322
405
406
  wbportfolio/viewsets/esg.py,sha256=27MxxdXQH3Cq_1UEYmcrF7htUOg6i81fUpbVQXAAKJI,6985
@@ -408,7 +409,7 @@ wbportfolio/viewsets/portfolio_cash_flow.py,sha256=jkBfdZRQ3KsxGMJpltRjmdrZ2qEFJ
408
409
  wbportfolio/viewsets/portfolio_cash_targets.py,sha256=CvHlrDE8qnnnfRpTYnFu-Uu15MDbF5d5gTmEKth2S24,322
409
410
  wbportfolio/viewsets/portfolio_relationship.py,sha256=RGyvxd8NfFEs8YdqEvVD3VbrISvAO5UtCTlocSIuWQw,2109
410
411
  wbportfolio/viewsets/portfolio_swing_pricing.py,sha256=-57l3WLQZRslIV67OT0ucHE5JXTtTtLvd3t7MppdVn8,357
411
- wbportfolio/viewsets/portfolios.py,sha256=eUty1gVN0hLKMmW0IGe_l8u6CFSTJW9ruSPlZbZnU7E,14050
412
+ wbportfolio/viewsets/portfolios.py,sha256=1POzE9jrt2iLVMnIY_BWDr0A_zpOlO3Z8tM80TaXkhk,14454
412
413
  wbportfolio/viewsets/positions.py,sha256=MDf_0x9La2qE6qjaIqBtfV5VC0RfJ1chZIim45Emk10,13198
413
414
  wbportfolio/viewsets/product_groups.py,sha256=YvmuXPPy98K1J_rz6YPsx9gNK-tCS2P-wc1uRYgfyo0,2399
414
415
  wbportfolio/viewsets/product_performance.py,sha256=dRfRgifjGS1RgZSu9uJRM0SmB7eLnNUkPuqARMO4gyo,28371
@@ -436,24 +437,24 @@ wbportfolio/viewsets/configs/buttons/trade_proposals.py,sha256=eZBfYk5ZhancCVcu7
436
437
  wbportfolio/viewsets/configs/buttons/trades.py,sha256=X2B1l0iEIdHb3ZMf9rLVoiX_H8lSyLD12wopgumOIX4,2318
437
438
  wbportfolio/viewsets/configs/display/__init__.py,sha256=SmazY-YEp-Xf8G08Uz1-CzePZRCRtHrziRMIYYIGpCk,2176
438
439
  wbportfolio/viewsets/configs/display/adjustments.py,sha256=jIOEc23OCYBguLaZRlZxC916kocYT35ZV9Jsiocs9nk,3334
439
- wbportfolio/viewsets/configs/display/assets.py,sha256=8yLAXyrZLl7L1m1rhBnkh4uaqVpSQJhRVkcM7_AHBsc,10943
440
+ wbportfolio/viewsets/configs/display/assets.py,sha256=SkWwzvrn4gOVbCThGJMV63V-iJh517qfUs4f4Wt3dBA,10573
440
441
  wbportfolio/viewsets/configs/display/claim.py,sha256=MRtEdxWjsDjt1k6O-ltM-SpPmHWBk85sCycsRgzd9jI,12041
441
442
  wbportfolio/viewsets/configs/display/custodians.py,sha256=R-tnktfY48K-8Zz-Fg_M6IXKHv42J1ZeSpom7lvYVS8,801
442
443
  wbportfolio/viewsets/configs/display/esg.py,sha256=W8uetCPN2TjHtU2kvQjKOmkq7uoaYvzX2iLN6Nz78Pk,3111
443
444
  wbportfolio/viewsets/configs/display/fees.py,sha256=_8HDJADh5bA5VMVPkhKObPmNNdPHWR8Oz23PEgd04F0,5834
444
445
  wbportfolio/viewsets/configs/display/portfolio_cash_flow.py,sha256=CS_UGhKZSXnX-0SZ0RXzbG1WMJm6NX3GUljwIa4RWBk,5025
445
446
  wbportfolio/viewsets/configs/display/portfolio_relationship.py,sha256=8DvsYWFCX29qj5wUf1wCtjlPwzKRd_E7JDuo_CopaJ0,1294
446
- wbportfolio/viewsets/configs/display/portfolios.py,sha256=ke60jc2yUjEHjkyt4yruM3LXnKC63oO2RMmaj_4ljks,10716
447
+ wbportfolio/viewsets/configs/display/portfolios.py,sha256=HFUFYajD7l-7WhwsxEeBp8QvJ5ZZY03EA8Am-DQ6f6w,10726
447
448
  wbportfolio/viewsets/configs/display/positions.py,sha256=yolWLxzGPIpSQSiVhVQChURqbomPt5kSjkYrmXT1Mik,3123
448
449
  wbportfolio/viewsets/configs/display/product_groups.py,sha256=PwI-A0_ofShT2pub9-C1HqreiqpHxKMHd51JYwEzvbM,2500
449
450
  wbportfolio/viewsets/configs/display/product_performance.py,sha256=6Mme48JBn_okwClR44dBK2OK26ejvdasDvBa5DI33_0,10070
450
- wbportfolio/viewsets/configs/display/products.py,sha256=KRDAaDIBYFkC4mYgiBR0_7E-jK8DwxjNsQuUjf_p_GE,11251
451
+ wbportfolio/viewsets/configs/display/products.py,sha256=WraPeMIS87Vd3wstA4Vt3zi41vgLUxK5eoqCIQLyr8k,10679
451
452
  wbportfolio/viewsets/configs/display/rebalancing.py,sha256=yw9X1Nf2-V_KP_mCX4pVKnJSjSstHMoocMkyO73KpkU,1112
452
453
  wbportfolio/viewsets/configs/display/reconciliations.py,sha256=YvMAuwmpX0HExvGsuf5UvcRQxe4eMo1iyNJX68GGC_k,6021
453
454
  wbportfolio/viewsets/configs/display/registers.py,sha256=1np75exIk5rfct6UkVN_RnfJ9ozvIkcWJgFV4_4rJns,3182
454
455
  wbportfolio/viewsets/configs/display/roles.py,sha256=SFUyCdxSlHZ3NsMrJmpVBSlg-XKGaEFteV89nyLMMAQ,1815
455
456
  wbportfolio/viewsets/configs/display/trade_proposals.py,sha256=sRLSUjKlarBhnTwg7tX_Juldor3beswJlyvZfFPvNEk,4315
456
- wbportfolio/viewsets/configs/display/trades.py,sha256=_J60o7s1gQ-2Jg3JvWLDWgfsobIy60kzYWdJ8ea0Fn8,16823
457
+ wbportfolio/viewsets/configs/display/trades.py,sha256=3rvIYMCe3IOyDw5HTrXcPvstdDg6Zt13oskh1yKQyRY,16613
457
458
  wbportfolio/viewsets/configs/display/transactions.py,sha256=DOM3eV1DxBwX6Iiw3C2sJamWh6A_3ZSYC9447Jc3Wmo,2586
458
459
  wbportfolio/viewsets/configs/endpoints/__init__.py,sha256=E13AYY3CIW4CZtmqwBVMPDYA5zNyKJeRZtiXKtad68Y,2871
459
460
  wbportfolio/viewsets/configs/endpoints/adjustments.py,sha256=9CcnfNuFcxsZ8YvfUitSeyCvpLxY-jU-gIw3GG0mIp4,697
@@ -473,7 +474,7 @@ wbportfolio/viewsets/configs/endpoints/reconciliations.py,sha256=xriq1KSctxQa6AE
473
474
  wbportfolio/viewsets/configs/endpoints/roles.py,sha256=5Nwi2sdUYc85YL0wJ7SUtt87ZBTOVdFnEexUgVrmcX4,367
474
475
  wbportfolio/viewsets/configs/endpoints/trade_proposals.py,sha256=sno7SOHWjKvvF_YdgYx5KthntM--F3DX-9hsrY5vRTM,783
475
476
  wbportfolio/viewsets/configs/endpoints/trades.py,sha256=5NKMSYaGWDd8OcV16HLRSlAgYYXObLikLf2w4ix2DoE,3483
476
- wbportfolio/viewsets/configs/endpoints/transactions.py,sha256=HQDuQ_wrnkCGTNhnx1zKsPifcDCnlpaOD2NYYiNYd54,633
477
+ wbportfolio/viewsets/configs/endpoints/transactions.py,sha256=6u2GWm67o4g1PoAtZXLdLyu3J4qhyOA1dm9Otu1i9f8,813
477
478
  wbportfolio/viewsets/configs/menu/__init__.py,sha256=gzUpv5f8FAK1Bdhn0AhcMLJR3r9RXHRXcHg21vuvGxs,1184
478
479
  wbportfolio/viewsets/configs/menu/adjustments.py,sha256=sQ25Jp2N8nsLSUOEJTJUBE0Y7bIPHK4Y6LE0e1OzX38,316
479
480
  wbportfolio/viewsets/configs/menu/assets.py,sha256=FkddF1_mu01e1mnkJkXPQcE0izI0Q3wU7VBFIkeT-Qw,340
@@ -517,10 +518,10 @@ wbportfolio/viewsets/transactions/claim.py,sha256=m_Fy4J_QZSve1VlR_sPQrVBDopgCqq
517
518
  wbportfolio/viewsets/transactions/fees.py,sha256=7VUXIogmRrXCz_D9tvDiiTae0t5j09W9zPUzxXzBGTE,7031
518
519
  wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
519
520
  wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQVTN2MH1kG_yCeVkmyK8k,1263
520
- wbportfolio/viewsets/transactions/trade_proposals.py,sha256=nZsomopPfeLg1owPpOdIbhdHfCFa3-Qh-uyqoSATOjw,5821
521
- wbportfolio/viewsets/transactions/trades.py,sha256=2w6c5ExZNmyMBGTWR3_3SEuuJ727AjKr_HgZE3sDt1Y,20950
521
+ wbportfolio/viewsets/transactions/trade_proposals.py,sha256=iQpC_Thbj56SmM05vPRsF1JZguGBDaTUH3I-_iCHCV0,5958
522
+ wbportfolio/viewsets/transactions/trades.py,sha256=xeEzx7GP34aBNPlDmiUmT86labsbb8_f1U2RCN1Jatg,21494
522
523
  wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
523
- wbportfolio-1.49.0.dist-info/METADATA,sha256=4ie6HSvXOoA_xHCciE716QeJG61w5Y93BDzIYvuOeVw,734
524
- wbportfolio-1.49.0.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
525
- wbportfolio-1.49.0.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
526
- wbportfolio-1.49.0.dist-info/RECORD,,
524
+ wbportfolio-1.49.2.dist-info/METADATA,sha256=cFNyn_eGy7Dwxdfb_oqZH6Z2zj79fYUOsKj6cTy5XNo,734
525
+ wbportfolio-1.49.2.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
+ wbportfolio-1.49.2.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
+ wbportfolio-1.49.2.dist-info/RECORD,,