wbfdm 1.55.9__py2.py3-none-any.whl → 1.56.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 wbfdm might be problematic. Click here for more details.
- wbfdm/contrib/dsws/client.py +3 -3
- wbfdm/contrib/dsws/dataloaders/market_data.py +1 -1
- wbfdm/contrib/internal/dataloaders/market_data.py +1 -1
- wbfdm/contrib/metric/backends/base.py +2 -2
- wbfdm/contrib/metric/backends/statistics.py +2 -2
- wbfdm/contrib/metric/exceptions.py +1 -1
- wbfdm/contrib/metric/models.py +6 -0
- wbfdm/contrib/metric/orchestrators.py +4 -4
- wbfdm/contrib/metric/viewsets/mixins.py +6 -6
- wbfdm/contrib/msci/client.py +3 -0
- 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/market_data.py +2 -2
- wbfdm/contrib/qa/dataloaders/officers.py +1 -1
- wbfdm/contrib/qa/sync/utils.py +14 -9
- wbfdm/dataloaders/protocols.py +1 -1
- wbfdm/figures/financials/financial_analysis_charts.py +2 -8
- wbfdm/filters/instrument_prices.py +0 -1
- wbfdm/filters/instruments.py +1 -1
- 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 +17 -9
- wbfdm/import_export/parsers/cbinsights/equities.py +2 -3
- wbfdm/jinja2.py +2 -1
- wbfdm/models/esg/controversies.py +3 -0
- wbfdm/models/fields.py +2 -2
- wbfdm/models/fk_fields.py +3 -3
- wbfdm/models/instruments/instrument_relationships.py +3 -0
- wbfdm/models/instruments/instruments.py +9 -9
- wbfdm/models/instruments/mixin/instruments.py +2 -2
- wbfdm/models/instruments/options.py +6 -0
- wbfdm/models/instruments/private_equities.py +3 -0
- wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +0 -1
- wbfdm/viewsets/configs/display/instruments_relationships.py +3 -1
- 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 +1 -1
- wbfdm/viewsets/instruments/financials_analysis.py +9 -12
- wbfdm/viewsets/market_data.py +1 -1
- {wbfdm-1.55.9.dist-info → wbfdm-1.56.0.dist-info}/METADATA +1 -1
- {wbfdm-1.55.9.dist-info → wbfdm-1.56.0.dist-info}/RECORD +46 -46
- {wbfdm-1.55.9.dist-info → wbfdm-1.56.0.dist-info}/WHEEL +0 -0
wbfdm/contrib/dsws/client.py
CHANGED
|
@@ -3,7 +3,7 @@ import re
|
|
|
3
3
|
from datetime import date, datetime
|
|
4
4
|
from typing import Generator, List, Optional
|
|
5
5
|
|
|
6
|
-
import DatastreamPy as dsweb
|
|
6
|
+
import DatastreamPy as dsweb # noqa
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
9
|
import pytz
|
|
@@ -13,7 +13,7 @@ from wbcore.contrib.currency.models import Currency, CurrencyFXRates
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class CachedTokenDataClient(dsweb.DataClient):
|
|
16
|
-
def _get_token(self, isProxy=False):
|
|
16
|
+
def _get_token(self, isProxy=False): # noqa
|
|
17
17
|
if (token := cache.get("dsws_token")) and (token_expiry := cache.get("dsws_token_expiry")):
|
|
18
18
|
self.token = token
|
|
19
19
|
self.tokenExpiry = timezone.make_aware(datetime.fromtimestamp(token_expiry), timezone=pytz.UTC)
|
|
@@ -102,7 +102,7 @@ class Client:
|
|
|
102
102
|
if len(requests_data) > self.MAXIMUM_REQUESTS_PER_BUNDLE:
|
|
103
103
|
raise ValueError(f"number of request exceed {self.MAXIMUM_REQUESTS_PER_BUNDLE}")
|
|
104
104
|
# Construct the requests bundle
|
|
105
|
-
for request_tickers,
|
|
105
|
+
for request_tickers, _ in requests_data:
|
|
106
106
|
# Convert a list of string into a valid string
|
|
107
107
|
converted_ticker = ",".join(request_tickers)
|
|
108
108
|
if "start" in extra_client_kwargs or "end" in extra_client_kwargs:
|
|
@@ -30,7 +30,7 @@ FIELD_MAP = {
|
|
|
30
30
|
class DSWSMarketDataDataloader(MarketDataProtocol, Dataloader):
|
|
31
31
|
def market_data(
|
|
32
32
|
self,
|
|
33
|
-
values: list[MarketData] =
|
|
33
|
+
values: list[MarketData] | None = None,
|
|
34
34
|
from_date: date | None = None,
|
|
35
35
|
to_date: date | None = None,
|
|
36
36
|
exact_date: date | None = None,
|
|
@@ -36,7 +36,7 @@ def _cast_decimal_to_float(value: float | Decimal) -> float:
|
|
|
36
36
|
class MarketDataDataloader(MarketDataProtocol, Dataloader):
|
|
37
37
|
def market_data(
|
|
38
38
|
self,
|
|
39
|
-
values: list[MarketData] =
|
|
39
|
+
values: list[MarketData] | None = None,
|
|
40
40
|
from_date: date | None = None,
|
|
41
41
|
to_date: date | None = None,
|
|
42
42
|
exact_date: date | None = None,
|
|
@@ -13,7 +13,7 @@ from wbcore.contrib.currency.models import CurrencyFXRates
|
|
|
13
13
|
from wbfdm.models import Instrument, InstrumentPrice
|
|
14
14
|
|
|
15
15
|
from ..dto import Metric, MetricField, MetricKey
|
|
16
|
-
from ..exceptions import
|
|
16
|
+
from ..exceptions import MetricInvalidParameterError
|
|
17
17
|
from .utils import get_today
|
|
18
18
|
|
|
19
19
|
T = TypeVar("T", bound=Model)
|
|
@@ -94,7 +94,7 @@ class InstrumentMetricBaseBackend(AbstractBackend[Instrument]):
|
|
|
94
94
|
[val_date, (get_today() - pd.tseries.offsets.BDay(1)).date()]
|
|
95
95
|
) # ensure that value date is at least lower than today (otherwise, we might compute performance for intraday, which we do not want yet
|
|
96
96
|
else:
|
|
97
|
-
raise
|
|
97
|
+
raise MetricInvalidParameterError()
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
class BaseDataloader:
|
|
@@ -11,7 +11,7 @@ from wbfdm.models import Instrument, InstrumentPrice
|
|
|
11
11
|
|
|
12
12
|
from ..decorators import register
|
|
13
13
|
from ..dto import Metric, MetricField, MetricKey
|
|
14
|
-
from ..exceptions import
|
|
14
|
+
from ..exceptions import MetricInvalidParameterError
|
|
15
15
|
from .base import BaseDataloader, InstrumentMetricBaseBackend
|
|
16
16
|
|
|
17
17
|
STATISTICS_METRIC = MetricKey(
|
|
@@ -207,7 +207,7 @@ class InstrumentFinancialStatisticsMetricBackend(InstrumentMetricBaseBackend):
|
|
|
207
207
|
elif self.val_date:
|
|
208
208
|
with suppress(InstrumentPrice.DoesNotExist):
|
|
209
209
|
return instrument.valuations.filter(date__lte=self.val_date).latest("date").date
|
|
210
|
-
raise
|
|
210
|
+
raise MetricInvalidParameterError()
|
|
211
211
|
|
|
212
212
|
|
|
213
213
|
@register(move_first=True)
|
wbfdm/contrib/metric/models.py
CHANGED
|
@@ -48,6 +48,12 @@ class InstrumentMetric(models.Model):
|
|
|
48
48
|
self.basket_repr = str(self.basket)
|
|
49
49
|
super().save(*args, **kwargs)
|
|
50
50
|
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
repr = f"{self.basket} - {self.key}"
|
|
53
|
+
if self.date:
|
|
54
|
+
repr += f"({self.date})"
|
|
55
|
+
return repr
|
|
56
|
+
|
|
51
57
|
@classmethod
|
|
52
58
|
def update_or_create_from_metric(cls, metric: Metric, parent_instrument_metric: Self | None = None):
|
|
53
59
|
"""
|
|
@@ -8,7 +8,7 @@ from tqdm import tqdm
|
|
|
8
8
|
|
|
9
9
|
from .backends.base import AbstractBackend
|
|
10
10
|
from .dto import Metric
|
|
11
|
-
from .exceptions import
|
|
11
|
+
from .exceptions import MetricInvalidParameterError
|
|
12
12
|
from .models import InstrumentMetric
|
|
13
13
|
from .registry import backend_registry
|
|
14
14
|
|
|
@@ -73,9 +73,9 @@ class MetricOrchestrator:
|
|
|
73
73
|
if debug:
|
|
74
74
|
# if debug mode is enabled, we wrap the parameters list into a tqdm generator
|
|
75
75
|
parameters = tqdm(parameters)
|
|
76
|
-
for
|
|
77
|
-
with suppress(
|
|
78
|
-
yield from
|
|
76
|
+
for param in parameters:
|
|
77
|
+
with suppress(MetricInvalidParameterError):
|
|
78
|
+
yield from param[0].compute_metrics(param[1])
|
|
79
79
|
|
|
80
80
|
def process(self, debug: bool = False):
|
|
81
81
|
"""
|
|
@@ -185,19 +185,19 @@ class InstrumentMetricMixin(_Base):
|
|
|
185
185
|
serializer_class = self.get_serializer_class()
|
|
186
186
|
kwargs.setdefault("context", self.get_serializer_context())
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
fields = list(getattr(
|
|
190
|
-
read_only_fields = list(getattr(
|
|
188
|
+
base_meta = serializer_class.Meta
|
|
189
|
+
fields = list(getattr(base_meta, "fields", ()))
|
|
190
|
+
read_only_fields = list(getattr(base_meta, "read_only_fields", ()))
|
|
191
191
|
for extra_field in self._metric_serializer_fields.keys():
|
|
192
192
|
fields.append(extra_field)
|
|
193
193
|
read_only_fields.append(extra_field)
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
meta = type(str("Meta"), (base_meta,), {"fields": fields, "read_only_fields": read_only_fields})
|
|
196
196
|
new_class = type(
|
|
197
197
|
serializer_class.__name__,
|
|
198
198
|
(serializer_class,),
|
|
199
199
|
{
|
|
200
|
-
"Meta":
|
|
200
|
+
"Meta": meta,
|
|
201
201
|
**self._metric_serializer_fields,
|
|
202
202
|
"SERIALIZER_CLASS_FOR_REMOTE_ADDITIONAL_RESOURCES": serializer_class,
|
|
203
203
|
},
|
|
@@ -233,7 +233,7 @@ class InstrumentMetricMixin(_Base):
|
|
|
233
233
|
# for all the missings keys (not present in the aggregates already), we compute the aggregatation based on the aggregate function given by the MetricField class
|
|
234
234
|
missing_aggregate_map = {}
|
|
235
235
|
for metric_key in self.metric_keys:
|
|
236
|
-
for field_key
|
|
236
|
+
for field_key in metric_key.subfields_filter_map.keys():
|
|
237
237
|
if field_key in self._metric_serializer_fields.keys() and field_key not in aggregates:
|
|
238
238
|
missing_aggregate_map[field_key] = metric_key.subfields_map[field_key]
|
|
239
239
|
missing_aggregate = queryset.aggregate(
|
wbfdm/contrib/msci/client.py
CHANGED
|
@@ -38,6 +38,7 @@ class MSCIClient:
|
|
|
38
38
|
"grant_type": "client_credentials",
|
|
39
39
|
"audience": "https://esg/data",
|
|
40
40
|
},
|
|
41
|
+
timeout=10,
|
|
41
42
|
)
|
|
42
43
|
if resp.status_code == requests.codes.ok:
|
|
43
44
|
with suppress(KeyError, requests.exceptions.JSONDecodeError):
|
|
@@ -58,6 +59,7 @@ class MSCIClient:
|
|
|
58
59
|
"factor_name_list": factors,
|
|
59
60
|
},
|
|
60
61
|
headers={"AUTHORIZATION": f"Bearer {self.oauth_token}"},
|
|
62
|
+
timeout=10,
|
|
61
63
|
)
|
|
62
64
|
if response.ok:
|
|
63
65
|
for row in response.json().get("result", {}).get("issuers", []):
|
|
@@ -78,6 +80,7 @@ class MSCIClient:
|
|
|
78
80
|
"offset": offset,
|
|
79
81
|
},
|
|
80
82
|
headers={"AUTHORIZATION": f"Bearer {self.oauth_token}"},
|
|
83
|
+
timeout=10,
|
|
81
84
|
)
|
|
82
85
|
|
|
83
86
|
if not response.ok:
|
|
@@ -57,7 +57,8 @@ class DatastreamAdjustmentsDataloader(AdjustmentsProtocol, Dataloader):
|
|
|
57
57
|
pk.MSSQLQuery.create_table(infocode).columns(pk.Column("infocode", SqlTypes.INTEGER)).get_sql()
|
|
58
58
|
)
|
|
59
59
|
for batch in batched(lookup.keys(), 1000):
|
|
60
|
-
|
|
60
|
+
placeholders = ",".join(map(lambda x: f"({x})", batch))
|
|
61
|
+
cursor.execute("insert into #ds2infocode values %s;", (placeholders,))
|
|
61
62
|
|
|
62
63
|
cursor.execute(query.get_sql())
|
|
63
64
|
|
|
@@ -58,7 +58,8 @@ class DatastreamCorporateActionsDataloader(CorporateActionsProtocol, Dataloader)
|
|
|
58
58
|
pk.MSSQLQuery.create_table(infocode).columns(pk.Column("infocode", SqlTypes.INTEGER)).get_sql()
|
|
59
59
|
)
|
|
60
60
|
for batch in batched(lookup.keys(), 1000):
|
|
61
|
-
|
|
61
|
+
placeholders = ",".join(map(lambda x: f"({x})", batch))
|
|
62
|
+
cursor.execute("insert into #ds2infocode values %s;", (placeholders,))
|
|
62
63
|
|
|
63
64
|
cursor.execute(query.get_sql())
|
|
64
65
|
for row in dictfetchall(cursor, CorporateActionDataDict):
|
|
@@ -39,9 +39,9 @@ class DS2MarketData(Enum):
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class DatastreamMarketDataDataloader(MarketDataProtocol, Dataloader):
|
|
42
|
-
def market_data(
|
|
42
|
+
def market_data( # noqa: C901
|
|
43
43
|
self,
|
|
44
|
-
values: list[MarketData] =
|
|
44
|
+
values: list[MarketData] | None = None,
|
|
45
45
|
from_date: date | None = None,
|
|
46
46
|
to_date: date | None = None,
|
|
47
47
|
exact_date: date | None = None,
|
|
@@ -60,7 +60,7 @@ class RKDOfficersDataloader(OfficersProtocol, Dataloader):
|
|
|
60
60
|
)
|
|
61
61
|
for batch in batched(lookup.keys(), 1000):
|
|
62
62
|
placeholders = ",".join(map(lambda x: f"('{x}')", batch))
|
|
63
|
-
cursor.execute(
|
|
63
|
+
cursor.execute("insert into #rkd_codes values %s;", (placeholders,))
|
|
64
64
|
|
|
65
65
|
cursor.execute(query.get_sql())
|
|
66
66
|
|
wbfdm/contrib/qa/sync/utils.py
CHANGED
|
@@ -148,12 +148,15 @@ def _delist_existing_duplicates(instrument: Instrument) -> None:
|
|
|
148
148
|
|
|
149
149
|
for identifier_field in unique_identifiers:
|
|
150
150
|
if identifier := getattr(instrument, identifier_field):
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
if instrument.delisted_date: # if delisted, we unset the identifier that can lead to constraint error
|
|
152
|
+
setattr(instrument, identifier_field, None)
|
|
153
|
+
else:
|
|
154
|
+
with suppress(Instrument.DoesNotExist):
|
|
155
|
+
duplicate = Instrument.objects.get(
|
|
156
|
+
is_security=True, delisted_date__isnull=True, **{identifier_field: identifier}
|
|
157
|
+
)
|
|
158
|
+
duplicate.delisted_date = date.today() - timedelta(days=1)
|
|
159
|
+
duplicate.save()
|
|
157
160
|
|
|
158
161
|
|
|
159
162
|
def _save_single_instrument(instrument: Instrument) -> None:
|
|
@@ -211,7 +214,7 @@ def _bulk_create_instruments_chunk(instruments: list[Instrument], update_unique_
|
|
|
211
214
|
)
|
|
212
215
|
except IntegrityError:
|
|
213
216
|
# we caught an integrity error on the bulk save, so we try to save one by one
|
|
214
|
-
logger.
|
|
217
|
+
logger.error(
|
|
215
218
|
"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. "
|
|
216
219
|
)
|
|
217
220
|
for instrument in instruments:
|
|
@@ -290,14 +293,16 @@ def trigger_partial_update(
|
|
|
290
293
|
else:
|
|
291
294
|
with connections["qa"].cursor() as cursor:
|
|
292
295
|
cursor.execute(
|
|
293
|
-
|
|
296
|
+
"SELECT MAX(last_user_update) FROM sys.dm_db_index_usage_stats WHERE OBJECT_NAME(object_id) = %S'",
|
|
297
|
+
(table_change_name,),
|
|
294
298
|
)
|
|
295
299
|
max_last_updated_qa = (
|
|
296
300
|
pytz.timezone(settings.TIME_ZONE).localize(result[0]) if (result := cursor.fetchone()) else None
|
|
297
301
|
)
|
|
298
302
|
if max_last_updated_qa and max_last_updated_qa > max_last_updated:
|
|
299
303
|
for _, security_id in cursor.execute(
|
|
300
|
-
|
|
304
|
+
"SELECT UpdateFlag_, %s FROM %s",
|
|
305
|
+
(id_field, table_change_name),
|
|
301
306
|
).fetchall():
|
|
302
307
|
try:
|
|
303
308
|
update_or_create_item(security_id)
|
wbfdm/dataloaders/protocols.py
CHANGED
|
@@ -49,7 +49,7 @@ class FXRateProtocol(Protocol):
|
|
|
49
49
|
class MarketDataProtocol(Protocol):
|
|
50
50
|
def market_data(
|
|
51
51
|
self,
|
|
52
|
-
values: list[MarketData] =
|
|
52
|
+
values: list[MarketData] | None = None,
|
|
53
53
|
from_date: date | None = None,
|
|
54
54
|
to_date: date | None = None,
|
|
55
55
|
exact_date: date | None = None,
|
|
@@ -115,13 +115,7 @@ class FinancialAnalysisGenerator:
|
|
|
115
115
|
self.instruments_repr_map = {i.id: i.name_repr for i in self.instruments}
|
|
116
116
|
self.currency_map = {i.id: i.currency.id for i in self.instruments}
|
|
117
117
|
|
|
118
|
-
def build_df(
|
|
119
|
-
self,
|
|
120
|
-
instrument_prices_field_names: list[str] = [],
|
|
121
|
-
fundamental_field_names: list[str] = [],
|
|
122
|
-
forecast_field_names: list[str] = [],
|
|
123
|
-
daily_fundamental_field_names: list[str] = [],
|
|
124
|
-
):
|
|
118
|
+
def build_df(self, **kwargs):
|
|
125
119
|
"""
|
|
126
120
|
Used to returns a df with all the variables passed in four separate lists
|
|
127
121
|
|
|
@@ -239,7 +233,7 @@ class FinancialAnalysisGenerator:
|
|
|
239
233
|
@staticmethod
|
|
240
234
|
def clean_data(
|
|
241
235
|
df: pd.DataFrame,
|
|
242
|
-
var_list: list[str]
|
|
236
|
+
var_list: list[str],
|
|
243
237
|
drop_negative=True,
|
|
244
238
|
q_low: float = 0.05,
|
|
245
239
|
q_high: float = 0.95,
|
|
@@ -26,7 +26,6 @@ class FakeDateRange(wb_filters.FilterSet):
|
|
|
26
26
|
|
|
27
27
|
class InstrumentPriceFilterSet(wb_filters.FilterSet):
|
|
28
28
|
date = wb_filters.FinancialPerformanceDateRangeFilter(
|
|
29
|
-
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
30
29
|
label="Date Range",
|
|
31
30
|
required=True,
|
|
32
31
|
clearable=False,
|
wbfdm/filters/instruments.py
CHANGED
|
@@ -138,7 +138,7 @@ class InstrumentFilterSet(TagFilterMixin, InstrumentFavoriteGroupFilterSet):
|
|
|
138
138
|
if "parent" in data:
|
|
139
139
|
data.pop("classifications", None) # remove classifications in case we are navigating the tree
|
|
140
140
|
data.pop("level", None)
|
|
141
|
-
super().__init__(data=data,
|
|
141
|
+
super().__init__(*args, data=data, **kwargs)
|
|
142
142
|
|
|
143
143
|
class Meta:
|
|
144
144
|
model = Instrument
|
|
@@ -7,11 +7,11 @@ from celery import shared_task
|
|
|
7
7
|
from tqdm import tqdm
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class RateLimitError(Exception):
|
|
11
11
|
pass
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class CreditLimitError(Exception):
|
|
15
15
|
pass
|
|
16
16
|
|
|
17
17
|
|
|
@@ -38,17 +38,18 @@ class Client:
|
|
|
38
38
|
url,
|
|
39
39
|
params={**params, **{"nextPageToken": next_page_token}} if next_page_token else params,
|
|
40
40
|
headers={"authorization": self.jwt_header_value},
|
|
41
|
+
timeout=10,
|
|
41
42
|
)
|
|
42
43
|
if resp.status_code != 200:
|
|
43
44
|
if resp.status_code == 429:
|
|
44
|
-
raise
|
|
45
|
+
raise RateLimitError()
|
|
45
46
|
raise requests.ConnectionError(
|
|
46
47
|
f"unexpected error from api\nstatus_code: {resp.status_code}\nerror: {resp.text}"
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
if credits_remaining_str := resp.headers.get("x-cbinsights-credits-remaining", None):
|
|
50
51
|
if int(credits_remaining_str) <= 0:
|
|
51
|
-
raise
|
|
52
|
+
raise CreditLimitError()
|
|
52
53
|
|
|
53
54
|
return resp.json()
|
|
54
55
|
|
|
@@ -72,11 +73,11 @@ class Client:
|
|
|
72
73
|
resp = self._request(data_url, params=params, next_page_token=next_page_token)
|
|
73
74
|
yield resp
|
|
74
75
|
next_page_token = resp["nextPageToken"]
|
|
75
|
-
except
|
|
76
|
+
except RateLimitError:
|
|
76
77
|
time.sleep(self.rate_limit_sleep)
|
|
77
78
|
retry += 1
|
|
78
79
|
if retry >= 5:
|
|
79
|
-
raise
|
|
80
|
+
raise RateLimitError()
|
|
80
81
|
|
|
81
82
|
def _chunk_paginated_request(self, data_url, org_ids, extra_params, endpoint, debug: bool = False):
|
|
82
83
|
data = []
|
|
@@ -96,8 +97,7 @@ class Client:
|
|
|
96
97
|
auth_url = "https://api.cbinsights.com/v1/authorize"
|
|
97
98
|
|
|
98
99
|
auth_resp = requests.get(
|
|
99
|
-
auth_url,
|
|
100
|
-
params={"clientId": self.client_id, "clientSecret": self.client_secret},
|
|
100
|
+
auth_url, params={"clientId": self.client_id, "clientSecret": self.client_secret}, timeout=10
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
if auth_resp.status_code != 200:
|
|
@@ -61,7 +61,7 @@ class Controller:
|
|
|
61
61
|
instrument_ric: str = None,
|
|
62
62
|
instrument_isin: str = None,
|
|
63
63
|
instrument_mnemonic: str = None,
|
|
64
|
-
perm_id_symbols:
|
|
64
|
+
perm_id_symbols: tuple[str, ...] = ("QPID", "IPID"),
|
|
65
65
|
) -> str | None:
|
|
66
66
|
def _process_ticker(ticker):
|
|
67
67
|
if not (df := self.client.get_static_df(tickers=[ticker], fields=perm_id_symbols)).empty:
|
|
@@ -46,7 +46,7 @@ class InstrumentLookup:
|
|
|
46
46
|
cache_key = self._get_cache_key(**kwargs)
|
|
47
47
|
self.cache[cache_key] = instrument
|
|
48
48
|
|
|
49
|
-
def _lookup_security(
|
|
49
|
+
def _lookup_security( # noqa: C901
|
|
50
50
|
self,
|
|
51
51
|
instrument_type=None,
|
|
52
52
|
currency=None,
|
|
@@ -57,7 +57,6 @@ class InstrumentLookup:
|
|
|
57
57
|
**identifiers,
|
|
58
58
|
):
|
|
59
59
|
identifiers = {k: v for k, v in identifiers.items() if v is not None}
|
|
60
|
-
|
|
61
60
|
instrument = None
|
|
62
61
|
|
|
63
62
|
# We need to lookup ticker because some provider gives us ticker with or without space in it
|
|
@@ -113,33 +112,41 @@ class InstrumentLookup:
|
|
|
113
112
|
conditions.append(Q(**{f"{field}": field_value}))
|
|
114
113
|
if field == "isin":
|
|
115
114
|
conditions.append(Q(old_isins__contains=[field_value]))
|
|
115
|
+
if field == "ticker":
|
|
116
|
+
conditions.append(Q(ticker__regex=rf"^{field_value}([A-Za-z]?|\.?[A-Za-z])$"))
|
|
117
|
+
conditions.append(Q(refinitiv_mnemonic_code=f"@{field_value}"))
|
|
118
|
+
|
|
116
119
|
if conditions:
|
|
117
120
|
instruments = instruments.filter(reduce(operator.or_, conditions))
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
if (
|
|
122
|
+
currency and instruments.filter(currency=currency).exists()
|
|
123
|
+
): # if currency is provides, we use it as validator
|
|
120
124
|
instruments = instruments.filter(currency=currency)
|
|
121
|
-
|
|
122
125
|
if exchange:
|
|
123
126
|
instruments_tmp = instruments.filter(exchange=exchange)
|
|
124
127
|
if instruments_tmp.exists():
|
|
125
128
|
instruments = instruments_tmp
|
|
129
|
+
|
|
126
130
|
# last chance
|
|
127
131
|
if name and instruments.count() > 1:
|
|
128
132
|
instruments = instruments.annotate(similarity_score=TrigramSimilarity("name", name))
|
|
129
133
|
if instruments.filter(similarity_score__gt=self.trigram_similarity_min_score).count() == 1:
|
|
130
134
|
instruments = instruments.filter(similarity_score__gt=self.trigram_similarity_min_score)
|
|
135
|
+
|
|
131
136
|
if instruments.count() == 1:
|
|
132
137
|
instrument = instruments.first()
|
|
133
|
-
elif instrument_type and identifiers:
|
|
138
|
+
elif instrument_type and (name or identifiers):
|
|
134
139
|
# if instrument type was provided but we still didn't find the security, we try without the instrument type in case it was mislabeled
|
|
135
140
|
instrument = self._lookup_security(
|
|
136
141
|
only_investable_universe=only_investable_universe,
|
|
137
142
|
exact_lookup=exact_lookup,
|
|
138
143
|
currency=currency,
|
|
139
144
|
exchange=exchange,
|
|
145
|
+
name=name,
|
|
140
146
|
**identifiers,
|
|
141
147
|
)
|
|
142
|
-
if not instrument and
|
|
148
|
+
if not instrument and identifiers:
|
|
149
|
+
identifiers.pop(list(identifiers.keys())[0])
|
|
143
150
|
# Sometime, identifier provided emptied the queryset of possible instruments. In a last chance approach, we try to only look for security with the given name
|
|
144
151
|
instrument = self._lookup_security(
|
|
145
152
|
only_investable_universe=only_investable_universe,
|
|
@@ -148,6 +155,7 @@ class InstrumentLookup:
|
|
|
148
155
|
currency=currency,
|
|
149
156
|
exchange=exchange,
|
|
150
157
|
name=name,
|
|
158
|
+
**identifiers,
|
|
151
159
|
)
|
|
152
160
|
if instrument and only_investable_universe and not instrument.is_security and instrument.parent:
|
|
153
161
|
instrument = instrument.parent
|
|
@@ -237,8 +245,8 @@ class InstrumentImportHandler(ImportExportHandler):
|
|
|
237
245
|
if instrument_id := data.pop("id", None):
|
|
238
246
|
try:
|
|
239
247
|
return self.model.objects.get(id=instrument_id)
|
|
240
|
-
except self.model.DoesNotExist:
|
|
241
|
-
raise DeserializationError("Instrument id does not match an existing instrument")
|
|
248
|
+
except self.model.DoesNotExist as e:
|
|
249
|
+
raise DeserializationError("Instrument id does not match an existing instrument") from e
|
|
242
250
|
else:
|
|
243
251
|
return self.instrument_lookup.lookup(only_security=only_security, **data)
|
|
244
252
|
|
|
@@ -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
|
|
@@ -56,6 +56,9 @@ class Controversy(models.Model):
|
|
|
56
56
|
review = models.DateField(verbose_name="Reviewed", null=True, blank=True)
|
|
57
57
|
initiated = models.DateField(verbose_name="initiated", null=True, blank=True)
|
|
58
58
|
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
return f"{self.headline} ({self.instrument})"
|
|
61
|
+
|
|
59
62
|
@classmethod
|
|
60
63
|
def dict_to_model(cls, controversy: dict[str, Any], instrument) -> Self:
|
|
61
64
|
return Controversy(
|
wbfdm/models/fields.py
CHANGED
|
@@ -23,7 +23,7 @@ class CompositeKey(models.AutoField):
|
|
|
23
23
|
def __init__(self, columns: list[str], db_column_ref: str | None = None, *args, **kwargs):
|
|
24
24
|
self.columns = columns
|
|
25
25
|
self.db_column_ref = db_column_ref
|
|
26
|
-
super().__init__(primary_key=True,
|
|
26
|
+
super().__init__(*args, primary_key=True, **kwargs)
|
|
27
27
|
|
|
28
28
|
def contribute_to_class(self, cls, name, private_only=False):
|
|
29
29
|
self.set_attributes_from_name(name)
|
|
@@ -108,7 +108,7 @@ class Exact(models.Lookup):
|
|
|
108
108
|
|
|
109
109
|
lookups = [
|
|
110
110
|
lookup_class(field.get_col(self.lhs.alias), self.rhs[column])
|
|
111
|
-
for lookup_class, field, column in zip(lookup_classes, fields, self.lhs.field.columns)
|
|
111
|
+
for lookup_class, field, column in zip(lookup_classes, fields, self.lhs.field.columns, strict=False)
|
|
112
112
|
]
|
|
113
113
|
|
|
114
114
|
value_constraint = WhereNode()
|
wbfdm/models/fk_fields.py
CHANGED
|
@@ -93,7 +93,7 @@ class CompositeForeignKey(ForeignObject):
|
|
|
93
93
|
kwargs["on_delete"] = self.override_on_delete(kwargs["on_delete"])
|
|
94
94
|
|
|
95
95
|
kwargs["to_fields"], kwargs["from_fields"] = zip(
|
|
96
|
-
*((k, v.value) for k, v in self._raw_fields.items() if v.is_local_field)
|
|
96
|
+
*((k, v.value) for k, v in self._raw_fields.items() if v.is_local_field), strict=False
|
|
97
97
|
)
|
|
98
98
|
super().__init__(to, **kwargs)
|
|
99
99
|
|
|
@@ -220,7 +220,7 @@ class CompositeForeignKey(ForeignObject):
|
|
|
220
220
|
|
|
221
221
|
def _check_nullifequal_fields_exists(self):
|
|
222
222
|
res = []
|
|
223
|
-
for field_name,
|
|
223
|
+
for field_name, _ in self.null_if_equal:
|
|
224
224
|
try:
|
|
225
225
|
self.model._meta.get_field(field_name)
|
|
226
226
|
except FieldDoesNotExist:
|
|
@@ -269,7 +269,7 @@ class CompositeForeignKey(ForeignObject):
|
|
|
269
269
|
|
|
270
270
|
return OrderedDict(
|
|
271
271
|
(k, (v if isinstance(v, CompositePart) else LocalFieldValue(v)))
|
|
272
|
-
for k, v in (to_fields.items() if isinstance(to_fields, dict) else zip(to_fields, to_fields))
|
|
272
|
+
for k, v in (to_fields.items() if isinstance(to_fields, dict) else zip(to_fields, to_fields, strict=False))
|
|
273
273
|
)
|
|
274
274
|
|
|
275
275
|
def db_type(self, connection):
|
|
@@ -162,6 +162,9 @@ class RelatedInstrumentThroughModel(models.Model):
|
|
|
162
162
|
class Meta:
|
|
163
163
|
unique_together = ("instrument", "related_instrument", "is_primary", "related_type")
|
|
164
164
|
|
|
165
|
+
def __str__(self) -> str:
|
|
166
|
+
return f"{self.instrument} - {self.related_instrument} ({self.related_type})"
|
|
167
|
+
|
|
165
168
|
def save(self, *args, **kwargs):
|
|
166
169
|
qs = RelatedInstrumentThroughModel.objects.filter(
|
|
167
170
|
instrument=self.instrument, related_type=self.related_type, is_primary=True
|
|
@@ -205,38 +205,38 @@ class InstrumentType(models.Model):
|
|
|
205
205
|
|
|
206
206
|
@classmethod
|
|
207
207
|
@property
|
|
208
|
-
def PRODUCT(cls):
|
|
208
|
+
def PRODUCT(cls): # noqa
|
|
209
209
|
return InstrumentType.objects.get_or_create(
|
|
210
210
|
key="product", defaults={"name": "Product", "short_name": "Product"}
|
|
211
211
|
)[0]
|
|
212
212
|
|
|
213
213
|
@classmethod
|
|
214
214
|
@property
|
|
215
|
-
def EQUITY(cls):
|
|
215
|
+
def EQUITY(cls): # noqa
|
|
216
216
|
return InstrumentType.objects.get_or_create(key="equity", defaults={"name": "equity", "short_name": "equity"})[
|
|
217
217
|
0
|
|
218
218
|
]
|
|
219
219
|
|
|
220
220
|
@classmethod
|
|
221
221
|
@property
|
|
222
|
-
def INDEX(cls):
|
|
222
|
+
def INDEX(cls): # noqa
|
|
223
223
|
return InstrumentType.objects.get_or_create(key="index", defaults={"name": "Index", "short_name": "Index"})[0]
|
|
224
224
|
|
|
225
225
|
@classmethod
|
|
226
226
|
@property
|
|
227
|
-
def CASH(cls):
|
|
227
|
+
def CASH(cls): # noqa
|
|
228
228
|
return InstrumentType.objects.get_or_create(key="cash", defaults={"name": "Cash", "short_name": "Cash"})[0]
|
|
229
229
|
|
|
230
230
|
@classmethod
|
|
231
231
|
@property
|
|
232
|
-
def CASHEQUIVALENT(cls):
|
|
232
|
+
def CASHEQUIVALENT(cls): # noqa
|
|
233
233
|
return InstrumentType.objects.get_or_create(
|
|
234
234
|
key="cash_equivalent", defaults={"name": "Cash Equivalents", "short_name": "Cash Equivalents"}
|
|
235
235
|
)[0]
|
|
236
236
|
|
|
237
237
|
@classmethod
|
|
238
238
|
@property
|
|
239
|
-
def PRODUCT_GROUP(cls):
|
|
239
|
+
def PRODUCT_GROUP(cls): # noqa
|
|
240
240
|
return InstrumentType.objects.get_or_create(
|
|
241
241
|
key="product_group", defaults={"name": "Product Group", "short_name": "Product Group"}
|
|
242
242
|
)[0]
|
|
@@ -671,11 +671,11 @@ class Instrument(ComplexToStringMixin, TagModelMixin, ImportMixin, InstrumentPMS
|
|
|
671
671
|
raise ValidationError("An instrument in the investable universe cannot have children")
|
|
672
672
|
return self
|
|
673
673
|
|
|
674
|
-
def pre_save(self):
|
|
674
|
+
def pre_save(self): # noqa: C901
|
|
675
675
|
if self.instrument_type:
|
|
676
676
|
self.is_security = self.instrument_type.is_security
|
|
677
|
-
if self.delisted_date:
|
|
678
|
-
|
|
677
|
+
# if self.delisted_date:
|
|
678
|
+
# self.is_security = False
|
|
679
679
|
if not self.name_repr:
|
|
680
680
|
self.name_repr = self.name
|
|
681
681
|
if not self.founded_year and self.inception_date:
|
|
@@ -63,7 +63,7 @@ class InstrumentPMSMixin:
|
|
|
63
63
|
market_capitalization=price.market_capitalization,
|
|
64
64
|
outstanding_shares=float(price.outstanding_shares) if price.outstanding_shares else None,
|
|
65
65
|
)
|
|
66
|
-
except InstrumentPrice.DoesNotExist:
|
|
66
|
+
except InstrumentPrice.DoesNotExist as e:
|
|
67
67
|
prices = sorted(
|
|
68
68
|
self.get_prices(from_date=(val_date - BDay(price_date_timedelta)).date(), to_date=val_date),
|
|
69
69
|
key=lambda x: x["valuation_date"],
|
|
@@ -87,7 +87,7 @@ class InstrumentPMSMixin:
|
|
|
87
87
|
market_capitalization=p.get("market_capitalization", None),
|
|
88
88
|
outstanding_shares=p.get("outstanding_shares", None),
|
|
89
89
|
)
|
|
90
|
-
raise ValueError("Not price was found")
|
|
90
|
+
raise ValueError("Not price was found") from e
|
|
91
91
|
|
|
92
92
|
# Instrument Prices Utility Functions
|
|
93
93
|
@classmethod
|
|
@@ -143,6 +143,9 @@ class OptionAggregate(BaseOptionAbstractModel):
|
|
|
143
143
|
),
|
|
144
144
|
]
|
|
145
145
|
|
|
146
|
+
def __str__(self) -> str:
|
|
147
|
+
return f"{self.instrument} - {self.date} - {self.type}"
|
|
148
|
+
|
|
146
149
|
|
|
147
150
|
class Option(BaseOptionAbstractModel):
|
|
148
151
|
import_export_handler_class = OptionImportHandler
|
|
@@ -224,3 +227,6 @@ class Option(BaseOptionAbstractModel):
|
|
|
224
227
|
fields=["type"],
|
|
225
228
|
),
|
|
226
229
|
]
|
|
230
|
+
|
|
231
|
+
def __str__(self):
|
|
232
|
+
return f"{self.contract_identifier} - {self.date} - {self.type}"
|
|
@@ -52,7 +52,9 @@ class ClassifiedInstrumentDisplayConfig(DisplayViewConfig):
|
|
|
52
52
|
if group := self.view.classification_group:
|
|
53
53
|
fields = [dp.Field(key="instrument", label="Instrument")]
|
|
54
54
|
level_representations = group.get_levels_representation()
|
|
55
|
-
for key, label in zip(
|
|
55
|
+
for key, label in zip(
|
|
56
|
+
reversed(group.get_fields_names(sep="_")), reversed(level_representations[1:]), strict=False
|
|
57
|
+
):
|
|
56
58
|
fields.append(
|
|
57
59
|
dp.Field(key=f"classification_{key}", label=label),
|
|
58
60
|
)
|
|
@@ -31,7 +31,8 @@ class MonthlyPerformancesInstrumentTitleConfig(TitleViewConfig):
|
|
|
31
31
|
|
|
32
32
|
class InstrumentTitleConfigMixin(TitleViewConfig):
|
|
33
33
|
def get_list_title(self):
|
|
34
|
-
|
|
34
|
+
if not self.message:
|
|
35
|
+
raise AssertionError("No message has been set")
|
|
35
36
|
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
36
37
|
return f"{self.message} {str(instrument)}"
|
|
37
38
|
|
wbfdm/viewsets/esg.py
CHANGED
|
@@ -66,8 +66,8 @@ class InstrumentESGPAIViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
66
66
|
def get_dataframe(self, request, queryset, **kwargs):
|
|
67
67
|
df = pd.DataFrame(queryset.dl.esg(values=list(ESG))).reset_index()
|
|
68
68
|
if not df.empty:
|
|
69
|
-
|
|
69
|
+
esg_mapping = ESG.mapping()
|
|
70
70
|
df[["section", "asi", "metric", "factor"]] = pd.DataFrame(
|
|
71
|
-
df.factor_code.map(
|
|
71
|
+
df.factor_code.map(esg_mapping).tolist(), index=df.index
|
|
72
72
|
)
|
|
73
73
|
return df
|
|
@@ -48,8 +48,8 @@ class FinancialMetricAnalysisPandasViewSet(InstrumentMixin, ExportPandasAPIViewS
|
|
|
48
48
|
if group_keys := request.GET.get("group_keys"):
|
|
49
49
|
try:
|
|
50
50
|
financial = Financial(group_keys.lower())
|
|
51
|
-
except ValueError:
|
|
52
|
-
raise ParseError()
|
|
51
|
+
except ValueError as e:
|
|
52
|
+
raise ParseError() from e
|
|
53
53
|
df, self._estimate_mapping, self._columns = financial_metric_estimate_analysis(
|
|
54
54
|
queryset.first().id, financial
|
|
55
55
|
)
|
|
@@ -43,7 +43,7 @@ class ValuationRatioChartViewSet(InstrumentMixin, viewsets.TimeSeriesChartViewSe
|
|
|
43
43
|
fig = go.Figure()
|
|
44
44
|
colors = iter(px.colors.qualitative.T10)
|
|
45
45
|
|
|
46
|
-
for ratio, color in zip(ratios, colors):
|
|
46
|
+
for ratio, color in zip(ratios, colors, strict=False):
|
|
47
47
|
with suppress(AttributeError):
|
|
48
48
|
series = getattr(df, ratio.value)
|
|
49
49
|
|
|
@@ -97,10 +97,10 @@ class FinancialSummary(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
97
97
|
|
|
98
98
|
# Adjust the columns to be in a different format
|
|
99
99
|
df.index = df.index.map(lambda x: x.strftime("%b/%y"))
|
|
100
|
-
|
|
101
|
-
if df.shape[0] >
|
|
100
|
+
max_row = 8
|
|
101
|
+
if df.shape[0] > max_row:
|
|
102
102
|
df = df.iloc[1:] # remove first row
|
|
103
|
-
df = df.iloc[0 : min([df.shape[0],
|
|
103
|
+
df = df.iloc[0 : min([df.shape[0], max_row])] # keep only 8 row maximum
|
|
104
104
|
|
|
105
105
|
self._estimate_columns = df["estimate"].to_dict()
|
|
106
106
|
df = df.drop(columns=["estimate"], errors="ignore")
|
|
@@ -190,7 +190,7 @@ class FinancialSummary(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
190
190
|
return getattr(self, "_estimate_columns", {})
|
|
191
191
|
|
|
192
192
|
@cached_property
|
|
193
|
-
def FINANCIAL_VALUES(self) -> list[Financial]:
|
|
193
|
+
def FINANCIAL_VALUES(self) -> list[Financial]: # noqa
|
|
194
194
|
return [
|
|
195
195
|
Financial.REVENUE, # SAL
|
|
196
196
|
Financial.GROSS_PROFIT, # GRI
|
|
@@ -216,7 +216,7 @@ class FinancialSummary(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
216
216
|
]
|
|
217
217
|
|
|
218
218
|
@cached_property
|
|
219
|
-
def FIELDS(self) -> list[str]:
|
|
219
|
+
def FIELDS(self) -> list[str]: # noqa
|
|
220
220
|
return [
|
|
221
221
|
"revenue",
|
|
222
222
|
"revenue_growth",
|
|
@@ -240,7 +240,7 @@ class FinancialSummary(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
240
240
|
]
|
|
241
241
|
|
|
242
242
|
@property
|
|
243
|
-
def LABELS(self) -> list[str]:
|
|
243
|
+
def LABELS(self) -> list[str]: # noqa
|
|
244
244
|
currency_key = self.instrument.currency.key if self.instrument.currency else "N.A."
|
|
245
245
|
return [
|
|
246
246
|
f"in {currency_key} MN",
|
|
@@ -139,7 +139,7 @@ class StatementWithEstimatesPandasViewSet(InstrumentMixin, ExportPandasAPIViewSe
|
|
|
139
139
|
return self.df.columns
|
|
140
140
|
|
|
141
141
|
def get_ordering_fields(self):
|
|
142
|
-
return self.columns
|
|
142
|
+
return [x for x in self.columns if x != "progress"]
|
|
143
143
|
|
|
144
144
|
@cached_property
|
|
145
145
|
def year_columns(self):
|
|
@@ -118,7 +118,7 @@ class ValuationRatiosChartView(InstrumentMixin, viewsets.ChartViewSet):
|
|
|
118
118
|
filterset_class = FinancialAnalysisValuationRatiosFilterSet
|
|
119
119
|
LIST_DOCUMENTATION = "wbfdm/markdown/documentation/financial_analysis_instrument_ratios.md"
|
|
120
120
|
|
|
121
|
-
def get_plotly(self, queryset):
|
|
121
|
+
def get_plotly(self, queryset): # noqa: C901
|
|
122
122
|
# Set plotly as the default plotting lib
|
|
123
123
|
pd.options.plotting.backend = "plotly"
|
|
124
124
|
|
|
@@ -297,7 +297,7 @@ class ValuationRatiosChartView(InstrumentMixin, viewsets.ChartViewSet):
|
|
|
297
297
|
|
|
298
298
|
fig = go.Figure()
|
|
299
299
|
dates = []
|
|
300
|
-
for
|
|
300
|
+
for single_date in daterange(date1, date2):
|
|
301
301
|
ratio_trace = ratios[
|
|
302
302
|
(ratios["datetxt"] == single_date.strftime("%Y-%m-%d")) & (ratios[z_axis] > 0)
|
|
303
303
|
]
|
|
@@ -424,16 +424,13 @@ class ValuationRatiosChartView(InstrumentMixin, viewsets.ChartViewSet):
|
|
|
424
424
|
if not ranges:
|
|
425
425
|
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
|
426
426
|
|
|
427
|
-
for
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
],
|
|
435
|
-
start=1,
|
|
436
|
-
):
|
|
427
|
+
for plot in [
|
|
428
|
+
("pe", "#ff6361", VariableChoices.PE.chart_label, VariableChoices.PE, False),
|
|
429
|
+
("peg", "#ffa600", VariableChoices.PEG.chart_label, VariableChoices.PEG, True),
|
|
430
|
+
("ps", "#58508d", VariableChoices.PS.chart_label, VariableChoices.PS, False),
|
|
431
|
+
("evebitda", "#003f5c", VariableChoices.EVEBITDA.chart_label, VariableChoices.EVEBITDA, False),
|
|
432
|
+
("pfcf", "#bc5090", VariableChoices.PFCF.chart_label, VariableChoices.PFCF, False),
|
|
433
|
+
]:
|
|
437
434
|
fig.add_trace(
|
|
438
435
|
go.Scatter(
|
|
439
436
|
x=ratios["date"],
|
wbfdm/viewsets/market_data.py
CHANGED
|
@@ -150,7 +150,7 @@ class MarketDataChartViewSet(InstrumentMixin, viewsets.TimeSeriesChartViewSet):
|
|
|
150
150
|
)
|
|
151
151
|
fig.update_xaxes(rangebreaks=[{"pattern": "day of week", "bounds": [6, 1]}])
|
|
152
152
|
|
|
153
|
-
for
|
|
153
|
+
for d in fig.data:
|
|
154
154
|
with suppress(AttributeError, IndexError, ValueError): # Either Candlestick or OHCL
|
|
155
155
|
if (y := d.y[-1]) is not None:
|
|
156
156
|
text_value = (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wbfdm
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.56.0
|
|
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.*
|
|
@@ -2,7 +2,7 @@ wbfdm/__init__.py,sha256=uyfGiipFnhOCQlqywx7wsgp6d-SYnqPqsPQd_xePZl8,23
|
|
|
2
2
|
wbfdm/apps.py,sha256=83jiPNQfxUbO7KYKBS5qo5SZNuz25Kmmy_txoVmIKG0,333
|
|
3
3
|
wbfdm/dynamic_preferences_registry.py,sha256=tE9ZxlQDD7rqKM3GXjxyORQqDjf39GTENkOCZFt8Ixo,1299
|
|
4
4
|
wbfdm/enums.py,sha256=5AuUouk5uuSNmRc6e-SiBu4FPmHVTN60ol9ftiuVrAc,33041
|
|
5
|
-
wbfdm/jinja2.py,sha256=
|
|
5
|
+
wbfdm/jinja2.py,sha256=E2EsNwt-4IryFH0FYFt1wwH0-gzYHOnsUAH5l7ZPaiE,332
|
|
6
6
|
wbfdm/preferences.py,sha256=8ghDcaapOMso1kjtNfKbSFykPUTxzqI5R77gM3BgiMs,927
|
|
7
7
|
wbfdm/signals.py,sha256=PhAsFpQZF1YVe5UpedaRelUD_TVjemqRYm1HzV-bhmY,597
|
|
8
8
|
wbfdm/tasks.py,sha256=Q7iuSgV8LdPoKxgyMmTg5qukxHrM5XW1k1H_zI958zU,5104
|
|
@@ -37,21 +37,21 @@ wbfdm/analysis/technical_analysis/traces.py,sha256=GhyvVzdg1fmG5i1fai2zz9BnJ__5N
|
|
|
37
37
|
wbfdm/backends/dto.py,sha256=5IdeGVsrk8trEi9rqtq-zisqiEDE_VLBP8RxlfZZnjk,596
|
|
38
38
|
wbfdm/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
39
|
wbfdm/contrib/dsws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
wbfdm/contrib/dsws/client.py,sha256=
|
|
41
|
-
wbfdm/contrib/dsws/dataloaders/market_data.py,sha256=
|
|
40
|
+
wbfdm/contrib/dsws/client.py,sha256=vc59bxDU35Z5X76nSqGltIwUF6_ZYMTBLvBQvrJnLEk,14510
|
|
41
|
+
wbfdm/contrib/dsws/dataloaders/market_data.py,sha256=ES8aoREclKx56EcbHzQyLWJSg7cGCRTZrzs42Ka1uLk,7479
|
|
42
42
|
wbfdm/contrib/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
43
|
wbfdm/contrib/internal/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
-
wbfdm/contrib/internal/dataloaders/market_data.py,sha256=
|
|
44
|
+
wbfdm/contrib/internal/dataloaders/market_data.py,sha256=OjM4I3c3Ac_ZQn-OsAZQ5FklVHVi3UE6F8EqtUhyFik,4602
|
|
45
45
|
wbfdm/contrib/metric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
wbfdm/contrib/metric/apps.py,sha256=T-Na_lyFeLR8ebGvaW-mcjKFLqiZcsPXnUlOuVPOiWw,297
|
|
47
47
|
wbfdm/contrib/metric/decorators.py,sha256=VPmyjEJaq5HqXOt_ZizqcXfRqSEXw4lanDmubUjb2GY,476
|
|
48
48
|
wbfdm/contrib/metric/dispatch.py,sha256=GdJoYRFiZXWwo3hZeNTDxOUwg7iniv63ddb-A-7Pc6w,1090
|
|
49
49
|
wbfdm/contrib/metric/dto.py,sha256=vS93tUvfusWdE7gUJsp320GwfwWpb8bGJDdrb1GB6M8,3413
|
|
50
|
-
wbfdm/contrib/metric/exceptions.py,sha256=
|
|
50
|
+
wbfdm/contrib/metric/exceptions.py,sha256=NH9xFsrQJJcbt-Yn8GWNsmcRfw1MiSjHLfyii4STTDA,196
|
|
51
51
|
wbfdm/contrib/metric/factories.py,sha256=b6-3fSERbZGjGw6QC0k3hhSpyQKVy2MJoZzX4y3GPEo,1123
|
|
52
52
|
wbfdm/contrib/metric/filters.py,sha256=TBFnA2gWuJU_QE5BPvpnBU4SVAsUemM__gSeUvmHQxQ,1690
|
|
53
|
-
wbfdm/contrib/metric/models.py,sha256=
|
|
54
|
-
wbfdm/contrib/metric/orchestrators.py,sha256=
|
|
53
|
+
wbfdm/contrib/metric/models.py,sha256=1Tws04Ocd9JDt0Ql7zVDGyRvcL7x6FqDsXsen6ZNkzg,7157
|
|
54
|
+
wbfdm/contrib/metric/orchestrators.py,sha256=CTs2ho2TI4zMAxH1MUM-zK1rBkMo5AjSJCWm0Lv_HyI,3713
|
|
55
55
|
wbfdm/contrib/metric/registry.py,sha256=SdZff6FIrtdbyAO0X0BpXndJAIhU6VVojNui_MEw7jI,3167
|
|
56
56
|
wbfdm/contrib/metric/serializers.py,sha256=FqX2pgP4SL1oteVrnAWQezBOJD6JyTidLBEK72GMxrM,1636
|
|
57
57
|
wbfdm/contrib/metric/signals.py,sha256=qe-CGNZEc7csseiGgNbz8legvz0EM3e31k8kPQ19Zyw,417
|
|
@@ -61,9 +61,9 @@ wbfdm/contrib/metric/admin/__init__.py,sha256=FW3qhfmS5tA4RNOv9Zse56dICDG7zgdHgB
|
|
|
61
61
|
wbfdm/contrib/metric/admin/instruments.py,sha256=mPwwdwxeuo6nvyJ2_xolVdlpK05KMlzOPLaSlh1PtHE,419
|
|
62
62
|
wbfdm/contrib/metric/admin/metrics.py,sha256=SJU-5ona3npgRjTgDoHvo9aCztq-VMnIVvvsdk5bWSs,1133
|
|
63
63
|
wbfdm/contrib/metric/backends/__init__.py,sha256=Bl7bGEYVx38uiN2BS-0pr2WpIkbhUl7yPCEwt1W4tUg,128
|
|
64
|
-
wbfdm/contrib/metric/backends/base.py,sha256=
|
|
64
|
+
wbfdm/contrib/metric/backends/base.py,sha256=LyU02bfcfEVWKMMMhSq4PJqXeOP5nW7NEFJwxb_X4i8,6757
|
|
65
65
|
wbfdm/contrib/metric/backends/performances.py,sha256=yt9E7A_OEWQBQabf9bqt4Rc3S6oUXbSeM6NR1nYidtg,11642
|
|
66
|
-
wbfdm/contrib/metric/backends/statistics.py,sha256=
|
|
66
|
+
wbfdm/contrib/metric/backends/statistics.py,sha256=gWvpBqpkLIroBZZ-KceCMWRKRwdJX54OUvtbWLwwmXI,7886
|
|
67
67
|
wbfdm/contrib/metric/backends/utils.py,sha256=xpsDmoL2Jv9-KTuo-ay65D4m9Kf35jGc9cbeejhFLQU,131
|
|
68
68
|
wbfdm/contrib/metric/migrations/0001_initial.py,sha256=EwXRJrG7zQYT1bD6jjaf1gZNfpd8f2gzta09GYqukiM,3403
|
|
69
69
|
wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py,sha256=xp6MACDohMSs4YfFbprCLa5G7zEuBhfZgpVXyMsZPWI,808
|
|
@@ -78,14 +78,14 @@ wbfdm/contrib/metric/tests/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQ
|
|
|
78
78
|
wbfdm/contrib/metric/tests/backends/test_performances.py,sha256=SvZerb0y2ifpr2gLKWgXIiV6Shqne6di0ONeC7lFoRs,7896
|
|
79
79
|
wbfdm/contrib/metric/tests/backends/test_statistics.py,sha256=jtQOslHiCdZcfSJy9NU-xhoz1_BUx6FrdmhPZpJU03I,1921
|
|
80
80
|
wbfdm/contrib/metric/viewsets/__init__.py,sha256=LTOyOBlhagANq164m7f0oJuz8qCkuq2R8-3lwEZejGs,85
|
|
81
|
-
wbfdm/contrib/metric/viewsets/mixins.py,sha256=
|
|
81
|
+
wbfdm/contrib/metric/viewsets/mixins.py,sha256=Nb9jjxov3Zzz_5UFpY_Sg54bUpL7-iJ-RNpJfzs-82o,11063
|
|
82
82
|
wbfdm/contrib/metric/viewsets/viewsets.py,sha256=nZBZ9MUfIwmgLSgEtpQdFKdPLYc2vaH-vC8xUplbXQY,1447
|
|
83
83
|
wbfdm/contrib/metric/viewsets/configs/__init__.py,sha256=jHM2GSU1E9Bu8ZxaKjWweO3mQKuOUmIu1rmYzpccbtU,51
|
|
84
84
|
wbfdm/contrib/metric/viewsets/configs/display.py,sha256=aZLy3wSCgJlfrS71qxjPfT56u52C5hi9dxqGtGRqCAo,3722
|
|
85
85
|
wbfdm/contrib/metric/viewsets/configs/menus.py,sha256=GJbDm3p6qeobet09kpQSSuJT3Bs14gYpXxQ1P16b5VM,422
|
|
86
86
|
wbfdm/contrib/metric/viewsets/configs/utils.py,sha256=SREiSRS4faohL6lVxmeD3zJN8bb-C-PlANUr8AgpIkI,5009
|
|
87
87
|
wbfdm/contrib/msci/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
88
|
-
wbfdm/contrib/msci/client.py,sha256=
|
|
88
|
+
wbfdm/contrib/msci/client.py,sha256=pqfwFwsRrqd_ywpBP7FJblCEH0QI0fdiUAKbgVCYN7E,3721
|
|
89
89
|
wbfdm/contrib/msci/sync.py,sha256=StD2TliqxVnZ7YsITjYvdh7E95USS3_P9j8KgRlcDKY,2289
|
|
90
90
|
wbfdm/contrib/msci/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
91
|
wbfdm/contrib/msci/dataloaders/esg.py,sha256=VM2zprCC0q0CYSwGPA252czcVx9QpmxYv8FsLTKCGAI,4820
|
|
@@ -95,15 +95,15 @@ wbfdm/contrib/msci/tests/conftest.py,sha256=ae6CXNcGkDStajhWAgZWarsA2fh3PCDwOv4W
|
|
|
95
95
|
wbfdm/contrib/msci/tests/test_client.py,sha256=cR3uE_Ln21U2p0fpKurKHxzzfmgX3V0h6ldH6lyUaiU,2866
|
|
96
96
|
wbfdm/contrib/qa/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
97
97
|
wbfdm/contrib/qa/apps.py,sha256=NVH9FSbJh3XnddR5coJ51jka7nd9wXjwnCqO7h8gNXA,528
|
|
98
|
-
wbfdm/contrib/qa/database_routers.py,sha256=
|
|
98
|
+
wbfdm/contrib/qa/database_routers.py,sha256=PAcgT-oU1W8znNGhqUon2MZS9XXEmhsB-iM5UuD3Hnw,943
|
|
99
99
|
wbfdm/contrib/qa/tasks.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
100
100
|
wbfdm/contrib/qa/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
101
|
-
wbfdm/contrib/qa/dataloaders/adjustments.py,sha256=
|
|
102
|
-
wbfdm/contrib/qa/dataloaders/corporate_actions.py,sha256=
|
|
101
|
+
wbfdm/contrib/qa/dataloaders/adjustments.py,sha256=YAYAiCm71QjPt5dZwzezgCQpm5zRWb6EKCzw-0mdHXI,3032
|
|
102
|
+
wbfdm/contrib/qa/dataloaders/corporate_actions.py,sha256=NWk8OCem9niwY5mJCIR11_Wq2gVBbtcrAZedTpzQDYc,2964
|
|
103
103
|
wbfdm/contrib/qa/dataloaders/financials.py,sha256=xUHpvhUkvmdPL_RyWCrs7XgChgTklX5qemmaXMedgkY,3475
|
|
104
104
|
wbfdm/contrib/qa/dataloaders/fx_rates.py,sha256=IYkUV8_8Vmvm4_K9xJpz7VaTgjQUz0y4pb3KyaoiqCM,1985
|
|
105
|
-
wbfdm/contrib/qa/dataloaders/market_data.py,sha256
|
|
106
|
-
wbfdm/contrib/qa/dataloaders/officers.py,sha256=
|
|
105
|
+
wbfdm/contrib/qa/dataloaders/market_data.py,sha256=whb3xE0A36xly2_bCImgesgYjETOwTYB1DVDXBRz2UI,8009
|
|
106
|
+
wbfdm/contrib/qa/dataloaders/officers.py,sha256=s3kW3sgsXuGE0T-TaZwxrF3YrUN-2v-_hN4ugupryic,2944
|
|
107
107
|
wbfdm/contrib/qa/dataloaders/reporting_dates.py,sha256=q25ccB0pbGfLJLV1A1_AY1XYWJ_Fa10egY09L1J-C5A,2628
|
|
108
108
|
wbfdm/contrib/qa/dataloaders/statements.py,sha256=6k8dDwJPLY6XE3G5ZA03_4wRvT7XduRsro4lzuAWCvM,10337
|
|
109
109
|
wbfdm/contrib/qa/dataloaders/utils.py,sha256=E0qav459E7razVOvHKVt9ld_gteJ6eQ2oR4xN-CIOns,2941
|
|
@@ -117,10 +117,10 @@ wbfdm/contrib/qa/jinja2/qa/sql/ibes/estimates.sql,sha256=OWgeogSFj7-OdXvJTvNsThN
|
|
|
117
117
|
wbfdm/contrib/qa/jinja2/qa/sql/ibes/financials.sql,sha256=i8esPCG_ARiXlfSKajqBRH0jiXT_efOvw3No6zEvwn4,2612
|
|
118
118
|
wbfdm/contrib/qa/sync/exchanges.py,sha256=XU7dj-rQzMlDku9lnmAACaTRoxx8pFSyr5kCK79cYAc,3124
|
|
119
119
|
wbfdm/contrib/qa/sync/instruments.py,sha256=8aTQVJ_cw1phe4FWikn79pjCfUijaTcwkdhQCtSXKH0,3156
|
|
120
|
-
wbfdm/contrib/qa/sync/utils.py,sha256=
|
|
120
|
+
wbfdm/contrib/qa/sync/utils.py,sha256=eGAG_eeaYGoWO9HAu9aIsXVl465r2nuLF8QFk2SvZTo,12204
|
|
121
121
|
wbfdm/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
122
122
|
wbfdm/dataloaders/cache.py,sha256=K9BeVxT7p-BMvjurINt18bfrUDccp840uIjfDBLJRNk,4841
|
|
123
|
-
wbfdm/dataloaders/protocols.py,sha256=
|
|
123
|
+
wbfdm/dataloaders/protocols.py,sha256=QLa0y890gwnTeDGTnM58iNEYxugzj9Q9bmIRoapzc_0,3211
|
|
124
124
|
wbfdm/dataloaders/proxies.py,sha256=1BTY7w3A32axWEOhP9fPZtZHGxHY2GzguteWRSxdhnM,7488
|
|
125
125
|
wbfdm/dataloaders/types.py,sha256=VUqZgZJZ0mBnFp1T7AaQuWfplMx4AkPA_u52qqo8PQ4,5687
|
|
126
126
|
wbfdm/factories/__init__.py,sha256=yYxAKBde_ksIr-3g4RjL6d5Wu-nmsuEDdYNyJpgfpQU,660
|
|
@@ -134,15 +134,15 @@ wbfdm/factories/instruments_relationships.py,sha256=opGQMM3sHQV5F04nGPCCsRw8ux8v
|
|
|
134
134
|
wbfdm/factories/options.py,sha256=nna8LgB_2-XNGm37--Edkdv1oc49oeKtr7f8tcIJPU4,2463
|
|
135
135
|
wbfdm/figures/__init__.py,sha256=PDF_OWeTocmJIQozLxj_OjDUeUG7OYzcS2DLpe-ECEA,49
|
|
136
136
|
wbfdm/figures/financials/__init__.py,sha256=6PcHUFJeTEtz9BweUlLANHHOshiNgqX1Wj3KIbZZr90,56
|
|
137
|
-
wbfdm/figures/financials/financial_analysis_charts.py,sha256=
|
|
137
|
+
wbfdm/figures/financials/financial_analysis_charts.py,sha256=vkLI_BoNNT3L6KA8fad0iXzNzbc4G4JnqDnrBdcZV4Y,18181
|
|
138
138
|
wbfdm/figures/financials/financials_charts.py,sha256=tKqBWvkLkDB2GGsPe5g4GR6d3tgnBY2LgRYdXamzBWQ,32717
|
|
139
139
|
wbfdm/filters/__init__.py,sha256=skWeFdAcemysUeGEvIJ_cIxn-SXuJsUxAroTxq0N8Qo,926
|
|
140
140
|
wbfdm/filters/classifications.py,sha256=8tKTAPwMzOIXkGWa4ez1layLBAlkDtdG53-75Rg0d9k,3625
|
|
141
141
|
wbfdm/filters/exchanges.py,sha256=84N4AQoCwgdnxTxmhMob5Yndfr5gy_DRQ_-m2ilVLVM,835
|
|
142
142
|
wbfdm/filters/financials.py,sha256=jdubT5xfFTMcwJSbC_z0WkF8KlO6eAxkaMS7-oQFFp0,2512
|
|
143
143
|
wbfdm/filters/financials_analysis.py,sha256=aDA33RhKCOmA62GwuAr5xDK71T0racdyDUEaxOBp4Oc,4401
|
|
144
|
-
wbfdm/filters/instrument_prices.py,sha256=
|
|
145
|
-
wbfdm/filters/instruments.py,sha256=
|
|
144
|
+
wbfdm/filters/instrument_prices.py,sha256=R1K8xrvOgPB4m3tWItz_3TMUw7a7UeKe4D3h28xe_3w,3637
|
|
145
|
+
wbfdm/filters/instruments.py,sha256=giL1QaJqGPRV1BBzmsPjxgCOCPuSNT5HkO27Q0kF-i4,7967
|
|
146
146
|
wbfdm/filters/utils.py,sha256=6IpNoZ_yjRUCdpVXrj3kriTceAX-VGrS5NpZA2NgSmY,1870
|
|
147
147
|
wbfdm/import_export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
148
148
|
wbfdm/import_export/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -152,7 +152,7 @@ wbfdm/import_export/backends/cbinsights/equities.py,sha256=ucXQmFEffg-JGjvvjuubg
|
|
|
152
152
|
wbfdm/import_export/backends/cbinsights/mixin.py,sha256=zbknufq_YKy-itQR_7XYLyU3qEWCptvCt_BCQEBc46M,557
|
|
153
153
|
wbfdm/import_export/backends/cbinsights/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
154
154
|
wbfdm/import_export/backends/cbinsights/utils/classifications.py,sha256=z8toT_3FauBsFZ7h2NBxp-k0Pc-gwrtjBhajCwfmokw,118238
|
|
155
|
-
wbfdm/import_export/backends/cbinsights/utils/client.py,sha256=
|
|
155
|
+
wbfdm/import_export/backends/cbinsights/utils/client.py,sha256=GGTG9sKTF0-6ZCwAu_nOOvK10W9zwb3kCUvwzlV3AIk,7392
|
|
156
156
|
wbfdm/import_export/backends/refinitiv/__init__.py,sha256=sXILMzUD-PIIbmWtotQWvkPxQyRRDANIrj7ma6BKLGI,139
|
|
157
157
|
wbfdm/import_export/backends/refinitiv/daily_fundamental.py,sha256=0vIAHPIL5lvk9Q2Kt0lUYOfJg5NFBAd5klQM5fB-itk,1459
|
|
158
158
|
wbfdm/import_export/backends/refinitiv/fiscal_period.py,sha256=CKHX59DUU_TYkCDBsi1u0FLyKFC3RiE7NfkWDq9_wmM,2823
|
|
@@ -163,9 +163,9 @@ wbfdm/import_export/backends/refinitiv/instrument.py,sha256=JB39UNkWm4bjRHO0PLqU
|
|
|
163
163
|
wbfdm/import_export/backends/refinitiv/instrument_price.py,sha256=cH0GzHAZn5jVXEOfHZOHwCWNULrGPzZV5W_SL_mW0N0,2942
|
|
164
164
|
wbfdm/import_export/backends/refinitiv/mixin.py,sha256=DlNHOWOO71PgY0umaZd0NbbjsZyi4wa0JVOkWs_9jY8,1128
|
|
165
165
|
wbfdm/import_export/backends/refinitiv/utils/__init__.py,sha256=Rz38xsLAHEyEwIuJksejYExEznlPJb9tRzwJ7JG9L1s,35
|
|
166
|
-
wbfdm/import_export/backends/refinitiv/utils/controller.py,sha256=
|
|
166
|
+
wbfdm/import_export/backends/refinitiv/utils/controller.py,sha256=SCmVSyHo-NHDL1mzneS_Gl4JYrvEUhahOQfBmNSKnI8,7354
|
|
167
167
|
wbfdm/import_export/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
168
|
-
wbfdm/import_export/handlers/instrument.py,sha256=
|
|
168
|
+
wbfdm/import_export/handlers/instrument.py,sha256=BeDhlwoMXLREH7wno7mcpaAKj4Vg0SpHEKiIr0dgTl0,13134
|
|
169
169
|
wbfdm/import_export/handlers/instrument_list.py,sha256=mZRfpJFi6BhhrjH2qaFEPqqCK2ybg-DQm43Uck7G9_w,4864
|
|
170
170
|
wbfdm/import_export/handlers/instrument_price.py,sha256=RbNTo78zZuttzlVFKxJrHcW7DRfcsta7QDEI8OiiDrA,3498
|
|
171
171
|
wbfdm/import_export/handlers/option.py,sha256=MPzluMPJ3Yu7Ahmw9BA7-ssAbvPDdyca_rC-YVhU8bY,2378
|
|
@@ -173,7 +173,7 @@ wbfdm/import_export/handlers/private_equities.py,sha256=tOx4lgQOB68lYTi3UzIPzDQs
|
|
|
173
173
|
wbfdm/import_export/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
174
174
|
wbfdm/import_export/parsers/cbinsights/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
175
175
|
wbfdm/import_export/parsers/cbinsights/deals.py,sha256=aHzFxF1D397xzAp3x3QpjV1comROIrbdfj3BGEI4RWI,1358
|
|
176
|
-
wbfdm/import_export/parsers/cbinsights/equities.py,sha256=
|
|
176
|
+
wbfdm/import_export/parsers/cbinsights/equities.py,sha256=0ohIXnZXbTZ6_qI_NVIsFw1M1UXznSNgaqOBzZDfh98,2437
|
|
177
177
|
wbfdm/import_export/parsers/cbinsights/fundamentals.py,sha256=FgG8yFwRBL4_Jpp2-unFB-OosgYcMlR7wrzJRDUCLrc,2273
|
|
178
178
|
wbfdm/import_export/parsers/refinitiv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
179
179
|
wbfdm/import_export/parsers/refinitiv/daily_fundamental.py,sha256=YJIRudY0KbiAMUYNx20Vpfnuc5EapDTxKS3gTuKlAHA,279
|
|
@@ -229,22 +229,22 @@ wbfdm/migrations/0032_alter_instrumentprice_outstanding_shares.py,sha256=uRgkf6j
|
|
|
229
229
|
wbfdm/migrations/0033_alter_controversy_review.py,sha256=A9npErIpE0QUEjqIqpxmO5GMi0VA2Id8whn1nswWXpU,445
|
|
230
230
|
wbfdm/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
231
231
|
wbfdm/models/__init__.py,sha256=PWsLtlKJFDYycCohPbjsRLeWi1xaxEkZbaoUKo0yOBU,96
|
|
232
|
-
wbfdm/models/fields.py,sha256=
|
|
233
|
-
wbfdm/models/fk_fields.py,sha256=
|
|
232
|
+
wbfdm/models/fields.py,sha256=drOOKhF3dMu4nVPL8Q5IM3xrw4heX6vb0hD3DQGRS3I,3956
|
|
233
|
+
wbfdm/models/fk_fields.py,sha256=jTgvptVlFIrUbvHZt0WwYchy5kpqBsVUAeXGayQFIQE,15065
|
|
234
234
|
wbfdm/models/indicators.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
235
235
|
wbfdm/models/esg/__init__.py,sha256=FEYbRFSdfoWRsfKVxM-h6TLobj5P8bYQlZPG1iD9RMQ,29
|
|
236
|
-
wbfdm/models/esg/controversies.py,sha256=
|
|
236
|
+
wbfdm/models/esg/controversies.py,sha256=jp-DxxTW7uwU7hmUYjwj9qorGabJheufiSOh08YqR_k,2717
|
|
237
237
|
wbfdm/models/exchanges/__init__.py,sha256=sScz2KX9fxhhmi4CEUssC8HCL4ENvrIqSSwk0_J7v-g,32
|
|
238
238
|
wbfdm/models/exchanges/exchanges.py,sha256=RmM5shyyuxEGN2Y3JmeSWyU-SbpVARrvVFW72HtHwfg,7502
|
|
239
239
|
wbfdm/models/instruments/__init__.py,sha256=OvEkECJaCubBQC7B9yUrx15V982labvegeGXyEASVno,636
|
|
240
240
|
wbfdm/models/instruments/classifications.py,sha256=EeZ_P8f1F1NfjUmLf9fDMF0iPM73qxQoArUfvjuCwHg,10906
|
|
241
241
|
wbfdm/models/instruments/instrument_lists.py,sha256=GxfFyfYxEcJS36LAarHja49TOM8ffhIivpZX2-tPtZg,4234
|
|
242
242
|
wbfdm/models/instruments/instrument_prices.py,sha256=K7oMIz76WSrLmpNwcabThvtrP6WpBZZnrE9CHB5-UPQ,22345
|
|
243
|
-
wbfdm/models/instruments/instrument_relationships.py,sha256=
|
|
243
|
+
wbfdm/models/instruments/instrument_relationships.py,sha256=orBZY46jDvPfgkXakBRiByib5M_Iyeyelzg3CZWozSM,10777
|
|
244
244
|
wbfdm/models/instruments/instrument_requests.py,sha256=XbpofRS8WHadHlTFjvXJyd0o7K9r2pzJtnpjVQZOLdI,7832
|
|
245
|
-
wbfdm/models/instruments/instruments.py,sha256=
|
|
246
|
-
wbfdm/models/instruments/options.py,sha256=
|
|
247
|
-
wbfdm/models/instruments/private_equities.py,sha256
|
|
245
|
+
wbfdm/models/instruments/instruments.py,sha256=TbSOdQOzIcuS1KdPlFnpQmbrdv6NNzieQ26m-OoV-3Y,44551
|
|
246
|
+
wbfdm/models/instruments/options.py,sha256=AW6mwvVL8IN9K6dEApJsGgDz7T8SBIxQrJvQZ804oas,7286
|
|
247
|
+
wbfdm/models/instruments/private_equities.py,sha256=-MYXLLvcOO3JVvSpT2H_HxbQzZasipPUQyUgwoaW5xw,2275
|
|
248
248
|
wbfdm/models/instruments/querysets.py,sha256=7r3pXNlpROkYgKc6gQH07RNeWX6jGeBAPUabUevE6Jo,11587
|
|
249
249
|
wbfdm/models/instruments/utils.py,sha256=88jnWINSSC0OwH-mCEOPLZXuhBCtEsxBpSaZ38GteaE,1365
|
|
250
250
|
wbfdm/models/instruments/llm/__init__.py,sha256=dSmxRmEWb0A4O_lUoWuRKt2mBtUuLCTPVVJqGyi_n40,52
|
|
@@ -252,7 +252,7 @@ wbfdm/models/instruments/llm/create_instrument_news_relationships.py,sha256=f9MT
|
|
|
252
252
|
wbfdm/models/instruments/mixin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
253
253
|
wbfdm/models/instruments/mixin/financials_computed.py,sha256=E87I7O2WQgjY3zM3so4dzfExBzVtKTkTqnRjPwLHbyM,34920
|
|
254
254
|
wbfdm/models/instruments/mixin/financials_serializer_fields.py,sha256=-OkpcUt1rZmB3nUcO2vckpJdVm8IxRqkPDEgcPqqoRU,68886
|
|
255
|
-
wbfdm/models/instruments/mixin/instruments.py,sha256=
|
|
255
|
+
wbfdm/models/instruments/mixin/instruments.py,sha256=Em9Jvm8jMLu65vvi9d8rO4mCtNbAE3-HSvJUOpZimGQ,10034
|
|
256
256
|
wbfdm/serializers/__init__.py,sha256=AXb03RKHo6B0ts_IQOvx89n9wKG8pxiumYv9cr4EhvA,197
|
|
257
257
|
wbfdm/serializers/esg.py,sha256=epoX8cjkPuVv45aW-Jf85-rSO_ZrSnXcTxMcadKdC-I,1086
|
|
258
258
|
wbfdm/serializers/exchanges.py,sha256=wYvy0XBS9tRFHqin23gABQ_pj3Rmsx1B075SZ5GqwDo,1211
|
|
@@ -275,7 +275,7 @@ wbfdm/tests/tests.py,sha256=i0CntYKvaHWmdJe34sZMLeKRiHL4uRfhdM30_4gp08Q,253
|
|
|
275
275
|
wbfdm/tests/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
276
276
|
wbfdm/tests/analysis/test_esg.py,sha256=dE5IIzEAr6C2iwH1AreDvCjVkSyC-GOg5x2pvOeIdhM,7229
|
|
277
277
|
wbfdm/tests/analysis/financial_analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
278
|
-
wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py,sha256
|
|
278
|
+
wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py,sha256=-m-2YbCgn4CTPnJw7r_HRuBlYRLZFBBBgiBYgl8ik4Y,13628
|
|
279
279
|
wbfdm/tests/analysis/financial_analysis/test_utils.py,sha256=O1_L8oj1OSTEG-qetlFMkbOriSG4_lMqW-RFEiwi4lo,12475
|
|
280
280
|
wbfdm/tests/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
281
281
|
wbfdm/tests/dataloaders/test_cache.py,sha256=y1qHndDWO50ULE-VzZa1DIm97v1sRlKGItuzCS27_L8,2473
|
|
@@ -289,9 +289,9 @@ wbfdm/tests/models/test_merge.py,sha256=tXD5xIxZyZtXpm9WIQ4Yc8TQwsUnkxkKIvMNwaHO
|
|
|
289
289
|
wbfdm/tests/models/test_options.py,sha256=DoEAHhNQE4kucpBRRm2S05ozabkERz-I4mUolsE2Qi8,2269
|
|
290
290
|
wbfdm/tests/models/test_queryset.py,sha256=bVNDU498vbh7ind9NbOzsI8TMv3Qe47fSMPzd58K0R4,4113
|
|
291
291
|
wbfdm/viewsets/__init__.py,sha256=ZM29X0-5AkSH2rzF_gKUI4FoaEWCcMTXPjruJ4Gi_x8,383
|
|
292
|
-
wbfdm/viewsets/esg.py,sha256=
|
|
292
|
+
wbfdm/viewsets/esg.py,sha256=Vjqp4rCpJTLOXh7-Wcxgdz4NuhMMzyCQVdkJprQfBmw,2867
|
|
293
293
|
wbfdm/viewsets/exchanges.py,sha256=OAYOIKVVEihReeks2Pq6qcAafOxY4UL8l4TFzzr7Ckc,1785
|
|
294
|
-
wbfdm/viewsets/market_data.py,sha256=
|
|
294
|
+
wbfdm/viewsets/market_data.py,sha256=ZkvQVQ_hh5aMTruGFRYjjy2RIcbOQAKDKNS73uPH3Ds,6724
|
|
295
295
|
wbfdm/viewsets/mixins.py,sha256=KLL89AXC8Fdex6uCTBNllbIztg6vdWsGCVEydN2zSiI,325
|
|
296
296
|
wbfdm/viewsets/officers.py,sha256=_NktFvGV79v3AAm7gOFmGdpep-Vr5TMe2MvIoVtg3Fs,1059
|
|
297
297
|
wbfdm/viewsets/prices.py,sha256=cE0vRs8UEML7OjgwMPfq0ns2OHSokbLPZKFjni-9kl4,2400
|
|
@@ -310,7 +310,7 @@ wbfdm/viewsets/configs/display/instrument_lists.py,sha256=UF_M41sBDDxGLDlKxLdAHq
|
|
|
310
310
|
wbfdm/viewsets/configs/display/instrument_prices.py,sha256=CdZg-Fwd__DP0Ym8TBsupbVZzhoMbNfOJQKCmgKvwCc,4908
|
|
311
311
|
wbfdm/viewsets/configs/display/instrument_requests.py,sha256=CarX1MGe64roHZETm2N4HsEMssHVI5mWw2xTUBjgi-k,5129
|
|
312
312
|
wbfdm/viewsets/configs/display/instruments.py,sha256=fG7vGnyznAi6J4SrS4FXyDjBgSCNOEaFy-M92oAmO9Y,17417
|
|
313
|
-
wbfdm/viewsets/configs/display/instruments_relationships.py,sha256=
|
|
313
|
+
wbfdm/viewsets/configs/display/instruments_relationships.py,sha256=c47-iz4hFvKl4luRgCZyjtefIs4VJspTIAyw6PdMIvI,2524
|
|
314
314
|
wbfdm/viewsets/configs/display/monthly_performances.py,sha256=ESsGTLhPmmXz3YjEWNjwaOjA1RNzVDKtJt3JILL2MMc,2973
|
|
315
315
|
wbfdm/viewsets/configs/display/officers.py,sha256=qUwN9HPSSlT85aUSFNY-30550P35j3RZCYqRPj_rFAs,654
|
|
316
316
|
wbfdm/viewsets/configs/display/prices.py,sha256=09PVAbrgxE7vkZjYaloNqi2Jot2iyRjHILcJPsVj5gI,890
|
|
@@ -339,7 +339,7 @@ wbfdm/viewsets/configs/titles/esg.py,sha256=a0BMJtSiumxz11Cswl5US87uH93pvzmbHmeH
|
|
|
339
339
|
wbfdm/viewsets/configs/titles/exchanges.py,sha256=W9HKCsmZPV8ck2BYUJi-uY5XtaLg-gT8LfoABAJiT8g,286
|
|
340
340
|
wbfdm/viewsets/configs/titles/financial_ratio_analysis.py,sha256=k1t9s6QjF8mE9KoObP9ZEsYIG5NLQiCzrjXqqqbY0s4,221
|
|
341
341
|
wbfdm/viewsets/configs/titles/financials_analysis.py,sha256=jReN2cagd3hNv11_aNw_qYC8eDthD_CdF8oj3Ps_5o4,2070
|
|
342
|
-
wbfdm/viewsets/configs/titles/instrument_prices.py,sha256=
|
|
342
|
+
wbfdm/viewsets/configs/titles/instrument_prices.py,sha256=l1OnnGySNNgscWPbMIoe2VUZxj51LXs1jV_VK7b5QMQ,1734
|
|
343
343
|
wbfdm/viewsets/configs/titles/instrument_requests.py,sha256=v7oaokbww-dRFuhoC7hv-Y2kSpecX7BYFKZ3cQatHD0,479
|
|
344
344
|
wbfdm/viewsets/configs/titles/instruments.py,sha256=-OfXcEgwoST1_pLCBNWag6Swx0ZM23EHVHXAEPPGeSA,886
|
|
345
345
|
wbfdm/viewsets/configs/titles/instruments_relationships.py,sha256=vpgQrXekD64P422i2lc_PXzuuZtjThSgt18BD0fhRLY,609
|
|
@@ -347,13 +347,13 @@ wbfdm/viewsets/configs/titles/market_data.py,sha256=L1ANMper59jNDEVL8HU4UnM_Iu4c
|
|
|
347
347
|
wbfdm/viewsets/configs/titles/prices.py,sha256=O43pAxiZsfRl6CwJX1hX6zABWKD0a9nQLSaywtNZEBM,382
|
|
348
348
|
wbfdm/viewsets/configs/titles/statement_with_estimates.py,sha256=ZL4K0hOM0dVBplu76toF4-U3VXBS31gyV3_eTGQYHjE,407
|
|
349
349
|
wbfdm/viewsets/financial_analysis/__init__.py,sha256=cTTcvGnhz2Ep9RpkyUyTOpjTXJz7BWt1AGAMjiIY9C8,263
|
|
350
|
-
wbfdm/viewsets/financial_analysis/financial_metric_analysis.py,sha256=
|
|
351
|
-
wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py,sha256=
|
|
352
|
-
wbfdm/viewsets/financial_analysis/financial_summary.py,sha256=
|
|
353
|
-
wbfdm/viewsets/financial_analysis/statement_with_estimates.py,sha256=
|
|
350
|
+
wbfdm/viewsets/financial_analysis/financial_metric_analysis.py,sha256=ISG53diS2LcjxH7Fex1wsMGHTHoRdLDHwLDKn3qA4UE,3328
|
|
351
|
+
wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py,sha256=szq78oFKCxVjUkCVzsU7Ditq0qQlwcrnvnXVIao18Ic,3122
|
|
352
|
+
wbfdm/viewsets/financial_analysis/financial_summary.py,sha256=MWz5Nip5ePKkHX3W7bYJRvzY_hCvSsHmKd0gvvPtXL8,9245
|
|
353
|
+
wbfdm/viewsets/financial_analysis/statement_with_estimates.py,sha256=rhGxXFjTcDZ9zq0e9Djh2x9p7iBZUQ9Al-mdAvg2bBI,6248
|
|
354
354
|
wbfdm/viewsets/instruments/__init__.py,sha256=uydDdU6oZ6W2lgFkr3-cU7WZU7TeokXAA1J0xEgiLD0,2826
|
|
355
355
|
wbfdm/viewsets/instruments/classifications.py,sha256=CMRTeI6hUClXzZUr7PeRWBXhT9fMiPiu-FvNP_jUQkM,11947
|
|
356
|
-
wbfdm/viewsets/instruments/financials_analysis.py,sha256=
|
|
356
|
+
wbfdm/viewsets/instruments/financials_analysis.py,sha256=bobhqY23m83v53BJIe8mWz99ZpaT7u4Y6zgPS9-ozBs,29088
|
|
357
357
|
wbfdm/viewsets/instruments/instrument_lists.py,sha256=hwwHDNpHjjffxw08N_1LtkL5Fdi8c1Om-PLz6pTu4Ok,2878
|
|
358
358
|
wbfdm/viewsets/instruments/instrument_prices.py,sha256=9mdNPU1D6ZFS5Bf0U1d3c6ZlYSCjrNMWv6Vhas3D8Ns,24075
|
|
359
359
|
wbfdm/viewsets/instruments/instrument_requests.py,sha256=mmaARNl6ymDdlcLzcu16vVfFsunhtJkMw2r2NBfUp9U,1839
|
|
@@ -364,6 +364,6 @@ wbfdm/viewsets/statements/__init__.py,sha256=odxtFYUDICPmz8WCE3nx93EvKZLSPBEI4d7
|
|
|
364
364
|
wbfdm/viewsets/statements/statements.py,sha256=gA6RCI8-B__JwjEb6OZxpn8Y-9aF-YQ3HIQ7e1vfJMw,4304
|
|
365
365
|
wbfdm/viewsets/technical_analysis/__init__.py,sha256=qtCIBg0uSiZeJq_1tEQFilnorMBkMe6uCMfqar6-cLE,77
|
|
366
366
|
wbfdm/viewsets/technical_analysis/monthly_performances.py,sha256=O1j8CGfOranL74LqVvcf7jERaDIboEJZiBf_AbbVDQ8,3974
|
|
367
|
-
wbfdm-1.
|
|
368
|
-
wbfdm-1.
|
|
369
|
-
wbfdm-1.
|
|
367
|
+
wbfdm-1.56.0.dist-info/METADATA,sha256=Zx7lkqlQDkbSm3GvfZXnEDSPt-2sHRlndd36Cqq4LWs,768
|
|
368
|
+
wbfdm-1.56.0.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
369
|
+
wbfdm-1.56.0.dist-info/RECORD,,
|
|
File without changes
|