wbportfolio 1.50.14__py2.py3-none-any.whl → 1.51.0__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/analysis/claims.py +6 -1
- wbportfolio/import_export/handlers/dividend.py +15 -5
- wbportfolio/import_export/handlers/fees.py +11 -4
- wbportfolio/import_export/handlers/trade.py +21 -3
- wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +3 -4
- wbportfolio/import_export/parsers/jpmorgan/fees.py +3 -4
- wbportfolio/import_export/parsers/jpmorgan/valuation.py +1 -3
- wbportfolio/import_export/parsers/leonteq/equity.py +1 -4
- wbportfolio/import_export/parsers/leonteq/fees.py +2 -4
- wbportfolio/import_export/parsers/leonteq/valuation.py +1 -4
- wbportfolio/import_export/parsers/natixis/customer_trade.py +11 -15
- wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +9 -10
- wbportfolio/import_export/parsers/natixis/d1_equity.py +4 -12
- wbportfolio/import_export/parsers/natixis/d1_fees.py +3 -5
- wbportfolio/import_export/parsers/natixis/d1_trade.py +4 -13
- wbportfolio/import_export/parsers/natixis/d1_valuation.py +2 -5
- wbportfolio/import_export/parsers/natixis/dividend.py +5 -5
- wbportfolio/import_export/parsers/natixis/equity.py +4 -4
- wbportfolio/import_export/parsers/natixis/fees.py +3 -6
- wbportfolio/import_export/parsers/natixis/trade.py +4 -4
- wbportfolio/import_export/parsers/natixis/utils.py +5 -9
- wbportfolio/import_export/parsers/natixis/valuation.py +4 -5
- wbportfolio/import_export/parsers/sg_lux/fees.py +6 -6
- wbportfolio/import_export/parsers/sg_lux/perf_fees.py +3 -6
- wbportfolio/import_export/parsers/sg_lux/registers.py +6 -2
- wbportfolio/import_export/parsers/sg_lux/valuation.py +3 -4
- wbportfolio/import_export/parsers/societe_generale/customer_trade.py +13 -12
- wbportfolio/import_export/parsers/societe_generale/valuation.py +2 -3
- wbportfolio/import_export/parsers/tellco/customer_trade.py +4 -6
- wbportfolio/import_export/parsers/tellco/equity.py +2 -3
- wbportfolio/import_export/parsers/tellco/valuation.py +2 -4
- wbportfolio/import_export/parsers/ubs/api/fees.py +6 -9
- wbportfolio/import_export/parsers/ubs/customer_trade.py +14 -20
- wbportfolio/import_export/parsers/ubs/equity.py +3 -6
- wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +19 -38
- wbportfolio/import_export/parsers/ubs/valuation.py +2 -3
- wbportfolio/import_export/parsers/vontobel/instrument.py +2 -2
- wbportfolio/import_export/parsers/vontobel/management_fees.py +3 -5
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +2 -3
- wbportfolio/import_export/parsers/vontobel/trade.py +1 -3
- wbportfolio/import_export/parsers/vontobel/utils.py +0 -12
- wbportfolio/locale/de/LC_MESSAGES/django.po +197 -0
- wbportfolio/locale/fr/LC_MESSAGES/django.po +197 -0
- wbportfolio/models/portfolio.py +8 -0
- wbportfolio/models/transactions/trade_proposals.py +22 -15
- wbportfolio/models/transactions/trades.py +8 -2
- wbportfolio/serializers/transactions/trades.py +2 -0
- wbportfolio/viewsets/configs/display/trades.py +12 -0
- wbportfolio/viewsets/transactions/claim.py +1 -1
- wbportfolio/viewsets/transactions/trade_proposals.py +1 -1
- {wbportfolio-1.50.14.dist-info → wbportfolio-1.51.0.dist-info}/METADATA +1 -1
- {wbportfolio-1.50.14.dist-info → wbportfolio-1.51.0.dist-info}/RECORD +54 -52
- {wbportfolio-1.50.14.dist-info → wbportfolio-1.51.0.dist-info}/WHEEL +0 -0
- {wbportfolio-1.50.14.dist-info → wbportfolio-1.51.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,7 +17,7 @@ ASSET_TYPE_MAP = {
|
|
|
17
17
|
"EQUBEA": "equity",
|
|
18
18
|
"EQUREG": "equity",
|
|
19
19
|
"FNDETF": "etf",
|
|
20
|
-
"OTHOTH":
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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 ""
|
wbportfolio/models/portfolio.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
|
@@ -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)
|
|
@@ -371,10 +371,22 @@ class TradeTradeProposalDisplayConfig(DisplayViewConfig):
|
|
|
371
371
|
style={"color": WBColor.RED_DARK.value, "fontWeight": "bold"},
|
|
372
372
|
condition=("==", Trade.Type.SELL.name),
|
|
373
373
|
),
|
|
374
|
+
dp.FormattingRule(
|
|
375
|
+
style={"color": WBColor.RED_DARK.value, "fontWeight": "bold"},
|
|
376
|
+
condition=("==", Trade.Type.DECREASE.name),
|
|
377
|
+
),
|
|
378
|
+
dp.FormattingRule(
|
|
379
|
+
style={"color": WBColor.GREEN_DARK.value, "fontWeight": "bold"},
|
|
380
|
+
condition=("==", Trade.Type.INCREASE.name),
|
|
381
|
+
),
|
|
374
382
|
dp.FormattingRule(
|
|
375
383
|
style={"color": WBColor.GREEN_DARK.value, "fontWeight": "bold"},
|
|
376
384
|
condition=("==", Trade.Type.BUY.name),
|
|
377
385
|
),
|
|
386
|
+
dp.FormattingRule(
|
|
387
|
+
style={"color": WBColor.GREY.value, "fontWeight": "bold"},
|
|
388
|
+
condition=("==", Trade.Type.NO_CHANGE.name),
|
|
389
|
+
),
|
|
378
390
|
],
|
|
379
391
|
width=Unit.PIXEL(125),
|
|
380
392
|
),
|
|
@@ -383,7 +383,7 @@ class ConsolidatedTradeSummaryTableView(ClaimPermissionMixin, ExportPandasAPIVie
|
|
|
383
383
|
pf.PKField(key="id", label="ID"),
|
|
384
384
|
pf.CharField(key="title", label="Title"),
|
|
385
385
|
pf.DateField(key="initial_investment_date", label="First Investment"),
|
|
386
|
-
pf.SparklineField(key="aum_sparkline", label="AUM"),
|
|
386
|
+
pf.SparklineField(key="aum_sparkline", label="AUM", dimension="double"),
|
|
387
387
|
pf.FloatField(key="sum_shares_start", label="Shares Start", precision=0),
|
|
388
388
|
pf.FloatField(key="sum_shares_end", label="Shares End", precision=0),
|
|
389
389
|
pf.FloatField(key="sum_shares_diff", label="Share difference", precision=0),
|
|
@@ -114,7 +114,7 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
|
|
|
114
114
|
def replay(self, request, pk=None):
|
|
115
115
|
trade_proposal = get_object_or_404(TradeProposal, pk=pk)
|
|
116
116
|
if trade_proposal.portfolio.is_manageable:
|
|
117
|
-
replay_as_task.delay(trade_proposal.id)
|
|
117
|
+
replay_as_task.delay(trade_proposal.id, user_id=self.request.user.id)
|
|
118
118
|
return Response({"send": True})
|
|
119
119
|
return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
120
120
|
|