wbportfolio 2.2.3__py2.py3-none-any.whl → 2.2.5__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.

@@ -0,0 +1,40 @@
1
+ from datetime import datetime
2
+ from io import BytesIO
3
+ from typing import Optional
4
+
5
+ from django.db import models
6
+ from pandas.tseries.offsets import BDay
7
+ from wbcore.contrib.io.backends import AbstractDataBackend, register
8
+ from wbfdm.import_export.backends.refinitiv.utils import Controller
9
+ from wbportfolio.import_export.backends.utils import (
10
+ get_timedelta_import_instrument_price,
11
+ )
12
+
13
+ from ..wbfdm.mixin import DataBackendMixin
14
+
15
+ DEFAULT_MAPPING = {"AX": "factor"}
16
+
17
+
18
+ @register("Adjustment", provider_key="refinitiv")
19
+ class DataBackend(DataBackendMixin, AbstractDataBackend):
20
+ CHUNK_SIZE = 50
21
+
22
+ def __init__(self, import_credential: Optional[models.Model] = None, **kwargs):
23
+ self.controller = Controller(import_credential.username, import_credential.password)
24
+
25
+ def get_files(
26
+ self,
27
+ execution_time: datetime,
28
+ obj_external_ids: list[str] = None,
29
+ **kwargs,
30
+ ) -> BytesIO:
31
+ execution_date = execution_time.date()
32
+ start = kwargs.get("start", (execution_date - BDay(get_timedelta_import_instrument_price())).date())
33
+ fields = list(DEFAULT_MAPPING.keys())
34
+ if obj_external_ids:
35
+ df = self.controller.get_data(obj_external_ids, fields, start, execution_date)
36
+ if not df.empty:
37
+ content_file = BytesIO()
38
+ df.to_json(content_file, orient="records")
39
+ file_name = f"adjustment_chunk-{start:%Y-%m-%d}-{execution_date:%Y-%m-%d}_{datetime.timestamp(execution_time)}.json"
40
+ yield file_name, content_file
@@ -0,0 +1,163 @@
1
+ from decimal import Decimal
2
+ from typing import Type
3
+
4
+ from django.db.models import Exists, OuterRef, Sum
5
+ from django.db.models.expressions import F
6
+ from django.db.models.functions import Coalesce
7
+ from django.db.models.query import QuerySet
8
+ from wbcore import serializers
9
+ from wbcore.contrib.currency.models import CurrencyFXRates
10
+ from wbcore.serializers.serializers import Serializer
11
+ from wbcrm.models import Account
12
+ from wbhuman_resources.models.kpi import KPI, KPIHandler
13
+ from wbhuman_resources.serializers import KPIModelSerializer
14
+ from wbportfolio.models.transactions.claim import Claim
15
+
16
+
17
+ class NetNewMoneyKPISerializer(KPIModelSerializer):
18
+ transaction_subtype = serializers.ChoiceField(
19
+ default="all",
20
+ choices=[("SUBSCRIPTION", "Only Subscriptions"), ("REDEMPTION", "Only Redemptions"), ("all", "All")],
21
+ )
22
+ nnm_from = serializers.ChoiceField(
23
+ default="all",
24
+ label="NNM from",
25
+ choices=[("new_clients", "New clients"), ("existing_clients", "Existing clients"), ("all", "All")],
26
+ )
27
+ only_approved = serializers.BooleanField(
28
+ default=False, label="Only Approved claims", help_text="Filter only approve claims"
29
+ )
30
+
31
+ creator = serializers.BooleanField(
32
+ default=True, label="Creator of claim", help_text="NNM considered are related to the creator of claim"
33
+ )
34
+ claimant = serializers.BooleanField(
35
+ default=True, label="Claimant of claim", help_text="NNM considered are related to the claimant"
36
+ )
37
+ in_charge_of_customer = serializers.BooleanField(
38
+ default=True,
39
+ label="In charge of customer",
40
+ help_text="NNM considered are related to the persons in charge of customer",
41
+ )
42
+
43
+ def update(self, instance, validated_data):
44
+ transaction_subtype = validated_data.get(
45
+ "transaction_subtype",
46
+ instance.additional_data["serializer_data"].get("transaction_subtype", "all"),
47
+ )
48
+ only_approved = validated_data.get(
49
+ "only_approved",
50
+ instance.additional_data["serializer_data"].get("only_approved", False),
51
+ )
52
+ nnm_from = validated_data.get(
53
+ "nnm_from",
54
+ instance.additional_data["serializer_data"].get("nnm_from", "all"),
55
+ )
56
+
57
+ creator = validated_data.get(
58
+ "creator",
59
+ instance.additional_data["serializer_data"].get("creator", True),
60
+ )
61
+ claimant = validated_data.get(
62
+ "claimant",
63
+ instance.additional_data["serializer_data"].get("claimant", True),
64
+ )
65
+ in_charge_of_customer = validated_data.get(
66
+ "in_charge_of_customer",
67
+ instance.additional_data["serializer_data"].get("in_charge_of_customer", True),
68
+ )
69
+
70
+ additional_data = instance.additional_data
71
+ additional_data["serializer_data"]["only_approved"] = only_approved
72
+ additional_data["serializer_data"]["transaction_subtype"] = transaction_subtype
73
+ additional_data["serializer_data"]["nnm_from"] = nnm_from
74
+ additional_data["serializer_data"]["creator"] = creator
75
+ additional_data["serializer_data"]["claimant"] = claimant
76
+ additional_data["serializer_data"]["in_charge_of_customer"] = in_charge_of_customer
77
+
78
+ additional_data["list_data"] = instance.get_handler().get_list_data(additional_data["serializer_data"])
79
+ validated_data["additional_data"] = additional_data
80
+
81
+ return super().update(instance, validated_data)
82
+
83
+ class Meta(KPIModelSerializer.Meta):
84
+ fields = (
85
+ *KPIModelSerializer.Meta.fields,
86
+ "transaction_subtype",
87
+ "nnm_from",
88
+ "creator",
89
+ "claimant",
90
+ "in_charge_of_customer",
91
+ "only_approved",
92
+ )
93
+
94
+
95
+ class NetNewMoneyKPI(KPIHandler):
96
+ def get_name(self) -> str:
97
+ return "Net New Money"
98
+
99
+ def get_serializer(self) -> Type[Serializer]:
100
+ return NetNewMoneyKPISerializer
101
+
102
+ def annotate_parameters(self, queryset: QuerySet[KPI]) -> QuerySet[KPI]:
103
+ return queryset.annotate(
104
+ transaction_subtype=F("additional_data__serializer_data__transaction_subtype"),
105
+ nnm_from=F("additional_data__serializer_data__nnm_from"),
106
+ creator=F("additional_data__serializer_data__creator"),
107
+ claimant=F("additional_data__serializer_data__claimant"),
108
+ in_charge_of_customer=F("additional_data__serializer_data__in_charge_of_customer"),
109
+ only_approved=F("additional_data__serializer_data__only_approved"),
110
+ )
111
+
112
+ def get_list_data(self, serializer_data: dict) -> list[str]:
113
+ return [
114
+ f"Claim Area: {serializer_data['transaction_subtype']}",
115
+ f"NNM from: {serializer_data['nnm_from']}",
116
+ f"Creator: {serializer_data['creator']}",
117
+ f"Claimant: {serializer_data['claimant']}",
118
+ f"In charge of customer: {serializer_data['in_charge_of_customer']}",
119
+ f"Only Approved: {serializer_data['only_approved']}",
120
+ ]
121
+
122
+ def get_display_grid(self) -> list[list[str]]:
123
+ return [
124
+ ["nnm_from"] * 3,
125
+ ["only_approved"] * 3,
126
+ ["transaction_subtype"] * 3,
127
+ ["creator", "claimant", "in_charge_of_customer"],
128
+ ]
129
+
130
+ def evaluate(self, kpi: "KPI", evaluated_person, evaluation_date=None) -> int:
131
+ serializer_data = kpi.additional_data.get("serializer_data")
132
+ to_date = evaluation_date if evaluation_date else kpi.period.upper
133
+ claims = (
134
+ Claim.objects.exclude(status=Claim.Status.WITHDRAWN)
135
+ .filter(date__gte=kpi.period.lower, date__lte=to_date, account__isnull=False)
136
+ .annotate(
137
+ existing_client=Exists(Claim.objects.filter(date__lte=kpi.period.lower, account=OuterRef("account")))
138
+ )
139
+ )
140
+
141
+ if serializer_data.get("only_approved") is True:
142
+ claims = claims.filter(status=Claim.Status.APPROVED)
143
+
144
+ if (nnm_from := serializer_data.get("nnm_from")) and (nnm_from != "all"):
145
+ if nnm_from == "new_clients":
146
+ claims = claims.filter(existing_client=False)
147
+ elif nnm_from == "existing_clients":
148
+ claims = claims.filter(existing_client=True)
149
+
150
+ # TODO This introduced duplicates and can't be removed easily with distinct("id") because of the aggregation
151
+ accounts_ids = list(
152
+ Account.get_managed_accounts_for_entry(evaluated_person.entry_ptr).values_list("id", flat=True)
153
+ )
154
+ for employer in evaluated_person.employers.all():
155
+ accounts_ids.extend(list(Account.get_managed_accounts_for_entry(employer).values_list("id", flat=True)))
156
+ accounts = Account.objects.filter(id__in=accounts_ids)
157
+ claims = claims.filter(account__in=accounts)
158
+
159
+ return claims.annotate(
160
+ net_new_money=F("trade__price") * F("shares"),
161
+ fx_rate=CurrencyFXRates.get_fx_rates_subquery("date", currency="product__currency", lookup_expr="exact"),
162
+ net_new_money_usd=Coalesce(F("net_new_money") * F("fx_rate"), Decimal(0)),
163
+ ).aggregate(s=Sum("net_new_money"))["s"] or Decimal(0)
@@ -0,0 +1,58 @@
1
+ import json
2
+ from typing import TYPE_CHECKING
3
+
4
+ from django.db.models import F, FloatField, Sum
5
+ from django.db.models.functions import Cast, ExtractYear
6
+ from langchain_core.messages import HumanMessage, SystemMessage
7
+ from wbfdm.models import Instrument
8
+
9
+ if TYPE_CHECKING:
10
+ from langchain_core.messages import BaseMessage
11
+ from wbcrm.models import Account
12
+
13
+
14
+ def get_holding_prompt(account: "Account") -> list["BaseMessage"]:
15
+ from wbportfolio.models import Product
16
+ from wbportfolio.models.transactions.claim import Claim
17
+
18
+ products = (
19
+ Claim.get_valid_and_approved_claims(account=account)
20
+ .distinct("product")
21
+ .values_list("product", "product__isin")
22
+ )
23
+
24
+ performances = {}
25
+ for product_id, product_name in products:
26
+ performances[product_name] = Instrument.extract_annual_performance_df(
27
+ Product.objects.get(id=product_id).get_prices_df()
28
+ ).to_dict()
29
+
30
+ return [
31
+ SystemMessage(
32
+ "The following products are held by the account holder. Analyze their performances and check correlations between the holdings and their performances/interactions."
33
+ ),
34
+ HumanMessage(json.dumps(performances)),
35
+ ]
36
+
37
+
38
+ def get_performances_prompt(account: "Account") -> list["BaseMessage"]:
39
+ from wbportfolio.models.transactions.claim import Claim
40
+
41
+ holdings = (
42
+ Claim.get_valid_and_approved_claims(account=account)
43
+ .annotate(year=ExtractYear("date"))
44
+ .values("year", "product")
45
+ .annotate(
46
+ sum_shares=Cast(Sum("shares"), FloatField()),
47
+ product_name=F("product__name"),
48
+ product_isin=F("product__isin"),
49
+ )
50
+ .values("product_name", "product_isin", "sum_shares", "year")
51
+ )
52
+
53
+ return [
54
+ SystemMessage(
55
+ "The following holdings (subscriptions/redemptions) have been found for this account. Please include this data in the analysis and check if there is any correlation between the holding data and the interactions."
56
+ ),
57
+ HumanMessage(json.dumps(list(holdings.order_by("year", "product")))),
58
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wbportfolio
3
- Version: 2.2.3
3
+ Version: 2.2.5
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  Requires-Dist: cryptography==3.4.*
6
6
  Requires-Dist: dateparser==1.*
@@ -99,6 +99,7 @@ wbportfolio/import_export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
99
99
  wbportfolio/import_export/utils.py,sha256=mzybnwU2y-NOk43QDNDlOCaSPO-0BFlSX_X8uxplNiY,1083
100
100
  wbportfolio/import_export/backends/__init__.py,sha256=Ki5jE95usAbxfV3zeZMkAmjh7G8TodBkKTDBPa_HJX0,40
101
101
  wbportfolio/import_export/backends/utils.py,sha256=joFlyCrstrXSFb0RmQ4T7lfRVT7lxb7qe8TePis8wFU,2116
102
+ wbportfolio/import_export/backends/refinitiv/adjustment.py,sha256=ahVjWMHY1FuuDnMeascAlVUa3Mys6rvROG1uKDq6QeA,1554
102
103
  wbportfolio/import_export/backends/ubs/__init__.py,sha256=jHm6zfyZPfvdzyMfJGngGbwROxtYWyJdpQorpleVKMA,82
103
104
  wbportfolio/import_export/backends/ubs/asset_position.py,sha256=xN9VBsl8wdXDbfQOKS_tOlmlwuNqv-yGGg20QUBbavI,1901
104
105
  wbportfolio/import_export/backends/ubs/fees.py,sha256=wKGWyzmtvXloUYeMN6mkfoAwtn4qVqyw0OX3e8qGtrw,2863
@@ -190,6 +191,7 @@ wbportfolio/import_export/parsers/vontobel/valuation.py,sha256=iav8_xYpTJchmTa7K
190
191
  wbportfolio/import_export/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
191
192
  wbportfolio/import_export/resources/assets.py,sha256=mfQY2md1X4JgMxsvSMoPBKaYKmklZOaFFafqntOMswA,2248
192
193
  wbportfolio/import_export/resources/trades.py,sha256=gsF7SRL9JrCHSrCekndrEkKrLjTw1YXLltmyXYK2K0Y,1124
194
+ wbportfolio/kpi_handlers/nnm.py,sha256=hCn0oG0C-6dQ0G-6S4r31nAS633NZdlOT-ntZrzvXZI,7180
193
195
  wbportfolio/metric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
194
196
  wbportfolio/metric/backends/__init__.py,sha256=jfim5By5KqXfraENRFlhXy3i3gIzmElD6sfwL3qK2KM,108
195
197
  wbportfolio/metric/backends/base.py,sha256=FlKR00BHrvFEDHG_s1k1_YPyW2gJeJwcI8Bc1UilDys,3222
@@ -250,6 +252,7 @@ wbportfolio/models/products.py,sha256=d9gst_FnkNzkn4IGUnMJBe6MgyfwSRgAmnfep6dE1B
250
252
  wbportfolio/models/registers.py,sha256=nusTwh08YFIEPlRq-4Fuv1az72NxvBVaH7V9M_5lhyI,4666
251
253
  wbportfolio/models/roles.py,sha256=34BwZleaPMHnUqDK1nHett45xaNNsUqSHY44844itW8,7387
252
254
  wbportfolio/models/utils.py,sha256=wYAqgqIfBPafDY9CZWwSCDhSt0ucdyMTrvDwnrnk6H8,393
255
+ wbportfolio/models/llm/wbcrm/analyze_relationship.py,sha256=_y2Myc-M2hXQDkRGXvzsM0ZNC31dmxSHHz5BKMtymww,2106
253
256
  wbportfolio/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
254
257
  wbportfolio/models/mixins/instruments.py,sha256=oZoehE8gX0jquMgELZYce2C7p2GPgPC8dAuqOZgRcqY,5485
255
258
  wbportfolio/models/mixins/liquidity_stress_test.py,sha256=whkzjtbOyl_ncNyaQBORb_Z_rDgcvfdTYPgqPolu7dA,58865
@@ -480,7 +483,7 @@ wbportfolio/viewsets/transactions/mixins.py,sha256=i9ICaUXZfryIrbgS-bdCcoBJO-pTn
480
483
  wbportfolio/viewsets/transactions/trade_proposals.py,sha256=5mgAk60cINZHd2HfTu7StFOc882FGakNcIVZpNSwBCs,3528
481
484
  wbportfolio/viewsets/transactions/trades.py,sha256=_rZuPRDjf3MbwZ5xvdCgczCxcIbvI-RvyAf-50Y_Ga4,14744
482
485
  wbportfolio/viewsets/transactions/transactions.py,sha256=VgSBUs1g5etsXjt7zi4wQi82XwOGuHsd_zppnsbMZMs,4517
483
- wbportfolio-2.2.3.dist-info/METADATA,sha256=Fb8yJ_h0Irnn6uOwqGz08tZUdBdWO8v3Me9qa-VqhfI,622
484
- wbportfolio-2.2.3.dist-info/WHEEL,sha256=aO3RJuuiFXItVSnAUEmQ0yRBvv9e1sbJh68PtuQkyAE,105
485
- wbportfolio-2.2.3.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
486
- wbportfolio-2.2.3.dist-info/RECORD,,
486
+ wbportfolio-2.2.5.dist-info/METADATA,sha256=5BgM91I5D6LmcjXAmQi44idFcDIHHHgcvVIk4kpKYKg,622
487
+ wbportfolio-2.2.5.dist-info/WHEEL,sha256=aO3RJuuiFXItVSnAUEmQ0yRBvv9e1sbJh68PtuQkyAE,105
488
+ wbportfolio-2.2.5.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
489
+ wbportfolio-2.2.5.dist-info/RECORD,,