wbfdm 2.2.1__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/__init__.py +2 -0
- wbfdm/admin/__init__.py +42 -0
- wbfdm/admin/classifications.py +39 -0
- wbfdm/admin/esg.py +23 -0
- wbfdm/admin/exchanges.py +53 -0
- wbfdm/admin/instrument_lists.py +23 -0
- wbfdm/admin/instrument_prices.py +62 -0
- wbfdm/admin/instrument_requests.py +33 -0
- wbfdm/admin/instruments.py +117 -0
- wbfdm/admin/instruments_relationships.py +25 -0
- wbfdm/admin/options.py +101 -0
- wbfdm/analysis/__init__.py +2 -0
- wbfdm/analysis/esg/__init__.py +0 -0
- wbfdm/analysis/esg/enums.py +82 -0
- wbfdm/analysis/esg/esg_analysis.py +217 -0
- wbfdm/analysis/esg/utils.py +13 -0
- wbfdm/analysis/financial_analysis/__init__.py +1 -0
- wbfdm/analysis/financial_analysis/financial_metric_analysis.py +88 -0
- wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +125 -0
- wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +271 -0
- wbfdm/analysis/financial_analysis/statement_with_estimates.py +558 -0
- wbfdm/analysis/financial_analysis/utils.py +316 -0
- wbfdm/analysis/technical_analysis/__init__.py +1 -0
- wbfdm/analysis/technical_analysis/technical_analysis.py +138 -0
- wbfdm/analysis/technical_analysis/traces.py +165 -0
- wbfdm/analysis/utils.py +32 -0
- wbfdm/apps.py +14 -0
- wbfdm/contrib/__init__.py +0 -0
- wbfdm/contrib/dsws/__init__.py +0 -0
- wbfdm/contrib/dsws/client.py +285 -0
- wbfdm/contrib/internal/__init__.py +0 -0
- wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
- wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
- wbfdm/contrib/metric/__init__.py +0 -0
- wbfdm/contrib/metric/admin/__init__.py +2 -0
- wbfdm/contrib/metric/admin/instruments.py +12 -0
- wbfdm/contrib/metric/admin/metrics.py +43 -0
- wbfdm/contrib/metric/apps.py +10 -0
- wbfdm/contrib/metric/backends/__init__.py +2 -0
- wbfdm/contrib/metric/backends/base.py +159 -0
- wbfdm/contrib/metric/backends/performances.py +265 -0
- wbfdm/contrib/metric/backends/statistics.py +182 -0
- wbfdm/contrib/metric/decorators.py +14 -0
- wbfdm/contrib/metric/dispatch.py +23 -0
- wbfdm/contrib/metric/dto.py +88 -0
- wbfdm/contrib/metric/exceptions.py +6 -0
- wbfdm/contrib/metric/factories.py +33 -0
- wbfdm/contrib/metric/filters.py +28 -0
- wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
- wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
- wbfdm/contrib/metric/migrations/__init__.py +0 -0
- wbfdm/contrib/metric/models.py +180 -0
- wbfdm/contrib/metric/orchestrators.py +94 -0
- wbfdm/contrib/metric/registry.py +80 -0
- wbfdm/contrib/metric/serializers.py +44 -0
- wbfdm/contrib/metric/tasks.py +27 -0
- wbfdm/contrib/metric/tests/__init__.py +0 -0
- wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
- wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
- wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
- wbfdm/contrib/metric/tests/conftest.py +92 -0
- wbfdm/contrib/metric/tests/test_dto.py +73 -0
- wbfdm/contrib/metric/tests/test_models.py +72 -0
- wbfdm/contrib/metric/tests/test_tasks.py +24 -0
- wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
- wbfdm/contrib/metric/urls.py +19 -0
- wbfdm/contrib/metric/viewsets/__init__.py +1 -0
- wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
- wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
- wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
- wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
- wbfdm/contrib/metric/viewsets/mixins.py +245 -0
- wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
- wbfdm/contrib/msci/__init__.py +0 -0
- wbfdm/contrib/msci/client.py +92 -0
- wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
- wbfdm/contrib/msci/dataloaders/esg.py +87 -0
- wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
- wbfdm/contrib/msci/sync.py +58 -0
- wbfdm/contrib/msci/tests/__init__.py +0 -0
- wbfdm/contrib/msci/tests/conftest.py +1 -0
- wbfdm/contrib/msci/tests/test_client.py +70 -0
- wbfdm/contrib/qa/__init__.py +0 -0
- wbfdm/contrib/qa/apps.py +22 -0
- wbfdm/contrib/qa/database_routers.py +25 -0
- wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
- wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
- wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
- wbfdm/contrib/qa/dataloaders/financials.py +83 -0
- wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
- wbfdm/contrib/qa/dataloaders/officers.py +59 -0
- wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
- wbfdm/contrib/qa/dataloaders/statements.py +267 -0
- wbfdm/contrib/qa/tasks.py +0 -0
- wbfdm/dataloaders/__init__.py +0 -0
- wbfdm/dataloaders/cache.py +129 -0
- wbfdm/dataloaders/protocols.py +112 -0
- wbfdm/dataloaders/proxies.py +201 -0
- wbfdm/dataloaders/types.py +209 -0
- wbfdm/dynamic_preferences_registry.py +45 -0
- wbfdm/enums.py +657 -0
- wbfdm/factories/__init__.py +13 -0
- wbfdm/factories/classifications.py +56 -0
- wbfdm/factories/controversies.py +27 -0
- wbfdm/factories/exchanges.py +21 -0
- wbfdm/factories/instrument_list.py +22 -0
- wbfdm/factories/instrument_prices.py +79 -0
- wbfdm/factories/instruments.py +63 -0
- wbfdm/factories/instruments_relationships.py +31 -0
- wbfdm/factories/options.py +66 -0
- wbfdm/figures/__init__.py +1 -0
- wbfdm/figures/financials/__init__.py +1 -0
- wbfdm/figures/financials/financial_analysis_charts.py +469 -0
- wbfdm/figures/financials/financials_charts.py +711 -0
- wbfdm/filters/__init__.py +31 -0
- wbfdm/filters/classifications.py +100 -0
- wbfdm/filters/exchanges.py +22 -0
- wbfdm/filters/financials.py +95 -0
- wbfdm/filters/financials_analysis.py +119 -0
- wbfdm/filters/instrument_prices.py +112 -0
- wbfdm/filters/instruments.py +198 -0
- wbfdm/filters/utils.py +44 -0
- wbfdm/import_export/__init__.py +0 -0
- wbfdm/import_export/backends/__init__.py +0 -0
- wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
- wbfdm/import_export/backends/cbinsights/deals.py +44 -0
- wbfdm/import_export/backends/cbinsights/equities.py +41 -0
- wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
- wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
- wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
- wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
- wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
- wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
- wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
- wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
- wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
- wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
- wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
- wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
- wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
- wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
- wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
- wbfdm/import_export/handlers/__init__.py +0 -0
- wbfdm/import_export/handlers/instrument.py +253 -0
- wbfdm/import_export/handlers/instrument_list.py +101 -0
- wbfdm/import_export/handlers/instrument_price.py +71 -0
- wbfdm/import_export/handlers/option.py +54 -0
- wbfdm/import_export/handlers/private_equities.py +49 -0
- wbfdm/import_export/parsers/__init__.py +0 -0
- wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
- wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
- wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
- wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
- wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
- wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
- wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
- wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
- wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
- wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
- wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
- wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
- wbfdm/import_export/resources/__init__.py +0 -0
- wbfdm/import_export/resources/classification.py +23 -0
- wbfdm/import_export/resources/instrument_prices.py +33 -0
- wbfdm/import_export/resources/instruments.py +176 -0
- wbfdm/jinja2.py +7 -0
- wbfdm/management/__init__.py +30 -0
- wbfdm/menu.py +11 -0
- wbfdm/migrations/0001_initial.py +71 -0
- wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
- wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
- wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
- wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
- wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
- wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
- wbfdm/migrations/0008_controversy.py +75 -0
- wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
- wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
- wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
- wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
- wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
- wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
- wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
- wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
- wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
- wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
- wbfdm/migrations/0019_alter_controversy_source.py +17 -0
- wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
- wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
- wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
- wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
- wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
- wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
- wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
- wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
- wbfdm/migrations/__init__.py +0 -0
- wbfdm/models/__init__.py +4 -0
- wbfdm/models/esg/__init__.py +1 -0
- wbfdm/models/esg/controversies.py +81 -0
- wbfdm/models/exchanges/__init__.py +1 -0
- wbfdm/models/exchanges/exchanges.py +223 -0
- wbfdm/models/fields.py +117 -0
- wbfdm/models/fk_fields.py +403 -0
- wbfdm/models/indicators.py +0 -0
- wbfdm/models/instruments/__init__.py +19 -0
- wbfdm/models/instruments/classifications.py +265 -0
- wbfdm/models/instruments/instrument_lists.py +120 -0
- wbfdm/models/instruments/instrument_prices.py +540 -0
- wbfdm/models/instruments/instrument_relationships.py +251 -0
- wbfdm/models/instruments/instrument_requests.py +196 -0
- wbfdm/models/instruments/instruments.py +991 -0
- wbfdm/models/instruments/llm/__init__.py +1 -0
- wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
- wbfdm/models/instruments/mixin/__init__.py +0 -0
- wbfdm/models/instruments/mixin/financials_computed.py +804 -0
- wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
- wbfdm/models/instruments/mixin/instruments.py +294 -0
- wbfdm/models/instruments/options.py +225 -0
- wbfdm/models/instruments/private_equities.py +59 -0
- wbfdm/models/instruments/querysets.py +73 -0
- wbfdm/models/instruments/utils.py +41 -0
- wbfdm/preferences.py +21 -0
- wbfdm/serializers/__init__.py +4 -0
- wbfdm/serializers/esg.py +36 -0
- wbfdm/serializers/exchanges.py +39 -0
- wbfdm/serializers/instruments/__init__.py +37 -0
- wbfdm/serializers/instruments/classifications.py +139 -0
- wbfdm/serializers/instruments/instrument_lists.py +61 -0
- wbfdm/serializers/instruments/instrument_prices.py +73 -0
- wbfdm/serializers/instruments/instrument_relationships.py +170 -0
- wbfdm/serializers/instruments/instrument_requests.py +61 -0
- wbfdm/serializers/instruments/instruments.py +274 -0
- wbfdm/serializers/instruments/mixins.py +104 -0
- wbfdm/serializers/officers.py +20 -0
- wbfdm/signals.py +7 -0
- wbfdm/sync/__init__.py +0 -0
- wbfdm/sync/abstract.py +31 -0
- wbfdm/sync/runner.py +22 -0
- wbfdm/tasks.py +69 -0
- wbfdm/tests/__init__.py +0 -0
- wbfdm/tests/analysis/__init__.py +0 -0
- wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
- wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
- wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
- wbfdm/tests/analysis/test_esg.py +159 -0
- wbfdm/tests/conftest.py +92 -0
- wbfdm/tests/dataloaders/__init__.py +0 -0
- wbfdm/tests/dataloaders/test_cache.py +73 -0
- wbfdm/tests/models/__init__.py +0 -0
- wbfdm/tests/models/test_classifications.py +99 -0
- wbfdm/tests/models/test_exchanges.py +7 -0
- wbfdm/tests/models/test_instrument_list.py +117 -0
- wbfdm/tests/models/test_instrument_prices.py +306 -0
- wbfdm/tests/models/test_instruments.py +202 -0
- wbfdm/tests/models/test_merge.py +99 -0
- wbfdm/tests/models/test_options.py +69 -0
- wbfdm/tests/test_tasks.py +6 -0
- wbfdm/tests/tests.py +10 -0
- wbfdm/urls.py +222 -0
- wbfdm/utils.py +54 -0
- wbfdm/viewsets/__init__.py +10 -0
- wbfdm/viewsets/configs/__init__.py +5 -0
- wbfdm/viewsets/configs/buttons/__init__.py +8 -0
- wbfdm/viewsets/configs/buttons/classifications.py +23 -0
- wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
- wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
- wbfdm/viewsets/configs/buttons/instruments.py +283 -0
- wbfdm/viewsets/configs/display/__init__.py +22 -0
- wbfdm/viewsets/configs/display/classifications.py +138 -0
- wbfdm/viewsets/configs/display/esg.py +75 -0
- wbfdm/viewsets/configs/display/exchanges.py +42 -0
- wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
- wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
- wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
- wbfdm/viewsets/configs/display/instruments.py +618 -0
- wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
- wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
- wbfdm/viewsets/configs/display/officers.py +16 -0
- wbfdm/viewsets/configs/display/prices.py +21 -0
- wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
- wbfdm/viewsets/configs/display/statements.py +48 -0
- wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
- wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
- wbfdm/viewsets/configs/endpoints/esg.py +20 -0
- wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
- wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
- wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
- wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
- wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
- wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
- wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
- wbfdm/viewsets/configs/endpoints/statements.py +6 -0
- wbfdm/viewsets/configs/menus/__init__.py +9 -0
- wbfdm/viewsets/configs/menus/classifications.py +19 -0
- wbfdm/viewsets/configs/menus/exchanges.py +10 -0
- wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
- wbfdm/viewsets/configs/menus/instruments.py +20 -0
- wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
- wbfdm/viewsets/configs/titles/__init__.py +42 -0
- wbfdm/viewsets/configs/titles/classifications.py +79 -0
- wbfdm/viewsets/configs/titles/esg.py +11 -0
- wbfdm/viewsets/configs/titles/exchanges.py +12 -0
- wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
- wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
- wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
- wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
- wbfdm/viewsets/configs/titles/instruments.py +31 -0
- wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
- wbfdm/viewsets/configs/titles/market_data.py +13 -0
- wbfdm/viewsets/configs/titles/prices.py +15 -0
- wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
- wbfdm/viewsets/esg.py +72 -0
- wbfdm/viewsets/exchanges.py +63 -0
- wbfdm/viewsets/financial_analysis/__init__.py +3 -0
- wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
- wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
- wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
- wbfdm/viewsets/instruments/__init__.py +80 -0
- wbfdm/viewsets/instruments/classifications.py +279 -0
- wbfdm/viewsets/instruments/financials_analysis.py +614 -0
- wbfdm/viewsets/instruments/instrument_lists.py +77 -0
- wbfdm/viewsets/instruments/instrument_prices.py +542 -0
- wbfdm/viewsets/instruments/instrument_requests.py +51 -0
- wbfdm/viewsets/instruments/instruments.py +106 -0
- wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
- wbfdm/viewsets/instruments/utils.py +27 -0
- wbfdm/viewsets/market_data.py +172 -0
- wbfdm/viewsets/mixins.py +9 -0
- wbfdm/viewsets/officers.py +27 -0
- wbfdm/viewsets/prices.py +62 -0
- wbfdm/viewsets/statements/__init__.py +1 -0
- wbfdm/viewsets/statements/statements.py +100 -0
- wbfdm/viewsets/technical_analysis/__init__.py +1 -0
- wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
- wbfdm-2.2.1.dist-info/METADATA +15 -0
- wbfdm-2.2.1.dist-info/RECORD +337 -0
- wbfdm-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from wbcore.contrib.color.enums import WBColor
|
|
3
|
+
from wbcore.metadata.configs import display as dp
|
|
4
|
+
from wbcore.metadata.configs.display.formatting import Condition, Operator
|
|
5
|
+
|
|
6
|
+
PERFORMANCE_FORMATTING = [
|
|
7
|
+
dp.FormattingRule(
|
|
8
|
+
style={"color": WBColor.GREY.value, "fontWeight": "bold"},
|
|
9
|
+
condition=Condition(Operator("=="), 0),
|
|
10
|
+
),
|
|
11
|
+
dp.FormattingRule(
|
|
12
|
+
style={"color": WBColor.RED_DARK.value, "fontWeight": "bold"},
|
|
13
|
+
condition=Condition(Operator("<"), 0),
|
|
14
|
+
),
|
|
15
|
+
dp.FormattingRule(
|
|
16
|
+
style={"color": WBColor.GREEN_DARK.value, "fontWeight": "bold"},
|
|
17
|
+
condition=Condition(Operator(">"), 0),
|
|
18
|
+
),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
BORDER_LEFT = dp.FormattingRule(
|
|
22
|
+
style={
|
|
23
|
+
"borderLeft": "1px solid #bdc3c7",
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_performance_fields(with_comparison_performances: bool = False) -> list[dp.Field]:
|
|
29
|
+
def _get_performance_field(key: str, label: str, show=None):
|
|
30
|
+
if not with_comparison_performances:
|
|
31
|
+
return dp.Field(
|
|
32
|
+
key=f"performance_{key}",
|
|
33
|
+
label=label,
|
|
34
|
+
width=100,
|
|
35
|
+
formatting_rules=PERFORMANCE_FORMATTING,
|
|
36
|
+
)
|
|
37
|
+
return dp.Field(
|
|
38
|
+
key=None,
|
|
39
|
+
label=label,
|
|
40
|
+
width=100,
|
|
41
|
+
show=show,
|
|
42
|
+
children=[
|
|
43
|
+
dp.Field(
|
|
44
|
+
key=f"performance_{key}",
|
|
45
|
+
label="Absolute",
|
|
46
|
+
width=100,
|
|
47
|
+
formatting_rules=PERFORMANCE_FORMATTING + [BORDER_LEFT],
|
|
48
|
+
),
|
|
49
|
+
dp.Field(
|
|
50
|
+
key=f"performance_peer_{key}",
|
|
51
|
+
label="vs Peer",
|
|
52
|
+
width=100,
|
|
53
|
+
formatting_rules=PERFORMANCE_FORMATTING,
|
|
54
|
+
show="open",
|
|
55
|
+
tooltip=dp.Tooltip(key="peers"),
|
|
56
|
+
),
|
|
57
|
+
dp.Field(
|
|
58
|
+
key=f"performance_benchmark_{key}",
|
|
59
|
+
label="vs Benchmark",
|
|
60
|
+
width=100,
|
|
61
|
+
formatting_rules=PERFORMANCE_FORMATTING,
|
|
62
|
+
show="open",
|
|
63
|
+
tooltip=dp.Tooltip(key="benchmarks"),
|
|
64
|
+
),
|
|
65
|
+
],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
dp.Field(
|
|
70
|
+
key=None,
|
|
71
|
+
label="Rolling",
|
|
72
|
+
open_by_default=False,
|
|
73
|
+
children=[
|
|
74
|
+
_get_performance_field(key="daily", label="Daily"),
|
|
75
|
+
_get_performance_field(
|
|
76
|
+
key="weekly",
|
|
77
|
+
label="Weekly",
|
|
78
|
+
show="open",
|
|
79
|
+
),
|
|
80
|
+
_get_performance_field(
|
|
81
|
+
key="monthly",
|
|
82
|
+
label="Monthly",
|
|
83
|
+
show="open",
|
|
84
|
+
),
|
|
85
|
+
_get_performance_field(
|
|
86
|
+
key="quarterly",
|
|
87
|
+
label="Quarterly",
|
|
88
|
+
show="open",
|
|
89
|
+
),
|
|
90
|
+
_get_performance_field(
|
|
91
|
+
key="yearly",
|
|
92
|
+
label="Yearly",
|
|
93
|
+
show="open",
|
|
94
|
+
),
|
|
95
|
+
],
|
|
96
|
+
),
|
|
97
|
+
dp.Field(
|
|
98
|
+
key=None,
|
|
99
|
+
label="Performance-To-Date (PTD)",
|
|
100
|
+
open_by_default=False,
|
|
101
|
+
children=[
|
|
102
|
+
_get_performance_field(key="week_to_date", label="Week", show="open"),
|
|
103
|
+
_get_performance_field(key="month_to_date", label="Month"),
|
|
104
|
+
_get_performance_field(key="quarter_to_date", label="Quarter", show="open"),
|
|
105
|
+
_get_performance_field(key="year_to_date", label="Year", show="open"),
|
|
106
|
+
],
|
|
107
|
+
),
|
|
108
|
+
dp.Field(
|
|
109
|
+
key=None,
|
|
110
|
+
label="Previous Performance",
|
|
111
|
+
open_by_default=False,
|
|
112
|
+
show="open",
|
|
113
|
+
children=[
|
|
114
|
+
_get_performance_field(key="previous_week_to_date", label="Week", show="open"),
|
|
115
|
+
_get_performance_field(key="previous_month_to_date", label="Month"),
|
|
116
|
+
_get_performance_field(key="previous_quarter_to_date", label="Quarter", show="open"),
|
|
117
|
+
_get_performance_field(key="previous_year_to_date", label="Year", show="open"),
|
|
118
|
+
],
|
|
119
|
+
),
|
|
120
|
+
_get_performance_field(key="inception", label="Inception"),
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_statistic_field():
|
|
125
|
+
return dp.Field(
|
|
126
|
+
key=None,
|
|
127
|
+
label=_("Fundamentals"),
|
|
128
|
+
open_by_default=False,
|
|
129
|
+
children=[
|
|
130
|
+
dp.Field(key="statistic_price", label="Last Price", width=100),
|
|
131
|
+
dp.Field(key="statistic_market_capitalization", label="Market Capitalization", width=100),
|
|
132
|
+
dp.Field(key="statistic_revenue_y_1", label="Revenue Y-1", width=100, show="closed"),
|
|
133
|
+
dp.Field(key="statistic_revenue_y0", label="Revenue Y", width=100, show="closed"),
|
|
134
|
+
dp.Field(key="statistic_revenue_y1", label="Revenue Y+1", width=100),
|
|
135
|
+
dp.Field(key="statistic_volume_50d", label="Avg Volume (3m)", width=100, show="closed"),
|
|
136
|
+
],
|
|
137
|
+
)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import TYPE_CHECKING, Optional, Type
|
|
3
|
+
|
|
4
|
+
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
from django.utils.functional import cached_property
|
|
6
|
+
from wbcore import filters
|
|
7
|
+
from wbcore.metadata.metadata import WBCoreMetadata
|
|
8
|
+
from wbcore.utils.strings import get_aggregate_symbol
|
|
9
|
+
from wbfdm.contrib.metric.models import InstrumentMetric
|
|
10
|
+
from wbfdm.contrib.metric.registry import backend_registry
|
|
11
|
+
from wbfdm.contrib.metric.viewsets.configs.display import (
|
|
12
|
+
InstrumentMetricPivotedListDisplayConfig,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ..dto import MetricKey
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from django.db.models import Model
|
|
19
|
+
from wbcore.models import WBModel
|
|
20
|
+
from wbcore.viewsets import ModelViewSet
|
|
21
|
+
|
|
22
|
+
_Base = ModelViewSet
|
|
23
|
+
else:
|
|
24
|
+
_Base = object
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InstrumentMetricMetaData(WBCoreMetadata):
|
|
28
|
+
def determine_metadata(self, request, view):
|
|
29
|
+
metadata = super().determine_metadata(request, view)
|
|
30
|
+
if "filter_fields" in metadata and view.METRIC_SHOW_FILTERS:
|
|
31
|
+
view_metric_key_choices = backend_registry.get_choices(view.METRIC_KEYS)
|
|
32
|
+
metric_show_filter_field = filters.BooleanFilter(
|
|
33
|
+
default=view.METRIC_SHOW_BY_DEFAULT,
|
|
34
|
+
field_name="metric_show_by_default",
|
|
35
|
+
required=True,
|
|
36
|
+
label="Show Metric Keys",
|
|
37
|
+
) # TODO implement this as a dependant filter when frontend allows it
|
|
38
|
+
metric_keys_filter_field = filters.MultipleChoiceFilter(
|
|
39
|
+
choices=view_metric_key_choices,
|
|
40
|
+
field_name="metric_keys",
|
|
41
|
+
required=True,
|
|
42
|
+
label="Metric Keys",
|
|
43
|
+
default=list(map(lambda x: x[0], view_metric_key_choices)),
|
|
44
|
+
)
|
|
45
|
+
_, metric_show_by_default_rep = metric_show_filter_field.get_representation(
|
|
46
|
+
request, "Show Metric Keys", view
|
|
47
|
+
)
|
|
48
|
+
metadata["filter_fields"]["metric_show_by_default"] = {
|
|
49
|
+
"label": "Show Metrics",
|
|
50
|
+
"lookup_expr": [metric_show_by_default_rep],
|
|
51
|
+
}
|
|
52
|
+
_, metric_keys_rep = metric_keys_filter_field.get_representation(request, "Metric Keys", view)
|
|
53
|
+
metadata["filter_fields"]["metric_keys"] = {"label": "Metric Keys", "lookup_expr": [metric_keys_rep]}
|
|
54
|
+
|
|
55
|
+
return metadata
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class InstrumentMetricMixin(_Base):
|
|
59
|
+
"""
|
|
60
|
+
Mixin to register automatically a set of metrics defines in METRIC_KEYS into a particular view
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
METRIC_KEYS: tuple[MetricKey] | tuple[()] = () # The set of MetricKey to inject into the view
|
|
64
|
+
METRIC_BASKET_LABEL: str = "id" # The filter path of the basket in the queryset
|
|
65
|
+
METRIC_INSTRUMENT_LABEL: str | None = None # The filter path of the instrument in the queryset. (default to None)
|
|
66
|
+
METRIC_WITH_PREFIXED_KEYS: bool = (
|
|
67
|
+
False # Set to True if prefixed metrics needs to be appended to the list of metric keys
|
|
68
|
+
)
|
|
69
|
+
METRIC_SHOW_AGGREGATES: bool = True # set to False if no aggregation needs to be shown for the inserted metrics
|
|
70
|
+
METRIC_SHOW_BY_DEFAULT: bool = True # If false, the metric are hidden by default
|
|
71
|
+
METRIC_SHOW_FILTERS: bool = False
|
|
72
|
+
|
|
73
|
+
display_config_class = InstrumentMetricPivotedListDisplayConfig # Default display class that automatically regised display Fields into the ListDisplay
|
|
74
|
+
metadata_class = InstrumentMetricMetaData
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def metric_date(self) -> date | None:
|
|
78
|
+
"""
|
|
79
|
+
Property to define at which date the metrics need to be fetched from. Expected to be override
|
|
80
|
+
"""
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def metric_basket(self) -> Optional["WBModel"]:
|
|
85
|
+
"""
|
|
86
|
+
Property to define at for which basket the metrics need to be fetched from. Expected to be override
|
|
87
|
+
"""
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def metric_basket_class(self) -> Type["Model"]:
|
|
92
|
+
"""
|
|
93
|
+
Define the basket class from which the metrics need to be fetched from.
|
|
94
|
+
|
|
95
|
+
Default to `metric_basket.__class__` if `metric_basket` is defined. Otherwise default to the viewset Model property.
|
|
96
|
+
"""
|
|
97
|
+
metric_basket_class = self.metric_basket.__class__ if self.metric_basket else self.model
|
|
98
|
+
if not metric_basket_class:
|
|
99
|
+
raise ValueError("Metric Basket Class needs to be defined")
|
|
100
|
+
return metric_basket_class
|
|
101
|
+
|
|
102
|
+
@cached_property
|
|
103
|
+
def metric_basket_content_type(self) -> ContentType:
|
|
104
|
+
"""
|
|
105
|
+
cached property to store the related basket content type instance
|
|
106
|
+
"""
|
|
107
|
+
return ContentType.objects.get_for_model(self.metric_basket_class)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def metric_keys(self) -> tuple[MetricKey] | tuple[()]:
|
|
111
|
+
"""
|
|
112
|
+
Property used to get the list of metric keys. Can be overridden to define custom logic based on the viewset attributes (e.g. request)
|
|
113
|
+
"""
|
|
114
|
+
if self.request.GET.get("metric_show_by_default", "true") == "false":
|
|
115
|
+
return ()
|
|
116
|
+
metric_keys = getattr(self, "METRIC_KEYS", ())
|
|
117
|
+
if metric_keys_filter_repr := self.request.GET.get("metric_keys"):
|
|
118
|
+
metric_keys_filter = metric_keys_filter_repr.split(",")
|
|
119
|
+
metric_keys = tuple(filter(lambda x: x.key in metric_keys_filter, metric_keys))
|
|
120
|
+
return metric_keys
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def metric_instrument_label(self) -> str | None:
|
|
124
|
+
"""
|
|
125
|
+
Property used to get the list of metric instrument label. Can be overridden to define custom logic based on the viewset attributes (e.g. request)
|
|
126
|
+
"""
|
|
127
|
+
return getattr(self, "METRIC_INSTRUMENT_LABEL", None)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def metric_basket_label(self) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Property used to get the list of metric basket label. Can be overridden to define custom logic based on the viewset attributes (e.g. request)
|
|
133
|
+
"""
|
|
134
|
+
return getattr(self, "METRIC_BASKET_LABEL", "id")
|
|
135
|
+
|
|
136
|
+
@cached_property
|
|
137
|
+
def _metric_serializer_fields(self):
|
|
138
|
+
"""
|
|
139
|
+
return the set of serializer fields necessary to display the related metric fields
|
|
140
|
+
"""
|
|
141
|
+
extra_serializer_fields = {}
|
|
142
|
+
for metric_key in self.metric_keys:
|
|
143
|
+
metric_backend = backend_registry[metric_key, self.metric_basket_class](self.metric_date)
|
|
144
|
+
extra_serializer_fields.update(
|
|
145
|
+
metric_backend.get_serializer_fields(
|
|
146
|
+
with_prefixed_key=self.METRIC_WITH_PREFIXED_KEYS, metric_key=metric_key
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
return extra_serializer_fields
|
|
150
|
+
|
|
151
|
+
def get_ordering_fields(self) -> list[str]:
|
|
152
|
+
"""
|
|
153
|
+
Inject the metric field keys as ordering fields
|
|
154
|
+
"""
|
|
155
|
+
ordering_fields = list(super().get_ordering_fields())
|
|
156
|
+
for metric_field in self._metric_serializer_fields.keys():
|
|
157
|
+
ordering_fields.append(metric_field)
|
|
158
|
+
return ordering_fields
|
|
159
|
+
|
|
160
|
+
def get_queryset(self):
|
|
161
|
+
"""
|
|
162
|
+
Annotate the metrics into the viewset queryset
|
|
163
|
+
"""
|
|
164
|
+
base_qs = super().get_queryset()
|
|
165
|
+
for metric_key in self.metric_keys:
|
|
166
|
+
base_qs = InstrumentMetric.annotate_with_metrics(
|
|
167
|
+
base_qs,
|
|
168
|
+
metric_key,
|
|
169
|
+
self.metric_basket_class,
|
|
170
|
+
self.metric_date,
|
|
171
|
+
basket_label=self.metric_basket_label,
|
|
172
|
+
instrument_label=self.metric_instrument_label,
|
|
173
|
+
)
|
|
174
|
+
return base_qs
|
|
175
|
+
|
|
176
|
+
def get_serializer(self, *args, **kwargs):
|
|
177
|
+
"""
|
|
178
|
+
Unwrap defined serializer class and inject the metric fields into a new class
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
serializer_class = self.get_serializer_class()
|
|
182
|
+
kwargs.setdefault("context", self.get_serializer_context())
|
|
183
|
+
|
|
184
|
+
BaseMeta = serializer_class.Meta
|
|
185
|
+
fields = list(getattr(BaseMeta, "fields", ()))
|
|
186
|
+
read_only_fields = list(getattr(BaseMeta, "read_only_fields", ()))
|
|
187
|
+
for extra_field in self._metric_serializer_fields.keys():
|
|
188
|
+
fields.append(extra_field)
|
|
189
|
+
read_only_fields.append(extra_field)
|
|
190
|
+
|
|
191
|
+
Meta = type(str("Meta"), (BaseMeta,), {"fields": fields, "read_only_fields": read_only_fields})
|
|
192
|
+
new_class = type(
|
|
193
|
+
serializer_class.__name__,
|
|
194
|
+
(serializer_class,),
|
|
195
|
+
{
|
|
196
|
+
"Meta": Meta,
|
|
197
|
+
**self._metric_serializer_fields,
|
|
198
|
+
"SERIALIZER_CLASS_FOR_REMOTE_ADDITIONAL_RESOURCES": serializer_class,
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return new_class(*args, **kwargs)
|
|
203
|
+
|
|
204
|
+
def get_aggregates(self, queryset, paginated_queryset):
|
|
205
|
+
"""
|
|
206
|
+
Automatically register the metric aggregation
|
|
207
|
+
"""
|
|
208
|
+
aggregates = dict()
|
|
209
|
+
if self.METRIC_SHOW_AGGREGATES:
|
|
210
|
+
# we try to inject the instrument metric aggregates automatically
|
|
211
|
+
keys_map = {key.key: key for key in self.metric_keys}
|
|
212
|
+
if self.metric_basket and (ct := self.metric_basket_content_type):
|
|
213
|
+
# for each "general" metric (i.e. without particular instrument attached), we add the raw subfield value as aggregates
|
|
214
|
+
for metric in InstrumentMetric.objects.filter(
|
|
215
|
+
basket_id=self.metric_basket.id,
|
|
216
|
+
basket_content_type=ct,
|
|
217
|
+
date=self.metric_date,
|
|
218
|
+
key__in=keys_map.keys(),
|
|
219
|
+
instrument__isnull=True,
|
|
220
|
+
):
|
|
221
|
+
metric_key = keys_map[metric.key]
|
|
222
|
+
for subfield_key, subfield_filter in metric_key.subfields_filter_map.items():
|
|
223
|
+
if subfield_key in self._metric_serializer_fields and (
|
|
224
|
+
aggregate_fct := metric_key.subfields_map[subfield_key].aggregate
|
|
225
|
+
):
|
|
226
|
+
aggregates[subfield_key] = {
|
|
227
|
+
get_aggregate_symbol(aggregate_fct.name): metric.metrics.get(subfield_filter)
|
|
228
|
+
}
|
|
229
|
+
# 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
|
|
230
|
+
missing_aggregate_map = {}
|
|
231
|
+
for metric_key in self.metric_keys:
|
|
232
|
+
for field_key, field_filter in metric_key.subfields_filter_map.items():
|
|
233
|
+
if field_key in self._metric_serializer_fields.keys() and field_key not in aggregates:
|
|
234
|
+
missing_aggregate_map[field_key] = metric_key.subfields_map[field_key]
|
|
235
|
+
missing_aggregate = queryset.aggregate(
|
|
236
|
+
**{
|
|
237
|
+
"agg_" + subfield_key: subfield.aggregate(subfield_key)
|
|
238
|
+
for subfield_key, subfield in missing_aggregate_map.items()
|
|
239
|
+
if subfield.aggregate is not None
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
for k, v in missing_aggregate.items():
|
|
243
|
+
key = k.replace("agg_", "")
|
|
244
|
+
aggregates[key] = {get_aggregate_symbol(missing_aggregate_map[key].aggregate.name): v}
|
|
245
|
+
return aggregates
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from django.db.models import Case, Exists, F, IntegerField, OuterRef, When
|
|
2
|
+
from wbcore import viewsets
|
|
3
|
+
from wbfdm.contrib.metric.filters import InstrumentMetricFilterSet
|
|
4
|
+
from wbfdm.contrib.metric.models import InstrumentMetric
|
|
5
|
+
from wbfdm.contrib.metric.serializers import (
|
|
6
|
+
InstrumentMetricModelSerializer,
|
|
7
|
+
InstrumentMetricRepresentationSerializer,
|
|
8
|
+
)
|
|
9
|
+
from wbfdm.contrib.metric.viewsets.configs import InstrumentMetricDisplayConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InstrumentMetricRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
13
|
+
ordering = ["-date"]
|
|
14
|
+
ordering_fields = [
|
|
15
|
+
"key",
|
|
16
|
+
"date",
|
|
17
|
+
]
|
|
18
|
+
filterset_class = InstrumentMetricFilterSet
|
|
19
|
+
|
|
20
|
+
serializer_class = InstrumentMetricRepresentationSerializer
|
|
21
|
+
queryset = InstrumentMetric.objects.all()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InstrumentMetricViewSet(viewsets.ModelViewSet):
|
|
25
|
+
queryset = InstrumentMetric.objects.select_related("basket_content_type", "instrument").annotate(
|
|
26
|
+
has_children=Exists(InstrumentMetric.objects.filter(parent_metric=OuterRef("pk"))),
|
|
27
|
+
_group_key=Case(When(has_children=True, then=F("id")), default=None, output_field=IntegerField()),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
serializer_class = InstrumentMetricModelSerializer
|
|
31
|
+
filterset_class = InstrumentMetricFilterSet
|
|
32
|
+
|
|
33
|
+
display_config_class = InstrumentMetricDisplayConfig
|
|
34
|
+
|
|
35
|
+
ordering = ["-date"]
|
|
36
|
+
ordering_fields = [
|
|
37
|
+
"key",
|
|
38
|
+
"date",
|
|
39
|
+
]
|
|
40
|
+
search_fields = ["key", "instrument__computed_str", "basket_repr"]
|
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import UTC, datetime
|
|
3
|
+
from typing import Generator
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from django.core.cache import cache as cache_layer
|
|
7
|
+
from django.utils import timezone
|
|
8
|
+
from django.utils.functional import cached_property
|
|
9
|
+
from jwt import decode as jwt_decode
|
|
10
|
+
from jwt.exceptions import DecodeError
|
|
11
|
+
from requests.exceptions import ConnectionError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_expired(token: str) -> bool:
|
|
15
|
+
with suppress(DecodeError, KeyError, ValueError):
|
|
16
|
+
expiry_ts = int(jwt_decode(token, options={"verify_signature": False})["exp"])
|
|
17
|
+
expiry_datetime = datetime.fromtimestamp(expiry_ts, UTC)
|
|
18
|
+
return expiry_datetime < timezone.now()
|
|
19
|
+
return True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MSCIClient:
|
|
23
|
+
AUTH_SERVER_ULR: str = "https://accounts.msci.com/oauth/token"
|
|
24
|
+
|
|
25
|
+
def __init__(self, client_id: str, client_secret: str):
|
|
26
|
+
self.client_id = client_id
|
|
27
|
+
self.client_secret = client_secret
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def oauth_token(self) -> str:
|
|
31
|
+
token = cache_layer.get("msci_oauth_token")
|
|
32
|
+
if not token or is_expired(token):
|
|
33
|
+
resp = requests.post(
|
|
34
|
+
self.AUTH_SERVER_ULR,
|
|
35
|
+
json={
|
|
36
|
+
"client_id": self.client_id,
|
|
37
|
+
"client_secret": self.client_secret,
|
|
38
|
+
"grant_type": "client_credentials",
|
|
39
|
+
"audience": "https://esg/data",
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
if resp.status_code == requests.codes.ok:
|
|
43
|
+
with suppress(KeyError, requests.exceptions.JSONDecodeError):
|
|
44
|
+
resp_json = resp.json()
|
|
45
|
+
token = resp_json["access_token"]
|
|
46
|
+
expires_in = resp_json["expires_in"]
|
|
47
|
+
cache_layer.set("msci_oauth_token", token, expires_in)
|
|
48
|
+
if token:
|
|
49
|
+
return token
|
|
50
|
+
raise ConnectionError()
|
|
51
|
+
|
|
52
|
+
def esg(self, identifiers: list[str], factors: list[str]) -> Generator[dict[str, str], None, None]:
|
|
53
|
+
with suppress(ConnectionError):
|
|
54
|
+
response = requests.post(
|
|
55
|
+
url="https://api2.msci.com/esg/data/v2.0/issuers",
|
|
56
|
+
json={
|
|
57
|
+
"issuer_identifier_list": identifiers,
|
|
58
|
+
"factor_name_list": factors,
|
|
59
|
+
},
|
|
60
|
+
headers={"AUTHORIZATION": f"Bearer {self.oauth_token}"},
|
|
61
|
+
)
|
|
62
|
+
if response.ok:
|
|
63
|
+
for row in response.json().get("result", {}).get("issuers", []):
|
|
64
|
+
yield row
|
|
65
|
+
|
|
66
|
+
def controversies(self, identifiers: list[str], factors: list[str]) -> Generator[dict[str, str], None, None]:
|
|
67
|
+
next_url = "https://api2.msci.com/esg/data/v2.0/issuers"
|
|
68
|
+
offset = 0
|
|
69
|
+
while next_url:
|
|
70
|
+
with suppress(ConnectionError):
|
|
71
|
+
response = requests.post(
|
|
72
|
+
url=next_url,
|
|
73
|
+
json={
|
|
74
|
+
"issuer_identifier_list": identifiers,
|
|
75
|
+
"factor_name_list": factors,
|
|
76
|
+
"limit": 100,
|
|
77
|
+
"offset": offset,
|
|
78
|
+
},
|
|
79
|
+
headers={"AUTHORIZATION": f"Bearer {self.oauth_token}"},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if not response.ok:
|
|
83
|
+
next_url = None
|
|
84
|
+
else:
|
|
85
|
+
json_res = response.json()
|
|
86
|
+
try:
|
|
87
|
+
next_url = json_res["paging"]["links"]["next"]
|
|
88
|
+
except KeyError:
|
|
89
|
+
next_url = None
|
|
90
|
+
offset += 100
|
|
91
|
+
for row in json_res.get("result", {}).get("issuers", []):
|
|
92
|
+
yield row
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from wbcore.contrib.dataloader.dataloaders import Dataloader
|
|
6
|
+
from wbfdm.dataloaders.protocols import ESGProtocol
|
|
7
|
+
from wbfdm.dataloaders.types import ESGDataDict
|
|
8
|
+
from wbfdm.enums import ESG
|
|
9
|
+
|
|
10
|
+
from ..client import MSCIClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MSCIESG(enum.Enum):
|
|
14
|
+
CARBON_EMISSIONS_SCOPE_1 = "CARBON_EMISSIONS_SCOPE_1"
|
|
15
|
+
CARBON_EMISSIONS_SCOPE_1_KEY = "CARBON_EMISSIONS_SCOPE_1_KEY"
|
|
16
|
+
CARBON_EMISSIONS_SCOPE_2 = "CARBON_EMISSIONS_SCOPE_2"
|
|
17
|
+
CARBON_EMISSIONS_SCOPE_2_KEY = "CARBON_EMISSIONS_SCOPE_2_KEY"
|
|
18
|
+
CARBON_EMISSIONS_SCOPE_3_TOTAL = "CARBON_EMISSIONS_SCOPE_3_TOTAL"
|
|
19
|
+
CARBON_EMISSIONS_SCOPE_3_YEAR = "CARBON_EMISSIONS_SCOPE_3_ESTIMATES_YEAR"
|
|
20
|
+
CARBON_EMISSIONS_SCOPE123 = "CARBON_EMISSIONS_SCOPE123"
|
|
21
|
+
CARBON_EMISSIONS_SCOPE123_KEY = "CARBON_EMISSIONS_SCOPE123_KEY"
|
|
22
|
+
CARBON_EMISSIONS_SALES_EUR_SCOPE_ALL = "CARBON_EMISSIONS_SALES_EUR_SCOPE123_INTEN"
|
|
23
|
+
CARBON_EMISSIONS_SCOPE123_EVIC_EUR = "CARBON_EMISSIONS_EVIC_EUR_SCOPE123_INTEN"
|
|
24
|
+
CARBON_EMISSIONS_SOURCE = "CARBON_EMISSIONS_SOURCE"
|
|
25
|
+
CARBON_EMISSIONS_YEAR = "CARBON_EMISSIONS_YEAR"
|
|
26
|
+
ACTIVE_FF_SECTOR_EXPOSURE = "ACTIVE_FF_SECTOR_EXPOSURE"
|
|
27
|
+
ACTIVE_FF_SECTOR_EXPOSURE_SOURCE = "ACTIVE_FF_SECTOR_EXPOSURE_SOURCE"
|
|
28
|
+
ACTIVE_FF_SECTOR_EXPOSURE_YEAR = "ACTIVE_FF_SECTOR_EXPOSURE_YEAR"
|
|
29
|
+
PCT_NON_RENEW_CONSUMPTION_PRODUCTION = "PCT_NONRENEW_CONSUMP_PROD"
|
|
30
|
+
PCT_TOTAL_NON_RENEW_CONSUMPTION = "PCT_TOTL_ERGY_CONSUMP_NONRENEW"
|
|
31
|
+
PCT_TOTAL_NON_RENEW_PRODUCTION = "PCT_TOTL_ERGY_PRODUCT_NONRENEW"
|
|
32
|
+
TOTAL_RENEW_ENERGY_CONSUMPTION = "TOTL_ERGY_CONSUMP_RENEW_GWH"
|
|
33
|
+
TOTAL_RENEW_ENERGY_CONSUMPTION_SOURCE = "TOTL_ERGY_CONSUMP_RENEW_GWH_SOURCE"
|
|
34
|
+
TOTAL_RENEW_ENERGY_CONSUMPTION_YEAR = "TOTL_ERGY_CONSUMP_RENEW_GWH_YEAR"
|
|
35
|
+
TOTAL_NON_RENEW_ENERGY_CONSUMPTION = "TOTL_ERGY_CONSUMP_NONRENEW_GWH"
|
|
36
|
+
TOTAL_NON_RENEW_ENERGY_CONSUMPTION_SOURCE = "TOTL_ERGY_CONSUMP_NONRENEW_GWH_SOURCE"
|
|
37
|
+
TOTAL_NON_RENEW_ENERGY_CONSUMPTION_YEAR = "TOTL_ERGY_CONSUMP_NONRENEW_GWH_YEAR"
|
|
38
|
+
TOTAL_NON_RENEW_ENERGY_CONSUMPTION_PRODUCTION = "TOTL_ERGY_CONSUMP_PRODUCT_NONRENEW_GWH"
|
|
39
|
+
TOTAL_NON_RENEW_ENERGY_CONSUMPTION_PRODUCTION_SOURCE = "TOTL_ERGY_CONSUMP_PRODUCT_NONRENEW_GWH_SOURCE"
|
|
40
|
+
TOTAL_NON_RENEW_ENERGY_CONSUMPTION_PRODUCTION_YEAR = "TOTL_ERGY_CONSUMP_PRODUCT_NONRENEW_GWH_YEAR"
|
|
41
|
+
ENERGY_CONSUMPTION_INTENSITY_EUR = "ENERGY_CONSUMP_INTEN_EUR"
|
|
42
|
+
ENERGY_CONSUMPTION_INTENSITY_EUR_SOURCE = "ENERGY_CONSUMP_INTEN_EUR_SOURCE"
|
|
43
|
+
ENERGY_CONSUMPTION_INTENSITY_EUR_YEAR = "ENERGY_CONSUMP_INTEN_EUR_YEAR"
|
|
44
|
+
TOTAL_ENERGY_CONSUMPTION = "TOTL_ERGY_CONSUMP_GWH"
|
|
45
|
+
TOTAL_ENERGY_CONSUMPTION_SOURCE = "TOTL_ERGY_CONSUMP_GWH_SOURCE"
|
|
46
|
+
TOTAL_ENERGY_CONSUMPTION_YEAR = "TOTL_ENRGY_CONSUMP_YEAR"
|
|
47
|
+
OPS_BIODIV_CONTROVERSITIES = "OPS_PROT_BIODIV_CONTROVS"
|
|
48
|
+
OPS_BIODIV_AREAS = "OPS_PROT_BIODIV_AREAS"
|
|
49
|
+
WATER_EMISSIONS = "WATER_EM_EFF_METRIC_TONS"
|
|
50
|
+
WATER_EMISSIONS_YEAR = "WATER_EM_EFF_METRIC_TONS_YEAR"
|
|
51
|
+
WATER_EMISSIONS_SOURCE = "WATER_EM_EFF_METRIC_TONS_SOURCE"
|
|
52
|
+
HAZARD_WASTE = "HAZARD_WASTE_METRIC_TON"
|
|
53
|
+
HAZARD_WASTE_YEAR = "HAZARD_WASTE_METRIC_TON_YEAR"
|
|
54
|
+
HAZARD_WASTE_SOURCE = "HAZARD_WASTE_METRIC_TON_SOURCE"
|
|
55
|
+
OECD_ALIGNMENT = "OECD_ALIGNMENT"
|
|
56
|
+
LABOR_DDP = "LABOR_DDIL_POL_ILO"
|
|
57
|
+
COMPLIANCE_GLOBAL_IMPACT = "MECH_UN_GLOBAL_COMPACT"
|
|
58
|
+
GENDER_PAY_GAP_RATIO = "GENDER_PAY_GAP_RATIO"
|
|
59
|
+
GENDER_PAY_GAP_RATIO_YEAR = "GENDER_PAY_GAP_YEAR"
|
|
60
|
+
GENDER_PAY_GAP_RATIO_SOURCE = "GENDER_PAY_GAP_RATIO_SOURCE"
|
|
61
|
+
PCT_FEMALE_DIRECTORS = "FEMALE_DIRECTORS_PCT"
|
|
62
|
+
CONTROVERSIAL_WEAPONS = "CONTRO_WEAP_CBLMBW_ANYTIE"
|
|
63
|
+
CONTROVERSIAL_WEAPONS_SOURCE = "CONTRO_WEAP_CBLMBW_ANYTIE_SOURCE"
|
|
64
|
+
CVI_FACTOR = "CVI_FACTOR"
|
|
65
|
+
EVIC_EUR = "EVIC_EUR"
|
|
66
|
+
NACE_SECTION_CODE = "NACE_SECTION_CODE"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MSCIESGDataloader(ESGProtocol, Dataloader):
|
|
70
|
+
CACHE_IDENTIFIER_KEY: str = "instrument_id"
|
|
71
|
+
CACHE_SYMBOL_KEY: str = "factor_code"
|
|
72
|
+
CACHE_VALUE_KEY: str = "value"
|
|
73
|
+
|
|
74
|
+
def esg(self, values: list[ESG]) -> Iterator[ESGDataDict]:
|
|
75
|
+
msci_client_id = getattr(settings, "MSCI_CLIENT_ID", None)
|
|
76
|
+
msci_client_secret = getattr(settings, "MSCI_CLIENT_SECRET", None)
|
|
77
|
+
if msci_client_id and msci_client_secret:
|
|
78
|
+
client = MSCIClient(msci_client_id, msci_client_secret)
|
|
79
|
+
lookup = {k: v for k, v in self.entities.values_list("dl_parameters__esg__parameters", "id")}
|
|
80
|
+
for row in client.esg(list(lookup.keys()), [MSCIESG[value.name].value for value in values]):
|
|
81
|
+
if instrument_id := lookup.get(row["ISSUER_ISIN"], None):
|
|
82
|
+
for value in values:
|
|
83
|
+
yield ESGDataDict(
|
|
84
|
+
instrument_id=instrument_id,
|
|
85
|
+
factor_code=value.value,
|
|
86
|
+
value=row.get(MSCIESG[value.name].value, None),
|
|
87
|
+
)
|