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,81 @@
|
|
|
1
|
+
from datetime import datetime
|
|
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 ESGControversyProtocol
|
|
7
|
+
from wbfdm.dataloaders.types import ESGControversyDataDict
|
|
8
|
+
|
|
9
|
+
from ..client import MSCIClient
|
|
10
|
+
|
|
11
|
+
CONTROVERY_ASSESSMENT_MAP = {
|
|
12
|
+
"Minor": "MINOR",
|
|
13
|
+
"Moderate": "MODERATE",
|
|
14
|
+
"Severe": "SEVERE",
|
|
15
|
+
"Very Severe": "VERY_SEVERE",
|
|
16
|
+
}
|
|
17
|
+
CONTROVERSY_STATUS_MAP = {
|
|
18
|
+
"Ongoing": "ONGOING",
|
|
19
|
+
"Partially Concluded": "PARTIALLY_CONCLUDED",
|
|
20
|
+
"Concluded": "CONCLUDED",
|
|
21
|
+
}
|
|
22
|
+
CONTROVERSY_TYPE_MAP = {
|
|
23
|
+
"Structural": "STRUCTURAL",
|
|
24
|
+
"Non-Structural": "NON_STRUCTURAL",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
CONTROVERSY_FLAG_MAP = {
|
|
28
|
+
"Green": "GREEN",
|
|
29
|
+
"Yellow": "YELLOW",
|
|
30
|
+
"Orange": "ORANGE",
|
|
31
|
+
"Red": "RED",
|
|
32
|
+
"Unknown": "UNKNOWN",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MSCIESGControversyDataloader(ESGControversyProtocol, Dataloader):
|
|
37
|
+
def esg_controversies(self) -> Iterator[ESGControversyDataDict]:
|
|
38
|
+
msci_client_id = getattr(settings, "MSCI_CLIENT_ID", None)
|
|
39
|
+
msci_client_secret = getattr(settings, "MSCI_CLIENT_SECRET", None)
|
|
40
|
+
if msci_client_id and msci_client_secret:
|
|
41
|
+
client = MSCIClient(msci_client_id, msci_client_secret)
|
|
42
|
+
lookup = {k: v for k, v in self.entities.values_list("dl_parameters__esg_controversies__parameters", "id")}
|
|
43
|
+
factors = [
|
|
44
|
+
"CONTROVERSY_CASE_ID",
|
|
45
|
+
"CONTROVERSY_CASE_HEADLINE",
|
|
46
|
+
"CONTROVERSY_LAST_REVIEWED",
|
|
47
|
+
"CONTROVERSY_CASE_NARRATIVE",
|
|
48
|
+
"CONTROVERSY_CASE_SOURCE",
|
|
49
|
+
"CONTROVERSY_CASE_STATUS",
|
|
50
|
+
"CONTROVERSY_CASE_TYPE",
|
|
51
|
+
"CONTROVERSY_CASE_ASSESSMENT",
|
|
52
|
+
"CONTROVERSY_CASE_COMPANY_RESPONSE",
|
|
53
|
+
"CONTROVERSY_CASE_DATE_INITIATED",
|
|
54
|
+
"CONTROVERSY_CASE_FLAG",
|
|
55
|
+
]
|
|
56
|
+
for row in client.controversies(list(lookup.keys()), factors):
|
|
57
|
+
isin = row.get("CLIENT_IDENTIFIER", "")
|
|
58
|
+
instrument_id = lookup.get(isin, "")
|
|
59
|
+
|
|
60
|
+
headline = row.get("CONTROVERSY_CASE_HEADLINE", "")
|
|
61
|
+
narrative = row.get("CONTROVERSY_CASE_NARRATIVE", "")
|
|
62
|
+
reviewed = row.get("CONTROVERSY_LAST_REVIEWED", None)
|
|
63
|
+
initiated = row.get("CONTROVERSY_CASE_DATE_INITIATED", None)
|
|
64
|
+
controversy_id = row.get("CONTROVERSY_CASE_ID", f"{isin}-{hash(headline)}")
|
|
65
|
+
if controversy_id and narrative and headline and isin and instrument_id:
|
|
66
|
+
yield ESGControversyDataDict(
|
|
67
|
+
id=controversy_id,
|
|
68
|
+
instrument_id=instrument_id,
|
|
69
|
+
headline=headline,
|
|
70
|
+
narrative=narrative,
|
|
71
|
+
source=row.get("CONTROVERSY_CASE_SOURCE", ""),
|
|
72
|
+
review=datetime.strptime(reviewed, "%Y%m%d").date() if reviewed else None,
|
|
73
|
+
initiated=datetime.strptime(initiated, "%Y%m%d").date() if initiated else None,
|
|
74
|
+
response=row.get("CONTROVERSY_CASE_COMPANY_RESPONSE", ""),
|
|
75
|
+
assessment=CONTROVERY_ASSESSMENT_MAP.get(
|
|
76
|
+
row.get("CONTROVERSY_CASE_ASSESSMENT", "Minor"), None
|
|
77
|
+
),
|
|
78
|
+
status=CONTROVERSY_STATUS_MAP.get(row.get("CONTROVERSY_CASE_STATUS", "Ongoing"), None),
|
|
79
|
+
type=CONTROVERSY_TYPE_MAP.get(row.get("CONTROVERSY_CASE_TYPE", "Structural"), None),
|
|
80
|
+
flag=CONTROVERSY_FLAG_MAP.get(row.get("CONTROVERSY_CASE_FLAG", "Green"), None),
|
|
81
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from django.db.models import Q, QuerySet
|
|
2
|
+
from wbfdm.models import Instrument
|
|
3
|
+
from wbfdm.sync.abstract import Sync
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MsciInstrumentSync(Sync[Instrument]):
|
|
7
|
+
ESG_PATH = "wbfdm.contrib.msci.dataloaders.esg.MSCIESGDataloader"
|
|
8
|
+
ESG_CONTROVERSY_PATH = "wbfdm.contrib.msci.dataloaders.esg_controversies.MSCIESGControversyDataloader"
|
|
9
|
+
|
|
10
|
+
def get_updatable_instruments(self) -> QuerySet[Instrument]:
|
|
11
|
+
return Instrument.objects.filter(
|
|
12
|
+
Q(isin__isnull=False)
|
|
13
|
+
& (Q(dl_parameters__esg__isnull=True) | Q(dl_parameters__esg_controversies__isnull=True))
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def update(self):
|
|
17
|
+
instruments = []
|
|
18
|
+
|
|
19
|
+
for instrument in self.get_updatable_instruments():
|
|
20
|
+
self._update_dl_parameters(instrument)
|
|
21
|
+
instruments.append(instrument)
|
|
22
|
+
|
|
23
|
+
if len(instruments) % 10000 == 0:
|
|
24
|
+
Instrument.objects.bulk_update(instruments, ["dl_parameters"])
|
|
25
|
+
instruments = []
|
|
26
|
+
Instrument.objects.bulk_update(instruments, ["dl_parameters"])
|
|
27
|
+
|
|
28
|
+
def update_or_create_item(self, external_id: int) -> Instrument:
|
|
29
|
+
raise NotImplementedError(f"The {__class__} is not allowed to create instruments")
|
|
30
|
+
|
|
31
|
+
def get_item(self, external_id: int) -> dict:
|
|
32
|
+
raise NotImplementedError(f"The {__class__} is not allowed to instantiate from remote resources")
|
|
33
|
+
|
|
34
|
+
def _update_dl_parameters(self, instrument) -> Instrument:
|
|
35
|
+
if isin := instrument.isin:
|
|
36
|
+
instrument.dl_parameters["esg"] = {
|
|
37
|
+
"path": self.ESG_PATH,
|
|
38
|
+
"parameters": isin,
|
|
39
|
+
}
|
|
40
|
+
instrument.dl_parameters["esg_controversies"] = {
|
|
41
|
+
"path": self.ESG_CONTROVERSY_PATH,
|
|
42
|
+
"parameters": isin,
|
|
43
|
+
}
|
|
44
|
+
return instrument
|
|
45
|
+
|
|
46
|
+
def update_item(self, instrument: Instrument) -> Instrument:
|
|
47
|
+
self._update_dl_parameters(instrument)
|
|
48
|
+
instrument.save()
|
|
49
|
+
return instrument
|
|
50
|
+
|
|
51
|
+
def trigger_partial_update(self):
|
|
52
|
+
instruments_to_update = self.get_updatable_instruments()
|
|
53
|
+
instruments_to_update_count = instruments_to_update.count()
|
|
54
|
+
if instruments_to_update_count > 30:
|
|
55
|
+
self.update()
|
|
56
|
+
else:
|
|
57
|
+
for instrument in instruments_to_update:
|
|
58
|
+
self.update_item(instrument)
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from wbcore.tests.conftest import * # type: ignore # isort:skip
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from django.core.cache import cache as cache_layer
|
|
6
|
+
from django.utils import timezone
|
|
7
|
+
from faker import Faker
|
|
8
|
+
from jwt import encode
|
|
9
|
+
from requests.exceptions import ConnectionError
|
|
10
|
+
|
|
11
|
+
from ..client import MSCIClient, is_expired
|
|
12
|
+
|
|
13
|
+
fake = Faker()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestMSCIClient:
|
|
17
|
+
@pytest.fixture()
|
|
18
|
+
def jwt(self, expiry_date: datetime):
|
|
19
|
+
payload = {"exp": int(expiry_date.timestamp()), "iat": int(timezone.now().timestamp())}
|
|
20
|
+
return encode(payload, "secret", algorithm="HS256")
|
|
21
|
+
|
|
22
|
+
@pytest.fixture()
|
|
23
|
+
def msci_client(self):
|
|
24
|
+
return MSCIClient("username", "password")
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize("expiry_date", [fake.past_datetime()])
|
|
27
|
+
def test_is_expired_true(self, expiry_date, jwt):
|
|
28
|
+
assert is_expired(jwt)
|
|
29
|
+
|
|
30
|
+
@pytest.mark.parametrize("expiry_date", [fake.future_datetime()])
|
|
31
|
+
def test_is_expired_false(self, expiry_date, jwt):
|
|
32
|
+
assert not is_expired(jwt)
|
|
33
|
+
|
|
34
|
+
def test_oauth_token_not_in_cache(self, requests_mock, msci_client):
|
|
35
|
+
access_token = fake.word()
|
|
36
|
+
expiry = fake.pyint(min_value=60)
|
|
37
|
+
requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token": access_token, "expires_in": expiry})
|
|
38
|
+
t0 = time.time()
|
|
39
|
+
res = msci_client.oauth_token
|
|
40
|
+
assert res == access_token
|
|
41
|
+
assert cache_layer.get("msci_oauth_token") == access_token
|
|
42
|
+
assert int(cache_layer._expire_info.get(":1:msci_oauth_token")) == int(t0 + expiry)
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parametrize("expiry_date", [fake.future_datetime()])
|
|
45
|
+
def test_oauth_token_in_cache_and_valid(self, requests_mock, msci_client, expiry_date, jwt):
|
|
46
|
+
requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token": fake.word()})
|
|
47
|
+
cache_layer.set("msci_oauth_token", jwt)
|
|
48
|
+
assert msci_client.oauth_token == jwt
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize("expiry_date", [fake.past_datetime()])
|
|
51
|
+
def test_oauth_token_in_cache_but_expired(self, requests_mock, msci_client, expiry_date, jwt):
|
|
52
|
+
requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token": fake.word()})
|
|
53
|
+
cache_layer.set("msci_oauth_token", jwt)
|
|
54
|
+
|
|
55
|
+
access_token = fake.word()
|
|
56
|
+
requests_mock.post(
|
|
57
|
+
MSCIClient.AUTH_SERVER_ULR, json={"access_token": access_token, "expires_in": fake.pyint(min_value=60)}
|
|
58
|
+
)
|
|
59
|
+
assert msci_client.oauth_token == access_token
|
|
60
|
+
|
|
61
|
+
def test_oauth_token_connection_error(self, requests_mock, msci_client):
|
|
62
|
+
cache_layer.clear()
|
|
63
|
+
# keyerror
|
|
64
|
+
requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token_typo": fake.word()})
|
|
65
|
+
with pytest.raises(ConnectionError):
|
|
66
|
+
t = msci_client.oauth_token # noqa
|
|
67
|
+
|
|
68
|
+
requests_mock.post(MSCIClient.AUTH_SERVER_ULR, status_code=500)
|
|
69
|
+
with pytest.raises(ConnectionError):
|
|
70
|
+
t = msci_client.oauth_token # noqa
|
|
File without changes
|
wbfdm/contrib/qa/apps.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from django.core.checks import Warning, register
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@register()
|
|
7
|
+
def check_qa_db_available(app_configs, **kwargs):
|
|
8
|
+
warnings = []
|
|
9
|
+
|
|
10
|
+
if "qa" not in settings.DATABASES:
|
|
11
|
+
warnings.append(
|
|
12
|
+
Warning(
|
|
13
|
+
"A database with the name qa needs to be installed in order to run QA",
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return warnings
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QAAppConfig(AppConfig):
|
|
21
|
+
name = "wbfdm.contrib.qa"
|
|
22
|
+
verbose_name = "Quantitative Analytics (QA)"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class QARouter:
|
|
2
|
+
"""
|
|
3
|
+
QA from Refinitiv is usually served through an external mssql, oracle or snowflake database.
|
|
4
|
+
We are going to use it in a mostly read-only way, therefore we need a DBRouter that routes
|
|
5
|
+
all database connections for models from QA to the correct database. The database needs to be
|
|
6
|
+
registered in the settings of the project like this:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
DATABASES = {
|
|
10
|
+
"default": {}, # This is the default django database
|
|
11
|
+
"qa": {}, # This is the important database. The name qa is really important here.
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
Afterwards the settings needs to declare this router explicately as one of the `DATABASE_ROUTERS`
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def db_for_read(self, model, **hints):
|
|
18
|
+
if model._meta.app_label == "qa":
|
|
19
|
+
return "qa"
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
def allow_migrate(self, db, app_label, model_name=None, **hints):
|
|
23
|
+
if app_label == "qa":
|
|
24
|
+
return False
|
|
25
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from itertools import batched # type: ignore
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
|
|
5
|
+
from django.db import connections
|
|
6
|
+
from jinjasql import JinjaSql # type: ignore
|
|
7
|
+
from wbcore.contrib.dataloader.dataloaders import Dataloader
|
|
8
|
+
from wbcore.contrib.dataloader.utils import dictfetchall
|
|
9
|
+
from wbfdm.dataloaders.protocols import AdjustmentsProtocol
|
|
10
|
+
from wbfdm.dataloaders.types import AdjustmentDataDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DatastreamAdjustmentsDataloader(AdjustmentsProtocol, Dataloader):
|
|
14
|
+
def adjustments(self, from_date: date, to_date: date) -> Iterator[AdjustmentDataDict]:
|
|
15
|
+
lookup = {k: v for k, v in self.entities.values_list("dl_parameters__adjustments__parameters", "id")}
|
|
16
|
+
|
|
17
|
+
sql = """
|
|
18
|
+
SELECT
|
|
19
|
+
InfoCode as external_identifier,
|
|
20
|
+
CONCAT(InfoCode, '_', CONVERT(DATE, AdjDate)) as id,
|
|
21
|
+
CONVERT(DATE, AdjDate) as adjustment_date,
|
|
22
|
+
CONVERT(DATE, EndAdjDate) as adjustment_end_date,
|
|
23
|
+
'qa-ds2' as source,
|
|
24
|
+
AdjFactor as adjustment_factor,
|
|
25
|
+
CumAdjFactor as cumulative_adjustment_factor
|
|
26
|
+
|
|
27
|
+
FROM Ds2Adj
|
|
28
|
+
WHERE
|
|
29
|
+
AdjType = 2
|
|
30
|
+
AND (
|
|
31
|
+
{% for instrument in instruments %}
|
|
32
|
+
InfoCode = {{instrument}} {% if not loop.last %} OR {% endif %}
|
|
33
|
+
{% endfor %}
|
|
34
|
+
)
|
|
35
|
+
{% if from_date %} AND AdjDate >= {{ from_date }} {% endif %}
|
|
36
|
+
{% if to_date %} AND AdjDate <= {{ to_date }} {% endif %}
|
|
37
|
+
|
|
38
|
+
ORDER BY AdjDate DESC
|
|
39
|
+
"""
|
|
40
|
+
for batch in batched(lookup.keys(), 1000):
|
|
41
|
+
query, bind_params = JinjaSql(param_style="format").prepare_query(
|
|
42
|
+
sql,
|
|
43
|
+
{
|
|
44
|
+
"instruments": batch,
|
|
45
|
+
"from_date": from_date,
|
|
46
|
+
"to_date": to_date,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
with connections["qa"].cursor() as cursor:
|
|
50
|
+
cursor.execute(
|
|
51
|
+
query,
|
|
52
|
+
bind_params,
|
|
53
|
+
)
|
|
54
|
+
for row in dictfetchall(cursor):
|
|
55
|
+
row["instrument_id"] = lookup[row["external_identifier"]]
|
|
56
|
+
yield row
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
|
|
4
|
+
from django.db import connections
|
|
5
|
+
from jinjasql import JinjaSql # type: ignore
|
|
6
|
+
from wbcore.contrib.dataloader.dataloaders import Dataloader
|
|
7
|
+
from wbcore.contrib.dataloader.utils import dictfetchall
|
|
8
|
+
from wbfdm.dataloaders.protocols import CorporateActionsProtocol
|
|
9
|
+
from wbfdm.dataloaders.types import CorporateActionDataDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DatastreamCorporateActionsDataloader(CorporateActionsProtocol, Dataloader):
|
|
13
|
+
def corporate_actions(
|
|
14
|
+
self,
|
|
15
|
+
from_date: date | None = None,
|
|
16
|
+
to_date: date | None = None,
|
|
17
|
+
) -> Iterator[CorporateActionDataDict]:
|
|
18
|
+
lookup = {k: v for k, v in self.entities.values_list("dl_parameters__corporate_actions__parameters", "id")}
|
|
19
|
+
|
|
20
|
+
sql = """
|
|
21
|
+
SELECT
|
|
22
|
+
InfoCode as external_identifier,
|
|
23
|
+
CONCAT(InfoCode, '_', CONVERT(DATE, EffectiveDate)) as id,
|
|
24
|
+
CONVERT(DATE, EffectiveDate) as valuation_date,
|
|
25
|
+
'qa-ds2' as source,
|
|
26
|
+
ActionTypeCode as action_code,
|
|
27
|
+
EventStatusCode as event_code,
|
|
28
|
+
NumOldShares as old_shares,
|
|
29
|
+
NumNewShares as new_shares,
|
|
30
|
+
ISOCurrCode as currency
|
|
31
|
+
|
|
32
|
+
FROM Ds2CapEvent
|
|
33
|
+
WHERE (
|
|
34
|
+
{% for instrument in instruments %}
|
|
35
|
+
InfoCode = {{instrument}} {% if not loop.last %} OR {% endif %}
|
|
36
|
+
{% endfor %}
|
|
37
|
+
)
|
|
38
|
+
{% if from_date %} AND EffectiveDate >= {{ from_date }} {% endif %}
|
|
39
|
+
{% if to_date %} AND EffectiveDate <= {{ to_date }} {% endif %}
|
|
40
|
+
|
|
41
|
+
ORDER BY EffectiveDate DESC
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
query, bind_params = JinjaSql(param_style="format").prepare_query(
|
|
45
|
+
sql,
|
|
46
|
+
{
|
|
47
|
+
"instruments": self.entities.values_list("dl_parameters__corporate_actions__parameters", flat=True),
|
|
48
|
+
"from_date": from_date,
|
|
49
|
+
"to_date": to_date,
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
with connections["qa"].cursor() as cursor:
|
|
53
|
+
cursor.execute(
|
|
54
|
+
query,
|
|
55
|
+
bind_params,
|
|
56
|
+
)
|
|
57
|
+
for row in dictfetchall(cursor):
|
|
58
|
+
row["instrument_id"] = lookup[row["external_identifier"]]
|
|
59
|
+
yield row
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from itertools import batched # type: ignore
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
|
|
5
|
+
from django.db import connections
|
|
6
|
+
from django.template.loader import get_template
|
|
7
|
+
from jinjasql import JinjaSql # type: ignore
|
|
8
|
+
from wbcore.contrib.dataloader.dataloaders import Dataloader
|
|
9
|
+
from wbcore.contrib.dataloader.utils import dictfetchall
|
|
10
|
+
from wbfdm.dataloaders.protocols import FinancialsProtocol
|
|
11
|
+
from wbfdm.dataloaders.types import FinancialDataDict
|
|
12
|
+
from wbfdm.enums import (
|
|
13
|
+
CalendarType,
|
|
14
|
+
DataType,
|
|
15
|
+
EstimateType,
|
|
16
|
+
Financial,
|
|
17
|
+
PeriodType,
|
|
18
|
+
SeriesType,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class IBESFinancialsDataloader(FinancialsProtocol, Dataloader):
|
|
23
|
+
def financials(
|
|
24
|
+
self,
|
|
25
|
+
values: list[Financial],
|
|
26
|
+
from_date: date | None = None,
|
|
27
|
+
to_date: date | None = None,
|
|
28
|
+
from_year: int | None = None,
|
|
29
|
+
to_year: int | None = None,
|
|
30
|
+
from_index: int | None = None,
|
|
31
|
+
to_index: int | None = None,
|
|
32
|
+
from_valid: date | None = None,
|
|
33
|
+
to_valid: date | None = None,
|
|
34
|
+
period_type: PeriodType = PeriodType.ANNUAL,
|
|
35
|
+
calendar_type: CalendarType = CalendarType.FISCAL,
|
|
36
|
+
series_type: SeriesType = SeriesType.COMPLETE,
|
|
37
|
+
data_type: DataType = DataType.STANDARDIZED,
|
|
38
|
+
estimate_type: EstimateType = EstimateType.VALID,
|
|
39
|
+
target_currency: str | None = None,
|
|
40
|
+
) -> Iterator[FinancialDataDict]:
|
|
41
|
+
lookup = {k: v for k, v in self.entities.values_list("dl_parameters__financials__parameters", "id")}
|
|
42
|
+
for batch in batched(lookup.keys(), 1000):
|
|
43
|
+
sql = ""
|
|
44
|
+
if series_type == SeriesType.COMPLETE:
|
|
45
|
+
sql = "qa/sql/ibes/complete.sql"
|
|
46
|
+
if calendar_type == CalendarType.CALENDAR:
|
|
47
|
+
sql = "qa/sql/ibes/calendarized.sql"
|
|
48
|
+
|
|
49
|
+
elif series_type == SeriesType.FULL_ESTIMATE:
|
|
50
|
+
sql = "qa/sql/ibes/estimates.sql"
|
|
51
|
+
|
|
52
|
+
query, bind_params = JinjaSql(param_style="format").prepare_query(
|
|
53
|
+
get_template(sql, using="jinja").template,
|
|
54
|
+
{
|
|
55
|
+
"instruments": batch,
|
|
56
|
+
"financials": [financial.value for financial in values],
|
|
57
|
+
"estimate_type": estimate_type.value,
|
|
58
|
+
"from_date": from_date,
|
|
59
|
+
"to_date": to_date,
|
|
60
|
+
"from_year": from_year,
|
|
61
|
+
"to_year": to_year,
|
|
62
|
+
"from_index": from_index,
|
|
63
|
+
"to_index": to_index,
|
|
64
|
+
"from_valid": from_valid,
|
|
65
|
+
"to_valid": to_valid,
|
|
66
|
+
"period_type": period_type.value,
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
with connections["qa"].cursor() as cursor:
|
|
70
|
+
cursor.execute(
|
|
71
|
+
query,
|
|
72
|
+
bind_params,
|
|
73
|
+
)
|
|
74
|
+
for row in dictfetchall(cursor):
|
|
75
|
+
row["instrument_id"] = lookup[row["external_identifier"]]
|
|
76
|
+
row["estimate"] = bool(row["estimate"])
|
|
77
|
+
row["value"] = row.get("value", None)
|
|
78
|
+
row["difference_pct"] = row.get("difference_pct", 0)
|
|
79
|
+
row["value_high"] = row.get("value_high", row["value"])
|
|
80
|
+
row["value_low"] = row.get("value_low", row["value"])
|
|
81
|
+
row["value_amount"] = row.get("value_amount", row["value"])
|
|
82
|
+
row["value_stdev"] = row.get("value_stdev", row["value"])
|
|
83
|
+
yield row
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
|
|
5
|
+
from django.db import connections
|
|
6
|
+
from jinjasql import JinjaSql # type: ignore
|
|
7
|
+
from wbcore.contrib.dataloader.dataloaders import Dataloader
|
|
8
|
+
from wbcore.contrib.dataloader.utils import dictfetchall
|
|
9
|
+
from wbfdm.dataloaders.protocols import MarketDataProtocol
|
|
10
|
+
from wbfdm.dataloaders.types import MarketDataDict
|
|
11
|
+
from wbfdm.enums import Frequency, MarketData
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DS2MarketData(Enum):
|
|
15
|
+
OPEN = ("Open_", None)
|
|
16
|
+
CLOSE = ("Close_", None)
|
|
17
|
+
HIGH = ("High", None)
|
|
18
|
+
LOW = ("Low", None)
|
|
19
|
+
BID = ("Bid", None)
|
|
20
|
+
ASK = ("Ask", None)
|
|
21
|
+
VWAP = ("VWAP", None)
|
|
22
|
+
VOLUME = ("Volume", None)
|
|
23
|
+
MARKET_CAPITALIZATION = ("ConsolMktVal", 1_000_000)
|
|
24
|
+
SHARES_OUTSTANDING = ("ConsolNumShrs", 1_000)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DatastreamMarketDataDataloader(MarketDataProtocol, Dataloader):
|
|
28
|
+
def market_data(
|
|
29
|
+
self,
|
|
30
|
+
values: list[MarketData] | None = [MarketData.CLOSE],
|
|
31
|
+
from_date: date | None = None,
|
|
32
|
+
to_date: date | None = None,
|
|
33
|
+
exact_date: date | None = None,
|
|
34
|
+
frequency: Frequency = Frequency.DAILY,
|
|
35
|
+
target_currency: str | None = None,
|
|
36
|
+
**kwargs,
|
|
37
|
+
) -> Iterator[MarketDataDict]:
|
|
38
|
+
"""Get market data for instruments.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
queryset (QuerySet["Instrument"]): The queryset of instruments.
|
|
42
|
+
values (list[MarketData]): List of values to include in the results.
|
|
43
|
+
from_date (date | None): The starting date for filtering prices. Defaults to None.
|
|
44
|
+
to_date (date | None): The ending date for filtering prices. Defaults to None.
|
|
45
|
+
frequency (Frequency): The frequency of the requested data
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Iterator[MarketDataDict]: An iterator of dictionaries conforming to the DailyValuationDict.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
lookup = {
|
|
52
|
+
f"{k[0]},{k[1]}": v for k, v in self.entities.values_list("dl_parameters__market_data__parameters", "id")
|
|
53
|
+
}
|
|
54
|
+
value_mapping = [(DS2MarketData[x.name].value, x.value) for x in values or []]
|
|
55
|
+
|
|
56
|
+
sql = """
|
|
57
|
+
SELECT
|
|
58
|
+
CONCAT(pricing.InfoCode, ',', ExchIntCode) as external_identifier,
|
|
59
|
+
CONCAT(pricing.InfoCode, ',', ExchIntCode, '_', CONVERT(DATE, MarketDate)) as id,
|
|
60
|
+
CONVERT(DATE, MarketDate) as valuation_date,
|
|
61
|
+
'qa-ds2' as source,
|
|
62
|
+
{% if target_currency %}
|
|
63
|
+
'{{ target_currency|sqlsafe }}' as 'currency',
|
|
64
|
+
{% else %}
|
|
65
|
+
Currency as 'currency',
|
|
66
|
+
{% endif %}
|
|
67
|
+
{% for value in values %}
|
|
68
|
+
{% if target_currency %}
|
|
69
|
+
COALESCE(fx_rate.midrate, 1) *
|
|
70
|
+
{% endif %}
|
|
71
|
+
{{ value[0][0] | sqlsafe }}{% if value[0][1] %} * {{ value[0][1] | sqlsafe }}{% endif %} as '{{ value[1] | sqlsafe }}'{% if not loop.last %}, {% endif %}
|
|
72
|
+
{% endfor %}
|
|
73
|
+
FROM vw_Ds2Pricing as pricing
|
|
74
|
+
LEFT JOIN DS2MktVal as market_val
|
|
75
|
+
ON pricing.InfoCode = market_val.InfoCode
|
|
76
|
+
AND pricing.MarketDate = market_val.ValDate
|
|
77
|
+
{% if target_currency %}
|
|
78
|
+
LEFT JOIN Ds2FxCode as fx_code
|
|
79
|
+
ON fx_code.FromCurrCode = '{{target_currency|sqlsafe}}'
|
|
80
|
+
AND fx_code.ToCurrCode = pricing.Currency
|
|
81
|
+
AND fx_code.RateTypeCode = 'SPOT'
|
|
82
|
+
LEFT JOIN Ds2FxRate as fx_rate
|
|
83
|
+
ON fx_rate.ExRateIntCode = fx_code.ExRateIntCode
|
|
84
|
+
AND fx_rate.ExRateDate = pricing.MarketDate
|
|
85
|
+
{% endif %}
|
|
86
|
+
|
|
87
|
+
WHERE (
|
|
88
|
+
{% for instrument in instruments %}
|
|
89
|
+
(pricing.InfoCode = {{instrument[0]}} AND ExchIntCode = {{instrument[1]}}) {% if not loop.last %} OR {% endif %}
|
|
90
|
+
{% endfor %}
|
|
91
|
+
)
|
|
92
|
+
AND AdjType = 2
|
|
93
|
+
{% if from_date %} AND MarketDate >= {{ from_date }} {% endif %}
|
|
94
|
+
{% if to_date %} AND MarketDate <= {{ to_date }} {% endif %}
|
|
95
|
+
{% if exact_date %} AND MarketDate = {{ exact_date }} {% endif %}
|
|
96
|
+
|
|
97
|
+
ORDER BY MarketDate DESC
|
|
98
|
+
"""
|
|
99
|
+
query, bind_params = JinjaSql(param_style="format").prepare_query(
|
|
100
|
+
sql,
|
|
101
|
+
{
|
|
102
|
+
"instruments": self.entities.values_list("dl_parameters__market_data__parameters", flat=True),
|
|
103
|
+
"values": value_mapping,
|
|
104
|
+
"from_date": from_date,
|
|
105
|
+
"to_date": to_date,
|
|
106
|
+
"exact_date": exact_date,
|
|
107
|
+
"target_currency": target_currency,
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
with connections["qa"].cursor() as cursor:
|
|
111
|
+
cursor.execute(
|
|
112
|
+
query,
|
|
113
|
+
bind_params,
|
|
114
|
+
)
|
|
115
|
+
for row in dictfetchall(cursor):
|
|
116
|
+
row["instrument_id"] = lookup[row["external_identifier"]]
|
|
117
|
+
yield row
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import Iterator
|
|
2
|
+
|
|
3
|
+
from django.db import connections
|
|
4
|
+
from jinjasql import JinjaSql # type: ignore
|
|
5
|
+
from wbcore.contrib.dataloader.dataloaders import Dataloader
|
|
6
|
+
from wbcore.contrib.dataloader.utils import dictfetchall
|
|
7
|
+
from wbfdm.dataloaders.protocols import OfficersProtocol
|
|
8
|
+
from wbfdm.dataloaders.types import OfficerDataDict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RKDOfficersDataloader(OfficersProtocol, Dataloader):
|
|
12
|
+
def officers(
|
|
13
|
+
self,
|
|
14
|
+
) -> Iterator[OfficerDataDict]:
|
|
15
|
+
lookup = {k: v for k, v in self.entities.values_list("dl_parameters__officers__parameters", "id")}
|
|
16
|
+
|
|
17
|
+
sql = """
|
|
18
|
+
SELECT
|
|
19
|
+
CONCAT(designation.Code, '-', ROW_NUMBER() OVER (ORDER BY officer.OfficerRank)) as id,
|
|
20
|
+
designation.Code as external_identifier,
|
|
21
|
+
designation.Title as position,
|
|
22
|
+
CONCAT(
|
|
23
|
+
officer.Prefix,
|
|
24
|
+
' ',
|
|
25
|
+
officer.FirstName,
|
|
26
|
+
' ',
|
|
27
|
+
officer.LastName,
|
|
28
|
+
CASE
|
|
29
|
+
WHEN officer.Suffix IS NOT NULL THEN CONCAT(', ', officer.Suffix)
|
|
30
|
+
ELSE ''
|
|
31
|
+
END
|
|
32
|
+
) as name,
|
|
33
|
+
officer.Age as age,
|
|
34
|
+
officer.Sex as sex,
|
|
35
|
+
CONVERT(DATE, designation.DesgStartDt) as start
|
|
36
|
+
FROM RKDFndCmpOffTitleChg AS designation
|
|
37
|
+
JOIN RKDFndCmpOfficer AS officer
|
|
38
|
+
ON designation.Code = officer.Code
|
|
39
|
+
AND designation.OfficerID = officer.Officerid
|
|
40
|
+
|
|
41
|
+
WHERE
|
|
42
|
+
designation.Code in (
|
|
43
|
+
{% for instrument in instruments %}
|
|
44
|
+
{{instrument}} {% if not loop.last %}, {% endif %}
|
|
45
|
+
{% endfor %})
|
|
46
|
+
AND DesgEndDt IS NULL
|
|
47
|
+
|
|
48
|
+
ORDER BY
|
|
49
|
+
officer.OfficerRank
|
|
50
|
+
"""
|
|
51
|
+
query, bind_params = JinjaSql(param_style="format").prepare_query(sql, {"instruments": lookup.keys()})
|
|
52
|
+
with connections["qa"].cursor() as cursor:
|
|
53
|
+
cursor.execute(
|
|
54
|
+
query,
|
|
55
|
+
bind_params,
|
|
56
|
+
)
|
|
57
|
+
for row in dictfetchall(cursor):
|
|
58
|
+
row["instrument_id"] = lookup[row["external_identifier"]]
|
|
59
|
+
yield row
|