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.
Files changed (109) hide show
  1. wbfdm/admin/exchanges.py +1 -1
  2. wbfdm/admin/instruments.py +3 -2
  3. wbfdm/analysis/financial_analysis/change_point_detection.py +88 -0
  4. wbfdm/analysis/financial_analysis/statement_with_estimates.py +5 -6
  5. wbfdm/analysis/financial_analysis/utils.py +6 -0
  6. wbfdm/contrib/dsws/client.py +3 -3
  7. wbfdm/contrib/dsws/dataloaders/market_data.py +31 -3
  8. wbfdm/contrib/internal/dataloaders/market_data.py +43 -9
  9. wbfdm/contrib/metric/backends/base.py +2 -2
  10. wbfdm/contrib/metric/backends/statistics.py +47 -13
  11. wbfdm/contrib/metric/dispatch.py +3 -0
  12. wbfdm/contrib/metric/exceptions.py +1 -1
  13. wbfdm/contrib/metric/filters.py +19 -0
  14. wbfdm/contrib/metric/models.py +6 -0
  15. wbfdm/contrib/metric/orchestrators.py +4 -4
  16. wbfdm/contrib/metric/signals.py +7 -0
  17. wbfdm/contrib/metric/tasks.py +2 -3
  18. wbfdm/contrib/metric/viewsets/configs/display.py +2 -2
  19. wbfdm/contrib/metric/viewsets/mixins.py +6 -6
  20. wbfdm/contrib/msci/client.py +6 -2
  21. wbfdm/contrib/qa/database_routers.py +1 -1
  22. wbfdm/contrib/qa/dataloaders/adjustments.py +2 -1
  23. wbfdm/contrib/qa/dataloaders/corporate_actions.py +2 -1
  24. wbfdm/contrib/qa/dataloaders/financials.py +19 -1
  25. wbfdm/contrib/qa/dataloaders/fx_rates.py +86 -0
  26. wbfdm/contrib/qa/dataloaders/market_data.py +29 -40
  27. wbfdm/contrib/qa/dataloaders/officers.py +1 -1
  28. wbfdm/contrib/qa/dataloaders/statements.py +18 -3
  29. wbfdm/contrib/qa/jinja2/qa/sql/ibes/financials.sql +1 -1
  30. wbfdm/contrib/qa/sync/exchanges.py +2 -1
  31. wbfdm/contrib/qa/sync/utils.py +76 -17
  32. wbfdm/dataloaders/protocols.py +12 -1
  33. wbfdm/dataloaders/proxies.py +15 -1
  34. wbfdm/dataloaders/types.py +7 -1
  35. wbfdm/enums.py +2 -0
  36. wbfdm/factories/instruments.py +4 -2
  37. wbfdm/figures/financials/financial_analysis_charts.py +2 -8
  38. wbfdm/filters/classifications.py +2 -2
  39. wbfdm/filters/financials.py +9 -18
  40. wbfdm/filters/financials_analysis.py +36 -16
  41. wbfdm/filters/instrument_prices.py +8 -5
  42. wbfdm/filters/instruments.py +21 -7
  43. wbfdm/import_export/backends/cbinsights/utils/client.py +8 -8
  44. wbfdm/import_export/backends/refinitiv/utils/controller.py +1 -1
  45. wbfdm/import_export/handlers/instrument.py +160 -104
  46. wbfdm/import_export/handlers/option.py +2 -2
  47. wbfdm/import_export/parsers/cbinsights/equities.py +2 -3
  48. wbfdm/jinja2.py +2 -1
  49. wbfdm/locale/de/LC_MESSAGES/django.mo +0 -0
  50. wbfdm/locale/de/LC_MESSAGES/django.po +257 -0
  51. wbfdm/locale/en/LC_MESSAGES/django.mo +0 -0
  52. wbfdm/locale/en/LC_MESSAGES/django.po +255 -0
  53. wbfdm/locale/fr/LC_MESSAGES/django.mo +0 -0
  54. wbfdm/locale/fr/LC_MESSAGES/django.po +257 -0
  55. wbfdm/migrations/0031_exchange_apply_round_lot_size_and_more.py +23 -0
  56. wbfdm/migrations/0032_alter_instrumentprice_outstanding_shares.py +18 -0
  57. wbfdm/migrations/0033_alter_controversy_review.py +18 -0
  58. wbfdm/migrations/0034_alter_instrumentlist_instrument_list_type.py +18 -0
  59. wbfdm/models/esg/controversies.py +19 -23
  60. wbfdm/models/exchanges/exchanges.py +8 -4
  61. wbfdm/models/fields.py +2 -2
  62. wbfdm/models/fk_fields.py +3 -3
  63. wbfdm/models/instruments/instrument_lists.py +1 -0
  64. wbfdm/models/instruments/instrument_prices.py +8 -1
  65. wbfdm/models/instruments/instrument_relationships.py +3 -0
  66. wbfdm/models/instruments/instruments.py +139 -26
  67. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +29 -22
  68. wbfdm/models/instruments/mixin/financials_computed.py +0 -4
  69. wbfdm/models/instruments/mixin/financials_serializer_fields.py +118 -118
  70. wbfdm/models/instruments/mixin/instruments.py +7 -4
  71. wbfdm/models/instruments/options.py +6 -0
  72. wbfdm/models/instruments/private_equities.py +3 -0
  73. wbfdm/models/instruments/querysets.py +138 -37
  74. wbfdm/models/instruments/utils.py +5 -0
  75. wbfdm/serializers/exchanges.py +1 -0
  76. wbfdm/serializers/instruments/__init__.py +1 -0
  77. wbfdm/serializers/instruments/instruments.py +9 -2
  78. wbfdm/serializers/instruments/mixins.py +3 -3
  79. wbfdm/tasks.py +13 -2
  80. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +0 -1
  81. wbfdm/tests/models/test_instrument_prices.py +0 -14
  82. wbfdm/tests/models/test_instruments.py +21 -9
  83. wbfdm/tests/models/test_queryset.py +89 -0
  84. wbfdm/viewsets/configs/display/exchanges.py +1 -1
  85. wbfdm/viewsets/configs/display/financial_summary.py +2 -2
  86. wbfdm/viewsets/configs/display/instrument_prices.py +2 -70
  87. wbfdm/viewsets/configs/display/instruments.py +3 -4
  88. wbfdm/viewsets/configs/display/instruments_relationships.py +3 -1
  89. wbfdm/viewsets/configs/display/prices.py +1 -0
  90. wbfdm/viewsets/configs/display/statement_with_estimates.py +1 -2
  91. wbfdm/viewsets/configs/endpoints/classifications.py +0 -12
  92. wbfdm/viewsets/configs/endpoints/instrument_prices.py +4 -23
  93. wbfdm/viewsets/configs/titles/instrument_prices.py +2 -1
  94. wbfdm/viewsets/esg.py +2 -2
  95. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +2 -2
  96. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +1 -1
  97. wbfdm/viewsets/financial_analysis/financial_summary.py +6 -6
  98. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +7 -3
  99. wbfdm/viewsets/instruments/financials_analysis.py +9 -12
  100. wbfdm/viewsets/instruments/instrument_prices.py +9 -9
  101. wbfdm/viewsets/instruments/instruments.py +9 -7
  102. wbfdm/viewsets/instruments/utils.py +3 -3
  103. wbfdm/viewsets/market_data.py +1 -1
  104. wbfdm/viewsets/prices.py +5 -0
  105. wbfdm/viewsets/statements/statements.py +7 -3
  106. {wbfdm-1.49.5.dist-info → wbfdm-1.59.4.dist-info}/METADATA +2 -1
  107. {wbfdm-1.49.5.dist-info → wbfdm-1.59.4.dist-info}/RECORD +108 -95
  108. {wbfdm-1.49.5.dist-info → wbfdm-1.59.4.dist-info}/WHEEL +1 -1
  109. 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
