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
wbfdm/dataloaders/protocols.py
CHANGED
|
@@ -7,6 +7,7 @@ from wbfdm.dataloaders.types import (
|
|
|
7
7
|
ESGControversyDataDict,
|
|
8
8
|
ESGDataDict,
|
|
9
9
|
FinancialDataDict,
|
|
10
|
+
FXRateDict,
|
|
10
11
|
MarketDataDict,
|
|
11
12
|
OfficerDataDict,
|
|
12
13
|
ReportDateDataDict,
|
|
@@ -36,15 +37,25 @@ class AdjustmentsProtocol(Protocol):
|
|
|
36
37
|
) -> Iterator[AdjustmentDataDict]: ...
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
class FXRateProtocol(Protocol):
|
|
41
|
+
def fx_rates(
|
|
42
|
+
self,
|
|
43
|
+
from_date: date,
|
|
44
|
+
to_date: date,
|
|
45
|
+
target_currency: str,
|
|
46
|
+
) -> Iterator[FXRateDict]: ...
|
|
47
|
+
|
|
48
|
+
|
|
39
49
|
class MarketDataProtocol(Protocol):
|
|
40
50
|
def market_data(
|
|
41
51
|
self,
|
|
42
|
-
values: list[MarketData] =
|
|
52
|
+
values: list[MarketData] | None = None,
|
|
43
53
|
from_date: date | None = None,
|
|
44
54
|
to_date: date | None = None,
|
|
45
55
|
exact_date: date | None = None,
|
|
46
56
|
frequency: Frequency = Frequency.DAILY,
|
|
47
57
|
target_currency: str | None = None,
|
|
58
|
+
apply_fx_rate: bool = True,
|
|
48
59
|
) -> Iterator[MarketDataDict]: ...
|
|
49
60
|
|
|
50
61
|
|
wbfdm/dataloaders/proxies.py
CHANGED
|
@@ -9,6 +9,7 @@ from wbfdm.dataloaders.protocols import (
|
|
|
9
9
|
ESGControversyProtocol,
|
|
10
10
|
ESGProtocol,
|
|
11
11
|
FinancialsProtocol,
|
|
12
|
+
FXRateProtocol,
|
|
12
13
|
MarketDataProtocol,
|
|
13
14
|
OfficersProtocol,
|
|
14
15
|
ReportDateProtocol,
|
|
@@ -20,6 +21,7 @@ from wbfdm.dataloaders.types import (
|
|
|
20
21
|
ESGControversyDataDict,
|
|
21
22
|
ESGDataDict,
|
|
22
23
|
FinancialDataDict,
|
|
24
|
+
FXRateDict,
|
|
23
25
|
MarketDataDict,
|
|
24
26
|
OfficerDataDict,
|
|
25
27
|
ReportDateDataDict,
|
|
@@ -53,7 +55,8 @@ def _market_data_row_parser(row):
|
|
|
53
55
|
|
|
54
56
|
class InstrumentDataloaderProxy(
|
|
55
57
|
DataloaderProxy[
|
|
56
|
-
|
|
58
|
+
FXRateProtocol
|
|
59
|
+
| AdjustmentsProtocol
|
|
57
60
|
| MarketDataProtocol
|
|
58
61
|
| CorporateActionsProtocol
|
|
59
62
|
| OfficersProtocol
|
|
@@ -68,6 +71,15 @@ class InstrumentDataloaderProxy(
|
|
|
68
71
|
for dl in self.iterate_dataloaders("reporting_dates"):
|
|
69
72
|
yield from dl.reporting_dates(only_next=only_next)
|
|
70
73
|
|
|
74
|
+
def fx_rates(
|
|
75
|
+
self,
|
|
76
|
+
from_date: date,
|
|
77
|
+
to_date: date,
|
|
78
|
+
target_currency: str,
|
|
79
|
+
) -> Iterator[FXRateDict]:
|
|
80
|
+
for dl in self.iterate_dataloaders("fx_rates"):
|
|
81
|
+
yield from dl.fx_rates(from_date, to_date, target_currency)
|
|
82
|
+
|
|
71
83
|
def adjustments(self, from_date: date | None = None, to_date: date | None = None) -> Iterator[AdjustmentDataDict]:
|
|
72
84
|
for dl in self.iterate_dataloaders("adjustments"):
|
|
73
85
|
yield from dl.adjustments(from_date=from_date, to_date=to_date)
|
|
@@ -80,6 +92,7 @@ class InstrumentDataloaderProxy(
|
|
|
80
92
|
exact_date: date | None = None,
|
|
81
93
|
frequency: Frequency = Frequency.DAILY,
|
|
82
94
|
target_currency: str | None = None,
|
|
95
|
+
apply_fx_rate: bool = True,
|
|
83
96
|
) -> Iterator[MarketDataDict]:
|
|
84
97
|
if not values:
|
|
85
98
|
values = list(MarketData)
|
|
@@ -93,6 +106,7 @@ class InstrumentDataloaderProxy(
|
|
|
93
106
|
exact_date=exact_date,
|
|
94
107
|
frequency=frequency,
|
|
95
108
|
target_currency=target_currency,
|
|
109
|
+
apply_fx_rate=apply_fx_rate,
|
|
96
110
|
),
|
|
97
111
|
)
|
|
98
112
|
|
wbfdm/dataloaders/types.py
CHANGED
|
@@ -26,6 +26,12 @@ class BaseDict(TypedDict):
|
|
|
26
26
|
currency: NotRequired[str]
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
class FXRateDict(TypedDict):
|
|
30
|
+
currency_pair: str
|
|
31
|
+
fx_date: date
|
|
32
|
+
fx_rate: float
|
|
33
|
+
|
|
34
|
+
|
|
29
35
|
class MarketDataDict(BaseDict):
|
|
30
36
|
"""
|
|
31
37
|
Represents a dictionary for daily valuation data.
|
|
@@ -65,7 +71,7 @@ class MarketDataDict(BaseDict):
|
|
|
65
71
|
volume: NotRequired[float]
|
|
66
72
|
market_cap: NotRequired[float]
|
|
67
73
|
calculated: NotRequired[bool]
|
|
68
|
-
|
|
74
|
+
fx_rate: NotRequired[float]
|
|
69
75
|
unadjusted_close: NotRequired[float]
|
|
70
76
|
unadjusted_outstanding_shares: NotRequired[float | int]
|
|
71
77
|
|
wbfdm/enums.py
CHANGED
|
@@ -21,12 +21,14 @@ class MarketData(ChoiceEnum):
|
|
|
21
21
|
VOLUME = "volume"
|
|
22
22
|
SHARES_OUTSTANDING = "outstanding_shares"
|
|
23
23
|
MARKET_CAPITALIZATION = "market_capitalization"
|
|
24
|
+
MARKET_CAPITALIZATION_CONSOLIDATED = "market_capitalization_consolidated"
|
|
24
25
|
|
|
25
26
|
@classmethod
|
|
26
27
|
def name_mapping(cls):
|
|
27
28
|
return {
|
|
28
29
|
cls.CLOSE.value: "Closing Price",
|
|
29
30
|
cls.MARKET_CAPITALIZATION.value: "Market Cap.",
|
|
31
|
+
cls.MARKET_CAPITALIZATION_CONSOLIDATED.value: "Market Cap. (Consolidated)",
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
|
wbfdm/factories/instruments.py
CHANGED
|
@@ -21,13 +21,15 @@ class InstrumentFactory(factory.django.DjangoModelFactory):
|
|
|
21
21
|
instrument_type = factory.SubFactory(InstrumentTypeFactory)
|
|
22
22
|
inception_date = factory.Faker("past_date")
|
|
23
23
|
delisted_date = None
|
|
24
|
-
currency = factory.SubFactory("wbcore.contrib.currency.factories.
|
|
24
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyUSDFactory")
|
|
25
25
|
country = factory.SubFactory("wbcore.contrib.geography.factories.CountryFactory")
|
|
26
26
|
exchange = factory.SubFactory("wbfdm.factories.exchanges.ExchangeFactory")
|
|
27
27
|
|
|
28
28
|
identifier = factory.LazyAttribute(lambda o: rstr.xeger("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})"))
|
|
29
29
|
isin = factory.LazyAttribute(lambda o: rstr.xeger("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})"))
|
|
30
30
|
ticker = factory.LazyAttribute(lambda o: rstr.xeger("([A-Z]{4})"))
|
|
31
|
+
refinitiv_identifier_code = factory.LazyAttribute(lambda o: rstr.xeger("([A-Z]{4})"))
|
|
32
|
+
sedol = factory.LazyAttribute(lambda o: rstr.xeger("([A-Z]{4})"))
|
|
31
33
|
dl_parameters = {"market_data": {"path": "wbfdm.contrib.internal.dataloaders.market_data.MarketDataDataloader"}}
|
|
32
34
|
|
|
33
35
|
@factory.post_generation
|
|
@@ -54,7 +56,7 @@ class InstrumentFactory(factory.django.DjangoModelFactory):
|
|
|
54
56
|
|
|
55
57
|
class CashFactory(factory.django.DjangoModelFactory):
|
|
56
58
|
is_cash = True
|
|
57
|
-
currency = factory.SubFactory("wbcore.contrib.currency.factories.
|
|
59
|
+
currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyUSDFactory")
|
|
58
60
|
name = factory.LazyAttribute(lambda o: o.currency.title)
|
|
59
61
|
instrument_type = factory.LazyAttribute(
|
|
60
62
|
lambda o: InstrumentType.objects.get_or_create(key="cash", defaults={"name": "Cash", "short_name": "Cash"})[0]
|
|
@@ -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,
|
wbfdm/filters/classifications.py
CHANGED
|
@@ -67,7 +67,7 @@ class ClassificationTreeChartFilter(wb_filters.FilterSet):
|
|
|
67
67
|
|
|
68
68
|
aggregation_type = wb_filters.ChoiceFilter(
|
|
69
69
|
choices=[("classification_count", "Classification Count"), ("instrument_count", "Instrument Count")],
|
|
70
|
-
|
|
70
|
+
initial="classification_count",
|
|
71
71
|
label="Aggregation Type",
|
|
72
72
|
method="fake_filter",
|
|
73
73
|
)
|
|
@@ -89,7 +89,7 @@ class InstrumentClassificationThroughModelViewFilterSet(wb_filters.FilterSet):
|
|
|
89
89
|
endpoint=ClassificationGroup.get_representation_endpoint(),
|
|
90
90
|
value_key=ClassificationGroup.get_representation_value_key(),
|
|
91
91
|
label_key=ClassificationGroup.get_representation_label_key(),
|
|
92
|
-
|
|
92
|
+
initial=_get_default_classification_group_id,
|
|
93
93
|
)
|
|
94
94
|
|
|
95
95
|
class Meta:
|
wbfdm/filters/financials.py
CHANGED
|
@@ -7,15 +7,14 @@ from wbfdm.models.instruments import Instrument
|
|
|
7
7
|
|
|
8
8
|
class MarketDataChartFilterSet(filters.FilterSet):
|
|
9
9
|
period = filters.FinancialPerformanceDateRangeFilter(
|
|
10
|
-
label="Period",
|
|
11
|
-
method="fake_filter",
|
|
12
|
-
default=five_year_data_range,
|
|
10
|
+
label="Period", method="fake_filter", initial=five_year_data_range, required=True
|
|
13
11
|
)
|
|
14
12
|
chart_type = filters.ChoiceFilter(
|
|
15
13
|
method="fake_filter",
|
|
16
14
|
label="Chart Type",
|
|
17
15
|
choices=MarketDataChartType.choices,
|
|
18
|
-
|
|
16
|
+
required=True,
|
|
17
|
+
initial=MarketDataChartType.CLOSE,
|
|
19
18
|
)
|
|
20
19
|
benchmarks = filters.ModelMultipleChoiceFilter(
|
|
21
20
|
label="Benchmarks",
|
|
@@ -35,14 +34,12 @@ class MarketDataChartFilterSet(filters.FilterSet):
|
|
|
35
34
|
volume = filters.BooleanFilter(
|
|
36
35
|
method="fake_filter",
|
|
37
36
|
label="Add Volume",
|
|
38
|
-
|
|
39
|
-
required=False,
|
|
37
|
+
initial=False,
|
|
40
38
|
)
|
|
41
39
|
show_estimates = filters.BooleanFilter(
|
|
42
40
|
method="fake_filter",
|
|
43
41
|
label="Show Estimates",
|
|
44
|
-
|
|
45
|
-
required=False,
|
|
42
|
+
initial=True,
|
|
46
43
|
)
|
|
47
44
|
|
|
48
45
|
class Meta:
|
|
@@ -51,16 +48,10 @@ class MarketDataChartFilterSet(filters.FilterSet):
|
|
|
51
48
|
|
|
52
49
|
|
|
53
50
|
class FinancialRatioFilterSet(filters.FilterSet):
|
|
54
|
-
ttm = filters.BooleanFilter(
|
|
55
|
-
method="fake_filter",
|
|
56
|
-
label="TTM/FTM",
|
|
57
|
-
default=True,
|
|
58
|
-
)
|
|
51
|
+
ttm = filters.BooleanFilter(method="fake_filter", label="TTM/FTM", initial=True, required=True)
|
|
59
52
|
|
|
60
53
|
period = filters.FinancialPerformanceDateRangeFilter(
|
|
61
|
-
method="fake_filter",
|
|
62
|
-
label="Period",
|
|
63
|
-
default=five_year_data_range,
|
|
54
|
+
method="fake_filter", label="Period", initial=five_year_data_range, required=True
|
|
64
55
|
)
|
|
65
56
|
|
|
66
57
|
class Meta:
|
|
@@ -74,7 +65,7 @@ class StatementFilter(filters.FilterSet):
|
|
|
74
65
|
label="Data Type",
|
|
75
66
|
choices=DataType.choices,
|
|
76
67
|
required=True,
|
|
77
|
-
|
|
68
|
+
initial=DataType.STANDARDIZED,
|
|
78
69
|
)
|
|
79
70
|
|
|
80
71
|
class Meta:
|
|
@@ -88,7 +79,7 @@ class StatementWithEstimateFilter(filters.FilterSet):
|
|
|
88
79
|
label="Calendar Type",
|
|
89
80
|
choices=CalendarType.choices,
|
|
90
81
|
required=True,
|
|
91
|
-
|
|
82
|
+
initial=CalendarType.FISCAL,
|
|
92
83
|
)
|
|
93
84
|
|
|
94
85
|
class Meta:
|
|
@@ -48,7 +48,7 @@ class FinancialAnalysisValuationRatiosFilterSet(wb_filters.FilterSet):
|
|
|
48
48
|
label="Date Range",
|
|
49
49
|
required=True,
|
|
50
50
|
clearable=False,
|
|
51
|
-
|
|
51
|
+
initial=lambda r, v, q: DateRange(_get_12m(r, v, q), date.today()),
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
class OutputChoices(models.TextChoices):
|
|
@@ -61,34 +61,46 @@ class FinancialAnalysisValuationRatiosFilterSet(wb_filters.FilterSet):
|
|
|
61
61
|
ROLLING = "ROLLING", "Rolling"
|
|
62
62
|
|
|
63
63
|
output = wb_filters.ChoiceFilter(
|
|
64
|
-
choices=OutputChoices.choices, label="Output", method="fake_filter",
|
|
64
|
+
choices=OutputChoices.choices, label="Output", method="fake_filter", initial=OutputChoices.CHART, required=True
|
|
65
65
|
)
|
|
66
66
|
period = wb_filters.ChoiceFilter(
|
|
67
|
-
choices=PeriodChoices.choices, label="Period", method="fake_filter",
|
|
67
|
+
choices=PeriodChoices.choices, label="Period", method="fake_filter", initial=PeriodChoices.NTM, required=True
|
|
68
68
|
)
|
|
69
|
-
vs_related = wb_filters.BooleanFilter(label="Versus related",
|
|
70
|
-
clean_data = wb_filters.BooleanFilter(label="Clean data",
|
|
71
|
-
ranges = wb_filters.BooleanFilter(label="Draw ranges",
|
|
69
|
+
vs_related = wb_filters.BooleanFilter(label="Versus related", initial=False, required=True, method="fake_filter")
|
|
70
|
+
clean_data = wb_filters.BooleanFilter(label="Clean data", initial=True, required=True, method="fake_filter")
|
|
71
|
+
ranges = wb_filters.BooleanFilter(label="Draw ranges", initial=False, method="fake_filter")
|
|
72
72
|
range_type = wb_filters.ChoiceFilter(
|
|
73
73
|
choices=RangeChoices.choices,
|
|
74
74
|
label="Range type",
|
|
75
75
|
method="fake_filter",
|
|
76
76
|
required=True,
|
|
77
|
-
|
|
77
|
+
initial=RangeChoices.MINMAX,
|
|
78
78
|
)
|
|
79
79
|
range_period = wb_filters.NumberFilter(
|
|
80
|
-
precision=0, label="Rolling period", method="fake_filter", required=True,
|
|
80
|
+
precision=0, label="Rolling period", method="fake_filter", required=True, initial=120
|
|
81
81
|
)
|
|
82
82
|
x_axis_var = wb_filters.ChoiceFilter(
|
|
83
|
-
choices=VariableChoices.choices,
|
|
83
|
+
choices=VariableChoices.choices,
|
|
84
|
+
label="X-Axis",
|
|
85
|
+
method="fake_filter",
|
|
86
|
+
initial=VariableChoices.EPSG,
|
|
87
|
+
required=True,
|
|
84
88
|
)
|
|
85
89
|
y_axis_var = wb_filters.ChoiceFilter(
|
|
86
|
-
choices=VariableChoices.choices,
|
|
90
|
+
choices=VariableChoices.choices,
|
|
91
|
+
label="Y-Axis",
|
|
92
|
+
method="fake_filter",
|
|
93
|
+
initial=VariableChoices.PE,
|
|
94
|
+
required=True,
|
|
87
95
|
)
|
|
88
96
|
z_axis_var = wb_filters.ChoiceFilter(
|
|
89
|
-
choices=VariableChoices.choices,
|
|
97
|
+
choices=VariableChoices.choices,
|
|
98
|
+
label="Bubble",
|
|
99
|
+
method="fake_filter",
|
|
100
|
+
initial=VariableChoices.MKTCAP,
|
|
101
|
+
required=True,
|
|
90
102
|
)
|
|
91
|
-
median = wb_filters.BooleanFilter(label="Median",
|
|
103
|
+
median = wb_filters.BooleanFilter(label="Median", initial=True, required=True, method="fake_filter")
|
|
92
104
|
|
|
93
105
|
class Meta:
|
|
94
106
|
model = Instrument
|
|
@@ -101,19 +113,27 @@ class EarningsAnalysisFilterSet(wb_filters.FilterSet):
|
|
|
101
113
|
label="Date Range",
|
|
102
114
|
required=True,
|
|
103
115
|
clearable=False,
|
|
104
|
-
|
|
116
|
+
initial=lambda r, v, q: DateRange(_get_12m(r, v, q), date.today()),
|
|
105
117
|
)
|
|
106
118
|
|
|
107
119
|
class OutputChoices(models.TextChoices):
|
|
108
120
|
EPS = "EPS", "Earnings ($)"
|
|
109
121
|
|
|
110
122
|
analysis = wb_filters.ChoiceFilter(
|
|
111
|
-
choices=OutputChoices.choices,
|
|
123
|
+
choices=OutputChoices.choices,
|
|
124
|
+
label="Analysis",
|
|
125
|
+
method=lambda q, n, v: q,
|
|
126
|
+
initial=OutputChoices.EPS,
|
|
127
|
+
required=True,
|
|
112
128
|
)
|
|
113
129
|
period = wb_filters.ChoiceFilter(
|
|
114
|
-
choices=PeriodChoices.choices,
|
|
130
|
+
choices=PeriodChoices.choices,
|
|
131
|
+
label="Period",
|
|
132
|
+
method=lambda q, n, v: q,
|
|
133
|
+
initial=PeriodChoices.NTM,
|
|
134
|
+
required=True,
|
|
115
135
|
)
|
|
116
|
-
vs_related = wb_filters.BooleanFilter(label="Show related",
|
|
136
|
+
vs_related = wb_filters.BooleanFilter(label="Show related", initial=False, required=True, method=lambda q, n, v: q)
|
|
117
137
|
|
|
118
138
|
class Meta:
|
|
119
139
|
model = Instrument
|
|
@@ -16,7 +16,7 @@ class FakeDateRange(wb_filters.FilterSet):
|
|
|
16
16
|
label="Date Range",
|
|
17
17
|
required=True,
|
|
18
18
|
clearable=False,
|
|
19
|
-
|
|
19
|
+
initial=lambda r, v, q: DateRange(byearend_2_year_ago(r, v, q), date.today()),
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
class Meta:
|
|
@@ -26,11 +26,10 @@ 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,
|
|
33
|
-
|
|
32
|
+
initial=lambda r, v, q: DateRange(get_earliest_date(r, v, q), get_latest_date(r, v, q)),
|
|
34
33
|
)
|
|
35
34
|
|
|
36
35
|
class Meta:
|
|
@@ -70,7 +69,7 @@ class InstrumentPriceMultipleBenchmarkChartFilterSet(InstrumentPriceFilterSet):
|
|
|
70
69
|
filter_params={"is_security": True},
|
|
71
70
|
method="fake_filter",
|
|
72
71
|
)
|
|
73
|
-
normalized = wb_filters.BooleanFilter(label="Normalize",
|
|
72
|
+
normalized = wb_filters.BooleanFilter(label="Normalize", initial=True, required=True, method="fake_filter")
|
|
74
73
|
|
|
75
74
|
class Meta:
|
|
76
75
|
model = InstrumentPrice
|
|
@@ -84,7 +83,11 @@ class InstrumentPriceFrequencyFilter(InstrumentPriceFilterSet):
|
|
|
84
83
|
MONTHLY = "BME", "Monthly"
|
|
85
84
|
|
|
86
85
|
frequency = wb_filters.ChoiceFilter(
|
|
87
|
-
label="Frequency",
|
|
86
|
+
label="Frequency",
|
|
87
|
+
choices=FrequencyChoice.choices,
|
|
88
|
+
initial=FrequencyChoice.DAILY,
|
|
89
|
+
required=True,
|
|
90
|
+
method="fake_filter",
|
|
88
91
|
)
|
|
89
92
|
|
|
90
93
|
class Meta:
|
wbfdm/filters/instruments.py
CHANGED
|
@@ -29,7 +29,7 @@ class InstrumentFavoriteGroupFilterSet(wb_filters.FilterSet):
|
|
|
29
29
|
endpoint=InstrumentFavoriteGroup.get_representation_endpoint(),
|
|
30
30
|
value_key=InstrumentFavoriteGroup.get_representation_value_key(),
|
|
31
31
|
label_key=InstrumentFavoriteGroup.get_representation_label_key(),
|
|
32
|
-
|
|
32
|
+
initial=get_default_favorite_group,
|
|
33
33
|
method="filter_favorite_group",
|
|
34
34
|
)
|
|
35
35
|
|
|
@@ -44,6 +44,21 @@ class InstrumentFavoriteGroupFilterSet(wb_filters.FilterSet):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class InstrumentFilterSet(TagFilterMixin, InstrumentFavoriteGroupFilterSet):
|
|
47
|
+
sibling_of = wb_filters.ModelChoiceFilter(
|
|
48
|
+
label="Sibling Of",
|
|
49
|
+
queryset=Instrument.objects.all(),
|
|
50
|
+
endpoint=Instrument.get_representation_endpoint(),
|
|
51
|
+
value_key=Instrument.get_representation_value_key(),
|
|
52
|
+
label_key=Instrument.get_representation_label_key(),
|
|
53
|
+
filter_params={"is_investable_universe": True},
|
|
54
|
+
method="filter_sibling_of",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def filter_sibling_of(self, queryset, name, value):
|
|
58
|
+
if value:
|
|
59
|
+
return queryset.filter(currency=value.currency, parent=value.parent).exclude(id=value.id)
|
|
60
|
+
return queryset
|
|
61
|
+
|
|
47
62
|
parent = wb_filters.ModelChoiceFilter(
|
|
48
63
|
label="Parent",
|
|
49
64
|
queryset=Instrument.objects.all(),
|
|
@@ -52,6 +67,7 @@ class InstrumentFilterSet(TagFilterMixin, InstrumentFavoriteGroupFilterSet):
|
|
|
52
67
|
label_key=Instrument.get_representation_label_key(),
|
|
53
68
|
hidden=True,
|
|
54
69
|
)
|
|
70
|
+
parent__isnull = wb_filters.BooleanFilter(field_name="parent", lookup_expr="isnull", hidden=True)
|
|
55
71
|
|
|
56
72
|
classifications = wb_filters.ModelChoiceFilter(
|
|
57
73
|
label="Classification",
|
|
@@ -92,9 +108,7 @@ class InstrumentFilterSet(TagFilterMixin, InstrumentFavoriteGroupFilterSet):
|
|
|
92
108
|
)
|
|
93
109
|
return queryset
|
|
94
110
|
|
|
95
|
-
is_investable = wb_filters.BooleanFilter(
|
|
96
|
-
label="Is Investable", default=True, method="filter_is_investable", hidden=True
|
|
97
|
-
)
|
|
111
|
+
is_investable = wb_filters.BooleanFilter(label="Is Investable", method="filter_is_investable", hidden=True)
|
|
98
112
|
|
|
99
113
|
def filter_is_investable(self, queryset, name, value):
|
|
100
114
|
if value:
|
|
@@ -139,7 +153,7 @@ class InstrumentFilterSet(TagFilterMixin, InstrumentFavoriteGroupFilterSet):
|
|
|
139
153
|
if "parent" in data:
|
|
140
154
|
data.pop("classifications", None) # remove classifications in case we are navigating the tree
|
|
141
155
|
data.pop("level", None)
|
|
142
|
-
super().__init__(data=data,
|
|
156
|
+
super().__init__(*args, data=data, **kwargs)
|
|
143
157
|
|
|
144
158
|
class Meta:
|
|
145
159
|
model = Instrument
|
|
@@ -171,7 +185,7 @@ class BaseClassifiedInstrumentFilterSet(TagFilterMixin, wb_filters.FilterSet):
|
|
|
171
185
|
endpoint=ClassificationGroup.get_representation_endpoint(),
|
|
172
186
|
value_key=ClassificationGroup.get_representation_value_key(),
|
|
173
187
|
label_key=ClassificationGroup.get_representation_label_key(),
|
|
174
|
-
|
|
188
|
+
initial=_get_default_classification_group_id,
|
|
175
189
|
method="fake_filter",
|
|
176
190
|
required=True,
|
|
177
191
|
)
|
|
@@ -195,7 +209,7 @@ class MonthlyPerformancesInstrumentFilterSet(wb_filters.FilterSet):
|
|
|
195
209
|
required=True,
|
|
196
210
|
clearable=False,
|
|
197
211
|
method="fake_filter",
|
|
198
|
-
|
|
212
|
+
initial=lambda r, v, q: DateRange(get_earliest_date(r, v, q), get_latest_date(r, v, q)),
|
|
199
213
|
)
|
|
200
214
|
|
|
201
215
|
class Meta:
|
|
@@ -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:
|