wbfdm 1.51.2rc1__py2.py3-none-any.whl → 1.51.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 wbfdm might be problematic. Click here for more details.

@@ -404,7 +404,6 @@ class StatementWithEstimates:
404
404
  "stock_compensation_employee_ratio",
405
405
  Financial.CAPEX.value,
406
406
  Financial.SHARES_OUTSTANDING.value,
407
- MarketData.MARKET_CAPITALIZATION.value,
408
407
  MarketData.CLOSE.value,
409
408
  MarketData.MARKET_CAPITALIZATION.value,
410
409
  "net_cash",
@@ -133,6 +133,12 @@ class Loader:
133
133
  self.errors["missing_data"].append(
134
134
  "We could not find any market data covering the financial statement period"
135
135
  )
136
+ ## TODO We might want to still exclude them from the final df but keep them for the estimate that used these
137
+ ## we actually want to keep the market data in the forecast column, because they are used for other statistic computation
138
+ # df.loc[df.index.get_level_values("estimate"), market_data_df.columns.difference(["period_end_date"])] = (
139
+ # None
140
+ # )
141
+
136
142
  return df.rename_axis("financial", axis="columns")
137
143
 
138
144
  def _annotate_statement_data(self, df: pd.DataFrame, statement_values: list[Financial]) -> pd.DataFrame:
@@ -171,7 +171,7 @@ class Dataloader(BaseDataloader):
171
171
  )
172
172
  fx_rate = df_price["fx_rate"]
173
173
  df = pd.concat([df_revenue, df_price.drop("fx_rate", axis=1)], axis=1)
174
- for key in ["revenue_y_1", "revenue_y0", "revenue_y1", "market_capitalization", "price"]:
174
+ for key in ["revenue_y_1", "revenue_y0", "revenue_y1", "market_capitalization"]:
175
175
  if key in df.columns:
176
176
  df[key] = df[key] / fx_rate
177
177
 
@@ -1,12 +1,15 @@
1
+ import logging
1
2
  from contextlib import suppress
3
+ from datetime import date, timedelta
2
4
  from typing import Callable
3
5
 
4
6
  import pytz
5
7
  from django.conf import settings
6
- from django.db import connections
8
+ from django.db import IntegrityError, connections
7
9
  from django.db.models import Max, QuerySet
8
10
  from django.template.loader import get_template
9
11
  from jinjasql import JinjaSql # type: ignore
12
+ from psycopg.errors import UniqueViolation
10
13
  from tqdm import tqdm
11
14
  from wbcore.contrib.currency.models import Currency
12
15
  from wbcore.contrib.dataloader.utils import dictfetchall, dictfetchone
@@ -15,6 +18,8 @@ from wbcore.utils.cache import mapping
15
18
  from wbfdm.models.exchanges.exchanges import Exchange
16
19
  from wbfdm.models.instruments.instruments import Instrument, InstrumentType
17
20
 
21
+ logger = logging.getLogger("pms")
22
+
18
23
  BATCH_SIZE: int = 10000
