wbportfolio 1.52.0__py2.py3-none-any.whl → 1.52.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.
- wbportfolio/admin/__init__.py +1 -1
- wbportfolio/admin/transactions/__init__.py +0 -1
- wbportfolio/admin/transactions/dividends.py +40 -4
- wbportfolio/admin/transactions/fees.py +24 -14
- wbportfolio/admin/transactions/trades.py +34 -12
- wbportfolio/defaults/fees/default.py +7 -15
- wbportfolio/factories/__init__.py +0 -1
- wbportfolio/factories/dividends.py +8 -3
- wbportfolio/factories/fees.py +8 -4
- wbportfolio/factories/trades.py +10 -3
- wbportfolio/filters/transactions/__init__.py +1 -2
- wbportfolio/filters/transactions/fees.py +5 -10
- wbportfolio/filters/transactions/trades.py +17 -8
- wbportfolio/filters/transactions/utils.py +42 -0
- wbportfolio/import_export/handlers/dividend.py +7 -7
- wbportfolio/import_export/handlers/fees.py +11 -21
- wbportfolio/import_export/handlers/trade.py +5 -7
- wbportfolio/import_export/parsers/jpmorgan/fees.py +2 -2
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
- wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
- wbportfolio/import_export/parsers/leonteq/trade.py +0 -5
- wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
- wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
- wbportfolio/import_export/parsers/natixis/fees.py +7 -9
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
- wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
- wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
- wbportfolio/import_export/parsers/vontobel/management_fees.py +7 -7
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +3 -3
- wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
- wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
- wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
- wbportfolio/models/mixins/liquidity_stress_test.py +3 -3
- wbportfolio/models/transactions/__init__.py +0 -2
- wbportfolio/models/transactions/claim.py +1 -1
- wbportfolio/models/transactions/dividends.py +41 -5
- wbportfolio/models/transactions/fees.py +55 -22
- wbportfolio/models/transactions/trade_proposals.py +26 -6
- wbportfolio/models/transactions/trades.py +111 -50
- wbportfolio/models/transactions/transactions.py +60 -156
- wbportfolio/serializers/signals.py +15 -10
- wbportfolio/serializers/transactions/__init__.py +0 -5
- wbportfolio/serializers/transactions/dividends.py +37 -9
- wbportfolio/serializers/transactions/fees.py +39 -10
- wbportfolio/serializers/transactions/trades.py +56 -16
- wbportfolio/tasks.py +2 -2
- wbportfolio/tests/conftest.py +2 -8
- wbportfolio/tests/models/test_imports.py +2 -7
- wbportfolio/tests/models/transactions/test_fees.py +7 -13
- wbportfolio/tests/models/transactions/test_trade_proposals.py +4 -2
- wbportfolio/urls.py +3 -6
- wbportfolio/viewsets/configs/buttons/__init__.py +1 -0
- wbportfolio/viewsets/configs/buttons/mixins.py +2 -2
- wbportfolio/viewsets/configs/buttons/trades.py +8 -0
- wbportfolio/viewsets/configs/display/__init__.py +2 -3
- wbportfolio/viewsets/configs/display/fees.py +3 -3
- wbportfolio/viewsets/configs/endpoints/__init__.py +3 -4
- wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
- wbportfolio/viewsets/configs/menu/__init__.py +0 -1
- wbportfolio/viewsets/configs/titles/__init__.py +2 -3
- wbportfolio/viewsets/configs/titles/fees.py +4 -8
- wbportfolio/viewsets/mixins.py +5 -1
- wbportfolio/viewsets/products.py +6 -6
- wbportfolio/viewsets/transactions/__init__.py +2 -7
- wbportfolio/viewsets/transactions/fees.py +22 -22
- wbportfolio/viewsets/transactions/trade_proposals.py +1 -0
- wbportfolio/viewsets/transactions/trades.py +2 -0
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.dist-info}/METADATA +1 -1
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.dist-info}/RECORD +75 -84
- wbportfolio/admin/transactions/transactions.py +0 -38
- wbportfolio/factories/transactions.py +0 -22
- wbportfolio/filters/transactions/transactions.py +0 -99
- wbportfolio/models/transactions/expiry.py +0 -7
- wbportfolio/serializers/transactions/expiry.py +0 -18
- wbportfolio/serializers/transactions/transactions.py +0 -85
- wbportfolio/viewsets/configs/display/transactions.py +0 -55
- wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
- wbportfolio/viewsets/configs/menu/transactions.py +0 -9
- wbportfolio/viewsets/configs/titles/transactions.py +0 -9
- wbportfolio/viewsets/transactions/transactions.py +0 -122
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.dist-info}/WHEEL +0 -0
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
-
|
|
100
|
-
|
|
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("
|
|
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("
|
|
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
|
-
|
|
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.
|
|
139
|
-
self.
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
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 .
|
|
4
|
-
|
|
5
|
-
TransactionRepresentationSerializer,
|
|
6
|
-
)
|
|
5
|
+
from wbportfolio.models import DividendTransaction
|
|
6
|
+
from wbportfolio.serializers import PortfolioRepresentationSerializer
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class DividendRepresentationSerializer(
|
|
9
|
+
class DividendRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
10
10
|
class Meta:
|
|
11
11
|
model = DividendTransaction
|
|
12
|
-
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 =
|
|
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",
|
|
13
|
+
fields = ("transaction_subtype", "total_value", "_detail")
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
class FeesModelSerializer(
|
|
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 =
|
|
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
|
-
"
|
|
33
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 = (
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
-
|
|
18
|
+
product.update_outstanding_shares()
|
|
19
19
|
product.check_and_notify_product_termination_on_date(today)
|
|
20
20
|
|
|
21
21
|
|
wbportfolio/tests/conftest.py
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
from
|
|
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["
|
|
80
|
-
data["
|
|
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)
|