wbportfolio 1.50.15__py2.py3-none-any.whl → 1.51.1__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 (56) hide show
  1. wbportfolio/analysis/claims.py +9 -8
  2. wbportfolio/import_export/handlers/dividend.py +15 -5
  3. wbportfolio/import_export/handlers/fees.py +11 -4
  4. wbportfolio/import_export/handlers/trade.py +21 -3
  5. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +3 -4
  6. wbportfolio/import_export/parsers/jpmorgan/fees.py +3 -4
  7. wbportfolio/import_export/parsers/jpmorgan/valuation.py +1 -3
  8. wbportfolio/import_export/parsers/leonteq/equity.py +1 -4
  9. wbportfolio/import_export/parsers/leonteq/fees.py +2 -4
  10. wbportfolio/import_export/parsers/leonteq/valuation.py +1 -4
  11. wbportfolio/import_export/parsers/natixis/customer_trade.py +11 -15
  12. wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +9 -10
  13. wbportfolio/import_export/parsers/natixis/d1_equity.py +4 -12
  14. wbportfolio/import_export/parsers/natixis/d1_fees.py +3 -5
  15. wbportfolio/import_export/parsers/natixis/d1_trade.py +4 -13
  16. wbportfolio/import_export/parsers/natixis/d1_valuation.py +2 -5
  17. wbportfolio/import_export/parsers/natixis/dividend.py +5 -5
  18. wbportfolio/import_export/parsers/natixis/equity.py +4 -4
  19. wbportfolio/import_export/parsers/natixis/fees.py +3 -6
  20. wbportfolio/import_export/parsers/natixis/trade.py +4 -4
  21. wbportfolio/import_export/parsers/natixis/utils.py +5 -9
  22. wbportfolio/import_export/parsers/natixis/valuation.py +4 -5
  23. wbportfolio/import_export/parsers/sg_lux/fees.py +6 -6
  24. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +3 -6
  25. wbportfolio/import_export/parsers/sg_lux/registers.py +6 -2
  26. wbportfolio/import_export/parsers/sg_lux/valuation.py +3 -4
  27. wbportfolio/import_export/parsers/societe_generale/customer_trade.py +13 -12
  28. wbportfolio/import_export/parsers/societe_generale/valuation.py +2 -3
  29. wbportfolio/import_export/parsers/tellco/customer_trade.py +4 -6
  30. wbportfolio/import_export/parsers/tellco/equity.py +2 -3
  31. wbportfolio/import_export/parsers/tellco/valuation.py +2 -4
  32. wbportfolio/import_export/parsers/ubs/api/fees.py +6 -9
  33. wbportfolio/import_export/parsers/ubs/customer_trade.py +14 -20
  34. wbportfolio/import_export/parsers/ubs/equity.py +3 -6
  35. wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +19 -38
  36. wbportfolio/import_export/parsers/ubs/valuation.py +2 -3
  37. wbportfolio/import_export/parsers/vontobel/instrument.py +2 -2
  38. wbportfolio/import_export/parsers/vontobel/management_fees.py +3 -5
  39. wbportfolio/import_export/parsers/vontobel/performance_fees.py +2 -3
  40. wbportfolio/import_export/parsers/vontobel/trade.py +1 -3
  41. wbportfolio/import_export/parsers/vontobel/utils.py +0 -12
  42. wbportfolio/locale/de/LC_MESSAGES/django.po +197 -0
  43. wbportfolio/locale/fr/LC_MESSAGES/django.po +197 -0
  44. wbportfolio/models/portfolio.py +8 -0
  45. wbportfolio/models/transactions/trade_proposals.py +22 -15
  46. wbportfolio/models/transactions/trades.py +8 -2
  47. wbportfolio/serializers/signals.py +1 -1
  48. wbportfolio/serializers/transactions/trades.py +2 -0
  49. wbportfolio/viewsets/configs/display/trades.py +12 -0
  50. wbportfolio/viewsets/configs/menu/trades.py +0 -1
  51. wbportfolio/viewsets/transactions/claim.py +1 -1
  52. wbportfolio/viewsets/transactions/trade_proposals.py +1 -1
  53. {wbportfolio-1.50.15.dist-info → wbportfolio-1.51.1.dist-info}/METADATA +1 -1
  54. {wbportfolio-1.50.15.dist-info → wbportfolio-1.51.1.dist-info}/RECORD +56 -54
  55. {wbportfolio-1.50.15.dist-info → wbportfolio-1.51.1.dist-info}/WHEEL +0 -0
  56. {wbportfolio-1.50.15.dist-info → wbportfolio-1.51.1.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,6 @@ import numpy as np
6
6
  import pandas as pd
7
7
 
8
8
  from wbportfolio.import_export.utils import get_file_extension
9
- from wbportfolio.models import Product
10
9
 
11
10
 
12
11
  def file_name_parse(file_name):
@@ -30,7 +29,7 @@ def parse(import_source):
30
29
  raise Exception("File not supported")
31
30
 
32
31
  parts = file_name_parse(import_source.file.name)
33
- product = Product.objects.get(isin=parts["isin"])
32
+ isin = parts["isin"]
34
33
 
35
34
  xx, yy = np.where(df == "Instrument")
36
35
  df = df.iloc[: xx[0], :].transpose().dropna(how="all", axis=0).reset_index(drop=True)
@@ -43,7 +42,7 @@ def parse(import_source):
43
42
 
44
43
  data = [
45
44
  {
46
- "instrument": {"instrument_type": "product", "id": product.id},
45
+ "instrument": {"instrument_type": "product", "isin": isin},
47
46
  "date": date.strftime("%Y-%m-%d"),
48
47
  "net_value": value,
49
48
  "calculated": False,
@@ -17,7 +17,7 @@ ASSET_TYPE_MAP = {
17
17
  "EQUBEA": "equity",
18
18
  "EQUREG": "equity",
19
19
  "FNDETF": "etf",
20
- "OTHOTH": np.nan,
20
+ "OTHOTH": None,
21
21
  "STROTH": "etf",
22
22
  "STRPRT": "etf",
23
23
  }
@@ -28,7 +28,7 @@ def parse(import_source):
28
28
  df = df.rename(columns=FIELD_MAP)
29
29
  df = df.replace([np.inf, -np.inf, np.nan], None)
30
30
  df["instrument_type"] = df["instrument_type"].apply(lambda x: ASSET_TYPE_MAP[x])
31
- df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
31
+ df = df.drop(columns=df.columns.difference(FIELD_MAP.values())).astype(str)
32
32
  df["exchange"] = df["exchange"].apply(lambda x: {"operating_mic_code": x})
33
33
  df = df.dropna(subset="instrument_type")
34
34
  return {"data": df.to_dict("records")}
@@ -3,9 +3,7 @@ from datetime import datetime
3
3
 
4
4
  import pandas as pd
5
5
 
6
- from wbportfolio.models import FeeProductPercentage, Fees
7
-
8
- from .utils import get_portfolio_id, get_product
6
+ from wbportfolio.models import FeeProductPercentage, Fees, Product
9
7
 
10
8
  FIELD_MAP = {
11
9
  "Transaction_Date": "transaction_date",
@@ -27,11 +25,11 @@ def parse(import_source):
27
25
  data = []
28
26
  if not df.empty:
29
27
  df = df.rename(columns=FIELD_MAP)
30
- df.linked_product = df.linked_product.apply(lambda x: get_product(x))
28
+ df.linked_product = df.linked_product.apply(lambda x: Product.objects.filter(identifier=x).first())
31
29
  df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True)
32
30
  df["book_date"] = pd.to_datetime(df["book_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
33
31
  df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
34
- df["portfolio"] = df.linked_product.apply(lambda x: get_portfolio_id(x))
32
+ df["portfolio"] = df.linked_product.apply(lambda x: x.primary_portfolio if x else None)
35
33
  df["base_management_fees"] = df.apply(
36
34
  lambda row: float(
37
35
  row["linked_product"].get_fees_percent(row["transaction_date"], FeeProductPercentage.Type.MANAGEMENT)
@@ -2,7 +2,7 @@ import pandas as pd
2
2
 
3
3
  from wbportfolio.models import Fees
4
4
 
5
- from .utils import get_perf_fee_isin, get_portfolio_id, get_product
5
+ from .utils import get_perf_fee_isin
6
6
 
7
7
  FIELD_MAP = {
8
8
  "LastPriceDate": "transaction_date",
@@ -30,7 +30,6 @@ def parse(import_source):
30
30
  df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
31
31
  df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
32
32
  df = df.dropna(subset=["linked_product"])
33
- df["portfolio"] = df.linked_product.apply(lambda x: get_portfolio_id(x))
34
- df.linked_product = df.linked_product.apply(lambda x: get_product(x).id)
33
+ df.linked_product = df.linked_product.apply(lambda x: {"identifier": x})
35
34
  data = df.to_dict("records")
36
35
  return {"data": data}
@@ -2,8 +2,6 @@ import pandas as pd
2
2
 
3
3
  from wbportfolio.models import Trade
4
4
 
5
- from .utils import get_portfolio_id
6
-
7
5
  FIELD_MAP = {
8
6
  "Transaction_Date": "transaction_date",
9
7
  "Trade_Date": "book_date",
@@ -30,7 +28,7 @@ def parse(import_source):
30
28
  df["book_date"] = pd.to_datetime(df["book_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
31
29
  df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
32
30
  df["transaction_subtype"] = df.shares.apply(lambda x: Trade.Type.SELL if x < 0 else Trade.Type.BUY)
33
- df["portfolio"] = df["portfolio"].apply(lambda x: get_portfolio_id(x))
31
+ df["portfolio"] = df["portfolio"].apply(lambda x: {"instrument_type": "product", "identifier": str(x)})
34
32
  df = df.dropna(subset=["underlying_instrument__isin"])
35
33
 
36
34
  df["underlying_instrument__currency__key"] = df["currency__key"]
@@ -1,15 +1,3 @@
1
- from wbportfolio.models import Product
2
-
3
-
4
- def get_product(identifier):
5
- return Product.objects.filter(identifier=identifier).first()
6
-
7
-
8
- def get_portfolio_id(identifier):
9
- if (product := get_product(identifier)) and (portfolio := product.primary_portfolio):
10
- return portfolio.id
11
-
12
-
13
1
  def get_perf_fee_isin(source):
14
2
  default_perf_fee_isin = "CH0040602242"
15
3
  if source:
@@ -0,0 +1,197 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2025-05-20 09:31+0200\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:25
21
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:29
22
+ msgid "AUM By Product"
23
+ msgstr ""
24
+
25
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:44
26
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:48
27
+ msgid "AUM"
28
+ msgstr ""
29
+
30
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:84
31
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:88
32
+ msgid "Asset Allocation"
33
+ msgstr ""
34
+
35
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:103
36
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:107
37
+ msgid "Geographic Focus"
38
+ msgstr ""
39
+
40
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:124
41
+ msgid "Customer Information"
42
+ msgstr ""
43
+
44
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:118
45
+ msgid "The last share price of the product"
46
+ msgstr ""
47
+
48
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:127
49
+ msgid "The number of shares computed through the Workbench"
50
+ msgstr ""
51
+
52
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:133
53
+ msgid "The nominal value computed through the Workbench"
54
+ msgstr ""
55
+
56
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:140
57
+ msgid ""
58
+ "The number of shares externally provided through the Account holder. "
59
+ "Initially set to the number of shares computed through the Workbench"
60
+ msgstr ""
61
+
62
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:148
63
+ msgid ""
64
+ "The nominal value externally provided through the Account holder. Initially "
65
+ "set to the number of shares computed through the Workbench"
66
+ msgstr ""
67
+
68
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:155
69
+ msgid "Account Reconciliation (Product)"
70
+ msgstr ""
71
+
72
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:156
73
+ msgid "Account Reconciliation (Products)"
74
+ msgstr ""
75
+
76
+ #: wbportfolio/models/reconciliations/reconciliations.py:19
77
+ msgid "Approved By"
78
+ msgstr ""
79
+
80
+ #: wbportfolio/models/reconciliations/reconciliations.py:22
81
+ msgid "Approved Timestamp"
82
+ msgstr ""
83
+
84
+ #: wbportfolio/models/transactions/claim.py:338
85
+ #: wbportfolio/models/transactions/claim.py:341
86
+ #: wbportfolio/models/transactions/claim.py:344
87
+ msgid "With this status, this has to be provided."
88
+ msgstr ""
89
+
90
+ #: wbportfolio/models/transactions/rebalancing.py:81
91
+ msgid "Evaluation Frequency"
92
+ msgstr ""
93
+
94
+ #: wbportfolio/models/transactions/rebalancing.py:82
95
+ msgid "The Evaluation Frequency in RRULE format"
96
+ msgstr ""
97
+
98
+ #: wbportfolio/models/transactions/trade_proposals.py:473
99
+ msgid "All trades need to be draft before submitting"
100
+ msgstr ""
101
+
102
+ #: wbportfolio/models/transactions/trade_proposals.py:482
103
+ msgid "There is no valid trade on this proposal"
104
+ msgstr ""
105
+
106
+ #: wbportfolio/models/transactions/trade_proposals.py:550
107
+ msgid "The portfolio does not allow manual rebalanced"
108
+ msgstr ""
109
+
110
+ #: wbportfolio/models/transactions/trade_proposals.py:553
111
+ msgid ""
112
+ "At least one trade needs to be submitted to be able to approve this proposal"
113
+ msgstr ""
114
+
115
+ #: wbportfolio/models/transactions/trade_proposals.py:557
116
+ msgid ""
117
+ "The portfolio needs to be a model portfolio in order to approve this trade "
118
+ "proposal manually"
119
+ msgstr ""
120
+
121
+ #: wbportfolio/models/transactions/trade_proposals.py:560
122
+ msgid "The pre trades rules did not passed successfully"
123
+ msgstr ""
124
+
125
+ #: wbportfolio/models/transactions/trade_proposals.py:592
126
+ msgid ""
127
+ "At least one trade needs to be submitted to be able to deny this proposal"
128
+ msgstr ""
129
+
130
+ #: wbportfolio/models/transactions/trade_proposals.py:671
131
+ msgid ""
132
+ "The portfolio needs to be a model portfolio in order to revert this trade "
133
+ "proposal manually"
134
+ msgstr ""
135
+
136
+ #: wbportfolio/models/transactions/trades.py:324
137
+ msgid "Cannot execute a trade without a valid quote price"
138
+ msgstr ""
139
+
140
+ #: wbportfolio/models/transactions/trades.py:327
141
+ msgid ""
142
+ "The portfolio needs to be a model portfolio in order to execute this trade "
143
+ "manually"
144
+ msgstr ""
145
+
146
+ #: wbportfolio/viewsets/configs/display/portfolios.py:105
147
+ msgid "Linked Instruments"
148
+ msgstr ""
149
+
150
+ #: wbportfolio/viewsets/configs/display/portfolios.py:112
151
+ msgid "Dependency Portfolios"
152
+ msgstr ""
153
+
154
+ #: wbportfolio/viewsets/configs/display/portfolios.py:119
155
+ msgid "Preferred Classification"
156
+ msgstr ""
157
+
158
+ #: wbportfolio/viewsets/configs/display/products.py:26
159
+ #: wbportfolio/viewsets/configs/display/products.py:156
160
+ msgid "Information"
161
+ msgstr ""
162
+
163
+ #: wbportfolio/viewsets/configs/display/products.py:62
164
+ msgid "Performance"
165
+ msgstr ""
166
+
167
+ #: wbportfolio/viewsets/configs/display/products.py:144
168
+ msgid "Classifications"
169
+ msgstr ""
170
+
171
+ #: wbportfolio/viewsets/configs/display/products.py:148
172
+ msgid "Fees"
173
+ msgstr ""
174
+
175
+ #: wbportfolio/viewsets/configs/display/products.py:166
176
+ msgid "White Label Customers"
177
+ msgstr ""
178
+
179
+ #: wbportfolio/viewsets/configs/display/products.py:170
180
+ msgid "Portfolios"
181
+ msgstr ""
182
+
183
+ #: wbportfolio/viewsets/configs/display/products.py:173
184
+ msgid "Related Instruments"
185
+ msgstr ""
186
+
187
+ #: wbportfolio/viewsets/configs/display/products.py:178
188
+ msgid "Index"
189
+ msgstr ""
190
+
191
+ #: wbportfolio/viewsets/configs/display/products.py:180
192
+ msgid "Roles"
193
+ msgstr ""
194
+
195
+ #: wbportfolio/viewsets/configs/display/products.py:183
196
+ msgid "Preferred Classification per Instrument"
197
+ msgstr ""
@@ -0,0 +1,197 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2025-05-20 09:32+0200\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=2; plural=(n > 1);\n"
20
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:25
21
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:29
22
+ msgid "AUM By Product"
23
+ msgstr ""
24
+
25
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:44
26
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:48
27
+ msgid "AUM"
28
+ msgstr ""
29
+
30
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:84
31
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:88
32
+ msgid "Asset Allocation"
33
+ msgstr ""
34
+
35
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:103
36
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:107
37
+ msgid "Geographic Focus"
38
+ msgstr ""
39
+
40
+ #: wbportfolio/contrib/company_portfolio/configs/display.py:124
41
+ msgid "Customer Information"
42
+ msgstr ""
43
+
44
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:118
45
+ msgid "The last share price of the product"
46
+ msgstr ""
47
+
48
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:127
49
+ msgid "The number of shares computed through the Workbench"
50
+ msgstr ""
51
+
52
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:133
53
+ msgid "The nominal value computed through the Workbench"
54
+ msgstr ""
55
+
56
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:140
57
+ msgid ""
58
+ "The number of shares externally provided through the Account holder. "
59
+ "Initially set to the number of shares computed through the Workbench"
60
+ msgstr ""
61
+
62
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:148
63
+ msgid ""
64
+ "The nominal value externally provided through the Account holder. Initially "
65
+ "set to the number of shares computed through the Workbench"
66
+ msgstr ""
67
+
68
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:155
69
+ msgid "Account Reconciliation (Product)"
70
+ msgstr ""
71
+
72
+ #: wbportfolio/models/reconciliations/account_reconciliation_lines.py:156
73
+ msgid "Account Reconciliation (Products)"
74
+ msgstr ""
75
+
76
+ #: wbportfolio/models/reconciliations/reconciliations.py:19
77
+ msgid "Approved By"
78
+ msgstr ""
79
+
80
+ #: wbportfolio/models/reconciliations/reconciliations.py:22
81
+ msgid "Approved Timestamp"
82
+ msgstr ""
83
+
84
+ #: wbportfolio/models/transactions/claim.py:338
85
+ #: wbportfolio/models/transactions/claim.py:341
86
+ #: wbportfolio/models/transactions/claim.py:344
87
+ msgid "With this status, this has to be provided."
88
+ msgstr ""
89
+
90
+ #: wbportfolio/models/transactions/rebalancing.py:81
91
+ msgid "Evaluation Frequency"
92
+ msgstr ""
93
+
94
+ #: wbportfolio/models/transactions/rebalancing.py:82
95
+ msgid "The Evaluation Frequency in RRULE format"
96
+ msgstr ""
97
+
98
+ #: wbportfolio/models/transactions/trade_proposals.py:473
99
+ msgid "All trades need to be draft before submitting"
100
+ msgstr ""
101
+
102
+ #: wbportfolio/models/transactions/trade_proposals.py:482
103
+ msgid "There is no valid trade on this proposal"
104
+ msgstr ""
105
+
106
+ #: wbportfolio/models/transactions/trade_proposals.py:550
107
+ msgid "The portfolio does not allow manual rebalanced"
108
+ msgstr ""
109
+
110
+ #: wbportfolio/models/transactions/trade_proposals.py:553
111
+ msgid ""
112
+ "At least one trade needs to be submitted to be able to approve this proposal"
113
+ msgstr ""
114
+
115
+ #: wbportfolio/models/transactions/trade_proposals.py:557
116
+ msgid ""
117
+ "The portfolio needs to be a model portfolio in order to approve this trade "
118
+ "proposal manually"
119
+ msgstr ""
120
+
121
+ #: wbportfolio/models/transactions/trade_proposals.py:560
122
+ msgid "The pre trades rules did not passed successfully"
123
+ msgstr ""
124
+
125
+ #: wbportfolio/models/transactions/trade_proposals.py:592
126
+ msgid ""
127
+ "At least one trade needs to be submitted to be able to deny this proposal"
128
+ msgstr ""
129
+
130
+ #: wbportfolio/models/transactions/trade_proposals.py:671
131
+ msgid ""
132
+ "The portfolio needs to be a model portfolio in order to revert this trade "
133
+ "proposal manually"
134
+ msgstr ""
135
+
136
+ #: wbportfolio/models/transactions/trades.py:324
137
+ msgid "Cannot execute a trade without a valid quote price"
138
+ msgstr ""
139
+
140
+ #: wbportfolio/models/transactions/trades.py:327
141
+ msgid ""
142
+ "The portfolio needs to be a model portfolio in order to execute this trade "
143
+ "manually"
144
+ msgstr ""
145
+
146
+ #: wbportfolio/viewsets/configs/display/portfolios.py:105
147
+ msgid "Linked Instruments"
148
+ msgstr ""
149
+
150
+ #: wbportfolio/viewsets/configs/display/portfolios.py:112
151
+ msgid "Dependency Portfolios"
152
+ msgstr ""
153
+
154
+ #: wbportfolio/viewsets/configs/display/portfolios.py:119
155
+ msgid "Preferred Classification"
156
+ msgstr ""
157
+
158
+ #: wbportfolio/viewsets/configs/display/products.py:26
159
+ #: wbportfolio/viewsets/configs/display/products.py:156
160
+ msgid "Information"
161
+ msgstr ""
162
+
163
+ #: wbportfolio/viewsets/configs/display/products.py:62
164
+ msgid "Performance"
165
+ msgstr ""
166
+
167
+ #: wbportfolio/viewsets/configs/display/products.py:144
168
+ msgid "Classifications"
169
+ msgstr ""
170
+
171
+ #: wbportfolio/viewsets/configs/display/products.py:148
172
+ msgid "Fees"
173
+ msgstr ""
174
+
175
+ #: wbportfolio/viewsets/configs/display/products.py:166
176
+ msgid "White Label Customers"
177
+ msgstr ""
178
+
179
+ #: wbportfolio/viewsets/configs/display/products.py:170
180
+ msgid "Portfolios"
181
+ msgstr ""
182
+
183
+ #: wbportfolio/viewsets/configs/display/products.py:173
184
+ msgid "Related Instruments"
185
+ msgstr ""
186
+
187
+ #: wbportfolio/viewsets/configs/display/products.py:178
188
+ msgid "Index"
189
+ msgstr ""
190
+
191
+ #: wbportfolio/viewsets/configs/display/products.py:180
192
+ msgid "Roles"
193
+ msgstr ""
194
+
195
+ #: wbportfolio/viewsets/configs/display/products.py:183
196
+ msgid "Preferred Classification per Instrument"
197
+ msgstr ""
@@ -429,6 +429,14 @@ class Portfolio(DeleteToDisableMixin, WBModel):
429
429
  True,
430
430
  True,
431
431
  ),
432
+ create_notification_type(
433
+ "wbportfolio.portfolio.replay_done",
434
+ "Portfolio Replay finished",
435
+ "Sends a notification when a the requested trade proposal replay is done",
436
+ True,
437
+ True,
438
+ True,
439
+ ),
432
440
  ]
433
441
 
434
442
  def is_active_at_date(self, val_date: date) -> bool:
@@ -12,8 +12,10 @@ from django.utils.translation import gettext_lazy as _
12
12
  from django_fsm import FSMField, transition
13
13
  from pandas._libs.tslibs.offsets import BDay
14
14
  from wbcompliance.models.risk_management.mixins import RiskCheckMixin
15
+ from wbcore.contrib.authentication.models import User
15
16
  from wbcore.contrib.currency.models import Currency
16
17
  from wbcore.contrib.icons import WBIcon
18
+ from wbcore.contrib.notifications.dispatch import send_notification
17
19
  from wbcore.enums import RequestType
18
20
  from wbcore.metadata.configs.buttons import ActionButton
19
21
  from wbcore.models import WBModel
@@ -313,6 +315,11 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
313
315
  positions, overriding_trade_proposal = self.portfolio.drift_weights(
314
316
  last_trade_proposal.trade_date, next_trade_date
315
317
  )
318
+ self.portfolio.assets.filter(
319
+ date__gt=last_trade_proposal.trade_date, date__lte=next_trade_date, is_estimated=False
320
+ ).update(
321
+ is_estimated=True
322
+ ) # ensure that we reset non estimated position leftover to estimated between trade proposal during replay
316
323
  self.portfolio.bulk_create_positions(
317
324
  positions, delete_leftovers=True, compute_metrics=False, evaluate_rebalancer=False
318
325
  )
@@ -542,7 +549,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
542
549
  AssetPositionIterator(self.portfolio).add(assets), evaluate_rebalancer=False, force_save=True
543
550
  )
544
551
  if replay and self.portfolio.is_manageable:
545
- replay_as_task.delay(self.id)
552
+ replay_as_task.delay(self.id, user_id=by.id if by else None)
546
553
 
547
554
  def can_approve(self):
548
555
  errors = dict()
@@ -646,23 +653,13 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
646
653
  with suppress(KeyError):
647
654
  del self.__dict__["validated_trading_service"]
648
655
  trades = []
649
- assets = []
656
+ self.portfolio.assets.filter(
657
+ date=self.trade_date, is_estimated=False
658
+ ).delete() # we delete the existing portfolio as it has been reverted
650
659
  for trade in self.trades.all():
651
660
  trade.status = Trade.Status.DRAFT
652
661
  trades.append(trade)
653
- with suppress(AssetPosition.DoesNotExist):
654
- asset = AssetPosition.unannotated_objects.get(
655
- underlying_quote=trade.underlying_instrument,
656
- portfolio=trade.portfolio,
657
- date=trade.transaction_date,
658
- is_estimated=False,
659
- )
660
- asset.set_weighting(asset.weighting - trade.weighting)
661
- assets.append(asset)
662
662
  Trade.objects.bulk_update(trades, ["status"])
663
- self.portfolio.bulk_create_positions(
664
- AssetPositionIterator(self.portfolio).add(assets), evaluate_rebalancer=False, force_save=True
665
- )
666
663
 
667
664
  def can_revert(self):
668
665
  errors = dict()
@@ -692,6 +689,16 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
692
689
 
693
690
 
694
691
  @shared_task(queue="portfolio")
695
- def replay_as_task(trade_proposal_id):
692
+ def replay_as_task(trade_proposal_id, user_id: int | None = None):
696
693
  trade_proposal = TradeProposal.objects.get(id=trade_proposal_id)
697
694
  trade_proposal.replay()
695
+ if user_id:
696
+ user = User.objects.get(id=user_id)
697
+ send_notification(
698
+ code="wbportfolio.portfolio.replay_done",
699
+ title="Trade Proposal Replay Completed",
700
+ body=f'We’ve successfully replayed your trade proposal for "{trade_proposal.portfolio}" from {trade_proposal.trade_date:%Y-%m-%d}. You can now review its updated composition.',
701
+ user=user,
702
+ reverse_name="wbportfolio:portfolio-detail",
703
+ reverse_args=[trade_proposal.portfolio.id],
704
+ )
@@ -536,9 +536,15 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
536
536
  self.transaction_subtype = Trade.Type.REDEMPTION
537
537
  elif self.weighting is not None:
538
538
  if self.weighting > 0:
539
- self.transaction_subtype = Trade.Type.BUY
539
+ if self._effective_weight:
540
+ self.transaction_subtype = Trade.Type.INCREASE
541
+ else:
542
+ self.transaction_subtype = Trade.Type.BUY
540
543
  elif self.weighting < 0:
541
- self.transaction_subtype = Trade.Type.SELL
544
+ if self._target_weight:
545
+ self.transaction_subtype = Trade.Type.DECREASE
546
+ else:
547
+ self.transaction_subtype = Trade.Type.SELL
542
548
  else:
543
549
  self.transaction_subtype = Trade.Type.REBALANCE
544
550
 
@@ -140,7 +140,7 @@ def claim_adding_additional_resource(sender, serializer, instance, request, user
140
140
  start = current_quarter_date_start()
141
141
  return {
142
142
  "claims": reverse("wbportfolio:entry-claim-list", args=[instance.id], request=request),
143
- "aum": f'{reverse("wbportfolio:aumtable-list", args=[], request=request)}?group_by=PRODUCT&account_owner={instance.id}&date_gte={start:%Y-%m-%d}&date_lte={date.today():%Y-%m-%d}',
143
+ "aum": f'{reverse("wbportfolio:aumtable-list", args=[], request=request)}?group_by=PRODUCT&account_owner={instance.id}&date={start:%Y-%m-%d},{date.today():%Y-%m-%d}',
144
144
  }
145
145
 
146
146
 
@@ -4,6 +4,7 @@ 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.metadata.configs.display.list_display import BaseTreeGroupLevelOption
7
8
  from wbfdm.models import Instrument
8
9
  from wbfdm.serializers import InvestableInstrumentRepresentationSerializer
9
10
  from wbfdm.serializers.instruments.instruments import CompanyRepresentationSerializer, SecurityRepresentationSerializer
@@ -270,6 +271,7 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
270
271
  source="underlying_instrument",
271
272
  optional_get_parameters={"security": "parent"},
272
273
  depends_on=[{"field": "security", "options": {}}],
274
+ tree_config=BaseTreeGroupLevelOption(clear_filter=True, filter_key="parent"),
273
275
  )
274
276
 
275
277
  status = wb_serializers.ChoiceField(default=Trade.Status.DRAFT, choices=Trade.Status.choices)