19
24
  instrument_type_map = {
20
25
  "ADR": "american_depository_receipt",
@@ -135,6 +140,36 @@ def get_instrument_from_data(data, parent_source: str | None = None) -> Instrume
135
140
  return instrument
136
141
 
137
142
 
143
+ def _delist_existing_duplicates(instrument: Instrument) -> None:
144
+ """Handle duplicate instruments by delisting existing entries"""
145
+ unique_identifiers = ["refinitiv_identifier_code", "refinitiv_mnemonic_code", "isin", "sedol", "valoren", "cusip"]
146
+
147
+ for identifier_field in unique_identifiers:
148
+ if identifier := getattr(instrument, identifier_field):
149
+ with suppress(Instrument.DoesNotExist):
150
+ duplicate = Instrument.objects.get(
151
+ is_security=True, delisted_date__isnull=True, **{identifier_field: identifier}
152
+ )
153
+ duplicate.delisted_date = date.today() - timedelta(days=1)
154
+ duplicate.save()
155
+
156
+
157
+ def _save_single_instrument(instrument: Instrument) -> None:
158
+ """Attempt to save an instrument with duplicate handling"""
159
+ try:
160
+ instrument.save()
161
+ except (UniqueViolation, IntegrityError) as e:
162
+ if instrument.is_security:
163
+ _delist_existing_duplicates(instrument)
164
+ try:
165
+ instrument.save()
166
+ logger.info(f"{instrument} successfully saved after automatic delisting")
167
+ except (UniqueViolation, IntegrityError) as e:
168
+ logger.error(f"Persistent integrity error: {e}")
169
+ else:
170
+ logger.error(f"Non-security instrument error: {e}")
171
+
172
+
138
173
  def _bulk_create_instruments_chunk(instruments: list[Instrument], update_unique_identifiers: bool = False):
139
174
  update_fields = [
140
175
  "name",
@@ -168,9 +203,17 @@ def _bulk_create_instruments_chunk(instruments: list[Instrument], update_unique_
168
203
  ]
169
204
  )
170
205
  bulk_update_kwargs = {"ignore_conflicts": True}
171
- Instrument.objects.bulk_create(
172
- instruments, update_fields=update_fields, unique_fields=["source", "source_id"], **bulk_update_kwargs
173
- )
206
+ try:
207
+ Instrument.objects.bulk_create(
208
+ instruments, update_fields=update_fields, unique_fields=["source", "source_id"], **bulk_update_kwargs
209
+ )
210
+ except IntegrityError:
211
+ # we caught an integrity error on the bulk save, so we try to save one by one
212
+ logger.info(
213
+ "we detected an integrity error while bulk saving instruments. We save them one by one and delist the already existing instrument from the db if we can. "
214
+ )
215
+ for instrument in instruments:
216
+ _save_single_instrument(instrument)
174
217
 
175
218
 
176
219
  def update_instruments(sql_name: str, parent_source: str | None = None, context=None, debug: bool = False, **kwargs):
@@ -211,7 +254,7 @@ def update_or_create_item(
211
254
  defaults=defaults,
212
255
  )
213
256
  instrument.dl_parameters.update(dl_parameters)
214
- instrument.save()
257
+ _save_single_instrument(instrument)
215
258
 
216
259
  return instrument
217
260
 
wbfdm/tasks.py CHANGED
@@ -84,6 +84,12 @@ def synchronize_exchanges_as_task():
84
84
 
85
85
  @shared_task(queue="portfolio")
86
86
  def full_synchronization_as_task():
87
+ # we get all instrument without name or where we would expect a parent and consider them for clean up.
88
+ qs = Instrument.objects.filter(prices__isnull=True).filter(
89
+ (Q(name="") & Q(name_repr="")) | (Q(source__in=["qa-ds2-security", "qa-ds2-quote"]) & Q(parent__isnull=True))
90
+ )
91
+ for instrument in qs:
92
+ instrument.delete()
87
93
  initialize_exchanges()
88
94
  initialize_instruments()
89
95
  with transaction.atomic():
@@ -54,7 +54,11 @@ class StatementPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
54
54
  pf.CharField(key="external_code", label="Code"),
55
55
  pf.CharField(key="external_description", label="Description"),
56
56
  pf.SparklineField(key="progress", label="Yearly Trend", dimension="double"),
57
- *[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
57
+ *[
58
+ pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED)
59
+ for field in self.columns
60
+ if field not in ["external_ordering", "external_code", "external_description", "progress"]
61
+ ],
58
62
  ]
59
63
  )
60
64
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbfdm
3
- Version: 1.51.2rc1
3
+ Version: 1.51.5
4
4
  Summary: The workbench module ensures rapid access to diverse financial data (market, fundamental, forecasts, ESG), with features for storing instruments, classifying them, and conducting financial analysis.
5
5
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
6
6
  Requires-Dist: roman==4.*
@@ -5,7 +5,7 @@ wbfdm/enums.py,sha256=5AuUouk5uuSNmRc6e-SiBu4FPmHVTN60ol9ftiuVrAc,33041
5
5
  wbfdm/jinja2.py,sha256=pkIC1U-0rf6vn0DDEUzZ8dPYiTGEPY8LBTRMi9wYiuc,199
6
6
  wbfdm/preferences.py,sha256=8ghDcaapOMso1kjtNfKbSFykPUTxzqI5R77gM3BgiMs,927
