wbfdm 1.49.5__py2.py3-none-any.whl → 1.59.4__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.
- wbfdm/admin/exchanges.py +1 -1
- wbfdm/admin/instruments.py +3 -2
- wbfdm/analysis/financial_analysis/change_point_detection.py +88 -0
- wbfdm/analysis/financial_analysis/statement_with_estimates.py +5 -6
- wbfdm/analysis/financial_analysis/utils.py +6 -0
- wbfdm/contrib/dsws/client.py +3 -3
- wbfdm/contrib/dsws/dataloaders/market_data.py +31 -3
- wbfdm/contrib/internal/dataloaders/market_data.py +43 -9
- wbfdm/contrib/metric/backends/base.py +2 -2
- wbfdm/contrib/metric/backends/statistics.py +47 -13
- wbfdm/contrib/metric/dispatch.py +3 -0
- wbfdm/contrib/metric/exceptions.py +1 -1
- wbfdm/contrib/metric/filters.py +19 -0
- wbfdm/contrib/metric/models.py +6 -0
- wbfdm/contrib/metric/orchestrators.py +4 -4
- wbfdm/contrib/metric/signals.py +7 -0
- wbfdm/contrib/metric/tasks.py +2 -3
- wbfdm/contrib/metric/viewsets/configs/display.py +2 -2
- wbfdm/contrib/metric/viewsets/mixins.py +6 -6
- wbfdm/contrib/msci/client.py +6 -2
- wbfdm/contrib/qa/database_routers.py +1 -1
- wbfdm/contrib/qa/dataloaders/adjustments.py +2 -1
- wbfdm/contrib/qa/dataloaders/corporate_actions.py +2 -1
- wbfdm/contrib/qa/dataloaders/financials.py +19 -1
- wbfdm/contrib/qa/dataloaders/fx_rates.py +86 -0
- wbfdm/contrib/qa/dataloaders/market_data.py +29 -40
- wbfdm/contrib/qa/dataloaders/officers.py +1 -1
- wbfdm/contrib/qa/dataloaders/statements.py +18 -3
- wbfdm/contrib/qa/jinja2/qa/sql/ibes/financials.sql +1 -1
- wbfdm/contrib/qa/sync/exchanges.py +2 -1
- wbfdm/contrib/qa/sync/utils.py +76 -17
- wbfdm/dataloaders/protocols.py +12 -1
- wbfdm/dataloaders/proxies.py +15 -1
- wbfdm/dataloaders/types.py +7 -1
- wbfdm/enums.py +2 -0
- wbfdm/factories/instruments.py +4 -2
- wbfdm/figures/financials/financial_analysis_charts.py +2 -8
- wbfdm/filters/classifications.py +2 -2
- wbfdm/filters/financials.py +9 -18
- wbfdm/filters/financials_analysis.py +36 -16
- wbfdm/filters/instrument_prices.py +8 -5
- wbfdm/filters/instruments.py +21 -7
- wbfdm/import_export/backends/cbinsights/utils/client.py +8 -8
- wbfdm/import_export/backends/refinitiv/utils/controller.py +1 -1
- wbfdm/import_export/handlers/instrument.py +160 -104
- wbfdm/import_export/handlers/option.py +2 -2
- wbfdm/import_export/parsers/cbinsights/equities.py +2 -3
- wbfdm/jinja2.py +2 -1
- wbfdm/locale/de/LC_MESSAGES/django.mo +0 -0
- wbfdm/locale/de/LC_MESSAGES/django.po +257 -0
- wbfdm/locale/en/LC_MESSAGES/django.mo +0 -0
- wbfdm/locale/en/LC_MESSAGES/django.po +255 -0
- wbfdm/locale/fr/LC_MESSAGES/django.mo +0 -0
- wbfdm/locale/fr/LC_MESSAGES/django.po +257 -0
- wbfdm/migrations/0031_exchange_apply_round_lot_size_and_more.py +23 -0
- wbfdm/migrations/0032_alter_instrumentprice_outstanding_shares.py +18 -0
- wbfdm/migrations/0033_alter_controversy_review.py +18 -0
- wbfdm/migrations/0034_alter_instrumentlist_instrument_list_type.py +18 -0
- wbfdm/models/esg/controversies.py +19 -23
- wbfdm/models/exchanges/exchanges.py +8 -4
- wbfdm/models/fields.py +2 -2
- wbfdm/models/fk_fields.py +3 -3
- wbfdm/models/instruments/instrument_lists.py +1 -0
- wbfdm/models/instruments/instrument_prices.py +8 -1
- wbfdm/models/instruments/instrument_relationships.py +3 -0
- wbfdm/models/instruments/instruments.py +139 -26
- wbfdm/models/instruments/llm/create_instrument_news_relationships.py +29 -22
- wbfdm/models/instruments/mixin/financials_computed.py +0 -4
- wbfdm/models/instruments/mixin/financials_serializer_fields.py +118 -118
- wbfdm/models/instruments/mixin/instruments.py +7 -4
- wbfdm/models/instruments/options.py +6 -0
- wbfdm/models/instruments/private_equities.py +3 -0
- wbfdm/models/instruments/querysets.py +138 -37
- wbfdm/models/instruments/utils.py +5 -0
- wbfdm/serializers/exchanges.py +1 -0
- wbfdm/serializers/instruments/__init__.py +1 -0
- wbfdm/serializers/instruments/instruments.py +9 -2
- wbfdm/serializers/instruments/mixins.py +3 -3
- wbfdm/tasks.py +13 -2
- wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +0 -1
- wbfdm/tests/models/test_instrument_prices.py +0 -14
- wbfdm/tests/models/test_instruments.py +21 -9
- wbfdm/tests/models/test_queryset.py +89 -0
- wbfdm/viewsets/configs/display/exchanges.py +1 -1
- wbfdm/viewsets/configs/display/financial_summary.py +2 -2
- wbfdm/viewsets/configs/display/instrument_prices.py +2 -70
- wbfdm/viewsets/configs/display/instruments.py +3 -4
- wbfdm/viewsets/configs/display/instruments_relationships.py +3 -1
- wbfdm/viewsets/configs/display/prices.py +1 -0
- wbfdm/viewsets/configs/display/statement_with_estimates.py +1 -2
- wbfdm/viewsets/configs/endpoints/classifications.py +0 -12
- wbfdm/viewsets/configs/endpoints/instrument_prices.py +4 -23
- wbfdm/viewsets/configs/titles/instrument_prices.py +2 -1
- wbfdm/viewsets/esg.py +2 -2
- wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +2 -2
- wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +1 -1
- wbfdm/viewsets/financial_analysis/financial_summary.py +6 -6
- wbfdm/viewsets/financial_analysis/statement_with_estimates.py +7 -3
- wbfdm/viewsets/instruments/financials_analysis.py +9 -12
- wbfdm/viewsets/instruments/instrument_prices.py +9 -9
- wbfdm/viewsets/instruments/instruments.py +9 -7
- wbfdm/viewsets/instruments/utils.py +3 -3
- wbfdm/viewsets/market_data.py +1 -1
- wbfdm/viewsets/prices.py +5 -0
- wbfdm/viewsets/statements/statements.py +7 -3
- {wbfdm-1.49.5.dist-info → wbfdm-1.59.4.dist-info}/METADATA +2 -1
- {wbfdm-1.49.5.dist-info → wbfdm-1.59.4.dist-info}/RECORD +108 -95
- {wbfdm-1.49.5.dist-info → wbfdm-1.59.4.dist-info}/WHEEL +1 -1
- wbfdm/menu.py +0 -11
|
@@ -7,7 +7,8 @@ from typing import Any, Dict, Optional
|
|
|
7
7
|
from django.contrib.postgres.search import TrigramSimilarity
|
|
8
8
|
from django.core.exceptions import MultipleObjectsReturned
|
|
9
9
|
from django.db import IntegrityError, models
|
|
10
|
-
from django.db.models import Q
|
|
10
|
+
from django.db.models import Exists, OuterRef, Q
|
|
11
|
+
from slugify import slugify
|
|
11
12
|
from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
12
13
|
from wbcore.contrib.geography.models import Geography
|
|
13
14
|
from wbcore.contrib.io.exceptions import DeserializationError
|
|
@@ -24,6 +25,7 @@ class InstrumentLookup:
|
|
|
24
25
|
"refinitiv_mnemonic_code",
|
|
25
26
|
"identifier",
|
|
26
27
|
"ticker",
|
|
28
|
+
"currency",
|
|
27
29
|
]
|
|
28
30
|
|
|
29
31
|
def __init__(self, model, trigram_similarity_min_score: float = 0.8):
|
|
@@ -33,34 +35,20 @@ class InstrumentLookup:
|
|
|
33
35
|
|
|
34
36
|
@classmethod
|
|
35
37
|
def _get_cache_key(cls, **data):
|
|
36
|
-
if data
|
|
37
|
-
return "-".join([f"{k}:{data.get(k, None)}" for k in cls.ORDERED_KEYS if data.get(k, None) is not None])
|
|
38
|
+
return "-".join([f"{k}:{slugify(str(data[k]))}" for k in cls.ORDERED_KEYS if data.get(k, None) is not None])
|
|
38
39
|
|
|
39
|
-
def
|
|
40
|
-
self
|
|
41
|
-
instrument_type=None,
|
|
42
|
-
currency=None,
|
|
43
|
-
exchange=None,
|
|
44
|
-
name=None,
|
|
45
|
-
only_investable_universe: bool = False,
|
|
46
|
-
exact_lookup: bool = False,
|
|
47
|
-
**identifiers,
|
|
48
|
-
):
|
|
49
|
-
identifiers = {k: v for k, v in identifiers.items() if v is not None}
|
|
50
|
-
# General lookup, we try to gracefully find the instrument based on all available identifier fields
|
|
51
|
-
cache_key = self._get_cache_key(**identifiers)
|
|
40
|
+
def _get_cache(self, **kwargs):
|
|
41
|
+
cache_key = self._get_cache_key(**kwargs)
|
|
52
42
|
if cache_key and cache_key in self.cache:
|
|
53
43
|
return self.cache[cache_key]
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if only_investable_universe:
|
|
59
|
-
instruments = self.model.objects.filter(is_investable_universe=True)
|
|
60
|
-
else:
|
|
61
|
-
instruments = self.model.objects.filter(is_security=True)
|
|
45
|
+
def _set_cache(self, instrument, **kwargs):
|
|
46
|
+
cache_key = self._get_cache_key(**kwargs)
|
|
47
|
+
self.cache[cache_key] = instrument
|
|
62
48
|
|
|
49
|
+
def _get_instruments_from_identifiers(self, queryset, **identifiers):
|
|
63
50
|
# Try exact lookup on the filtered out universe
|
|
51
|
+
identifiers = {k: v for k, v in identifiers.items() if v is not None}
|
|
64
52
|
for identifier_key in [
|
|
65
53
|
"isin",
|
|
66
54
|
"refinitiv_identifier_code",
|
|
@@ -76,87 +64,153 @@ class InstrumentLookup:
|
|
|
76
64
|
identifier_key != "refinitiv_identifier_code"
|
|
77
65
|
): # RIC cannot be uppercased because its symbology implies meaning for lowercase characters
|
|
78
66
|
identifier = identifier.upper()
|
|
79
|
-
instrument
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
67
|
+
# we try to lookup the instrument with the unique identifier
|
|
68
|
+
instrument = queryset.get(**{identifier_key: identifier})
|
|
69
|
+
return self.model.objects.filter(id=instrument.id)
|
|
70
|
+
return queryset
|
|
71
|
+
|
|
72
|
+
def _filter_identifiers(self, queryset, **identifiers):
|
|
73
|
+
lookup_fields = [
|
|
74
|
+
"isin",
|
|
75
|
+
"refinitiv_identifier_code",
|
|
76
|
+
"refinitiv_mnemonic_code",
|
|
77
|
+
"refinitiv_ticker",
|
|
78
|
+
"identifier",
|
|
79
|
+
"ticker",
|
|
80
|
+
]
|
|
81
|
+
conditions = []
|
|
82
|
+
for field in lookup_fields:
|
|
83
|
+
if field_value := identifiers.get(field, None):
|
|
84
|
+
conditions.append(Q(**{f"{field}": field_value}))
|
|
85
|
+
if field == "isin":
|
|
86
|
+
conditions.append(Q(old_isins__contains=[field_value]))
|
|
87
|
+
if field == "ticker":
|
|
88
|
+
conditions.append(
|
|
89
|
+
Q(ticker__regex=rf"^{field_value}(\.[A-Za-z])$")
|
|
90
|
+
) # initial regex: ([A-Za-z]?|\.?[A-Za-z])$ This led to a false positive, I don't remember why we allow an extra random character to be consider for eval
|
|
91
|
+
conditions.append(Q(refinitiv_mnemonic_code=f"@{field_value}"))
|
|
92
|
+
|
|
93
|
+
if conditions:
|
|
94
|
+
return queryset.filter(reduce(operator.or_, conditions))
|
|
95
|
+
return queryset
|
|
96
|
+
|
|
97
|
+
def _filter_name(self, queryset, name):
|
|
98
|
+
instruments = queryset.annotate(similarity_score=TrigramSimilarity("name", name))
|
|
99
|
+
if (
|
|
100
|
+
queryset.count() > 1
|
|
101
|
+
and instruments.filter(similarity_score__gt=self.trigram_similarity_min_score).count() == 1
|
|
102
|
+
):
|
|
103
|
+
return instruments.filter(similarity_score__gt=self.trigram_similarity_min_score)
|
|
104
|
+
return queryset
|
|
105
|
+
|
|
106
|
+
def _filter_currency(self, queryset, currency):
|
|
107
|
+
qs = queryset.filter(currency=currency)
|
|
108
|
+
if qs.exists():
|
|
109
|
+
return qs
|
|
110
|
+
return queryset
|
|
111
|
+
|
|
112
|
+
def _filter_exchange(self, queryset, exchange):
|
|
113
|
+
return queryset.filter(exchange=exchange)
|
|
114
|
+
|
|
115
|
+
def _filter_instrument_type(self, queryset, instrument_type):
|
|
116
|
+
if not isinstance(instrument_type, str):
|
|
117
|
+
instrument_type = instrument_type.key
|
|
118
|
+
if instrument_type == "equity":
|
|
119
|
+
qs = queryset.filter(instrument_type__key__in=["equity", "american_depository_receipt"])
|
|
120
|
+
else:
|
|
121
|
+
qs = queryset.filter(instrument_type__key=instrument_type)
|
|
122
|
+
if qs.exists():
|
|
123
|
+
return qs
|
|
124
|
+
return queryset
|
|
125
|
+
|
|
126
|
+
def _get_queryset(self, only_investable_universe: bool = False):
|
|
127
|
+
instruments = self.model.objects.filter(is_security=True)
|
|
128
|
+
if only_investable_universe:
|
|
129
|
+
instruments = instruments.annotate(
|
|
130
|
+
has_children_in_investable_universe=Exists(
|
|
131
|
+
self.model.objects.filter(is_investable_universe=True, parent=OuterRef("id"))
|
|
138
132
|
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return instrument
|
|
133
|
+
).filter(Q(is_investable_universe=True) | Q(has_children_in_investable_universe=True))
|
|
134
|
+
return instruments
|
|
142
135
|
|
|
143
|
-
def
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
def _get_security_candidates( # noqa: C901
|
|
137
|
+
self,
|
|
138
|
+
queryset,
|
|
139
|
+
instrument_type=None,
|
|
140
|
+
currency=None,
|
|
141
|
+
name=None,
|
|
142
|
+
**identifiers,
|
|
143
|
+
):
|
|
144
|
+
# We need to lookup ticker because some provider gives us ticker with or without space in it
|
|
145
|
+
instruments = self._get_instruments_from_identifiers(queryset, **identifiers)
|
|
146
|
+
if instrument_type:
|
|
147
|
+
instruments = self._filter_instrument_type(instruments, instrument_type)
|
|
148
|
+
|
|
149
|
+
if instruments.count() == 1:
|
|
150
|
+
return instruments
|
|
151
|
+
|
|
152
|
+
instruments = self._filter_identifiers(instruments, **identifiers)
|
|
153
|
+
|
|
154
|
+
if currency:
|
|
155
|
+
instruments = self._filter_currency(instruments, currency)
|
|
156
|
+
if name:
|
|
157
|
+
instruments = self._filter_name(instruments, name)
|
|
158
|
+
return instruments
|
|
159
|
+
|
|
160
|
+
def _lookup_quote(
|
|
161
|
+
self,
|
|
162
|
+
security,
|
|
163
|
+
currency=None,
|
|
164
|
+
exchange=None,
|
|
165
|
+
refinitiv_identifier_code=None,
|
|
166
|
+
refinitiv_mnemonic_code=None,
|
|
167
|
+
**kwargs,
|
|
168
|
+
):
|
|
169
|
+
quotes = security.children.all()
|
|
170
|
+
if not quotes.exists():
|
|
171
|
+
return security
|
|
172
|
+
if quotes.count() == 1:
|
|
173
|
+
return quotes.first()
|
|
174
|
+
if exchange:
|
|
175
|
+
quotes = self._filter_exchange(quotes, exchange)
|
|
176
|
+
if currency:
|
|
177
|
+
quotes = self._filter_currency(quotes, currency)
|
|
178
|
+
if refinitiv_mnemonic_code or refinitiv_identifier_code:
|
|
179
|
+
quotes = self._filter_identifiers(
|
|
180
|
+
quotes,
|
|
181
|
+
refinitiv_identifier_code=refinitiv_identifier_code,
|
|
182
|
+
refinitiv_mnemonic_code=refinitiv_mnemonic_code,
|
|
149
183
|
)
|
|
150
|
-
if
|
|
151
|
-
quotes =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
if quotes.filter(is_investable_universe=True).count() == 1:
|
|
185
|
+
quotes = quotes.filter(is_investable_universe=True)
|
|
186
|
+
if quotes.count() == 1:
|
|
187
|
+
return quotes.first()
|
|
188
|
+
return security.children.filter(is_primary=True).first()
|
|
189
|
+
|
|
190
|
+
def lookup_security(self, **kwargs):
|
|
191
|
+
queryset = self._get_queryset(only_investable_universe=True)
|
|
192
|
+
security_candidates_in_investable_universe = self._get_security_candidates(queryset, **kwargs)
|
|
193
|
+
|
|
194
|
+
if security_candidates_in_investable_universe.count() == 1:
|
|
195
|
+
return security_candidates_in_investable_universe.first()
|
|
196
|
+
queryset = self._get_queryset(only_investable_universe=False)
|
|
197
|
+
security_candidates = self._get_security_candidates(queryset, **kwargs)
|
|
198
|
+
|
|
199
|
+
if security_candidates.exists() > 1:
|
|
200
|
+
security_candidates = security_candidates.filter(delisted_date__isnull=True)
|
|
201
|
+
if security_candidates.count() == 1:
|
|
202
|
+
return security_candidates.first()
|
|
203
|
+
|
|
204
|
+
def lookup(self, only_security: bool = False, **lookup_kwargs):
|
|
205
|
+
# To speed up lookup process, we try to get the quote from the investable universe first
|
|
206
|
+
if instrument := self._get_cache(**lookup_kwargs):
|
|
207
|
+
return instrument
|
|
208
|
+
instrument = self.lookup_security(**lookup_kwargs)
|
|
209
|
+
if not only_security and instrument:
|
|
210
|
+
instrument = self._lookup_quote(instrument, **lookup_kwargs)
|
|
211
|
+
if instrument:
|
|
212
|
+
self._set_cache(instrument, **lookup_kwargs)
|
|
213
|
+
return instrument
|
|
160
214
|
|
|
161
215
|
|
|
162
216
|
class InstrumentImportHandler(ImportExportHandler):
|
|
@@ -175,7 +229,9 @@ class InstrumentImportHandler(ImportExportHandler):
|
|
|
175
229
|
if isinstance(data, int):
|
|
176
230
|
data = dict(id=data)
|
|
177
231
|
if data.get("currency", None):
|
|
178
|
-
data["currency"] = self.currency_handler.process_object(
|
|
232
|
+
data["currency"] = self.currency_handler.process_object(
|
|
233
|
+
data["currency"], read_only=True, raise_exception=False
|
|
234
|
+
)[0]
|
|
179
235
|
if instrument_type := data.get("instrument_type", None):
|
|
180
236
|
if isinstance(instrument_type, str):
|
|
181
237
|
data["instrument_type"] = InstrumentType.objects.get_or_create(
|
|
@@ -209,8 +265,8 @@ class InstrumentImportHandler(ImportExportHandler):
|
|
|
209
265
|
if instrument_id := data.pop("id", None):
|
|
210
266
|
try:
|
|
211
267
|
return self.model.objects.get(id=instrument_id)
|
|
212
|
-
except self.model.DoesNotExist:
|
|
213
|
-
raise DeserializationError("Instrument id does not match an existing instrument")
|
|
268
|
+
except self.model.DoesNotExist as e:
|
|
269
|
+
raise DeserializationError("Instrument id does not match an existing instrument") from e
|
|
214
270
|
else:
|
|
215
271
|
return self.instrument_lookup.lookup(only_security=only_security, **data)
|
|
216
272
|
|
|
@@ -26,7 +26,7 @@ class OptionAggregateImportHandler(ImportExportHandler):
|
|
|
26
26
|
|
|
27
27
|
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
28
28
|
with suppress(ObjectDoesNotExist):
|
|
29
|
-
return self.model.objects.
|
|
29
|
+
return self.model.objects.get(instrument=data["instrument"], date=data["date"], type=data["type"])
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class OptionImportHandler(ImportExportHandler):
|
|
@@ -46,7 +46,7 @@ class OptionImportHandler(ImportExportHandler):
|
|
|
46
46
|
|
|
47
47
|
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
48
48
|
with suppress(ObjectDoesNotExist):
|
|
49
|
-
return self.model.objects.
|
|
49
|
+
return self.model.objects.get(
|
|
50
50
|
instrument=data["instrument"],
|
|
51
51
|
contract_identifier=data["contract_identifier"],
|
|
52
52
|
date=data["date"],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from contextlib import suppress
|
|
2
3
|
|
|
3
4
|
from wbcore.contrib.geography.models import Geography
|
|
4
5
|
|
|
@@ -44,14 +45,12 @@ def parse(import_source):
|
|
|
44
45
|
d["headquarter_address"] = f"{street}, {postal_code} {city_name}"
|
|
45
46
|
|
|
46
47
|
if sector_id := summary.get("sectorId", None):
|
|
47
|
-
|
|
48
|
+
with suppress(Exception):
|
|
48
49
|
code_aggregated = f"{int(sector_id):03}"
|
|
49
50
|
if industry_id := summary.get("industryId", None):
|
|
50
51
|
code_aggregated += f"{int(industry_id):03}"
|
|
51
52
|
if subindustry_id := summary.get("subindustryId", None):
|
|
52
53
|
code_aggregated += f"{int(subindustry_id):03}"
|
|
53
|
-
except Exception:
|
|
54
|
-
pass
|
|
55
54
|
d["classifications"] = [{"code_aggregated": code_aggregated, "group": cbinsight_group.id}]
|
|
56
55
|
data.append(d)
|
|
57
56
|
return {"data": data}
|
wbfdm/jinja2.py
CHANGED
|
@@ -3,5 +3,6 @@ from jinjasql import JinjaSql # type: ignore
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def get_environment(**options):
|
|
6
|
-
|
|
6
|
+
# we except only SQL template, so we need to not escape special characters (possible XSS attack for HTML template)
|
|
7
|
+
env = Environment(**options) # noqa: S701
|
|
7
8
|
return JinjaSql(env=env, param_style="format").env
|
|
Binary file
|
|
@@ -0,0 +1,257 @@
|
|
|
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-30 11:37+0200\n"
|
|
12
|
+
"PO-Revision-Date: 2025-05-30 09:40+0000\n"
|
|
13
|
+
"Language-Team: German (https://app.transifex.com/stainly/teams/171242/de/)\n"
|
|
14
|
+
"MIME-Version: 1.0\n"
|
|
15
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
16
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
17
|
+
"Language: de\n"
|
|
18
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
19
|
+
|
|
20
|
+
#: contrib/metric/filters.py:27
|
|
21
|
+
msgid "Basket Content Type"
|
|
22
|
+
msgstr ""
|
|
23
|
+
|
|
24
|
+
#: contrib/metric/viewsets/configs/utils.py:127
|
|
25
|
+
msgid "Fundamentals"
|
|
26
|
+
msgstr ""
|
|
27
|
+
|
|
28
|
+
#: models/exchanges/exchanges.py:184
|
|
29
|
+
#: viewsets/configs/display/instruments.py:77
|
|
30
|
+
msgid "Exchange"
|
|
31
|
+
msgstr ""
|
|
32
|
+
|
|
33
|
+
#: models/exchanges/exchanges.py:185
|
|
34
|
+
msgid "Exchanges"
|
|
35
|
+
msgstr ""
|
|
36
|
+
|
|
37
|
+
#: models/instruments/instrument_requests.py:116
|
|
38
|
+
#: models/instruments/instrument_requests.py:182
|
|
39
|
+
msgid "An instrument already exists with the proposed identifier"
|
|
40
|
+
msgstr ""
|
|
41
|
+
|
|
42
|
+
#: viewsets/configs/buttons/classifications.py:10
|
|
43
|
+
msgid "Tree Chart"
|
|
44
|
+
msgstr ""
|
|
45
|
+
|
|
46
|
+
#: viewsets/configs/buttons/classifications.py:11
|
|
47
|
+
msgid "Icicle Chart"
|
|
48
|
+
msgstr ""
|
|
49
|
+
|
|
50
|
+
#: viewsets/configs/display/classifications.py:40
|
|
51
|
+
msgid "Child Classifications"
|
|
52
|
+
msgstr ""
|
|
53
|
+
|
|
54
|
+
#: viewsets/configs/display/classifications.py:43
|
|
55
|
+
msgid "Instruments"
|
|
56
|
+
msgstr ""
|
|
57
|
+
|
|
58
|
+
#: viewsets/configs/display/classifications.py:69
|
|
59
|
+
msgid "Classification"
|
|
60
|
+
msgstr ""
|
|
61
|
+
|
|
62
|
+
#: viewsets/configs/display/classifications.py:112
|
|
63
|
+
msgid "Related Instruments"
|
|
64
|
+
msgstr ""
|
|
65
|
+
|
|
66
|
+
#: viewsets/configs/display/esg.py:14
|
|
67
|
+
msgid "Initiated"
|
|
68
|
+
msgstr ""
|
|
69
|
+
|
|
70
|
+
#: viewsets/configs/display/esg.py:15
|
|
71
|
+
msgid "Review"
|
|
72
|
+
msgstr ""
|
|
73
|
+
|
|
74
|
+
#: viewsets/configs/display/esg.py:16
|
|
75
|
+
msgid "Headline"
|
|
76
|
+
msgstr ""
|
|
77
|
+
|
|
78
|
+
#: viewsets/configs/display/esg.py:17
|
|
79
|
+
msgid "Narrative"
|
|
80
|
+
msgstr ""
|
|
81
|
+
|
|
82
|
+
#: viewsets/configs/display/esg.py:18
|
|
83
|
+
msgid "Source"
|
|
84
|
+
msgstr ""
|
|
85
|
+
|
|
86
|
+
#: viewsets/configs/display/esg.py:19
|
|
87
|
+
msgid "Status"
|
|
88
|
+
msgstr ""
|
|
89
|
+
|
|
90
|
+
#: viewsets/configs/display/esg.py:20
|
|
91
|
+
msgid "Type"
|
|
92
|
+
msgstr ""
|
|
93
|
+
|
|
94
|
+
#: viewsets/configs/display/esg.py:21
|
|
95
|
+
msgid "Assessment"
|
|
96
|
+
msgstr ""
|
|
97
|
+
|
|
98
|
+
#: viewsets/configs/display/esg.py:22
|
|
99
|
+
msgid "Response"
|
|
100
|
+
msgstr ""
|
|
101
|
+
|
|
102
|
+
#: viewsets/configs/display/esg.py:70
|
|
103
|
+
msgid "Section"
|
|
104
|
+
msgstr ""
|
|
105
|
+
|
|
106
|
+
#: viewsets/configs/display/esg.py:71
|
|
107
|
+
msgid "Adverse sustainability indicator"
|
|
108
|
+
msgstr ""
|
|
109
|
+
|
|
110
|
+
#: viewsets/configs/display/esg.py:72
|
|
111
|
+
msgid "Metric"
|
|
112
|
+
msgstr ""
|
|
113
|
+
|
|
114
|
+
#: viewsets/configs/display/esg.py:73
|
|
115
|
+
msgid "Factor"
|
|
116
|
+
msgstr ""
|
|
117
|
+
|
|
118
|
+
#: viewsets/configs/display/esg.py:74
|
|
119
|
+
msgid "Value"
|
|
120
|
+
msgstr ""
|
|
121
|
+
|
|
122
|
+
#: viewsets/configs/display/instrument_lists.py:34
|
|
123
|
+
msgid "Exclusion List"
|
|
124
|
+
msgstr ""
|
|
125
|
+
|
|
126
|
+
#: viewsets/configs/display/instrument_lists.py:105
|
|
127
|
+
msgid "Validated"
|
|
128
|
+
msgstr ""
|
|
129
|
+
|
|
130
|
+
#: viewsets/configs/display/instrument_lists.py:109
|
|
131
|
+
msgid "Not Validated"
|
|
132
|
+
msgstr ""
|
|
133
|
+
|
|
134
|
+
#: viewsets/configs/display/instrument_requests.py:95
|
|
135
|
+
msgid "Instrument Data"
|
|
136
|
+
msgstr ""
|
|
137
|
+
|
|
138
|
+
#: viewsets/configs/display/instruments.py:70
|
|
139
|
+
#: viewsets/configs/display/officers.py:11
|
|
140
|
+
msgid "Name"
|
|
141
|
+
msgstr ""
|
|
142
|
+
|
|
143
|
+
#: viewsets/configs/display/instruments.py:73
|
|
144
|
+
msgid "Information"
|
|
145
|
+
msgstr ""
|
|
146
|
+
|
|
147
|
+
#: viewsets/configs/display/instruments.py:76
|
|
148
|
+
msgid "Instrument Type"
|
|
149
|
+
msgstr ""
|
|
150
|
+
|
|
151
|
+
#: viewsets/configs/display/instruments.py:78
|
|
152
|
+
msgid "ISIN"
|
|
153
|
+
msgstr ""
|
|
154
|
+
|
|
155
|
+
#: viewsets/configs/display/instruments.py:79
|
|
156
|
+
msgid "Ticker"
|
|
157
|
+
msgstr ""
|
|
158
|
+
|
|
159
|
+
#: viewsets/configs/display/instruments.py:80
|
|
160
|
+
msgid "RIC"
|
|
161
|
+
msgstr ""
|
|
162
|
+
|
|
163
|
+
#: viewsets/configs/display/instruments.py:81
|
|
164
|
+
msgid "Refinitiv Mnemonic"
|
|
165
|
+
msgstr ""
|
|
166
|
+
|
|
167
|
+
#: viewsets/configs/display/instruments.py:82
|
|
168
|
+
msgid "Description"
|
|
169
|
+
msgstr ""
|
|
170
|
+
|
|
171
|
+
#: viewsets/configs/display/instruments.py:83
|
|
172
|
+
msgid "Currency"
|
|
173
|
+
msgstr ""
|
|
174
|
+
|
|
175
|
+
#: viewsets/configs/display/instruments.py:84
|
|
176
|
+
msgid "Country"
|
|
177
|
+
msgstr ""
|
|
178
|
+
|
|
179
|
+
#: viewsets/configs/display/instruments.py:91
|
|
180
|
+
msgid "Extra"
|
|
181
|
+
msgstr ""
|
|
182
|
+
|
|
183
|
+
#: viewsets/configs/display/instruments.py:94
|
|
184
|
+
msgid "Primary"
|
|
185
|
+
msgstr ""
|
|
186
|
+
|
|
187
|
+
#: viewsets/configs/display/instruments.py:95
|
|
188
|
+
msgid "Investable Universe"
|
|
189
|
+
msgstr ""
|
|
190
|
+
|
|
191
|
+
#: viewsets/configs/display/instruments.py:96
|
|
192
|
+
msgid "Security"
|
|
193
|
+
msgstr ""
|
|
194
|
+
|
|
195
|
+
#: viewsets/configs/display/instruments.py:97
|
|
196
|
+
msgid "Internally Managed"
|
|
197
|
+
msgstr ""
|
|
198
|
+
|
|
199
|
+
#: viewsets/configs/display/instruments.py:100
|
|
200
|
+
msgid "Performance"
|
|
201
|
+
msgstr ""
|
|
202
|
+
|
|
203
|
+
#: viewsets/configs/display/instruments.py:330
|
|
204
|
+
msgid "Hierarchy"
|
|
205
|
+
msgstr ""
|
|
206
|
+
|
|
207
|
+
#: viewsets/configs/display/instruments.py:343
|
|
208
|
+
msgid "News"
|
|
209
|
+
msgstr ""
|
|
210
|
+
|
|
211
|
+
#: viewsets/configs/display/officers.py:10
|
|
212
|
+
msgid "Position"
|
|
213
|
+
msgstr ""
|
|
214
|
+
|
|
215
|
+
#: viewsets/configs/display/officers.py:12
|
|
216
|
+
msgid "Age"
|
|
217
|
+
msgstr ""
|
|
218
|
+
|
|
219
|
+
#: viewsets/configs/display/officers.py:13
|
|
220
|
+
msgid "Sex"
|
|
221
|
+
msgstr ""
|
|
222
|
+
|
|
223
|
+
#: viewsets/configs/display/officers.py:14
|
|
224
|
+
msgid "Start"
|
|
225
|
+
msgstr ""
|
|
226
|
+
|
|
227
|
+
#: viewsets/configs/display/prices.py:12
|
|
228
|
+
msgid "Date"
|
|
229
|
+
msgstr ""
|
|
230
|
+
|
|
231
|
+
#: viewsets/configs/display/prices.py:13
|
|
232
|
+
msgid "Open"
|
|
233
|
+
msgstr ""
|
|
234
|
+
|
|
235
|
+
#: viewsets/configs/display/prices.py:14
|
|
236
|
+
msgid "High"
|
|
237
|
+
msgstr ""
|
|
238
|
+
|
|
239
|
+
#: viewsets/configs/display/prices.py:15
|
|
240
|
+
msgid "Low"
|
|
241
|
+
msgstr ""
|
|
242
|
+
|
|
243
|
+
#: viewsets/configs/display/prices.py:16
|
|
244
|
+
msgid "Close"
|
|
245
|
+
msgstr ""
|
|
246
|
+
|
|
247
|
+
#: viewsets/configs/display/prices.py:17
|
|
248
|
+
msgid "Volume"
|
|
249
|
+
msgstr ""
|
|
250
|
+
|
|
251
|
+
#: viewsets/configs/display/prices.py:18
|
|
252
|
+
msgid "Oustanding Shares"
|
|
253
|
+
msgstr ""
|
|
254
|
+
|
|
255
|
+
#: viewsets/configs/display/prices.py:19
|
|
256
|
+
msgid "Market Cap."
|
|
257
|
+
msgstr ""
|
|
Binary file
|