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,322 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from datetime import date
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import pytest
|
|
8
|
+
from faker import Faker
|
|
9
|
+
from wbfdm.analysis.financial_analysis.utils import FinancialAnalysisResult, Loader
|
|
10
|
+
from wbfdm.dataloaders.proxies import InstrumentDataloaderProxy
|
|
11
|
+
from wbfdm.enums import Financial, MarketData
|
|
12
|
+
|
|
13
|
+
fake = Faker()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.django_db
|
|
17
|
+
class TestLoader:
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def data(self, value, year):
|
|
20
|
+
"""
|
|
21
|
+
Data Fixture for the Loader class
|
|
22
|
+
"""
|
|
23
|
+
year = int(year)
|
|
24
|
+
data = []
|
|
25
|
+
fake_key = fake.word()
|
|
26
|
+
for year_delta in [0, 1]:
|
|
27
|
+
for interim in range(1, 5):
|
|
28
|
+
data.append(
|
|
29
|
+
{
|
|
30
|
+
"year": year + year_delta,
|
|
31
|
+
"interim": interim,
|
|
32
|
+
"period_type": "Q",
|
|
33
|
+
"period_end_date": date(year, interim * 3, 1),
|
|
34
|
+
"estimate": fake.pybool(),
|
|
35
|
+
"source": "dsws",
|
|
36
|
+
"financial": value,
|
|
37
|
+
"value": fake.pyfloat(),
|
|
38
|
+
fake_key: fake.word(), # add noise key
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
# add a uncomplete year for year - 2
|
|
42
|
+
data.append(
|
|
43
|
+
{
|
|
44
|
+
"year": year - 1,
|
|
45
|
+
"interim": 1,
|
|
46
|
+
"period_type": "Q",
|
|
47
|
+
"period_end_date": date(year, 3, 1),
|
|
48
|
+
"estimate": fake.pybool(),
|
|
49
|
+
"source": "dsws",
|
|
50
|
+
"financial": value,
|
|
51
|
+
"value": fake.pyfloat(),
|
|
52
|
+
fake_key: fake.word(), # add noise key
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
@pytest.fixture()
|
|
58
|
+
def loader(self, instrument, value):
|
|
59
|
+
return Loader(instrument, [Financial(value)])
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize("value, year", [(random.choice(Financial.values()), fake.year())])
|
|
62
|
+
@patch.object(InstrumentDataloaderProxy, "financials")
|
|
63
|
+
def test_load(self, mock_fct, value, year, data, loader):
|
|
64
|
+
# Test that the load method returnn the normalized dataframe
|
|
65
|
+
|
|
66
|
+
mock_fct.return_value = data
|
|
67
|
+
pd.testing.assert_frame_equal(loader.load(), loader._normalize_df(*loader._get_base_df()), check_exact=True)
|
|
68
|
+
|
|
69
|
+
@pytest.mark.parametrize("value, year", [(random.choice(Financial.values()), fake.year())])
|
|
70
|
+
@patch.object(InstrumentDataloaderProxy, "financials")
|
|
71
|
+
def test_load_no_data(self, mock_fct, value, year, data, loader):
|
|
72
|
+
# Test that the load method returnn the normalized dataframe
|
|
73
|
+
|
|
74
|
+
mock_fct.return_value = []
|
|
75
|
+
assert loader.load().empty
|
|
76
|
+
|
|
77
|
+
@pytest.mark.parametrize("value, year", [(random.choice(Financial.values()), fake.year())])
|
|
78
|
+
@patch.object(InstrumentDataloaderProxy, "financials")
|
|
79
|
+
def test__get_base_df(self, mock_fct, value, year, data, loader):
|
|
80
|
+
year = int(year)
|
|
81
|
+
mock_fct.return_value = data
|
|
82
|
+
df, source_df = loader._get_base_df()
|
|
83
|
+
time_idx = df.index.droplevel([3, 4])
|
|
84
|
+
# Test if the _get_base_df private method returned tuple have the same index
|
|
85
|
+
pd.testing.assert_index_equal(time_idx, source_df.index)
|
|
86
|
+
|
|
87
|
+
# Test if the _get_base_df private method returned dataframe index corresponds to the expected year and interim
|
|
88
|
+
assert time_idx.tolist() == [
|
|
89
|
+
(year - 1, 1, "Q"),
|
|
90
|
+
(year, 1, "Q"),
|
|
91
|
+
(year, 2, "Q"),
|
|
92
|
+
(year, 3, "Q"),
|
|
93
|
+
(year, 4, "Q"),
|
|
94
|
+
(year + 1, 1, "Q"),
|
|
95
|
+
(year + 1, 2, "Q"),
|
|
96
|
+
(year + 1, 3, "Q"),
|
|
97
|
+
(year + 1, 4, "Q"),
|
|
98
|
+
]
|
|
99
|
+
assert source_df.unique() == "dsws"
|
|
100
|
+
assert df.columns == [value]
|
|
101
|
+
|
|
102
|
+
@pytest.mark.parametrize("market_value", [random.choice(MarketData.values())])
|
|
103
|
+
@patch.object(InstrumentDataloaderProxy, "market_data")
|
|
104
|
+
def test__annotate_market_data(self, mock_fct, instrument, market_value):
|
|
105
|
+
# test if annotation market data works and are merged properly to the initial dataframe
|
|
106
|
+
d1 = date(2021, 3, 30)
|
|
107
|
+
d2 = date(2023, 3, 31)
|
|
108
|
+
p1 = fake.pyfloat()
|
|
109
|
+
p2 = fake.pyfloat()
|
|
110
|
+
|
|
111
|
+
df = pd.DataFrame(
|
|
112
|
+
[
|
|
113
|
+
{
|
|
114
|
+
"year": d1.year,
|
|
115
|
+
"estimate": False,
|
|
116
|
+
"interim": 1,
|
|
117
|
+
"period_type": "P",
|
|
118
|
+
"period_end_date": d1,
|
|
119
|
+
"financial": "financial",
|
|
120
|
+
"value": fake.pyfloat(),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"year": d2.year,
|
|
124
|
+
"estimate": False,
|
|
125
|
+
"interim": 1,
|
|
126
|
+
"period_type": "P",
|
|
127
|
+
"period_end_date": d2,
|
|
128
|
+
"financial": "financial",
|
|
129
|
+
"value": fake.pyfloat(),
|
|
130
|
+
},
|
|
131
|
+
]
|
|
132
|
+
)
|
|
133
|
+
df["period_end_date"] = pd.to_datetime(df["period_end_date"])
|
|
134
|
+
df = df.pivot_table(
|
|
135
|
+
index=["year", "interim", "period_type", "estimate", "period_end_date"],
|
|
136
|
+
columns="financial",
|
|
137
|
+
values="value",
|
|
138
|
+
)
|
|
139
|
+
mock_fct.return_value = [{"valuation_date": d1, market_value: p1}, {"valuation_date": d2, market_value: p2}]
|
|
140
|
+
res = Loader(instrument, [])._annotate_market_data(df, [MarketData(market_value)])
|
|
141
|
+
assert res.loc[(d1.year, 1, "P", False, "2021-03-30"), market_value] == p1
|
|
142
|
+
assert res.loc[(d2.year, 1, "P", False, "2023-03-31"), market_value] == p2
|
|
143
|
+
|
|
144
|
+
@pytest.mark.parametrize("statement_value", [random.choice(Financial.values())])
|
|
145
|
+
@patch.object(InstrumentDataloaderProxy, "statements")
|
|
146
|
+
def test__annotate_statement_data(self, mock_fct, instrument, statement_value):
|
|
147
|
+
# test if annotation statement works and are merged properly to the initial dataframe
|
|
148
|
+
|
|
149
|
+
d1 = date(2021, 3, 30)
|
|
150
|
+
d2 = date(2023, 3, 31)
|
|
151
|
+
value = fake.pyfloat()
|
|
152
|
+
|
|
153
|
+
df = pd.DataFrame(
|
|
154
|
+
[
|
|
155
|
+
{
|
|
156
|
+
"year": d1.year,
|
|
157
|
+
"estimate": False,
|
|
158
|
+
"interim": 1,
|
|
159
|
+
"period_type": "P",
|
|
160
|
+
"period_end_date": d1,
|
|
161
|
+
"financial": "base",
|
|
162
|
+
"value": fake.pyfloat(),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"year": d2.year,
|
|
166
|
+
"estimate": False,
|
|
167
|
+
"interim": 1,
|
|
168
|
+
"period_type": "P",
|
|
169
|
+
"period_end_date": d2,
|
|
170
|
+
"financial": "base",
|
|
171
|
+
"value": fake.pyfloat(),
|
|
172
|
+
},
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
df["period_end_date"] = pd.to_datetime(df["period_end_date"])
|
|
176
|
+
df = df.pivot_table(
|
|
177
|
+
index=["year", "interim", "period_type", "estimate", "period_end_date"],
|
|
178
|
+
columns="financial",
|
|
179
|
+
values="value",
|
|
180
|
+
)
|
|
181
|
+
mock_fct.return_value = [
|
|
182
|
+
{
|
|
183
|
+
"year": d1.year,
|
|
184
|
+
"estimate": False,
|
|
185
|
+
"interim": 1,
|
|
186
|
+
"period_type": "P",
|
|
187
|
+
"period_end_date": d1,
|
|
188
|
+
"financial": statement_value,
|
|
189
|
+
"value": value,
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
res = Loader(instrument, [])._annotate_statement_data(df, [Financial(statement_value)])
|
|
194
|
+
assert res.loc[(d1.year, 1, "P", False, "2021-03-30"), statement_value] == value
|
|
195
|
+
assert np.isnan(res.loc[(d2.year, 1, "P", False, "2023-03-31"), statement_value])
|
|
196
|
+
|
|
197
|
+
def test__normalize_df(self, instrument):
|
|
198
|
+
d1 = date(2021, 3, 30)
|
|
199
|
+
df = pd.DataFrame(
|
|
200
|
+
[
|
|
201
|
+
{
|
|
202
|
+
"source": "dsws",
|
|
203
|
+
"year": d1.year,
|
|
204
|
+
"estimate": False,
|
|
205
|
+
"interim": 1,
|
|
206
|
+
"period_end_date": d1,
|
|
207
|
+
"period_type": "S",
|
|
208
|
+
"financial": "base",
|
|
209
|
+
"value": fake.pyfloat(),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"source": "dsws",
|
|
213
|
+
"year": d1.year,
|
|
214
|
+
"estimate": False,
|
|
215
|
+
"interim": 2,
|
|
216
|
+
"period_end_date": d1,
|
|
217
|
+
"period_type": "S",
|
|
218
|
+
"financial": "base",
|
|
219
|
+
"value": fake.pyfloat(),
|
|
220
|
+
},
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
source_df = df.source
|
|
224
|
+
df["period_end_date"] = pd.to_datetime(df["period_end_date"])
|
|
225
|
+
df = df.pivot_table(
|
|
226
|
+
index=["year", "interim", "period_type", "estimate", "period_end_date"],
|
|
227
|
+
columns="financial",
|
|
228
|
+
values="value",
|
|
229
|
+
)
|
|
230
|
+
res = Loader(instrument, [])._normalize_df(df, source_df)
|
|
231
|
+
# Test if normalize appends the missing "yearly" row and convert the interim into their verbose representation
|
|
232
|
+
assert res.index.tolist() == [
|
|
233
|
+
(d1.year, "Y"),
|
|
234
|
+
(d1.year, "S1"),
|
|
235
|
+
(d1.year, "S2"),
|
|
236
|
+
] # We test that the missing yearly row is reindexed
|
|
237
|
+
assert np.isnan(res.loc[(d1.year, "Y"), "base"]) # check that the extrapolated data is nan
|
|
238
|
+
|
|
239
|
+
def test__normalize_df_with_duplicates(self, instrument):
|
|
240
|
+
# we test if normalize detects the duplicates, remove it by taking the first and add the interim into the errors list
|
|
241
|
+
d1 = date(2021, 3, 30)
|
|
242
|
+
v1 = fake.pyfloat()
|
|
243
|
+
v2 = fake.pyfloat()
|
|
244
|
+
df = pd.DataFrame(
|
|
245
|
+
[
|
|
246
|
+
{
|
|
247
|
+
"source": "dsws",
|
|
248
|
+
"year": d1.year,
|
|
249
|
+
"estimate": False,
|
|
250
|
+
"interim": 1,
|
|
251
|
+
"period_end_date": d1,
|
|
252
|
+
"financial": "base",
|
|
253
|
+
"period_type": "P",
|
|
254
|
+
"value": v1,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"source": "dsws",
|
|
258
|
+
"year": d1.year,
|
|
259
|
+
"estimate": False,
|
|
260
|
+
"interim": 1,
|
|
261
|
+
"period_end_date": date(2021, 1, 30),
|
|
262
|
+
"financial": "base",
|
|
263
|
+
"period_type": "P",
|
|
264
|
+
"value": v2,
|
|
265
|
+
},
|
|
266
|
+
]
|
|
267
|
+
)
|
|
268
|
+
source_df = df[["year", "interim", "source"]].groupby(["year", "interim"]).first().source
|
|
269
|
+
df["period_end_date"] = pd.to_datetime(df["period_end_date"])
|
|
270
|
+
df = df.pivot_table(
|
|
271
|
+
index=["year", "interim", "period_type", "estimate", "period_end_date"],
|
|
272
|
+
columns="financial",
|
|
273
|
+
values="value",
|
|
274
|
+
)
|
|
275
|
+
loader = Loader(instrument, [])
|
|
276
|
+
res = loader._normalize_df(df, source_df)
|
|
277
|
+
assert res.index.tolist() == [(d1.year, "Y"), (d1.year, "P1")]
|
|
278
|
+
assert (
|
|
279
|
+
res.loc[(d1.year, "P1"), "base"] == v2
|
|
280
|
+
) # check that the retained value is the first one (with ordered index)
|
|
281
|
+
assert loader.errors["duplicated_interims"] == [f"{d1.year} Interim P1"]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class TestFinancialAnalysisResult:
|
|
285
|
+
@pytest.fixture()
|
|
286
|
+
def df(self, year, value):
|
|
287
|
+
df = pd.DataFrame(
|
|
288
|
+
[
|
|
289
|
+
{
|
|
290
|
+
"year": year,
|
|
291
|
+
"estimate": False,
|
|
292
|
+
"interim": "P1",
|
|
293
|
+
"period_end_date": date(int(year), 3, 1),
|
|
294
|
+
"financial": value,
|
|
295
|
+
"value": fake.pyfloat(),
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"year": year,
|
|
299
|
+
"estimate": True,
|
|
300
|
+
"interim": "Y",
|
|
301
|
+
"period_end_date": date(int(year), 3, 1),
|
|
302
|
+
"financial": value,
|
|
303
|
+
"value": fake.pyfloat(),
|
|
304
|
+
},
|
|
305
|
+
]
|
|
306
|
+
)
|
|
307
|
+
df = df.pivot_table(
|
|
308
|
+
index=["year", "interim", "estimate", "period_end_date"], columns="financial", values="value"
|
|
309
|
+
)
|
|
310
|
+
return df.reset_index(level=[2, 3])
|
|
311
|
+
|
|
312
|
+
@pytest.mark.parametrize("value, year", [(random.choice(Financial.values()), fake.year())])
|
|
313
|
+
def test_class(self, value, year, df):
|
|
314
|
+
"""
|
|
315
|
+
Test the basic esthetic transform (Transposing + group key addition + convertion of multi index to representation
|
|
316
|
+
"""
|
|
317
|
+
res = FinancialAnalysisResult(df)
|
|
318
|
+
assert res.formatted_df.loc[0, f"{year}-P1"] == df.loc[(year, "P1"), value]
|
|
319
|
+
assert res.formatted_df.loc[0, f"{year}-Y"] == df.loc[(year, "Y"), value]
|
|
320
|
+
assert res.formatted_df.loc[0, "financial"] == Financial.name_mapping()[value]
|
|
321
|
+
assert res.formatted_df.loc[0, "_group_key"] == value
|
|
322
|
+
assert res.estimated_mapping == {f"{year}-P1": False, f"{year}-Y": True}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import pytest
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from wbcore.contrib.currency.factories import CurrencyFXRatesFactory
|
|
6
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
7
|
+
from wbfdm.analysis.esg.enums import ESGAggregation
|
|
8
|
+
from wbfdm.analysis.esg.esg_analysis import DataLoader
|
|
9
|
+
|
|
10
|
+
fake = Faker()
|
|
11
|
+
|
|
12
|
+
WEIGHTS = [0.15, 0.05, 0.2, 0.1, 0.5]
|
|
13
|
+
ENTERPRISE_VALUE_INCLUDED_CASH = [1e6, 2e6, 3e6, 4e6, 1e6]
|
|
14
|
+
CURRENT_VALUE_INVESTMENT_FACTOR = [1.0, 1.0, 1.0, 1.2, 0.2]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.django_db
|
|
18
|
+
class TestESGDataLoader:
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def dataloader(self, weekday):
|
|
21
|
+
index = [1, 2, 3, 4, 5]
|
|
22
|
+
|
|
23
|
+
weight = pd.Series(WEIGHTS, index=index)
|
|
24
|
+
esg_data = pd.Series(np.random.randint(1, 100, size=4), index=[1, 2, 3, 4]) # we remove one data point
|
|
25
|
+
total_value_fx_usd = pd.Series(np.random.randint(1, 100, size=5)) # we remove one data point
|
|
26
|
+
CurrencyFXRatesFactory.create(currency__key="EUR", date=weekday)
|
|
27
|
+
|
|
28
|
+
dataloader = DataLoader(weight, esg_data, weekday, total_value_fx_usd=total_value_fx_usd)
|
|
29
|
+
dataloader.__dict__["enterprise_value_included_cash"] = pd.Series(ENTERPRISE_VALUE_INCLUDED_CASH, index=index)
|
|
30
|
+
dataloader.__dict__["current_value_investment_factor"] = pd.Series(
|
|
31
|
+
CURRENT_VALUE_INVESTMENT_FACTOR, index=index
|
|
32
|
+
)
|
|
33
|
+
dataloader.__dict__["nace_section_code"] = pd.Series(["C", "C", "J", "M", "M"], index=index)
|
|
34
|
+
return dataloader
|
|
35
|
+
|
|
36
|
+
def test_dataloader(self, dataloader):
|
|
37
|
+
assert dataloader.weights_in_coverage.to_dict() == {1: 0.15, 2: 0.05, 3: 0.2, 4: 0.1}
|
|
38
|
+
|
|
39
|
+
def test_get_percentage_sum(self, dataloader):
|
|
40
|
+
dataloader.esg_data = pd.Series(["Yes", "No Evidence", "No", "No", "No"], index=[1, 2, 3, 4, 5])
|
|
41
|
+
res = dataloader._get_percentage_sum("No Evidence")
|
|
42
|
+
assert res.tolist() == [0.0, 0.05, 0.0, 0.0]
|
|
43
|
+
|
|
44
|
+
def test_get_weighted_avg_normalized(self, dataloader):
|
|
45
|
+
res = dataloader._get_weighted_avg_normalized()
|
|
46
|
+
log = dataloader.intermediary_logs[0]
|
|
47
|
+
|
|
48
|
+
# ensure the intermediary log data is set properly
|
|
49
|
+
assert log.series.name == "weights_normalized"
|
|
50
|
+
assert log.series.to_dict() == {1: 0.3, 2: 0.1, 3: 0.4, 4: 0.2}
|
|
51
|
+
assert res.to_dict() == {
|
|
52
|
+
1: 0.3 * dataloader.esg_data.iloc[0],
|
|
53
|
+
2: 0.1 * dataloader.esg_data.iloc[1],
|
|
54
|
+
3: 0.4 * dataloader.esg_data.iloc[2],
|
|
55
|
+
4: 0.2 * dataloader.esg_data.iloc[3],
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def test_get_weighted_avg_normalized_per_category(self, dataloader):
|
|
59
|
+
res = dataloader._get_weighted_avg_normalized_per_category()
|
|
60
|
+
intermediary_logs = list(filter(lambda x: not x.series.empty, dataloader.intermediary_logs))
|
|
61
|
+
log_c = intermediary_logs[0]
|
|
62
|
+
log_j = intermediary_logs[1]
|
|
63
|
+
log_m = intermediary_logs[2]
|
|
64
|
+
weights_normalized = [
|
|
65
|
+
0.15 / (0.15 + 0.05),
|
|
66
|
+
0.05 / (0.15 + 0.05),
|
|
67
|
+
0.2 / 0.2,
|
|
68
|
+
0.1 / (0.1 + 0.5),
|
|
69
|
+
0.5 / (0.1 + 0.5),
|
|
70
|
+
]
|
|
71
|
+
# ensure the intermediary log data is set properly
|
|
72
|
+
assert log_c.series.name == "weights_normalized_sector_c"
|
|
73
|
+
assert log_c.series.to_dict() == {
|
|
74
|
+
1: weights_normalized[0],
|
|
75
|
+
2: weights_normalized[1],
|
|
76
|
+
# 3: weights_normalized[2],
|
|
77
|
+
# 4: weights_normalized[3],
|
|
78
|
+
# 5: weights_normalized[4],
|
|
79
|
+
}
|
|
80
|
+
assert log_j.series.name == "weights_normalized_sector_j"
|
|
81
|
+
assert log_j.series.to_dict() == {
|
|
82
|
+
3: weights_normalized[2],
|
|
83
|
+
}
|
|
84
|
+
assert log_m.series.name == "weights_normalized_sector_m"
|
|
85
|
+
assert log_m.series.to_dict() == {
|
|
86
|
+
4: weights_normalized[3],
|
|
87
|
+
5: weights_normalized[4],
|
|
88
|
+
}
|
|
89
|
+
assert res.to_dict() == {
|
|
90
|
+
1: weights_normalized[0] * dataloader.esg_data.iloc[0],
|
|
91
|
+
2: weights_normalized[1] * dataloader.esg_data.iloc[1],
|
|
92
|
+
3: weights_normalized[2] * dataloader.esg_data.iloc[2],
|
|
93
|
+
4: weights_normalized[3] * dataloader.esg_data.iloc[3],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def test_get_investor_allocation(self, dataloader):
|
|
97
|
+
exposure = pd.Series(np.random.rand(5), index=dataloader.weights.index)
|
|
98
|
+
res = dataloader._get_investor_allocation(exposure)
|
|
99
|
+
fx_rate = CurrencyFXRates.objects.get(currency__key="EUR", date=dataloader.val_date)
|
|
100
|
+
exposure_eur = exposure * float(fx_rate.value)
|
|
101
|
+
|
|
102
|
+
assert dataloader.intermediary_logs[0].series.to_dict() == exposure.to_dict()
|
|
103
|
+
assert (
|
|
104
|
+
dataloader.intermediary_logs[1].series.to_dict() == exposure_eur.to_dict()
|
|
105
|
+
) # exposure converted into eur
|
|
106
|
+
|
|
107
|
+
exposure_with_cvi = [exposure_eur.iloc[i] * CURRENT_VALUE_INVESTMENT_FACTOR[i] for i in range(5)]
|
|
108
|
+
assert dataloader.intermediary_logs[2].series.tolist() == exposure_with_cvi
|
|
109
|
+
|
|
110
|
+
rebase_factor = sum(exposure_with_cvi[:4]) / sum(exposure_with_cvi)
|
|
111
|
+
|
|
112
|
+
exposure_normalized = [exposure_with_cvi[i] / rebase_factor for i in range(4)]
|
|
113
|
+
assert dataloader.intermediary_logs[3].series.tolist() == exposure_normalized
|
|
114
|
+
|
|
115
|
+
attribution_factor = [exposure_normalized[i] / ENTERPRISE_VALUE_INCLUDED_CASH[i] for i in range(4)]
|
|
116
|
+
assert dataloader.intermediary_logs[4].series.dropna().tolist() == attribution_factor
|
|
117
|
+
|
|
118
|
+
assert res.to_dict() == {
|
|
119
|
+
1: dataloader.esg_data.iloc[0] * attribution_factor[0],
|
|
120
|
+
2: dataloader.esg_data.iloc[1] * attribution_factor[1],
|
|
121
|
+
3: dataloader.esg_data.iloc[2] * attribution_factor[2],
|
|
122
|
+
4: dataloader.esg_data.iloc[3] * attribution_factor[3],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def test_compute_percentage_sum(self, dataloader):
|
|
126
|
+
pd.testing.assert_series_equal(
|
|
127
|
+
dataloader.compute(ESGAggregation.FOSSIL_FUEL_EXPOSURE),
|
|
128
|
+
dataloader._get_percentage_sum("Yes"),
|
|
129
|
+
check_exact=True,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def test_compute_weighted_avg_normalized(self, dataloader):
|
|
133
|
+
res = dataloader.compute(ESGAggregation.GHG_INTENSITY_OF_COMPAGNIES)
|
|
134
|
+
assert res.empty is not None
|
|
135
|
+
pd.testing.assert_series_equal(
|
|
136
|
+
dataloader.compute(ESGAggregation.GHG_INTENSITY_OF_COMPAGNIES),
|
|
137
|
+
dataloader._get_weighted_avg_normalized(),
|
|
138
|
+
check_exact=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def test_compute_weighted_avg_normalized_per_category(self, dataloader):
|
|
142
|
+
res = dataloader.compute(ESGAggregation.ENERGY_CONSUMPTION_INTENSITY_PER_SECTOR)
|
|
143
|
+
assert res.empty is not None
|
|
144
|
+
pd.testing.assert_series_equal(res, dataloader._get_weighted_avg_normalized_per_category(), check_exact=True)
|
|
145
|
+
|
|
146
|
+
def test_compute_investor_allocation(self, dataloader):
|
|
147
|
+
res = dataloader.compute(ESGAggregation.GHG_EMISSIONS_SCOPE_1)
|
|
148
|
+
assert res.empty is not None
|
|
149
|
+
pd.testing.assert_series_equal(
|
|
150
|
+
res, dataloader._get_investor_allocation(dataloader.total_value_fx_usd), check_exact=True
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
res = dataloader.compute(ESGAggregation.GHG_EMISSIONS_SCOPE_1)
|
|
154
|
+
assert res.empty is not None
|
|
155
|
+
pd.testing.assert_series_equal(
|
|
156
|
+
dataloader.compute(ESGAggregation.EMISSIONS_TO_WATER),
|
|
157
|
+
dataloader._get_investor_allocation_per_million(dataloader.weights * 1000000),
|
|
158
|
+
check_exact=True,
|
|
159
|
+
)
|
wbfdm/tests/conftest.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from wbcore.tests.conftest import * # isort:skip
|
|
2
|
+
from datetime import date
|
|
3
|
+
|
|
4
|
+
from django.apps import apps
|
|
5
|
+
from django.db.models.signals import pre_migrate
|
|
6
|
+
from faker import Faker
|
|
7
|
+
from pandas.tseries.offsets import BDay
|
|
8
|
+
from pytest_factoryboy import register
|
|
9
|
+
from wbcore.contrib.authentication.factories import (
|
|
10
|
+
InternalUserFactory,
|
|
11
|
+
SuperUserFactory,
|
|
12
|
+
UserFactory,
|
|
13
|
+
)
|
|
14
|
+
from wbcore.contrib.currency.factories import CurrencyFactory, CurrencyFXRatesFactory
|
|
15
|
+
from wbcore.contrib.directory.factories.entries import (
|
|
16
|
+
CompanyFactory,
|
|
17
|
+
CompanyTypeFactory,
|
|
18
|
+
CustomerStatusFactory,
|
|
19
|
+
EntryFactory,
|
|
20
|
+
PersonFactory,
|
|
21
|
+
)
|
|
22
|
+
from wbcore.contrib.geography.factories import (
|
|
23
|
+
CityFactory,
|
|
24
|
+
ContinentFactory,
|
|
25
|
+
CountryFactory,
|
|
26
|
+
StateFactory,
|
|
27
|
+
)
|
|
28
|
+
from wbcore.contrib.geography.tests.signals import app_pre_migration
|
|
29
|
+
from wbcore.contrib.io.factories import (
|
|
30
|
+
CrontabScheduleFactory,
|
|
31
|
+
DataBackendFactory,
|
|
32
|
+
ImportSourceFactory,
|
|
33
|
+
ParserHandlerFactory,
|
|
34
|
+
ProviderFactory,
|
|
35
|
+
SourceFactory,
|
|
36
|
+
)
|
|
37
|
+
from wbfdm.factories import (
|
|
38
|
+
ClassificationFactory,
|
|
39
|
+
ClassificationGroupFactory,
|
|
40
|
+
ExchangeFactory,
|
|
41
|
+
InstrumentClassificationThroughModelFactory,
|
|
42
|
+
InstrumentFactory,
|
|
43
|
+
InstrumentFavoriteGroupFactory,
|
|
44
|
+
InstrumentListFactory,
|
|
45
|
+
InstrumentPriceFactory,
|
|
46
|
+
InstrumentTypeFactory,
|
|
47
|
+
OptionAggregateFactory,
|
|
48
|
+
OptionFactory,
|
|
49
|
+
ParentClassificationFactory,
|
|
50
|
+
RelatedInstrumentThroughModelFactory,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
fake = Faker()
|
|
54
|
+
register(ImportSourceFactory)
|
|
55
|
+
register(DataBackendFactory)
|
|
56
|
+
register(ProviderFactory)
|
|
57
|
+
register(SourceFactory)
|
|
58
|
+
register(ParserHandlerFactory)
|
|
59
|
+
register(CrontabScheduleFactory)
|
|
60
|
+
|
|
61
|
+
register(ClassificationFactory)
|
|
62
|
+
register(InstrumentClassificationThroughModelFactory)
|
|
63
|
+
register(ParentClassificationFactory)
|
|
64
|
+
register(ClassificationGroupFactory)
|
|
65
|
+
register(ExchangeFactory)
|
|
66
|
+
register(InstrumentFactory)
|
|
67
|
+
register(InstrumentTypeFactory)
|
|
68
|
+
register(InstrumentPriceFactory)
|
|
69
|
+
register(InstrumentFavoriteGroupFactory)
|
|
70
|
+
register(RelatedInstrumentThroughModelFactory)
|
|
71
|
+
register(CurrencyFXRatesFactory)
|
|
72
|
+
register(InstrumentListFactory)
|
|
73
|
+
register(OptionFactory)
|
|
74
|
+
register(OptionAggregateFactory)
|
|
75
|
+
|
|
76
|
+
register(CurrencyFactory)
|
|
77
|
+
register(CityFactory)
|
|
78
|
+
register(StateFactory)
|
|
79
|
+
register(CountryFactory)
|
|
80
|
+
register(ContinentFactory)
|
|
81
|
+
|
|
82
|
+
register(CompanyFactory)
|
|
83
|
+
register(PersonFactory)
|
|
84
|
+
register(InternalUserFactory)
|
|
85
|
+
register(EntryFactory)
|
|
86
|
+
register(CustomerStatusFactory)
|
|
87
|
+
register(CompanyTypeFactory)
|
|
88
|
+
|
|
89
|
+
register(UserFactory)
|
|
90
|
+
register(SuperUserFactory, "superuser")
|
|
91
|
+
|
|
92
|
+
pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("wbfdm"))
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from django.core.cache import cache as cache_layer
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from wbfdm.dataloaders.cache import Cache
|
|
5
|
+
|
|
6
|
+
fake = Faker()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestCache:
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def cache(self):
|
|
12
|
+
return Cache()
|
|
13
|
+
|
|
14
|
+
def test_initialize(self, cache):
|
|
15
|
+
cache.missing_keys = ["a"]
|
|
16
|
+
cache.missing_ids = ("a",)
|
|
17
|
+
cache.missing_symbols = ("a",)
|
|
18
|
+
cache.initialize([1], ["a", "b"])
|
|
19
|
+
|
|
20
|
+
# ensure initialize reset the missing attributes set
|
|
21
|
+
assert cache.missing_keys == list()
|
|
22
|
+
assert cache.missing_ids == set()
|
|
23
|
+
assert cache.missing_symbols == set()
|
|
24
|
+
assert cache.id_symbol_pairs == [("1", "a"), ("1", "b")]
|
|
25
|
+
|
|
26
|
+
cache.initialize([1, 2], ["a"])
|
|
27
|
+
assert cache.id_symbol_pairs == [("1", "a"), ("2", "a")]
|
|
28
|
+
|
|
29
|
+
def test__get_cache_key(self, cache):
|
|
30
|
+
assert cache._get_cache_key("a", "b") == "a_b"
|
|
31
|
+
|
|
32
|
+
def test__deserialize_cache(self, cache):
|
|
33
|
+
assert cache._deserialize_cache("1", "symbol", "value") == {
|
|
34
|
+
cache.identifier_key: 1,
|
|
35
|
+
cache.symbol_key: "symbol",
|
|
36
|
+
cache.value_key: "value",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def test_fetch_from_cache(self, cache):
|
|
40
|
+
cache.initialize([1], ["a"])
|
|
41
|
+
assert list(cache.fetch_from_cache()) == list()
|
|
42
|
+
assert cache.missing_keys == ["1_a"]
|
|
43
|
+
assert cache.missing_symbols == {"a"}
|
|
44
|
+
assert cache.missing_ids == {"1"}
|
|
45
|
+
|
|
46
|
+
cache.initialize([1], ["a"])
|
|
47
|
+
assert list(cache.fetch_from_cache()) == []
|
|
48
|
+
assert cache.missing_keys == ["1_a"]
|
|
49
|
+
|
|
50
|
+
value = fake.pyfloat()
|
|
51
|
+
cache_layer.set("1_a", value)
|
|
52
|
+
cache.initialize([1], ["a"])
|
|
53
|
+
assert list(cache.fetch_from_cache()) == [{"instrument_id": 1, "factor_code": "a", "value": value}]
|
|
54
|
+
assert cache.missing_keys == list()
|
|
55
|
+
|
|
56
|
+
cache_layer.set("1_a", None)
|
|
57
|
+
cache.initialize([1], ["a"])
|
|
58
|
+
assert list(cache.fetch_from_cache()) == list()
|
|
59
|
+
assert cache.missing_keys == list()
|
|
60
|
+
|
|
61
|
+
def test_write(self, cache):
|
|
62
|
+
cache.missing_keys = ["1_a"]
|
|
63
|
+
value = fake.pyfloat()
|
|
64
|
+
cache.write({"instrument_id": 1, "factor_code": "a", "value": value})
|
|
65
|
+
assert cache.missing_keys == list()
|
|
66
|
+
assert cache_layer.get("1_a") == value
|
|
67
|
+
|
|
68
|
+
def test_close(self, cache):
|
|
69
|
+
cache.missing_keys = ["3_c"]
|
|
70
|
+
sentinel = object()
|
|
71
|
+
assert cache_layer.get("3_c", sentinel) == sentinel
|
|
72
|
+
cache.close()
|
|
73
|
+
assert cache_layer.get("3_c", sentinel) is None
|
|
File without changes
|