7
7
  wbfdm/signals.py,sha256=PhAsFpQZF1YVe5UpedaRelUD_TVjemqRYm1HzV-bhmY,597
8
- wbfdm/tasks.py,sha256=rjbbDewuiDmfO54tBUwPiNV9AoDKnIx382riyExWwnI,4362
8
+ wbfdm/tasks.py,sha256=6rI2SH94ZGL4ycjNX8cntoYzUcb_wJciNmAomGWBILs,4711
9
9
  wbfdm/urls.py,sha256=pDp9I0kktxicad8sXXEUT7402jZPMJNcE5R1doTlcMw,8887
10
10
  wbfdm/utils.py,sha256=4cWrCpqXxHIjtSlt4DDPFvmtaqXw_H0nqhM6sGuXx0o,1938
11
11
  wbfdm/admin/__init__.py,sha256=Z1VtH_gjD71K79KcD-2Q2Lu_p_7j0akMZj7gNxdz1CQ,1398
@@ -28,8 +28,8 @@ wbfdm/analysis/financial_analysis/__init__.py,sha256=l0hGfgYIO4VAkSCVrMyzjvJ81fC
28
28
  wbfdm/analysis/financial_analysis/financial_metric_analysis.py,sha256=bZnt2zANYBpBiY_ZlDokzTx0iJESvXiNcbnX7lVRs9g,3491
29
29
  wbfdm/analysis/financial_analysis/financial_ratio_analysis.py,sha256=wx2ETs7XvNqhX-cUeIbhVKDiPDsnO39cQlT5qntv0GY,4623
30
30
  wbfdm/analysis/financial_analysis/financial_statistics_analysis.py,sha256=iABYkmWxVlF1H1zZiohbybxSlQXvpVlMkjU7336Zqww,13186
31
- wbfdm/analysis/financial_analysis/statement_with_estimates.py,sha256=Kv8phclh8XRVSwEktL3dIydrhqR_pQzz31v7iCK3hpI,24045
32
- wbfdm/analysis/financial_analysis/utils.py,sha256=Ve6WilLVTTTclL0KxNpdCcbdZJMbtpB9_5lZCHNXrIQ,13577
31
+ wbfdm/analysis/financial_analysis/statement_with_estimates.py,sha256=8BFhqHnvaqy4VwM0fZ6tljhQIuDbNHvl8B250LqXQR8,23989
32
+ wbfdm/analysis/financial_analysis/utils.py,sha256=tV7w0k7EyJ_3eaHwn2u90imFTUc-Tsyqukf11cGFVdI,13992
33
33
  wbfdm/analysis/technical_analysis/__init__.py,sha256=nQdpDl2okzWID6UT7fyfl36RSLNO1ZZzxRt_mkGVVkY,50
34
34
  wbfdm/analysis/technical_analysis/technical_analysis.py,sha256=3ZQOMUWovP6Fq0jOapHGcvBJmisWjEI4TcU5Qb0x8J0,4962
35
35
  wbfdm/analysis/technical_analysis/traces.py,sha256=GhyvVzdg1fmG5i1fai2zz9BnJ__5NH1-eKml2owXIO4,6484
@@ -61,7 +61,7 @@ wbfdm/contrib/metric/admin/metrics.py,sha256=SJU-5ona3npgRjTgDoHvo9aCztq-VMnIVvv
61
61
  wbfdm/contrib/metric/backends/__init__.py,sha256=Bl7bGEYVx38uiN2BS-0pr2WpIkbhUl7yPCEwt1W4tUg,128
62
62
  wbfdm/contrib/metric/backends/base.py,sha256=TTTcNHjuzGCnzoDBqYZOb-nT7fnuYjjp0D6ACqR5yXk,6765
63
63
  wbfdm/contrib/metric/backends/performances.py,sha256=yt9E7A_OEWQBQabf9bqt4Rc3S6oUXbSeM6NR1nYidtg,11642
64
- wbfdm/contrib/metric/backends/statistics.py,sha256=GeU4QOVGcz_Iri_18ku7PqhDDfPEpZpQJssBtCL6RsE,7903
64
+ wbfdm/contrib/metric/backends/statistics.py,sha256=EFbTuJpKxpntkSk4Qo0BSCFFjxBqaQ7ODUkX2pbjDyY,7894
65
65
  wbfdm/contrib/metric/backends/utils.py,sha256=xpsDmoL2Jv9-KTuo-ay65D4m9Kf35jGc9cbeejhFLQU,131
