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
|
@@ -51,8 +51,8 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
51
51
|
data["portfolio"] = trade_proposal.portfolio
|
|
52
52
|
data["status"] = "DRAFT"
|
|
53
53
|
else:
|
|
54
|
-
if
|
|
55
|
-
data["
|
|
54
|
+
if external_id_alternative := data.get("external_id_alternative", None):
|
|
55
|
+
data["external_id_alternative"] = str(external_id_alternative)
|
|
56
56
|
if transaction_date_str := data.get("transaction_date", None):
|
|
57
57
|
data["transaction_date"] = datetime.strptime(transaction_date_str, "%Y-%m-%d").date()
|
|
58
58
|
if value_date_str := data.get("value_date", None):
|
|
@@ -92,7 +92,6 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
92
92
|
def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
|
|
93
93
|
if "transaction_date" not in data: # we might get only book date and not transaction date
|
|
94
94
|
data["transaction_date"] = data["book_date"]
|
|
95
|
-
|
|
96
95
|
return self.model.objects.create(**data, import_source=self.import_source)
|
|
97
96
|
|
|
98
97
|
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
@@ -115,17 +114,16 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
115
114
|
)
|
|
116
115
|
if "shares" in data:
|
|
117
116
|
queryset = queryset.filter(shares=data["shares"])
|
|
118
|
-
|
|
119
117
|
if _id := data.get("id", None):
|
|
120
118
|
self.import_source.log += f"ID {_id} provided -> Load CustomerTrade"
|
|
121
119
|
return self.model.objects.get(id=_id)
|
|
122
120
|
# We need to check for external identifiers
|
|
123
121
|
if external_id := data.get("external_id"):
|
|
124
122
|
self.import_source.log += f"\nExternal Identifier used: {external_id}"
|
|
125
|
-
|
|
126
|
-
if
|
|
123
|
+
external_id_queryset = queryset.filter(external_id=external_id)
|
|
124
|
+
if external_id_queryset.count() == 1:
|
|
127
125
|
self.import_source.log += f"External ID {external_id} provided -> Load CustomerTrade"
|
|
128
|
-
return
|
|
126
|
+
return external_id_queryset.first()
|
|
129
127
|
|
|
130
128
|
if portfolio := data.get("portfolio", None):
|
|
131
129
|
queryset = queryset.filter(portfolio=portfolio)
|
|
@@ -34,8 +34,8 @@ def parse(import_source):
|
|
|
34
34
|
isin = fee_data["ISIN"]
|
|
35
35
|
fee_date = fee_data["Date"]
|
|
36
36
|
base_data = {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
37
|
+
"product": {"isin": isin},
|
|
38
|
+
"fee_date": fee_date.strftime("%Y-%m-%d"),
|
|
39
39
|
"calculated": False,
|
|
40
40
|
}
|
|
41
41
|
data.append(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from contextlib import suppress
|
|
2
|
+
from io import BytesIO
|
|
2
3
|
|
|
3
4
|
import pandas as pd
|
|
4
5
|
|
|
@@ -10,7 +11,7 @@ def parse(import_source):
|
|
|
10
11
|
df = pd.DataFrame()
|
|
11
12
|
try:
|
|
12
13
|
df = pd.read_excel(
|
|
13
|
-
import_source.file.read(),
|
|
14
|
+
BytesIO(import_source.file.read()),
|
|
14
15
|
engine="openpyxl",
|
|
15
16
|
index_col=1,
|
|
16
17
|
sheet_name="TRANSACTION CONSOLIDATION",
|
|
@@ -25,7 +26,8 @@ def parse(import_source):
|
|
|
25
26
|
with suppress(Product.DoesNotExist):
|
|
26
27
|
product = Product.objects.get(isin=trade["AMC ISIN"])
|
|
27
28
|
shares = (-1 * trade["TOTAL QUANTITY"]) / product.share_price
|
|
28
|
-
price = trade["
|
|
29
|
+
price = trade["NET PRICE"] * product.share_price
|
|
30
|
+
price_gross = trade["GROSS PRICE"] * product.share_price
|
|
29
31
|
portfolio = product.primary_portfolio
|
|
30
32
|
data.append(
|
|
31
33
|
{
|
|
@@ -38,10 +40,8 @@ def parse(import_source):
|
|
|
38
40
|
"bank": "Leonteq Cash Transfer",
|
|
39
41
|
"currency__key": product.currency.key,
|
|
40
42
|
"price": price,
|
|
41
|
-
"
|
|
42
|
-
"total_value_gross": trade["GROSS AMOUNT"],
|
|
43
|
+
"price_gross": price_gross,
|
|
43
44
|
"currency_fx_rate": trade["BOOKING FX"],
|
|
44
|
-
"total_value_fx_portfolio": trade["BOOKING NET AMOUNT"],
|
|
45
45
|
}
|
|
46
46
|
)
|
|
47
47
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
|
|
1
3
|
import pandas as pd
|
|
2
4
|
|
|
3
5
|
from wbportfolio.models import Fees
|
|
@@ -8,7 +10,11 @@ def parse(import_source):
|
|
|
8
10
|
df = pd.DataFrame()
|
|
9
11
|
try:
|
|
10
12
|
df = pd.read_excel(
|
|
11
|
-
import_source.file.read(),
|
|
13
|
+
BytesIO(import_source.file.read()),
|
|
14
|
+
engine="openpyxl",
|
|
15
|
+
sheet_name="FEE_CONSOLIDATION",
|
|
16
|
+
header=[0, 2],
|
|
17
|
+
skiprows=1,
|
|
12
18
|
).dropna(axis=1)
|
|
13
19
|
except (ValueError, IndexError):
|
|
14
20
|
pass
|
|
@@ -19,15 +25,13 @@ def parse(import_source):
|
|
|
19
25
|
|
|
20
26
|
product_fees_df = product_fees_df.rename(
|
|
21
27
|
columns={
|
|
22
|
-
"TRADE DATE": "
|
|
28
|
+
"TRADE DATE": "fee_date",
|
|
23
29
|
"INDEX SPONSOR FEE": "total_value",
|
|
24
30
|
"BUSINESS EVENT": "transaction_subtype",
|
|
25
31
|
}
|
|
26
32
|
)
|
|
27
33
|
|
|
28
|
-
product_fees_df.loc[:, "
|
|
29
|
-
product_fees_df.loc[:, "transaction_date"], dayfirst=True
|
|
30
|
-
)
|
|
34
|
+
product_fees_df.loc[:, "fee_date"] = pd.to_datetime(product_fees_df.loc[:, "fee_date"], dayfirst=True)
|
|
31
35
|
|
|
32
36
|
management_fees_eur = product_fees_df[
|
|
33
37
|
product_fees_df.loc[:, "transaction_subtype"] == "Management Fee EUR"
|
|
@@ -50,8 +54,8 @@ def parse(import_source):
|
|
|
50
54
|
product_fees_df = product_fees_df.where(pd.notnull(product_fees_df), None)
|
|
51
55
|
for fees in product_fees_df.to_dict("records"):
|
|
52
56
|
base_data = {
|
|
53
|
-
"
|
|
54
|
-
"
|
|
57
|
+
"product": {"isin": isin},
|
|
58
|
+
"fee_date": fees["fee_date"].strftime("%Y-%m-%d"),
|
|
55
59
|
"calculated": False,
|
|
56
60
|
}
|
|
57
61
|
|
|
@@ -24,11 +24,8 @@ _fields_map = {
|
|
|
24
24
|
"TOTAL QUANTITY": "shares",
|
|
25
25
|
"GROSS PRICE": "price_gross",
|
|
26
26
|
"NET PRICE": "price",
|
|
27
|
-
"total_value_gross": "total_value_gross",
|
|
28
27
|
"currency_fx_rate": "currency_fx_rate",
|
|
29
28
|
"NET AMOUNT": "total_value",
|
|
30
|
-
"GROSS AMOUNT": "total_value_gross_fx_portfolio",
|
|
31
|
-
"total_value_fx_portfolio": "total_value_fx_portfolio",
|
|
32
29
|
"N°": "external_id",
|
|
33
30
|
}
|
|
34
31
|
|
|
@@ -78,8 +75,6 @@ def parse(import_source):
|
|
|
78
75
|
df["exchange"] = df["IDENTIFIER"].apply(lambda x: _get_exchange(x))
|
|
79
76
|
df["currency_fx_rate"] = df["BOOKING FX"] * df["BASE CCY FX"]
|
|
80
77
|
|
|
81
|
-
df["total_value_gross"] = df["total_value_gross_fx_portfolio"] / df["BOOKING FX"]
|
|
82
|
-
df["total_value_fx_portfolio"] = df["total_value"] * df["currency_fx_rate"]
|
|
83
78
|
df = df.drop(df.columns.difference(_fields_map.values()), axis=1)
|
|
84
79
|
df["portfolio"] = product.primary_portfolio.id
|
|
85
80
|
|
|
@@ -37,8 +37,8 @@ def parse(import_source):
|
|
|
37
37
|
management_fees = row[management_fees_column].value
|
|
38
38
|
|
|
39
39
|
base_data = {
|
|
40
|
-
"
|
|
41
|
-
"
|
|
40
|
+
"product": {"isin": isin},
|
|
41
|
+
"fee_date": valuation_date.strftime("%Y-%m-%d"),
|
|
42
42
|
"calculated": False,
|
|
43
43
|
}
|
|
44
44
|
if management_fees != 0:
|
|
@@ -10,10 +10,9 @@ COLUMN_MAP = {
|
|
|
10
10
|
"Div Crncy": "currency__key",
|
|
11
11
|
"Quantity": "shares",
|
|
12
12
|
"Fx Rate": "currency_fx_rate",
|
|
13
|
-
"Net Amount": "total_value_fx_portfolio",
|
|
14
13
|
"Retro in%": "retrocession",
|
|
15
14
|
"Gross Div": "price_gross",
|
|
16
|
-
"Ex Div Date": "
|
|
15
|
+
"Ex Div Date": "ex_date",
|
|
17
16
|
"Value Date": "value_date",
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -29,25 +28,21 @@ def parse(import_source):
|
|
|
29
28
|
lambda x: _get_exchange_from_ticker(x)
|
|
30
29
|
)
|
|
31
30
|
df["underlying_instrument__ticker"] = df["underlying_instrument__ticker"].apply(lambda x: _get_ticker(x))
|
|
32
|
-
df["
|
|
31
|
+
df["ex_date"] = pd.to_datetime(df["ex_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
33
32
|
df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
34
33
|
df = df[df["underlying_instrument__isin"].str.contains("([A-Z]{2})([A-Z0-9]{9})([0-9]{1})", regex=True)]
|
|
35
|
-
float_columns = ["shares", "
|
|
34
|
+
float_columns = ["shares", "price_gross", "currency_fx_rate", "retrocession"]
|
|
36
35
|
for float_column in float_columns:
|
|
37
36
|
df[float_column] = df[float_column].str.replace(" ", "").astype("float")
|
|
38
37
|
df = df.drop(columns=df.columns.difference(COLUMN_MAP.values()))
|
|
39
38
|
|
|
40
39
|
df["retrocession"] = df["retrocession"] / 100.0
|
|
41
|
-
df["total_value"] = df["total_value_fx_portfolio"] / df["currency_fx_rate"]
|
|
42
|
-
df["price"] = df["total_value"] / df["shares"] / df["retrocession"]
|
|
43
|
-
df["total_value_gross"] = df["price_gross"] * df["shares"] * df["retrocession"]
|
|
44
|
-
df["total_value_gross_fx_portfolio"] = df["total_value_gross"] * df["currency_fx_rate"]
|
|
45
40
|
df["portfolio"] = [{"instrument_type": "product", **product_data}] * df.shape[0]
|
|
46
41
|
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
47
42
|
return {
|
|
48
43
|
"data": df.to_dict("records"),
|
|
49
44
|
"history": {
|
|
50
|
-
"
|
|
45
|
+
"value_date": parts["valuation_date"].strftime("%Y-%m-%d"),
|
|
51
46
|
"product": product_data,
|
|
52
47
|
},
|
|
53
48
|
}
|
|
@@ -4,7 +4,7 @@ from pandas.tseries.offsets import BDay
|
|
|
4
4
|
from .utils import file_name_parse_isin
|
|
5
5
|
|
|
6
6
|
FIELD_MAP = {
|
|
7
|
-
"Date": "
|
|
7
|
+
"Date": "fee_date",
|
|
8
8
|
"Manag. Fees Natixis": "ISSUER",
|
|
9
9
|
"Manag. Fees Client": "MANAGEMENT",
|
|
10
10
|
"Perf fees amount": "PERFORMANCE",
|
|
@@ -21,30 +21,28 @@ def parse(import_source):
|
|
|
21
21
|
df = pd.read_csv(import_source.file, encoding="utf-8", delimiter=";")
|
|
22
22
|
df = df.rename(columns=FIELD_MAP)
|
|
23
23
|
df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
|
|
24
|
-
df["
|
|
24
|
+
df["fee_date"] = pd.to_datetime(df["fee_date"], dayfirst=True)
|
|
25
25
|
|
|
26
26
|
# Switch the weeekend day fees to the next monday
|
|
27
|
-
df["
|
|
27
|
+
df["fee_date"] = df["fee_date"].apply(lambda x: x + BDay(1) if x.weekday() in [5, 6] else x)
|
|
28
28
|
|
|
29
29
|
# Ensure float columns are number
|
|
30
30
|
for col in ["MANAGEMENT", "ISSUER", "PERFORMANCE"]:
|
|
31
31
|
df[col] = df[col].astype("str").str.replace(" ", "").astype("float")
|
|
32
32
|
|
|
33
33
|
# Groupby and sum similar fees (e.g. Monday)
|
|
34
|
-
df = df.groupby("
|
|
34
|
+
df = df.groupby("fee_date").sum().reset_index()
|
|
35
35
|
df = pd.melt(
|
|
36
36
|
df,
|
|
37
|
-
id_vars=["
|
|
37
|
+
id_vars=["fee_date"],
|
|
38
38
|
value_vars=["MANAGEMENT", "ISSUER", "PERFORMANCE"],
|
|
39
39
|
var_name="transaction_subtype",
|
|
40
40
|
value_name="total_value",
|
|
41
41
|
)
|
|
42
42
|
df = df[df["total_value"] != 0]
|
|
43
43
|
|
|
44
|
-
df["
|
|
45
|
-
df["
|
|
44
|
+
df["product"] = [product] * df.shape[0]
|
|
45
|
+
df["fee_date"] = df["fee_date"].dt.strftime("%Y-%m-%d")
|
|
46
46
|
df["calculated"] = False
|
|
47
47
|
df["total_value_gross"] = df["total_value"]
|
|
48
|
-
df["total_value_fx_portfolio"] = df["total_value"]
|
|
49
|
-
df["total_value_gross_fx_portfolio"] = df["total_value"]
|
|
50
48
|
return {"data": df.to_dict("records")}
|
|
@@ -129,7 +129,7 @@ def parse(import_source):
|
|
|
129
129
|
)
|
|
130
130
|
|
|
131
131
|
df["bank"] = df["REGISTER_DEAL_NAME"]
|
|
132
|
-
df["
|
|
132
|
+
df["external_id_alternative"] = df["NORDER"]
|
|
133
133
|
df["register__register_reference"] = df["REGISTER_ID1"]
|
|
134
134
|
df["external_id"] = df.apply(assemble_transaction_reference, axis=1)
|
|
135
135
|
|
|
@@ -40,12 +40,12 @@ def parse(import_source):
|
|
|
40
40
|
date = datetime.datetime.strptime(fee_data["NAV Date"], "%Y/%m/%d")
|
|
41
41
|
data.append(
|
|
42
42
|
{
|
|
43
|
-
"
|
|
43
|
+
"product": {
|
|
44
44
|
"parent__identifier": fee_data["Code"],
|
|
45
45
|
"currency__key": fee_data["Local ccy"],
|
|
46
46
|
"identifier": share_class,
|
|
47
47
|
},
|
|
48
|
-
"
|
|
48
|
+
"fee_date": date.strftime("%Y-%m-%d"),
|
|
49
49
|
"calculated": False,
|
|
50
50
|
"transaction_subtype": Fees.Type.MANAGEMENT,
|
|
51
51
|
"total_value": round(convert_string_to_number(fee_data.get("Fees - Local ccy", 0)), 6),
|
|
@@ -26,7 +26,7 @@ def parse(import_source):
|
|
|
26
26
|
date = datetime.datetime.strptime(fee_data[0], "%m/%d/%Y")
|
|
27
27
|
data.append(
|
|
28
28
|
{
|
|
29
|
-
"
|
|
29
|
+
"product": {"isin": isin},
|
|
30
30
|
"transaction_date": date.strftime("%Y-%m-%d"),
|
|
31
31
|
"calculated": False,
|
|
32
32
|
"transaction_subtype": Fees.Type.PERFORMANCE,
|
|
@@ -35,7 +35,7 @@ def parse(import_source):
|
|
|
35
35
|
)
|
|
36
36
|
data.append(
|
|
37
37
|
{
|
|
38
|
-
"
|
|
38
|
+
"product": {"isin": isin},
|
|
39
39
|
"transaction_date": date.strftime("%Y-%m-%d"),
|
|
40
40
|
"calculated": False,
|
|
41
41
|
"transaction_subtype": Fees.Type.PERFORMANCE_CRYSTALIZED,
|
|
@@ -17,7 +17,7 @@ def parse_transaction_reference(trans_ref: str) -> tuple[str, str, bool]:
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def assemble_transaction_reference(data: Dict[str, Any]) -> str:
|
|
20
|
-
transaction_ref = data["
|
|
20
|
+
transaction_ref = data["external_id_alternative"]
|
|
21
21
|
register_reference = data["register__register_reference"]
|
|
22
22
|
if data.get("TRANSFER_REGISTER", None):
|
|
23
23
|
register_reference = data["TRANSFER_REGISTER"]
|
|
@@ -29,7 +29,7 @@ def assemble_transaction_reference(data: Dict[str, Any]) -> str:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def create_transaction_reference(customer_trade: "Trade") -> str:
|
|
32
|
-
transaction_id = customer_trade.
|
|
32
|
+
transaction_id = customer_trade.external_id_alternative
|
|
33
33
|
outlet_id = customer_trade.register.clearing_reference
|
|
34
34
|
credit_debit = "C" if customer_trade.initial_shares > 0 else "D"
|
|
35
35
|
|
|
@@ -4,14 +4,14 @@ import pandas as pd
|
|
|
4
4
|
|
|
5
5
|
from wbportfolio.models import Fees
|
|
6
6
|
|
|
7
|
-
BASE_MAPPING = {"managementFee": "total_value", "performanceFee": "total_value", "date": "
|
|
7
|
+
BASE_MAPPING = {"managementFee": "total_value", "performanceFee": "total_value", "date": "fee_date"}
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def parse(import_source):
|
|
11
11
|
def _process_df(df, product_isin):
|
|
12
12
|
df = df.rename(columns=BASE_MAPPING).dropna(how="all", axis=1)
|
|
13
13
|
df = df.drop(columns=df.columns.difference(BASE_MAPPING.values()))
|
|
14
|
-
df["
|
|
14
|
+
df["product"] = [{"isin": product_isin}] * df.shape[0]
|
|
15
15
|
return df
|
|
16
16
|
|
|
17
17
|
content = json.load(import_source.file)
|
|
@@ -11,10 +11,9 @@ FIELD_MAP = {
|
|
|
11
11
|
"Trade_Date": "book_date",
|
|
12
12
|
"Value_Date": "value_date",
|
|
13
13
|
"Price": "price",
|
|
14
|
-
"Quantity": "total_value",
|
|
15
14
|
"Currency": "currency__key",
|
|
16
15
|
"Portfolio_Identifier": "underlying_instrument__identifier",
|
|
17
|
-
"External_Reference": "
|
|
16
|
+
"External_Reference": "external_id_alternative",
|
|
18
17
|
"Booking_Comment": "comment",
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -36,8 +35,8 @@ def parse(import_source):
|
|
|
36
35
|
data = []
|
|
37
36
|
if not df.empty:
|
|
38
37
|
df = df.rename(columns=FIELD_MAP)
|
|
38
|
+
df["shares"] = df["Quantity"] / df["price"]
|
|
39
39
|
df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
|
|
40
|
-
df["shares"] = df["total_value"] / df["price"]
|
|
41
40
|
df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
42
41
|
df["book_date"] = pd.to_datetime(df["book_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
43
42
|
df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
@@ -11,7 +11,7 @@ FIELD_MAP = {
|
|
|
11
11
|
"Value_Date": "value_date",
|
|
12
12
|
"Quantity": "total_value",
|
|
13
13
|
"Currency": "currency__key",
|
|
14
|
-
"Portfolio_Identifier": "
|
|
14
|
+
"Portfolio_Identifier": "product",
|
|
15
15
|
"Booking_Comment": "comment",
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -25,20 +25,20 @@ def parse(import_source):
|
|
|
25
25
|
data = []
|
|
26
26
|
if not df.empty:
|
|
27
27
|
df = df.rename(columns=FIELD_MAP)
|
|
28
|
-
df
|
|
28
|
+
df["product"] = df["product"].apply(lambda x: Product.objects.filter(identifier=x).first())
|
|
29
29
|
df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True)
|
|
30
30
|
df["book_date"] = pd.to_datetime(df["book_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
|
-
df["portfolio"] = df.
|
|
32
|
+
df["portfolio"] = df["product"].apply(lambda x: x.primary_portfolio if x else None)
|
|
33
33
|
df["base_management_fees"] = df.apply(
|
|
34
34
|
lambda row: float(
|
|
35
|
-
row["
|
|
35
|
+
row["product"].get_fees_percent(row["transaction_date"], FeeProductPercentage.Type.MANAGEMENT)
|
|
36
36
|
),
|
|
37
37
|
axis=1,
|
|
38
38
|
)
|
|
39
39
|
df["base_bank_fees"] = df.apply(
|
|
40
40
|
lambda row: float(
|
|
41
|
-
row["
|
|
41
|
+
row["product"].get_fees_percent(row["transaction_date"], FeeProductPercentage.Type.BANK)
|
|
42
42
|
),
|
|
43
43
|
axis=1,
|
|
44
44
|
)
|
|
@@ -63,8 +63,8 @@ def parse(import_source):
|
|
|
63
63
|
df = pd.concat([df_management, df_bank], axis=0)
|
|
64
64
|
df = df.drop(columns=df.columns.difference(["transaction_subtype", *FIELD_MAP.values()]))
|
|
65
65
|
|
|
66
|
-
df["
|
|
67
|
-
df = df.dropna(subset=["
|
|
66
|
+
df["product"] = df["product"].apply(lambda x: x.id)
|
|
67
|
+
df = df.dropna(subset=["product"])
|
|
68
68
|
|
|
69
69
|
for row in df.to_dict("records"):
|
|
70
70
|
# The fee are sometime aggregated. The aggregate date range information is given in the comment. We therefore try to extract it and duplicate the averaged fees
|
|
@@ -10,7 +10,7 @@ FIELD_MAP = {
|
|
|
10
10
|
"AccrValueInstrCcy": "total_value",
|
|
11
11
|
"FxRate": "currency_fx_rate",
|
|
12
12
|
"TradingCurrency": "currency__key",
|
|
13
|
-
"#ClientName": "
|
|
13
|
+
"#ClientName": "product",
|
|
14
14
|
"InstrumentName": "comment",
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -29,7 +29,7 @@ def parse(import_source):
|
|
|
29
29
|
df["transaction_subtype"] = Fees.Type.MANAGEMENT
|
|
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
|
-
df = df.dropna(subset=["
|
|
33
|
-
df.
|
|
32
|
+
df = df.dropna(subset=["product"])
|
|
33
|
+
df.product = df["product"].apply(lambda x: {"identifier": x})
|
|
34
34
|
data = df.to_dict("records")
|
|
35
35
|
return {"data": data}
|
|
@@ -21,7 +21,7 @@ claims AS (
|
|
|
21
21
|
JOIN wbcrm_account AS account
|
|
22
22
|
ON claim.account_id = account.id
|
|
23
23
|
JOIN wbportfolio_trade AS trade
|
|
24
|
-
ON claim.trade_id = trade.
|
|
24
|
+
ON claim.trade_id = trade.id
|
|
25
25
|
WHERE claim.status = 'APPROVED'
|
|
26
26
|
),
|
|
27
27
|
-- CTE for claims, that fetches them with their shares and price at transaction date
|
|
@@ -35,7 +35,7 @@ claims_with_nav AS (
|
|
|
35
35
|
trade.marked_as_internal as marked_as_internal
|
|
36
36
|
FROM wbportfolio_claim AS claims
|
|
37
37
|
JOIN wbportfolio_trade AS trade
|
|
38
|
-
ON claims.trade_id = trade.
|
|
38
|
+
ON claims.trade_id = trade.id
|
|
39
39
|
LEFT JOIN wbcrm_account AS accounts
|
|
40
40
|
ON claims.account_id = accounts.id
|
|
41
41
|
LEFT JOIN LATERAL (
|
|
@@ -45,7 +45,7 @@ class Migration(migrations.Migration):
|
|
|
45
45
|
migrations.AddConstraint(
|
|
46
46
|
model_name="fees",
|
|
47
47
|
constraint=models.UniqueConstraint(
|
|
48
|
-
fields=("
|
|
48
|
+
fields=("product", "fee_date", "transaction_subtype", "calculated"), name="unique_fees"
|
|
49
49
|
),
|
|
50
50
|
),
|
|
51
51
|
]
|