- key="instrument_statistics", label="Instrument", formatting_rules=instrument_formatting_rules
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"), show="open"),
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
- # lookup="id_repr",
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(reversed(group.get_fields_names(sep="_")), reversed(level_representations[1:])):
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
- def get_list_endpoint(self, **kwargs):
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
- def get_list_endpoint(self, **kwargs):
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
- def get_list_endpoint(self, **kwargs):
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
- def get_list_endpoint(self, **kwargs):
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
- assert self.message, "No message has been set"
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
- ESG_mapping = ESG.mapping()
69
+ esg_mapping = ESG.mapping()
70
70
  df[["section", "asi", "metric", "factor"]] = pd.DataFrame(
71
- df.factor_code.map(ESG_mapping).tolist(), index=df.index
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
- MAX_ROW = 8
101
- if df.shape[0] > MAX_ROW:
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], MAX_ROW])] # keep only 8 row maximum
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"] = df[year_columns].replace([np.inf, -np.inf, np.nan], None).apply(list, axis=1)
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 index, single_date in enumerate(daterange(date1, date2)):
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 index, plot in enumerate(
428
- [
429
- ("pe", "#ff6361", VariableChoices.PE.chart_label, VariableChoices.PE, False),
430
- ("peg", "#ffa600", VariableChoices.PEG.chart_label, VariableChoices.PEG, True),
431
- ("ps", "#58508d", VariableChoices.PS.chart_label, VariableChoices.PS, False),
432
- ("evebitda", "#003f5c", VariableChoices.EVEBITDA.chart_label, VariableChoices.EVEBITDA, False),
433
- ("pfcf", "#bc5090", VariableChoices.PFCF.chart_label, VariableChoices.PFCF, False),
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
- fig.add_trace(
185
- go.Scatter(
186
- x=df.index,
187
- y=df.sharpe_ratio,
188
- mode="lines",
189
- name=f"Sharpe Ratio ({risk})",
190
- hovertemplate=get_hovertemplate_timeserie(currency=""),
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 = (InstrumentSearchFilter, ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter)
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 = ["-search_rank"]
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 = (InstrumentSearchFilter, ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter)
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 = ["-search_rank"]
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
- queryset = (
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
- else:
26
- queryset = queryset.annotate(search_rank=Value(-1.0))
25
+ elif "parent" in request.GET:
26
+ return queryset.order_by("-is_primary", "computed_str")
27
27
  return queryset
@@ -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 i, d in enumerate(fig.data):
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
- *[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
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.49.5
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