66
66
  wbfdm/contrib/metric/migrations/0001_initial.py,sha256=EwXRJrG7zQYT1bD6jjaf1gZNfpd8f2gzta09GYqukiM,3403
67
67
  wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py,sha256=xp6MACDohMSs4YfFbprCLa5G7zEuBhfZgpVXyMsZPWI,808
@@ -114,7 +114,7 @@ wbfdm/contrib/qa/jinja2/qa/sql/ibes/estimates.sql,sha256=OWgeogSFj7-OdXvJTvNsThN
114
114
  wbfdm/contrib/qa/jinja2/qa/sql/ibes/financials.sql,sha256=0jXNMdX8ixVRJ8YyvtoJRQ510pHFYEioMy7NTp5ecck,2582
115
115
  wbfdm/contrib/qa/sync/exchanges.py,sha256=XU7dj-rQzMlDku9lnmAACaTRoxx8pFSyr5kCK79cYAc,3124
116
116
  wbfdm/contrib/qa/sync/instruments.py,sha256=8aTQVJ_cw1phe4FWikn79pjCfUijaTcwkdhQCtSXKH0,3156
117
- wbfdm/contrib/qa/sync/utils.py,sha256=1diZFEMQwjdGhfL6jv2xX7N_-TyO0Fwtnqw_3K5ykYI,9326
117
+ wbfdm/contrib/qa/sync/utils.py,sha256=582wwGFRRqBxq_H45sTPe0GqnWemy9uU1cYDqQ3BtKs,11249
118
118
  wbfdm/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
119
  wbfdm/dataloaders/cache.py,sha256=K9BeVxT7p-BMvjurINt18bfrUDccp840uIjfDBLJRNk,4841
120
120
  wbfdm/dataloaders/protocols.py,sha256=xFbexmhh38MWmkWsOSsfcNxdtrrWfO02qNihsqwA_BQ,2987
@@ -354,9 +354,9 @@ wbfdm/viewsets/instruments/instruments.py,sha256=-UG8fceQumx06xmZ6tRnjFvFu_Jrjz5
354
354
  wbfdm/viewsets/instruments/instruments_relationships.py,sha256=D2Ym84zXBFegQC9nMThKSWqSAcWa0WvcS_GXxM2po68,11076
355
355
  wbfdm/viewsets/instruments/utils.py,sha256=kr5HwkSaWp-6GHVEQIb3CnIspDGSxfqghyOqg0oHO-A,1166
356
356
  wbfdm/viewsets/statements/__init__.py,sha256=odxtFYUDICPmz8WCE3nx93EvKZLSPBEI4d7pozcQz2A,47
357
- wbfdm/viewsets/statements/statements.py,sha256=bmS89l60gnSb2NcoadkUt3GyrAR2JdQINH38-_Eg5Xs,4135
357
+ wbfdm/viewsets/statements/statements.py,sha256=gA6RCI8-B__JwjEb6OZxpn8Y-9aF-YQ3HIQ7e1vfJMw,4304
358
358
  wbfdm/viewsets/technical_analysis/__init__.py,sha256=qtCIBg0uSiZeJq_1tEQFilnorMBkMe6uCMfqar6-cLE,77
359
359
  wbfdm/viewsets/technical_analysis/monthly_performances.py,sha256=O1j8CGfOranL74LqVvcf7jERaDIboEJZiBf_AbbVDQ8,3974
360
- wbfdm-1.51.2rc1.dist-info/METADATA,sha256=pNgk8s1BUhjPIkNxbvJ8cOk7iSZenAPhO8xNafU4Vvk,740
361
- wbfdm-1.51.2rc1.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
362
- wbfdm-1.51.2rc1.dist-info/RECORD,,
360
+ wbfdm-1.51.5.dist-info/METADATA,sha256=TaKLWPf99XxSz7Uu_EMluMgcQOHChYLVun8pTrb9uzc,737
361
+ wbfdm-1.51.5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
362
+ wbfdm-1.51.5.dist-info/RECORD,,