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
|
@@ -37,70 +37,6 @@ perf_formatting_rules_bold = [
|
|
|
37
37
|
),
|
|
38
38
|
]
|
|
39
39
|
|
|
40
|
-
instrument_formatting_rules = [
|
|
41
|
-
dp.Formatting(
|
|
42
|
-
column="instrument_vs_benchmark",
|
|
43
|
-
formatting_rules=[
|
|
44
|
-
dp.FormattingRule(
|
|
45
|
-
style={"color": WBColor.GREEN_DARK.value},
|
|
46
|
-
condition=("==", True),
|
|
47
|
-
),
|
|
48
|
-
dp.FormattingRule(
|
|
49
|
-
style={"color": WBColor.RED_DARK.value},
|
|
50
|
-
condition=("==", False),
|
|
51
|
-
),
|
|
52
|
-
],
|
|
53
|
-
),
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
benchmark_formatting_rules = [
|
|
57
|
-
dp.Formatting(
|
|
58
|
-
column="instrument_vs_benchmark",
|
|
59
|
-
formatting_rules=[
|
|
60
|
-
dp.FormattingRule(
|
|
61
|
-
style={"color": WBColor.GREEN_DARK.value},
|
|
62
|
-
condition=("==", False),
|
|
63
|
-
),
|
|
64
|
-
dp.FormattingRule(
|
|
65
|
-
style={"color": WBColor.RED_DARK.value},
|
|
66
|
-
condition=("==", True),
|
|
67
|
-
),
|
|
68
|
-
],
|
|
69
|
-
),
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
instrument_one_year_formatting_rules = [
|
|
73
|
-
dp.Formatting(
|
|
74
|
-
column="instrument_vs_benchmark_one_year",
|
|
75
|
-
formatting_rules=[
|
|
76
|
-
dp.FormattingRule(
|
|
77
|
-
style={"color": WBColor.GREEN_DARK.value},
|
|
78
|
-
condition=("==", True),
|
|
79
|
-
),
|
|
80
|
-
dp.FormattingRule(
|
|
81
|
-
style={"color": WBColor.RED_DARK.value},
|
|
82
|
-
condition=("==", False),
|
|
83
|
-
),
|
|
84
|
-
],
|
|
85
|
-
),
|
|
86
|
-
]
|
|
87
|
-
|
|
88
|
-
benchmark_one_year_formatting_rules = [
|
|
89
|
-
dp.Formatting(
|
|
90
|
-
column="instrument_vs_benchmark_one_year",
|
|
91
|
-
formatting_rules=[
|
|
92
|
-
dp.FormattingRule(
|
|
93
|
-
style={"color": WBColor.GREEN_DARK.value},
|
|
94
|
-
condition=("==", False),
|
|
95
|
-
),
|
|
96
|
-
dp.FormattingRule(
|
|
97
|
-
style={"color": WBColor.RED_DARK.value},
|
|
98
|
-
condition=("==", True),
|
|
99
|
-
),
|
|
100
|
-
],
|
|
101
|
-
),
|
|
102
|
-
]
|
|
103
|
-
|
|
104
40
|
|
|
105
41
|
class InstrumentPriceDisplayConfig(DisplayViewConfig):
|
|
106
42
|
def get_list_display(self) -> Optional[dp.ListDisplay]:
|
|
@@ -170,19 +106,15 @@ class FinancialStatisticsInstrumentPandasDisplayConfig(DisplayViewConfig):
|
|
|
170
106
|
),
|
|
171
107
|
],
|
|
172
108
|
),
|
|
173
|
-
dp.Field(
|
|
174
|
-
|
|
175
|
-
),
|
|
176
|
-
dp.Field(key="benchmark_statistics", label="Benchmark", formatting_rules=benchmark_formatting_rules),
|
|
109
|
+
dp.Field(key="instrument_statistics", label="Instrument"),
|
|
110
|
+
dp.Field(key="benchmark_statistics", label="Benchmark"),
|
|
177
111
|
dp.Field(
|
|
178
112
|
key="instrument_one_year",
|
|
179
113
|
label="Instrument - One Year",
|
|
180
|
-
formatting_rules=instrument_one_year_formatting_rules,
|
|
181
114
|
),
|
|
182
115
|
dp.Field(
|
|
183
116
|
key="benchmark_one_year",
|
|
184
117
|
label="Benchmark - One Year",
|
|
185
|
-
formatting_rules=benchmark_one_year_formatting_rules,
|
|
186
118
|
),
|
|
187
119
|
]
|
|
188
120
|
)
|
|
@@ -67,13 +67,14 @@ class InstrumentDisplayConfig(DisplayViewConfig):
|
|
|
67
67
|
def get_list_display(self) -> Optional[dp.ListDisplay]:
|
|
68
68
|
return dp.ListDisplay(
|
|
69
69
|
fields=[
|
|
70
|
+
dp.Field(key="name", label=_("Name"), pinned="left"),
|
|
70
71
|
dp.Field(
|
|
71
72
|
key=None,
|
|
72
73
|
label=_("Information"),
|
|
73
74
|
open_by_default=False,
|
|
74
75
|
children=[
|
|
75
76
|
dp.Field(key="instrument_type", label=_("Instrument Type"), show="open"),
|
|
76
|
-
dp.Field(key="exchange", label=_("Exchange")
|
|
77
|
+
dp.Field(key="exchange", label=_("Exchange")),
|
|
77
78
|
dp.Field(key="isin", label=_("ISIN")),
|
|
78
79
|
dp.Field(key="ticker", label=_("Ticker")),
|
|
79
80
|
dp.Field(key="refinitiv_identifier_code", label=_("RIC")),
|
|
@@ -100,14 +101,12 @@ class InstrumentDisplayConfig(DisplayViewConfig):
|
|
|
100
101
|
get_statistic_field(),
|
|
101
102
|
],
|
|
102
103
|
tree=True,
|
|
103
|
-
tree_group_pinned="left",
|
|
104
104
|
tree_group_field="name",
|
|
105
|
-
tree_group_label="Name",
|
|
106
105
|
tree_group_level_options=[
|
|
107
106
|
dp.TreeGroupLevelOption(
|
|
108
107
|
filter_key="parent",
|
|
109
108
|
filter_depth=1,
|
|
110
|
-
|
|
109
|
+
filter_blacklist=["parent__isnull"],
|
|
111
110
|
list_endpoint=reverse(
|
|
112
111
|
"wbfdm:instrument-list",
|
|
113
112
|
args=[],
|
|
@@ -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
|
)
|
|
@@ -17,5 +17,6 @@ class InstrumentPriceDisplayConfig(DisplayViewConfig):
|
|
|
17
17
|
dp.Field(key="volume", label=_("Volume")),
|
|
18
18
|
dp.Field(key="outstanding_shares", label=_("Oustanding Shares")),
|
|
19
19
|
dp.Field(key="market_capitalization", label=_("Market Cap.")),
|
|
20
|
+
dp.Field(key="market_capitalization_consolidated", label=_("Market Cap. (Consolidated)")),
|
|
20
21
|
],
|
|
21
22
|
)
|
|
@@ -70,6 +70,7 @@ class StatementWithEstimatesDisplayViewConfig(DisplayViewConfig):
|
|
|
70
70
|
|
|
71
71
|
return dp.ListDisplay(
|
|
72
72
|
fields=[
|
|
73
|
+
dp.Field(key="financial", label="Financial", pinned="left"),
|
|
73
74
|
dp.Field(key="progress", label="Yearly Trend", pinned="left"),
|
|
74
75
|
*map(generate_year_field, self.view.year_columns),
|
|
75
76
|
],
|
|
@@ -87,8 +88,6 @@ class StatementWithEstimatesDisplayViewConfig(DisplayViewConfig):
|
|
|
87
88
|
# ],
|
|
88
89
|
tree=True,
|
|
89
90
|
tree_group_field="financial",
|
|
90
|
-
tree_group_label="Financial",
|
|
91
|
-
tree_group_pinned="left",
|
|
92
91
|
tree_group_level_options=[
|
|
93
92
|
dp.TreeGroupLevelOption(
|
|
94
93
|
list_endpoint=reverse(
|
|
@@ -28,23 +28,11 @@ class ClassificationTreeChartEndpointConfig(EndpointViewConfig):
|
|
|
28
28
|
def get_endpoint(self, **kwargs):
|
|
29
29
|
return None
|
|
30
30
|
|
|
31
|
-
def get_list_endpoint(self, **kwargs):
|
|
32
|
-
return reverse(
|
|
33
|
-
"wbfdm:classificationgroup-treechart-list", args=[self.view.kwargs["group_id"]], request=self.request
|
|
34
|
-
)
|
|
35
|
-
|
|
36
31
|
|
|
37
32
|
class ClassificationIcicleChartEndpointConfig(EndpointViewConfig):
|
|
38
33
|
def get_endpoint(self, **kwargs):
|
|
39
34
|
return None
|
|
40
35
|
|
|
41
|
-
def get_list_endpoint(self, **kwargs):
|
|
42
|
-
return reverse(
|
|
43
|
-
"wbfdm:classificationgroup-iciclechart-list",
|
|
44
|
-
args=[self.view.kwargs["group_id"]],
|
|
45
|
-
request=self.request,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
36
|
|
|
49
37
|
class InstrumentClassificationThroughEndpointConfig(EndpointViewConfig):
|
|
50
38
|
def get_endpoint(self, **kwargs):
|
|
@@ -6,9 +6,6 @@ class InstrumentPriceInstrumentEndpointConfig(EndpointViewConfig):
|
|
|
6
6
|
def get_endpoint(self, **kwargs):
|
|
7
7
|
return None
|
|
8
8
|
|
|
9
|
-
def get_list_endpoint(self, **kwargs):
|
|
10
|
-
return reverse("wbfdm:instrument-price-list", [self.view.kwargs["instrument_id"]], request=self.request)
|
|
11
|
-
|
|
12
9
|
|
|
13
10
|
class InstrumentPriceStatisticsInstrumentEndpointConfig(EndpointViewConfig):
|
|
14
11
|
def get_endpoint(self, **kwargs):
|
|
@@ -20,32 +17,16 @@ class InstrumentPriceStatisticsInstrumentEndpointConfig(EndpointViewConfig):
|
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
class MonthlyPerformancesInstrumentEndpointConfig(InstrumentPriceInstrumentEndpointConfig):
|
|
23
|
-
|
|
24
|
-
return reverse("wbfdm:monthly_performances-list", [self.view.kwargs["instrument_id"]], request=self.request)
|
|
20
|
+
pass
|
|
25
21
|
|
|
26
22
|
|
|
27
23
|
class FinancialStatisticsInstrumentEndpointConfig(InstrumentPriceInstrumentEndpointConfig):
|
|
28
|
-
|
|
29
|
-
return reverse(
|
|
30
|
-
"wbfdm:instrument-financialstatistics-list",
|
|
31
|
-
[self.view.kwargs["instrument_id"]],
|
|
32
|
-
request=self.request,
|
|
33
|
-
)
|
|
24
|
+
pass
|
|
34
25
|
|
|
35
26
|
|
|
36
27
|
class InstrumentPriceInstrumentDistributionReturnsChartEndpointConfig(EndpointViewConfig):
|
|
37
|
-
|
|
38
|
-
return reverse(
|
|
39
|
-
"wbfdm:instrument-distributionreturnschart-list",
|
|
40
|
-
args=[self.view.kwargs["instrument_id"]],
|
|
41
|
-
request=self.request,
|
|
42
|
-
)
|
|
28
|
+
pass
|
|
43
29
|
|
|
44
30
|
|
|
45
31
|
class BestAndWorstReturnsInstrumentEndpointConfig(EndpointViewConfig):
|
|
46
|
-
|
|
47
|
-
return reverse(
|
|
48
|
-
"wbfdm:instrument-bestandworstreturns-list",
|
|
49
|
-
[self.view.kwargs["instrument_id"]],
|
|
50
|
-
request=self.request,
|
|
51
|
-
)
|
|
32
|
+
pass
|
|
@@ -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",
|
|
@@ -63,7 +63,7 @@ class StatementWithEstimatesPandasViewSet(InstrumentMixin, ExportPandasAPIViewSe
|
|
|
63
63
|
pf.CharField(key="financial", label="Financial"),
|
|
64
64
|
pf.CharField(key="_group_key", label="Group Key"),
|
|
65
65
|
pf.JsonField(key="_overwrites", label="Overwrites"),
|
|
66
|
-
pf.SparklineField(key="progress", label="Yearly Trend"),
|
|
66
|
+
pf.SparklineField(key="progress", label="Yearly Trend", dimension="double"),
|
|
67
67
|
]
|
|
68
68
|
)
|
|
69
69
|
|
|
@@ -76,7 +76,11 @@ class StatementWithEstimatesPandasViewSet(InstrumentMixin, ExportPandasAPIViewSe
|
|
|
76
76
|
|
|
77
77
|
year_columns = list(filter(lambda col: "Y" in col, df.columns))
|
|
78
78
|
if year_columns:
|
|
79
|
-
df["progress"] =
|
|
79
|
+
df["progress"] = (
|
|
80
|
+
df[year_columns]
|
|
81
|
+
.replace([np.inf, -np.inf, np.nan], None)
|
|
82
|
+
.apply(lambda x: list(x.to_dict().items()), axis=1)
|
|
83
|
+
)
|
|
80
84
|
|
|
81
85
|
self.extra_cache_kwargs["_estimate_mapping"] = financial_analysis_result.estimated_mapping
|
|
82
86
|
self.extra_cache_kwargs["_columns"] = df.columns
|
|
@@ -135,7 +139,7 @@ class StatementWithEstimatesPandasViewSet(InstrumentMixin, ExportPandasAPIViewSe
|
|
|
135
139
|
return self.df.columns
|
|
136
140
|
|
|
137
141
|
def get_ordering_fields(self):
|
|
138
|
-
return self.columns
|
|
142
|
+
return [x for x in self.columns if x != "progress"]
|
|
139
143
|
|
|
140
144
|
@cached_property
|
|
141
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"],
|
|
@@ -174,22 +174,22 @@ class InstrumentPriceInstrumentStatisticsChartView(InstrumentMixin, viewsets.Cha
|
|
|
174
174
|
|
|
175
175
|
if self.instrument.related_instruments.count() > 0:
|
|
176
176
|
reference = self.instrument.related_instruments.first().name_repr
|
|
177
|
-
risk = self.instrument.primary_risk_instrument.name_repr
|
|
178
177
|
df = pd.DataFrame(queryset.values("date", "sharpe_ratio", "correlation", "beta")).replace(
|
|
179
178
|
[np.inf, -np.inf], np.nan
|
|
180
179
|
)
|
|
181
180
|
|
|
182
181
|
if not df.empty:
|
|
183
182
|
df = df.set_index("date").sort_index().dropna()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
183
|
+
if risk_instrument := self.instrument.primary_risk_instrument:
|
|
184
|
+
fig.add_trace(
|
|
185
|
+
go.Scatter(
|
|
186
|
+
x=df.index,
|
|
187
|
+
y=df.sharpe_ratio,
|
|
188
|
+
mode="lines",
|
|
189
|
+
name=f"Sharpe Ratio ({risk_instrument.name_repr})",
|
|
190
|
+
hovertemplate=get_hovertemplate_timeserie(currency=""),
|
|
191
|
+
)
|
|
191
192
|
)
|
|
192
|
-
)
|
|
193
193
|
fig.add_trace(
|
|
194
194
|
go.Scatter(
|
|
195
195
|
x=df.index,
|
|
@@ -2,6 +2,7 @@ from django.db.models import Case, Exists, F, IntegerField, OuterRef, When
|
|
|
2
2
|
from rest_framework.filters import OrderingFilter
|
|
3
3
|
from wbcore import viewsets
|
|
4
4
|
from wbcore.contrib.guardian.filters import ObjectPermissionsFilter
|
|
5
|
+
from wbcore.pagination import LimitOffsetPagination
|
|
5
6
|
from wbcore.viewsets.mixins import DjangoFilterBackend
|
|
6
7
|
|
|
7
8
|
from wbfdm.contrib.metric.backends.performances import PERFORMANCE_METRIC
|
|
@@ -35,23 +36,23 @@ class InstrumentTypeRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class InstrumentRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
38
|
-
filter_backends = (
|
|
39
|
-
|
|
39
|
+
filter_backends = (ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter, InstrumentSearchFilter)
|
|
40
|
+
pagination_class = LimitOffsetPagination
|
|
40
41
|
queryset = Instrument.objects.annotate_base_data().exclude(name="")
|
|
41
42
|
serializer_class = InstrumentRepresentationSerializer
|
|
42
|
-
search_fields = ("name", "name_repr", "isin", "ticker")
|
|
43
|
+
search_fields = ("name", "name_repr", "isin", "ticker", "computed_str")
|
|
43
44
|
|
|
44
45
|
filterset_class = InstrumentFilterSet
|
|
45
46
|
ordering_fields = ("title", "ticker")
|
|
46
|
-
ordering = ["
|
|
47
|
+
ordering = ["name_repr"]
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
class InstrumentModelViewSet(InstrumentMetricMixin, viewsets.ModelViewSet):
|
|
50
51
|
METRIC_KEYS = (PERFORMANCE_METRIC, STATISTICS_METRIC)
|
|
51
52
|
METRIC_SHOW_AGGREGATES = False
|
|
52
|
-
filter_backends = (
|
|
53
|
+
filter_backends = (ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter, InstrumentSearchFilter)
|
|
53
54
|
|
|
54
|
-
queryset = Instrument.objects.annotate_all()
|
|
55
|
+
queryset = Instrument.objects.annotate_all().exclude(name="")
|
|
55
56
|
serializer_class = InstrumentModelListSerializer
|
|
56
57
|
ordering_fields = (
|
|
57
58
|
"instrument_type",
|
|
@@ -70,7 +71,7 @@ class InstrumentModelViewSet(InstrumentMetricMixin, viewsets.ModelViewSet):
|
|
|
70
71
|
"refinitiv_identifier_code",
|
|
71
72
|
"refinitiv_mnemonic_code",
|
|
72
73
|
)
|
|
73
|
-
ordering = ["
|
|
74
|
+
ordering = ["name_repr"]
|
|
74
75
|
|
|
75
76
|
def get_serializer_class(self):
|
|
76
77
|
if self.get_action() in ["list", "list-metadata"]:
|
|
@@ -102,6 +103,7 @@ class InstrumentModelViewSet(InstrumentMetricMixin, viewsets.ModelViewSet):
|
|
|
102
103
|
|
|
103
104
|
class ChildrenInstrumentModelViewSet(InstrumentMixin, InstrumentModelViewSet):
|
|
104
105
|
button_config_class = ChildrenInstrumentModelViewConfig
|
|
106
|
+
ordering = ("-is_primary", "computed_str")
|
|
105
107
|
|
|
106
108
|
def get_queryset(self):
|
|
107
109
|
return super().get_queryset().filter(parent=self.instrument)
|
|
@@ -12,7 +12,7 @@ class InstrumentSearchFilter:
|
|
|
12
12
|
if search := request.GET.get("search", None):
|
|
13
13
|
min_search_rank = self.get_min_search_rank(view)
|
|
14
14
|
query = SearchQuery(search, search_type="phrase")
|
|
15
|
-
|
|
15
|
+
return (
|
|
16
16
|
queryset.annotate(search_rank=Coalesce(SearchRank(F("search_vector"), query), Value(-1.0)))
|
|
17
17
|
.filter(
|
|
18
18
|
(Q(search_vector=query) & Q(search_rank__gte=min_search_rank))
|
|
@@ -22,6 +22,6 @@ class InstrumentSearchFilter:
|
|
|
22
22
|
)
|
|
23
23
|
.order_by("-search_rank")
|
|
24
24
|
).distinct()
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
elif "parent" in request.GET:
|
|
26
|
+
return queryset.order_by("-is_primary", "computed_str")
|
|
27
27
|
return queryset
|
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 = (
|
wbfdm/viewsets/prices.py
CHANGED
|
@@ -34,6 +34,11 @@ class InstrumentPriceViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
34
34
|
pf.FloatField(
|
|
35
35
|
key="market_capitalization", label="market_capitalization", display_mode=DisplayMode.SHORTENED
|
|
36
36
|
),
|
|
37
|
+
pf.FloatField(
|
|
38
|
+
key="market_capitalization_consolidated",
|
|
39
|
+
label="market_capitalization_consolidated",
|
|
40
|
+
display_mode=DisplayMode.SHORTENED,
|
|
41
|
+
),
|
|
37
42
|
)
|
|
38
43
|
)
|
|
39
44
|
permission_classes = []
|
|
@@ -53,8 +53,12 @@ class StatementPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
53
53
|
pf.PKField(key="external_ordering", label="ID"),
|
|
54
54
|
pf.CharField(key="external_code", label="Code"),
|
|
55
55
|
pf.CharField(key="external_description", label="Description"),
|
|
56
|
-
pf.SparklineField(key="progress", label="Yearly Trend"),
|
|
57
|
-
*[
|
|
56
|
+
pf.SparklineField(key="progress", label="Yearly Trend", dimension="double"),
|
|
57
|
+
*[
|
|
58
|
+
pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED)
|
|
59
|
+
for field in self.columns
|
|
60
|
+
if field not in ["external_ordering", "external_code", "external_description", "progress"]
|
|
61
|
+
],
|
|
58
62
|
]
|
|
59
63
|
)
|
|
60
64
|
|
|
@@ -83,7 +87,7 @@ class StatementPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
|
83
87
|
def manipulate_dataframe(self, df):
|
|
84
88
|
if not df.empty:
|
|
85
89
|
if year_cols := [col for col in df.columns if isinstance(col, str) and "Y" in col]:
|
|
86
|
-
df["progress"] = df[year_cols].fillna(0).apply(list, axis=1)
|
|
90
|
+
df["progress"] = df[year_cols].fillna(0).apply(lambda x: list(x.to_dict().items()), axis=1)
|
|
87
91
|
return df
|
|
88
92
|
|
|
89
93
|
@cached_property
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wbfdm
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.59.4
|
|
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.*
|
|
7
|
+
Requires-Dist: ruptures==1.1.*
|
|
7
8
|
Requires-Dist: sentry-sdk==2.*
|
|
8
9
|
Requires-Dist: stockstats==0.6.*
|
|
9
10
|
Requires-Dist: wbcore
|