wbportfolio 1.52.1__py2.py3-none-any.whl → 1.52.2rc0__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 (82) hide show
  1. wbportfolio/admin/__init__.py +1 -1
  2. wbportfolio/admin/transactions/__init__.py +0 -1
  3. wbportfolio/admin/transactions/dividends.py +40 -4
  4. wbportfolio/admin/transactions/fees.py +24 -14
  5. wbportfolio/admin/transactions/trades.py +34 -12
  6. wbportfolio/defaults/fees/default.py +7 -15
  7. wbportfolio/factories/__init__.py +0 -1
  8. wbportfolio/factories/dividends.py +8 -3
  9. wbportfolio/factories/fees.py +8 -4
  10. wbportfolio/factories/trades.py +10 -3
  11. wbportfolio/filters/transactions/__init__.py +1 -2
  12. wbportfolio/filters/transactions/fees.py +5 -10
  13. wbportfolio/filters/transactions/trades.py +17 -8
  14. wbportfolio/filters/transactions/utils.py +42 -0
  15. wbportfolio/import_export/handlers/dividend.py +7 -7
  16. wbportfolio/import_export/handlers/fees.py +11 -21
  17. wbportfolio/import_export/handlers/trade.py +5 -7
  18. wbportfolio/import_export/parsers/jpmorgan/fees.py +2 -2
  19. wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
  20. wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
  21. wbportfolio/import_export/parsers/leonteq/trade.py +0 -5
  22. wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
  23. wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
  24. wbportfolio/import_export/parsers/natixis/fees.py +7 -9
  25. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
  26. wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
  27. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
  28. wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
  29. wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
  30. wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
  31. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
  32. wbportfolio/import_export/parsers/vontobel/management_fees.py +7 -7
  33. wbportfolio/import_export/parsers/vontobel/performance_fees.py +3 -3
  34. wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
  35. wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
  36. wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
  37. wbportfolio/models/mixins/liquidity_stress_test.py +3 -3
  38. wbportfolio/models/transactions/__init__.py +0 -2
  39. wbportfolio/models/transactions/claim.py +1 -1
  40. wbportfolio/models/transactions/dividends.py +41 -5
  41. wbportfolio/models/transactions/fees.py +55 -22
  42. wbportfolio/models/transactions/trade_proposals.py +25 -3
  43. wbportfolio/models/transactions/trades.py +111 -50
  44. wbportfolio/models/transactions/transactions.py +60 -156
  45. wbportfolio/serializers/signals.py +15 -10
  46. wbportfolio/serializers/transactions/__init__.py +0 -5
  47. wbportfolio/serializers/transactions/dividends.py +37 -9
  48. wbportfolio/serializers/transactions/fees.py +39 -10
  49. wbportfolio/serializers/transactions/trades.py +56 -16
  50. wbportfolio/tasks.py +2 -2
  51. wbportfolio/tests/conftest.py +2 -8
  52. wbportfolio/tests/models/test_imports.py +2 -7
  53. wbportfolio/tests/models/transactions/test_fees.py +7 -13
  54. wbportfolio/tests/models/transactions/test_trade_proposals.py +4 -2
  55. wbportfolio/urls.py +3 -6
  56. wbportfolio/viewsets/configs/buttons/mixins.py +2 -2
  57. wbportfolio/viewsets/configs/display/__init__.py +2 -3
  58. wbportfolio/viewsets/configs/display/fees.py +3 -3
  59. wbportfolio/viewsets/configs/endpoints/__init__.py +3 -4
  60. wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
  61. wbportfolio/viewsets/configs/menu/__init__.py +0 -1
  62. wbportfolio/viewsets/configs/titles/__init__.py +2 -3
  63. wbportfolio/viewsets/configs/titles/fees.py +4 -8
  64. wbportfolio/viewsets/mixins.py +5 -1
  65. wbportfolio/viewsets/products.py +6 -6
  66. wbportfolio/viewsets/transactions/__init__.py +2 -7
  67. wbportfolio/viewsets/transactions/fees.py +22 -22
  68. {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/METADATA +1 -1
  69. {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/RECORD +71 -80
  70. wbportfolio/admin/transactions/transactions.py +0 -38
  71. wbportfolio/factories/transactions.py +0 -22
  72. wbportfolio/filters/transactions/transactions.py +0 -99
  73. wbportfolio/models/transactions/expiry.py +0 -7
  74. wbportfolio/serializers/transactions/expiry.py +0 -18
  75. wbportfolio/serializers/transactions/transactions.py +0 -85
  76. wbportfolio/viewsets/configs/display/transactions.py +0 -55
  77. wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
  78. wbportfolio/viewsets/configs/menu/transactions.py +0 -9
  79. wbportfolio/viewsets/configs/titles/transactions.py +0 -9
  80. wbportfolio/viewsets/transactions/transactions.py +0 -122
  81. {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/WHEEL +0 -0
  82. {wbportfolio-1.52.1.dist-info → wbportfolio-1.52.2rc0.dist-info}/licenses/LICENSE +0 -0
@@ -1,105 +1,78 @@
1
1
  from decimal import Decimal
2
2
 
3
- from django.apps import apps
4
3
  from django.db import models
5
- from django.dispatch import receiver
6
- from wbcore.contrib.io.mixins import ImportMixin
7
- from wbcore.signals import pre_merge
8
- from wbfdm.models.instruments.instruments import Instrument
9
- from wbfdm.signals import add_instrument_to_investable_universe
10
4
 
11
5
 
12
- class ShareMixin(models.Model):
13
- shares = models.DecimalField(
14
- max_digits=15,
15
- decimal_places=4,
16
- default=Decimal("0.0"),
17
- help_text="The number of shares that were traded.",
18
- verbose_name="Shares",
19
- )
20
- price = models.DecimalField(
21
- max_digits=16,
22
- decimal_places=4,
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
- help_text="The price per share.",
27
- verbose_name="Price",
28
- )
29
-
30
- price_gross = models.DecimalField(
31
- max_digits=16,
32
- decimal_places=4,
33
- help_text="The gross price per share.",
34
- verbose_name="Gross Price",
6
+ class TransactionMixin(models.Model):
7
+ value_date = models.DateField(
8
+ verbose_name="Value Date",
9
+ help_text="The date that this transaction was valuated/paid.",
35
10
  )
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
-
50
- class Meta:
51
- abstract = True
52
-
53
-
54
- class Transaction(ImportMixin, models.Model):
55
- class Type(models.TextChoices):
56
- # Standart Asset Types
57
- TRADE = "Trade", "Trade"
58
- DIVIDEND_TRANSACTION = "DividendTransaction", "Dividend Transaction"
59
- EXPIRY = "Expiry", "Expiry"
60
- FEES = "Fees", "Fees"
61
-
62
- transaction_type = models.CharField(max_length=255, verbose_name="Type", choices=Type.choices, default=Type.TRADE)
63
-
64
11
  portfolio = models.ForeignKey(
65
- "wbportfolio.Portfolio", related_name="transactions", on_delete=models.PROTECT, verbose_name="Portfolio"
12
+ "wbportfolio.Portfolio", related_name="%(class)ss", on_delete=models.PROTECT, verbose_name="Portfolio"
66
13
  )
67
-
68
14
  underlying_instrument = models.ForeignKey(
69
15
  to="wbfdm.Instrument",
70
- related_name="transactions",
16
+ related_name="%(class)ss",
71
17
  limit_choices_to=models.Q(children__isnull=True),
72
18
  on_delete=models.PROTECT,
73
19
  verbose_name="Underlying Instrument",
74
20
  help_text="The instrument that is this transaction.",
75
21
  )
76
-
77
- transaction_date = models.DateField(
78
- verbose_name="Trade Date",
79
- help_text="The date that this transaction was traded.",
80
- )
81
- book_date = models.DateField(
82
- verbose_name="Trade Date",
83
- help_text="The date that this transaction was booked.",
84
- )
85
- value_date = models.DateField(
86
- verbose_name="Value Date",
87
- help_text="The date that this transaction was valuated.",
88
- )
89
-
90
22
  currency = models.ForeignKey(
91
23
  "currency.Currency",
92
- related_name="transactions",
24
+ related_name="%(class)ss",
93
25
  on_delete=models.PROTECT,
94
26
  verbose_name="Currency",
95
27
  )
96
28
  currency_fx_rate = models.DecimalField(
97
29
  max_digits=14, decimal_places=8, default=Decimal(1.0), verbose_name="FOREX rate"
98
30
  )
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")
31
+ price = models.DecimalField(
32
+ max_digits=16,
33
+ decimal_places=4,
34
+ help_text="The price per share.",
35
+ verbose_name="Price",
36
+ )
37
+ price_gross = models.DecimalField(
38
+ max_digits=16,
39
+ decimal_places=4,
40
+ help_text="The gross price per share.",
41
+ verbose_name="Gross Price",
42
+ )
43
+ shares = models.DecimalField(
44
+ max_digits=15,
45
+ decimal_places=4,
46
+ default=Decimal("0.0"),
47
+ help_text="The number of shares held at record date, used to calculate the dividend",
48
+ verbose_name="Shares / Quantity",
49
+ )
50
+ fees = models.GeneratedField(
51
+ expression=models.F("price_gross") - models.F("price"),
52
+ output_field=models.DecimalField(
53
+ max_digits=20,
54
+ decimal_places=4,
55
+ ),
56
+ db_persist=True,
57
+ )
58
+ total_value_gross = models.GeneratedField(
59
+ expression=models.F("price_gross") * models.F("shares"),
60
+ output_field=models.DecimalField(
61
+ max_digits=20,
62
+ decimal_places=4,
63
+ ),
64
+ db_persist=True,
65
+ )
66
+ total_value = models.GeneratedField(
67
+ expression=models.F("price") * models.F("shares"),
68
+ output_field=models.DecimalField(
69
+ max_digits=20,
70
+ decimal_places=4,
71
+ ),
72
+ db_persist=True,
73
+ )
101
74
  total_value_fx_portfolio = models.GeneratedField(
102
- expression=models.F("currency_fx_rate") * models.F("total_value"),
75
+ expression=models.F("currency_fx_rate") * models.F("price") * models.F("shares"),
103
76
  output_field=models.DecimalField(
104
77
  max_digits=20,
105
78
  decimal_places=4,
@@ -107,99 +80,30 @@ class Transaction(ImportMixin, models.Model):
107
80
  db_persist=True,
108
81
  )
109
82
  total_value_gross_fx_portfolio = models.GeneratedField(
110
- expression=models.F("currency_fx_rate") * models.F("total_value_gross"),
83
+ expression=models.F("currency_fx_rate") * models.F("price_gross") * models.F("shares"),
111
84
  output_field=models.DecimalField(
112
85
  max_digits=20,
113
86
  decimal_places=4,
114
87
  ),
115
88
  db_persist=True,
116
89
  )
117
- external_id = models.CharField(
118
- max_length=255,
119
- null=True,
120
- blank=True,
121
- help_text="An external identifier that was supplied.",
122
- verbose_name="External Identifier",
123
- )
90
+
124
91
  comment = models.TextField(default="", verbose_name="Comment", blank=True)
92
+ created = models.DateTimeField(auto_now_add=True)
93
+ updated = models.DateTimeField(auto_now=True)
125
94
 
126
95
  def save(self, *args, **kwargs):
127
- if not self.value_date:
128
- self.value_date = self.transaction_date
129
- if not self.book_date:
130
- self.book_date = self.transaction_date
131
-
132
96
  if not getattr(self, "currency", None) and self.underlying_instrument:
133
97
  self.currency = self.underlying_instrument.currency
134
98
  if self.currency_fx_rate is None:
135
99
  self.currency_fx_rate = self.underlying_instrument.currency.convert(
136
100
  self.value_date, self.portfolio.currency, exact_lookup=True
137
101
  )
138
- if not self.transaction_type:
139
- self.transaction_type = self.__class__.__name__
140
-
141
- if self.total_value_gross is None:
142
- self.total_value_gross = self.total_value
143
-
102
+ if self.price is not None and self.price_gross is None:
103
+ self.price_gross = self.price
104
+ elif self.price_gross is not None and self.price is None:
105
+ self.price = self.price_gross
144
106
  super().save(*args, **kwargs)
145
107
 
146
- def __str__(self):
147
- return f"{self.total_value} - {self.transaction_date:%d.%m.%Y} : {str(self.underlying_instrument)} (in {str(self.portfolio)})"
148
-
149
- def get_casted_model(self):
150
- return apps.get_model(app_label="wbportfolio", model_name=self.transaction_type)
151
-
152
- def get_casted_transaction(self) -> models.Model:
153
- """
154
- Cast the asset into its child representative
155
- """
156
- model = self.get_casted_model()
157
- return model.objects.get(pk=self.pk)
158
-
159
108
  class Meta:
160
- verbose_name = "Transaction"
161
- verbose_name_plural = "Transactions"
162
- indexes = [
163
- models.Index(fields=["underlying_instrument", "transaction_date"]),
164
- # models.Index(fields=["date", "underlying_instrument"]),
165
- ]
166
-
167
- objects = models.Manager()
168
-
169
- @classmethod
170
- def get_representation_value_key(cls):
171
- return "id"
172
-
173
- @classmethod
174
- def get_representation_label_key(cls):
175
- return "{{total_value}}{{transaction_date}}"
176
-
177
- @classmethod
178
- def get_endpoint_basename(cls):
179
- return "wbportfolio:transaction"
180
-
181
-
182
- @receiver(pre_merge, sender="wbfdm.Instrument")
183
- def pre_merge_instrument(sender: models.Model, merged_object: "Instrument", main_object: "Instrument", **kwargs):
184
- """
185
- Simply reassign the transactions linked to the merged instrument to the main instrument
186
- """
187
- merged_object.transactions.update(underlying_instrument=main_object)
188
-
189
-
190
- @receiver(add_instrument_to_investable_universe, sender="wbfdm.Instrument")
191
- def add_instrument_to_investable_universe_from_transactions(sender: models.Model, **kwargs) -> list[int]:
192
- """
193
- register all instrument linked to assets as within the investible universe
194
- """
195
- return list(
196
- (
197
- Instrument.objects.annotate(
198
- transaction_exists=models.Exists(
199
- Transaction.objects.filter(underlying_instrument=models.OuterRef("pk"))
200
- )
201
- ).filter(transaction_exists=True)
202
- )
203
- .distinct()
204
- .values_list("id", flat=True)
205
- )
109
+ abstract = True
@@ -84,7 +84,7 @@ def transactions_resources(sender, serializer, instance, request, user, **kwargs
84
84
  additional_resources = dict()
85
85
  portfolio = instance.primary_portfolio
86
86
  if portfolio:
87
- if portfolio.transactions.exists() and user.profile.is_internal:
87
+ if portfolio.trades.exists() and user.profile.is_internal:
88
88
  additional_resources["portfolio_subscriptionsredemptions"] = (
89
89
  f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=True'
90
90
  )
@@ -92,16 +92,13 @@ def transactions_resources(sender, serializer, instance, request, user, **kwargs
92
92
  f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=False'
93
93
  )
94
94
 
95
- additional_resources["portfolio_transactions"] = reverse(
96
- "wbportfolio:portfolio-transaction-list", args=[portfolio.id], request=request
95
+ if instance.fees.exists() and PortfolioRole.is_portfolio_manager(user.profile, portfolio=portfolio):
96
+ additional_resources["product_fees"] = reverse(
97
+ "wbportfolio:product-fees-list", args=[portfolio.id], request=request
98
+ )
99
+ additional_resources["product_aggregatedfees"] = reverse(
100
+ "wbportfolio:product-feesaggregated-list", args=[portfolio.id], request=request
97
101
  )
98
- if PortfolioRole.is_portfolio_manager(user.profile, portfolio=portfolio):
99
- additional_resources["portfolio_fees"] = reverse(
100
- "wbportfolio:portfolio-fees-list", args=[portfolio.id], request=request
101
- )
102
- additional_resources["portfolio_aggregatedfees"] = reverse(
103
- "wbportfolio:portfolio-feesaggregated-list", args=[portfolio.id], request=request
104
- )
105
102
 
106
103
  return additional_resources
107
104
 
@@ -130,6 +127,14 @@ def product_resources(sender, serializer, instance, request, user, **kwargs):
130
127
  args=[instance.id],
131
128
  request=request,
132
129
  )
130
+ if instance.fees.exists() and PortfolioRole.is_portfolio_manager(user.profile, instrument=instance):
131
+ additional_resources["product_fees"] = reverse(
132
+ "wbportfolio:product-fees-list", args=[instance.id], request=request
133
+ )
134
+ additional_resources["product_aggregatedfees"] = reverse(
135
+ "wbportfolio:product-feesaggregated-list", args=[instance.id], request=request
136
+ )
137
+
133
138
  return additional_resources
134
139
 
135
140
 
@@ -9,7 +9,6 @@ from .claim import (
9
9
  NegativeTermimalAccountPerProductModelSerializer,
10
10
  )
11
11
  from .dividends import DividendModelSerializer, DividendRepresentationSerializer
12
- from .expiry import ExpiryModelSerializer, ExpiryRepresentationSerializer
13
12
  from .fees import FeesModelSerializer, FeesRepresentationSerializer
14
13
  from .trades import (
15
14
  TradeModelSerializer,
@@ -18,7 +17,3 @@ from .trades import (
18
17
  TradeTradeProposalModelSerializer,
19
18
  ReadOnlyTradeTradeProposalModelSerializer,
20
19
  )
21
- from .transactions import (
22
- TransactionModelSerializer,
23
- TransactionRepresentationSerializer,
24
- )
@@ -1,18 +1,46 @@
1
- from wbportfolio.models import DividendTransaction
1
+ from wbcore import serializers as wb_serializers
2
+ from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
3
+ from wbfdm.serializers import InvestableUniverseRepresentationSerializer
2
4
 
3
- from .transactions import (
4
- TransactionModelSerializer,
5
- TransactionRepresentationSerializer,
6
- )
5
+ from wbportfolio.models import DividendTransaction
6
+ from wbportfolio.serializers import PortfolioRepresentationSerializer
7
7
 
8
8
 
9
- class DividendRepresentationSerializer(TransactionRepresentationSerializer):
9
+ class DividendRepresentationSerializer(wb_serializers.RepresentationSerializer):
10
10
  class Meta:
11
11
  model = DividendTransaction
12
- fields = TransactionRepresentationSerializer.Meta.fields
12
+ fields = ("id", "value_date")
13
+
14
+
15
+ class DividendModelSerializer(wb_serializers.ModelSerializer):
16
+ _portfolio = PortfolioRepresentationSerializer(source="portfolio")
17
+ _underlying_instrument = InvestableUniverseRepresentationSerializer(source="underlying_instrument")
18
+ _currency = CurrencyRepresentationSerializer(source="currency")
13
19
 
20
+ total_value = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True, required=False)
21
+ total_value_fx_portfolio = wb_serializers.DecimalField(
22
+ max_digits=14, decimal_places=2, read_only=True, required=False
23
+ )
24
+ total_value_gross = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True, required=False)
25
+ total_value_gross_fx_portfolio = wb_serializers.DecimalField(
26
+ max_digits=14, decimal_places=2, read_only=True, required=False
27
+ )
14
28
 
15
- class DividendModelSerializer(TransactionModelSerializer):
16
29
  class Meta:
17
30
  model = DividendTransaction
18
- fields = TransactionModelSerializer.Meta.fields
31
+ fields = (
32
+ "id",
33
+ "value_date",
34
+ "record_date",
35
+ "ex_date",
36
+ "portfolio",
37
+ "underlying_instrument",
38
+ "currency",
39
+ "_portfolio",
40
+ "_underlying_instrument",
41
+ "_currency",
42
+ "total_value",
43
+ "total_value_fx_portfolio",
44
+ "total_value_gross",
45
+ "total_value_gross_fx_portfolio",
46
+ )
@@ -1,20 +1,28 @@
1
1
  from wbcore import serializers as wb_serializers
2
+ from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
2
3
 
3
4
  from wbportfolio.models import Fees
5
+ from wbportfolio.serializers.products import ProductRepresentationSerializer
4
6
 
5
- from .transactions import (
6
- TransactionModelSerializer,
7
- TransactionRepresentationSerializer,
8
- )
9
7
 
8
+ class FeesRepresentationSerializer(wb_serializers.RepresentationSerializer):
9
+ _detail = wb_serializers.HyperlinkField(reverse_name="wbportfolio:fees-detail")
10
10
 
11
- class FeesRepresentationSerializer(TransactionRepresentationSerializer):
12
11
  class Meta:
13
12
  model = Fees
14
- fields = ("transaction_subtype",) + TransactionRepresentationSerializer.Meta.fields
13
+ fields = ("transaction_subtype", "total_value", "_detail")
15
14
 
16
15
 
17
- class FeesModelSerializer(TransactionModelSerializer):
16
+ class FeesModelSerializer(wb_serializers.ModelSerializer):
17
+ _product = ProductRepresentationSerializer(source="product")
18
+ _currency = CurrencyRepresentationSerializer(source="currency")
19
+ total_value_fx_portfolio = wb_serializers.DecimalField(
20
+ max_digits=14, decimal_places=2, read_only=True, required=False
21
+ )
22
+ total_value_gross_fx_portfolio = wb_serializers.DecimalField(
23
+ max_digits=14, decimal_places=2, read_only=True, required=False
24
+ )
25
+
18
26
  @wb_serializers.register_resource()
19
27
  def additional_resources(self, instance, request, user):
20
28
  if (view := request.parser_context.get("view")) and view.is_manager and instance.import_source:
@@ -23,11 +31,32 @@ class FeesModelSerializer(TransactionModelSerializer):
23
31
 
24
32
  class Meta:
25
33
  model = Fees
26
- decorators = TransactionModelSerializer.Meta.decorators
34
+ decorators = {
35
+ "total_value": wb_serializers.decorator(
36
+ decorator_type="text", position="left", value="{{_currency.symbol}}"
37
+ ),
38
+ "total_value_fx_portfolio": wb_serializers.decorator(
39
+ position="left", value="{{_portfolio.currency_symbol}}"
40
+ ),
41
+ "total_value_gross": wb_serializers.decorator(
42
+ decorator_type="text", position="left", value="{{_currency.symbol}}"
43
+ ),
44
+ "total_value_gross_fx_portfolio": wb_serializers.decorator(
45
+ position="left", value="{{_portfolio.currency_symbol}}"
46
+ ),
47
+ }
27
48
  fields = (
49
+ "id",
28
50
  "_additional_resources",
29
51
  "transaction_subtype",
30
52
  "calculated",
31
53
  "fee_date",
32
- "linked_product",
33
- ) + TransactionModelSerializer.Meta.fields
54
+ "product",
55
+ "currency",
56
+ "_product",
57
+ "_currency",
58
+ "total_value",
59
+ "total_value_fx_portfolio",
60
+ "total_value_gross",
61
+ "total_value_gross_fx_portfolio",
62
+ )
@@ -4,20 +4,22 @@ from decimal import Decimal
4
4
  from rest_framework import serializers
5
5
  from rest_framework.reverse import reverse
6
6
  from wbcore import serializers as wb_serializers
7
+ from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
7
8
  from wbcore.metadata.configs.display.list_display import BaseTreeGroupLevelOption
8
9
  from wbfdm.models import Instrument
9
10
  from wbfdm.serializers import InvestableInstrumentRepresentationSerializer
10
- from wbfdm.serializers.instruments.instruments import CompanyRepresentationSerializer, SecurityRepresentationSerializer
11
+ from wbfdm.serializers.instruments.instruments import (
12
+ CompanyRepresentationSerializer,
13
+ InvestableUniverseRepresentationSerializer,
14
+ SecurityRepresentationSerializer,
15
+ )
11
16
 
12
17
  from wbportfolio.models import PortfolioRole, Trade, TradeProposal
13
18
  from wbportfolio.models.transactions.claim import Claim
14
19
  from wbportfolio.serializers.custodians import CustodianRepresentationSerializer
15
20
  from wbportfolio.serializers.registers import RegisterRepresentationSerializer
16
21
 
17
- from .transactions import (
18
- TransactionModelSerializer,
19
- TransactionRepresentationSerializer,
20
- )
22
+ from .. import PortfolioRepresentationSerializer
21
23
 
22
24
 
23
25
  class CopyClaimRepresentationSerializer(wb_serializers.RepresentationSerializer):
@@ -47,13 +49,23 @@ class TradeProposalRepresentationSerializer(wb_serializers.RepresentationSeriali
47
49
  fields = ("id", "trade_date", "status", "_detail")
48
50
 
49
51
 
50
- class TradeRepresentationSerializer(TransactionRepresentationSerializer):
52
+ class TradeRepresentationSerializer(wb_serializers.RepresentationSerializer):
51
53
  _detail = wb_serializers.HyperlinkField(reverse_name="wbportfolio:trade-detail")
52
- diff_shares = wb_serializers.DecimalField(max_digits=15, decimal_places=4)
54
+ diff_shares = wb_serializers.DecimalField(max_digits=15, decimal_places=4, read_only=True)
55
+ total_value = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True, required=False)
53
56
 
54
57
  class Meta:
55
58
  model = Trade
56
- fields = ("claimed_shares", "diff_shares", "bank", "shares") + TransactionRepresentationSerializer.Meta.fields
59
+ fields = (
60
+ "id",
61
+ "transaction_date",
62
+ "total_value",
63
+ "_detail",
64
+ "claimed_shares",
65
+ "diff_shares",
66
+ "bank",
67
+ "shares",
68
+ )
57
69
 
58
70
 
59
71
  class TradeClaimRepresentationSerializer(TradeRepresentationSerializer):
@@ -78,7 +90,11 @@ class TradeClaimRepresentationSerializer(TradeRepresentationSerializer):
78
90
  return {}
79
91
 
80
92
 
81
- class TradeModelSerializer(TransactionModelSerializer):
93
+ class TradeModelSerializer(wb_serializers.ModelSerializer):
94
+ _portfolio = PortfolioRepresentationSerializer(source="portfolio")
95
+ _underlying_instrument = InvestableUniverseRepresentationSerializer(source="underlying_instrument")
96
+ _currency = CurrencyRepresentationSerializer(source="currency")
97
+
82
98
  marked_for_deletion = wb_serializers.BooleanField(required=False, read_only=True)
83
99
  marked_as_internal = wb_serializers.BooleanField()
84
100
 
@@ -91,6 +107,19 @@ class TradeModelSerializer(TransactionModelSerializer):
91
107
  completely_claimed = wb_serializers.BooleanField(required=False, read_only=True, default=Decimal(0.0))
92
108
  completely_claimed_if_approved = wb_serializers.BooleanField(required=False, read_only=True, default=Decimal(0.0))
93
109
 
110
+ total_value = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True, required=False)
111
+ total_value_fx_portfolio = wb_serializers.DecimalField(
112
+ max_digits=14, decimal_places=2, read_only=True, required=False
113
+ )
114
+ total_value_gross = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True, required=False)
115
+ total_value_gross_fx_portfolio = wb_serializers.DecimalField(
116
+ max_digits=14, decimal_places=2, read_only=True, required=False
117
+ )
118
+ external_id = wb_serializers.CharField(required=False, read_only=True)
119
+ value_date = wb_serializers.DateField(required=False, read_only=True)
120
+ total_value_usd = wb_serializers.FloatField(default=0, read_only=True, label="Total Value ($)")
121
+ total_value_gross_usd = wb_serializers.FloatField(default=0, read_only=True, label="Total Value Gross ($)")
122
+
94
123
  # We commented it because people needs to filter with lookup equals onto these fields
95
124
  # shares = wb_serializers.DecimalField(max_digits=16, decimal_places=2)
96
125
  # price = wb_serializers.DecimalField(max_digits=16, decimal_places=2)
@@ -127,7 +156,24 @@ class TradeModelSerializer(TransactionModelSerializer):
127
156
 
128
157
  class Meta:
129
158
  model = Trade
130
- decorators = TransactionModelSerializer.Meta.decorators
159
+ decorators = {
160
+ "total_value": wb_serializers.decorator(
161
+ decorator_type="text", position="left", value="{{_currency.symbol}}"
162
+ ),
163
+ "total_value_gross": wb_serializers.decorator(
164
+ decorator_type="text", position="left", value="{{_currency.symbol}}"
165
+ ),
166
+ "total_value_usd": wb_serializers.decorator(decorator_type="text", position="left", value="{{$}}"),
167
+ "total_value_gross_usd": wb_serializers.decorator(decorator_type="text", position="left", value="{{$}}"),
168
+ "total_value_fx_portfolio": wb_serializers.decorator(
169
+ position="left", value="{{_portfolio.currency_symbol}}"
170
+ ),
171
+ "total_value_gross_fx_portfolio": wb_serializers.decorator(
172
+ position="left", value="{{_portfolio.currency_symbol}}"
173
+ ),
174
+ # "total_value_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}}"),
175
+ # "total_value_gross_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}"),
176
+ }
131
177
  read_only_fields = (
132
178
  "id",
133
179
  "shares",
@@ -145,9 +191,6 @@ class TradeModelSerializer(TransactionModelSerializer):
145
191
  "marked_for_deletion",
146
192
  "_claims",
147
193
  "claims",
148
- "transaction_type",
149
- "transaction_url_type",
150
- "transaction_underlying_type",
151
194
  "portfolio",
152
195
  "_portfolio",
153
196
  "underlying_instrument",
@@ -185,9 +228,6 @@ class TradeModelSerializer(TransactionModelSerializer):
185
228
  "marked_for_deletion",
186
229
  "_claims",
187
230
  "claims",
188
- "transaction_type",
189
- "transaction_url_type",
190
- "transaction_underlying_type",
191
231
  "portfolio",
192
232
  "_portfolio",
193
233
  "underlying_instrument",
wbportfolio/tasks.py CHANGED
@@ -4,7 +4,7 @@ from datetime import date, timedelta
4
4
  from django.db.models import ProtectedError, Q
5
5
 
6
6
  from wbportfolio.models import Portfolio, Trade
7
- from wbportfolio.models.products import Product, update_outstanding_shares_as_task
7
+ from wbportfolio.models.products import Product
8
8
 
9
9
  from .fdm.tasks import * # noqa
10
10
 
@@ -15,7 +15,7 @@ def daily_active_product_task(today: date | None = None):
15
15
  today = date.today()
16
16
  qs = Product.active_objects.all()
17
17
  for product in tqdm(qs, total=qs.count()):
18
- update_outstanding_shares_as_task(product.id)
18
+ product.update_outstanding_shares()
19
19
  product.check_and_notify_product_termination_on_date(today)
20
20
 
21
21
 
@@ -1,7 +1,4 @@
1
- from datetime import date
2
-
3
- from faker import Faker
4
- from pandas.tseries.offsets import BDay
1
+ from pytest_factoryboy import register
5
2
  from wbcompliance.factories.risk_management import (
6
3
  RiskRuleFactory,
7
4
  RuleBackendFactory,
@@ -10,6 +7,7 @@ from wbcompliance.factories.risk_management import (
10
7
  from wbcore.contrib.authentication.factories import (
11
8
  InternalUserFactory,
12
9
  SuperUserFactory,
10
+ UserFactory
13
11
  )
14
12
  from wbcore.contrib.currency.factories import CurrencyFactory, CurrencyFXRatesFactory
15
13
  from wbcore.contrib.directory.factories.entries import (
@@ -74,9 +72,6 @@ from wbportfolio.factories import (
74
72
 
75
73
  from wbcore.tests.conftest import * # isort:skip
76
74
 
77
-
78
- fake = Faker()
79
-
80
75
  register(AccountFactory)
81
76
  register(AccountWithOwnerFactory)
82
77
  register(AccountRoleFactory)
@@ -113,7 +108,6 @@ register(CurrencyFactory)
113
108
  register(CityFactory)
114
109
  register(StateFactory)
115
110
  register(CountryFactory)
116
- register(ContinentFactory)
117
111
 
118
112
  register(CompanyFactory)
119
113
  register(PersonFactory)
@@ -27,7 +27,6 @@ class TestImportMixinModel:
27
27
  data["currency"] = {"key": portfolio.currency.key}
28
28
  del data["id"]
29
29
  del data["import_source"]
30
- del data["transaction_ptr"]
31
30
  return data
32
31
 
33
32
  trade = trade_factory.build()
@@ -76,16 +75,12 @@ class TestImportMixinModel:
76
75
 
77
76
  def serialize(fees):
78
77
  data = model_to_dict(fees)
79
- data["transaction_date"] = fees.transaction_date.strftime("%Y-%m-%d")
80
- data["value_date"] = fees.value_date.strftime("%Y-%m-%d")
81
- data["portfolio"] = portfolio.id
82
- data["linked_product"] = product.id
83
- data["portfolio"] = portfolio.id
78
+ data["fee_date"] = fees.fee_date.strftime("%Y-%m-%d")
79
+ data["product"] = product.id
84
80
  data["currency"] = {"key": portfolio.currency.key}
85
81
  del data["calculated"]
86
82
  del data["id"]
87
83
  del data["import_source"]
88
- del data["transaction_ptr"]
89
84
  return data
90
85
 
91
86
  fees = fees_factory.build(calculated=False)