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,71 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from django.core.exceptions import ValidationError
|
|
5
|
+
from django.db import models
|
|
6
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
7
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
8
|
+
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
9
|
+
|
|
10
|
+
MAX_NB_DATES_BEFORE_HISTORICAL_IMPORT = 3
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InstrumentPriceImportHandler(ImportExportHandler):
|
|
14
|
+
MODEL_APP_LABEL: str = "wbfdm.InstrumentPrice"
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
19
|
+
self.updated_instruments = set()
|
|
20
|
+
|
|
21
|
+
def _deserialize(self, data):
|
|
22
|
+
if data.get("instrument", None) is None or data.get("date", None) is None:
|
|
23
|
+
raise DeserializationError("Instrument or Date is empty")
|
|
24
|
+
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
|
25
|
+
|
|
26
|
+
instrument = self.instrument_handler.process_object(data["instrument"], only_security=False, read_only=True)[0]
|
|
27
|
+
if not instrument:
|
|
28
|
+
raise DeserializationError("Instrument couldn't be found with given data")
|
|
29
|
+
self.updated_instruments.add(instrument)
|
|
30
|
+
data["instrument"] = instrument
|
|
31
|
+
if data.get("net_value", None) is None:
|
|
32
|
+
# casted_instrument = instrument.get_casted_instrument()
|
|
33
|
+
# # we try to find the primary field among the data to set the net value from it
|
|
34
|
+
# if primary_field_value := data.pop(casted_instrument.primary_field, None):
|
|
35
|
+
# data["net_value"] = primary_field_value
|
|
36
|
+
# # in a last case, we try to find the close value and set it as the net value
|
|
37
|
+
if close := data.pop("close", None):
|
|
38
|
+
data["net_value"] = close
|
|
39
|
+
else:
|
|
40
|
+
if data.get("gross_value", None) is None:
|
|
41
|
+
data["gross_value"] = data["net_value"]
|
|
42
|
+
|
|
43
|
+
# Backward compatibility with refinitiv parser.
|
|
44
|
+
for forbbiden_field in ["close", "price_index", "yield_redemption", "net_return", "offered_rate"]:
|
|
45
|
+
data.pop(forbbiden_field, None)
|
|
46
|
+
|
|
47
|
+
# We do this to ensure an invalid number won't make the import fails
|
|
48
|
+
for k, v in data.items():
|
|
49
|
+
if v and (field := self.model._meta.get_field(k)) and hasattr(field, "max_digits"):
|
|
50
|
+
try:
|
|
51
|
+
field.clean(value=str(round(v, field.decimal_places)), model_instance=None)
|
|
52
|
+
except ValidationError:
|
|
53
|
+
data[k] = None
|
|
54
|
+
|
|
55
|
+
if data.get("net_value", None) is None:
|
|
56
|
+
raise DeserializationError("Net value not set.")
|
|
57
|
+
|
|
58
|
+
def _get_instance(self, data, history=None, **kwargs):
|
|
59
|
+
self.import_source.log += f"\nParameter: Instrument={data['instrument']} Date={data['date']}"
|
|
60
|
+
price = data["instrument"].prices.filter(date=data["date"], calculated=False)
|
|
61
|
+
if price.exists():
|
|
62
|
+
return price.first()
|
|
63
|
+
|
|
64
|
+
def _post_processing_objects(
|
|
65
|
+
self,
|
|
66
|
+
created_objs: List[models.Model],
|
|
67
|
+
modified_objs: List[models.Model],
|
|
68
|
+
unmodified_objs: List[models.Model],
|
|
69
|
+
):
|
|
70
|
+
for instrument in self.updated_instruments:
|
|
71
|
+
instrument.update_last_valuation_date()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
6
|
+
from django.db import models
|
|
7
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
8
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
9
|
+
|
|
10
|
+
from .instrument import InstrumentImportHandler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OptionAggregateImportHandler(ImportExportHandler):
|
|
14
|
+
MODEL_APP_LABEL: str = "wbfdm.OptionAggregate"
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
19
|
+
|
|
20
|
+
def _deserialize(self, data):
|
|
21
|
+
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
|
22
|
+
instrument = self.instrument_handler.process_object(data["instrument"], read_only=True)[0]
|
|
23
|
+
if not instrument:
|
|
24
|
+
raise DeserializationError("Instrument couldn't be found with given data")
|
|
25
|
+
data["instrument"] = instrument
|
|
26
|
+
|
|
27
|
+
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
28
|
+
with suppress(ObjectDoesNotExist):
|
|
29
|
+
return self.model.objects.filter(instrument=data["instrument"], date=data["date"], type=data["type"])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class OptionImportHandler(ImportExportHandler):
|
|
33
|
+
MODEL_APP_LABEL: str = "wbfdm.Option"
|
|
34
|
+
|
|
35
|
+
def __init__(self, *args, **kwargs):
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
38
|
+
|
|
39
|
+
def _deserialize(self, data):
|
|
40
|
+
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
|
41
|
+
data["expiration_date"] = datetime.strptime(data["expiration_date"], "%Y-%m-%d").date()
|
|
42
|
+
instrument = self.instrument_handler.process_object(data["instrument"], read_only=True)[0]
|
|
43
|
+
if not instrument:
|
|
44
|
+
raise DeserializationError("Instrument couldn't be found with given data")
|
|
45
|
+
data["instrument"] = instrument
|
|
46
|
+
|
|
47
|
+
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
48
|
+
with suppress(ObjectDoesNotExist):
|
|
49
|
+
return self.model.objects.filter(
|
|
50
|
+
instrument=data["instrument"],
|
|
51
|
+
contract_identifier=data["contract_identifier"],
|
|
52
|
+
date=data["date"],
|
|
53
|
+
type=data["type"],
|
|
54
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
from django.db import models
|
|
5
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
6
|
+
from wbcore.contrib.io.imports import ImportExportHandler
|
|
7
|
+
|
|
8
|
+
from .instrument import InstrumentImportHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DealImportHandler(ImportExportHandler):
|
|
12
|
+
MODEL_APP_LABEL: str = "wbfdm.Deal"
|
|
13
|
+
|
|
14
|
+
def __init__(self, *args, **kwargs):
|
|
15
|
+
super().__init__(*args, **kwargs)
|
|
16
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
17
|
+
|
|
18
|
+
def _deserialize(self, data):
|
|
19
|
+
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
|
20
|
+
equity = self.instrument_handler.process_object(
|
|
21
|
+
{**data["equity"], "instrument_type": "private_equity"}, read_only=True
|
|
22
|
+
)[0]
|
|
23
|
+
if not equity:
|
|
24
|
+
raise DeserializationError("Private Equity couldn't be found with given data")
|
|
25
|
+
data["equity"] = equity
|
|
26
|
+
|
|
27
|
+
if investors := data.pop("investors", []):
|
|
28
|
+
data["investors"] = [
|
|
29
|
+
self.instrument_handler.process_object(investor_data, read_only=True)[0] for investor_data in investors
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
33
|
+
if external_id := data.get("external_id", None):
|
|
34
|
+
return self.model.objects.filter(external_id=external_id).first()
|
|
35
|
+
qs = self.model.objects.filter(
|
|
36
|
+
equity=data["equity"], date=data["date"], transaction_amount=data["transaction_amount"]
|
|
37
|
+
)
|
|
38
|
+
if qs.count() == 1:
|
|
39
|
+
return qs.first()
|
|
40
|
+
|
|
41
|
+
def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
|
|
42
|
+
investors = data.pop("investors", None)
|
|
43
|
+
obj = self.model.objects.create(
|
|
44
|
+
**data,
|
|
45
|
+
import_source=self.import_source,
|
|
46
|
+
)
|
|
47
|
+
if investors:
|
|
48
|
+
obj.investors.set([i for i in investors if i])
|
|
49
|
+
return obj
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from wbfdm.models import Deal
|
|
4
|
+
|
|
5
|
+
COLUMNS_MAP = {
|
|
6
|
+
"dealDate": "date",
|
|
7
|
+
"fundedOrg": "equity__provider_id",
|
|
8
|
+
"dealSizeInMillions": "transaction_amount",
|
|
9
|
+
"investors": "investors",
|
|
10
|
+
"fundingRound": "funding_round",
|
|
11
|
+
"fundingRoundCategory": "funding_round_category",
|
|
12
|
+
"valuationInMillions": "valuation",
|
|
13
|
+
"valuationIsEstimate": "valuation_estimated",
|
|
14
|
+
"valuationSourceType": "valuation_source_type",
|
|
15
|
+
"valuationSourceUrls": "valuation_media_mention_source_urls",
|
|
16
|
+
"dealId": "external_id",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse(import_source):
|
|
21
|
+
def _parse_investors(investors):
|
|
22
|
+
return [{"provider_id": investor["orgId"]} for investor in investors]
|
|
23
|
+
|
|
24
|
+
data = []
|
|
25
|
+
df = pd.read_json(import_source.file, orient="records")
|
|
26
|
+
|
|
27
|
+
df = df.rename(columns=COLUMNS_MAP)
|
|
28
|
+
df["equity__provider_id"] = df["equity__provider_id"].apply(lambda x: x["orgId"])
|
|
29
|
+
df["type"] = Deal.Types.DEAL
|
|
30
|
+
if not df.empty:
|
|
31
|
+
df["investors"] = df.investors.apply(lambda x: _parse_investors(x))
|
|
32
|
+
df["valuation"] *= 1e6
|
|
33
|
+
df["transaction_amount"] *= 1e6
|
|
34
|
+
df = df.drop(columns=df.columns.difference(COLUMNS_MAP.values()))
|
|
35
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
36
|
+
df = df[~df["transaction_amount"].isnull()]
|
|
37
|
+
data.extend(df.to_dict("records"))
|
|
38
|
+
|
|
39
|
+
return {"data": data}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from wbcore.contrib.geography.models import Geography
|
|
4
|
+
from wbfdm.models import ClassificationGroup
|
|
5
|
+
|
|
6
|
+
FIELDS_MAP = {
|
|
7
|
+
"orgName": "name",
|
|
8
|
+
"url": "primary_url",
|
|
9
|
+
"description": "description",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse(import_source):
|
|
14
|
+
cbinsight_group = ClassificationGroup.objects.get_or_create(name="CBinsights", max_depth=2)[0]
|
|
15
|
+
content = json.load(import_source.file)
|
|
16
|
+
|
|
17
|
+
data = []
|
|
18
|
+
for org_data in content:
|
|
19
|
+
d = {
|
|
20
|
+
"provider_id": org_data["orgId"],
|
|
21
|
+
"description": org_data["description"],
|
|
22
|
+
"name": org_data["orgName"],
|
|
23
|
+
"primary_url": org_data["url"],
|
|
24
|
+
"instrument_type": "private_equity",
|
|
25
|
+
}
|
|
26
|
+
if summary := org_data.get("orgSummary", None):
|
|
27
|
+
if additional_urls := summary.get("additionalUrls", None):
|
|
28
|
+
d["additional_urls"] = additional_urls
|
|
29
|
+
if alternative_names := summary.get("aliases", None):
|
|
30
|
+
d["alternative_names"] = alternative_names
|
|
31
|
+
d["founded_year"] = summary["foundedYear"]
|
|
32
|
+
|
|
33
|
+
if (country_name := summary.get("country")) and (
|
|
34
|
+
country := Geography.countries.get_by_name(country_name.strip())
|
|
35
|
+
):
|
|
36
|
+
d["country"] = country.id
|
|
37
|
+
|
|
38
|
+
if city_name := summary.get("city", None):
|
|
39
|
+
# We comment this out because looking up the city at import time drastically decrease speed
|
|
40
|
+
# if city := Geography.cities.get_by_name(city_name.strip(), parent__parent=country):
|
|
41
|
+
# d["headquarter_city"] = city.id
|
|
42
|
+
if (postal_code := summary.get("postalCode", None)) and (street := summary.get("street", None)):
|
|
43
|
+
d["headquarter_address"] = f"{street}, {postal_code} {city_name}"
|
|
44
|
+
|
|
45
|
+
if sector_id := summary.get("sectorId", None):
|
|
46
|
+
try:
|
|
47
|
+
code_aggregated = f"{int(sector_id):03}"
|
|
48
|
+
if industry_id := summary.get("industryId", None):
|
|
49
|
+
code_aggregated += f"{int(industry_id):03}"
|
|
50
|
+
if subindustry_id := summary.get("subindustryId", None):
|
|
51
|
+
code_aggregated += f"{int(subindustry_id):03}"
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
d["classifications"] = [{"code_aggregated": code_aggregated, "group": cbinsight_group.id}]
|
|
55
|
+
data.append(d)
|
|
56
|
+
return {"data": data}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
from wbfdm.models import Instrument
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse(import_source):
|
|
9
|
+
content = json.load(import_source.file)
|
|
10
|
+
data = []
|
|
11
|
+
equity_content_type_id = ContentType.objects.get_for_model(Instrument).id
|
|
12
|
+
for org_data in content:
|
|
13
|
+
if (revenue_data := org_data.get("orgRevenue", None)) and not (df := pd.DataFrame(revenue_data)).empty:
|
|
14
|
+
df = df[["revenueFiscalYear", "revenueMin", "revenueMax"]]
|
|
15
|
+
df["revenue"] = (df.revenueMin + df.revenueMax) * 1e6 / 2
|
|
16
|
+
df = df[["revenue", "revenueFiscalYear"]].rename(
|
|
17
|
+
columns={"revenueFiscalYear": "period__period_year", "revenue": "revenue"}
|
|
18
|
+
)
|
|
19
|
+
df = df.groupby("period__period_year").mean().reset_index()
|
|
20
|
+
df.period__period_year = df.period__period_year.astype(int)
|
|
21
|
+
df["period__period_type"] = "FiscalPeriod.PeriodTypeChoice.ANNUAL" # TODO REFACTORING
|
|
22
|
+
df["period__period_interim"] = True
|
|
23
|
+
df["period__period_end_date"] = df.period__period_year.apply(lambda x: f"{x}-12-31")
|
|
24
|
+
df["instrument__provider_id"] = org_data["orgId"]
|
|
25
|
+
df["instrument__content_type"] = equity_content_type_id
|
|
26
|
+
data.extend(df.to_dict("records"))
|
|
27
|
+
elif (
|
|
28
|
+
(kpi_data := org_data.get("orgKPIs", None))
|
|
29
|
+
and (last_revenue_max := kpi_data.get("latestRevenueMax", None))
|
|
30
|
+
and (last_revenue_min := kpi_data.get("latestRevenueMin", None))
|
|
31
|
+
and (last_revenue_year := kpi_data.get("latestRevenueFiscalYear", None))
|
|
32
|
+
):
|
|
33
|
+
fiscal_year = int(last_revenue_year)
|
|
34
|
+
data.append(
|
|
35
|
+
{
|
|
36
|
+
"revenue": (last_revenue_max + last_revenue_min) / 2,
|
|
37
|
+
"period__period_type": "FiscalPeriod.PeriodTypeChoice.ANNUAL", # TODO REFACTORING,
|
|
38
|
+
"period__period_interim": True,
|
|
39
|
+
"period__period_year": fiscal_year,
|
|
40
|
+
"period__period_end_date": f"{fiscal_year}-12-31",
|
|
41
|
+
"instrument__provider_id": org_data["orgId"],
|
|
42
|
+
"instrument__content_type": equity_content_type_id,
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
return {"data": data}
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from wbfdm.import_export.backends.refinitiv.daily_fundamental import DEFAULT_MAPPING
|
|
2
|
+
|
|
3
|
+
from .utils import parse_daily_fundamental_data
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse(import_source):
|
|
7
|
+
return parse_daily_fundamental_data(import_source, DEFAULT_MAPPING, extra_normalization_map={"free_cash": 1000})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from wbfdm.import_export.backends.refinitiv.fundamental import DEFAULT_MAPPING
|
|
2
|
+
|
|
3
|
+
from .utils import parse_periodic_fundamental_data
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse(import_source):
|
|
7
|
+
return parse_periodic_fundamental_data(
|
|
8
|
+
import_source, DEFAULT_MAPPING, extra_normalization_map={"company_tax_rate": 0.01}
|
|
9
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from wbfdm.import_export.backends.refinitiv.instrument import DEFAULT_MAPPING
|
|
4
|
+
from wbfdm.models import ClassificationGroup
|
|
5
|
+
|
|
6
|
+
from .utils import get_country, get_exchange
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_instrument_type(_type):
|
|
10
|
+
if _type == "BD":
|
|
11
|
+
return "bond"
|
|
12
|
+
elif _type in ["EQ", "ADR", "ET", "GDR", "INVT", "CF"]:
|
|
13
|
+
return "equity"
|
|
14
|
+
elif _type in ["CF"]:
|
|
15
|
+
return "close_ended_fund"
|
|
16
|
+
elif _type == "EQIND":
|
|
17
|
+
return "index"
|
|
18
|
+
elif _type == "OP":
|
|
19
|
+
return "option"
|
|
20
|
+
elif _type == "SWAPS":
|
|
21
|
+
return "swaps"
|
|
22
|
+
elif _type == "CMD":
|
|
23
|
+
return "commodity"
|
|
24
|
+
elif _type == "INT":
|
|
25
|
+
return "interest_rate_derivative"
|
|
26
|
+
elif _type == "FT":
|
|
27
|
+
return "future"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def parse(import_source):
|
|
31
|
+
# Read Json as dataframe
|
|
32
|
+
df = pd.read_json(import_source.file, orient="records")
|
|
33
|
+
# Sanitize df and rename columns to model knowledge's
|
|
34
|
+
data = list()
|
|
35
|
+
if not df.empty:
|
|
36
|
+
df = (
|
|
37
|
+
df.replace({"NA": None})
|
|
38
|
+
.rename(columns={**DEFAULT_MAPPING, "Instrument": "instrument"})
|
|
39
|
+
.set_index("instrument")
|
|
40
|
+
)
|
|
41
|
+
df = df.drop(columns=df.columns.difference(DEFAULT_MAPPING.values()))
|
|
42
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
43
|
+
df["ticker"] = df["ticker"].astype(str)
|
|
44
|
+
df = df.dropna(how="all", subset=df.columns)
|
|
45
|
+
df = df.groupby(level=0, axis=1).first()
|
|
46
|
+
df["inception_date"] = pd.to_datetime(df["inception_date"], format="%Y%m%d", errors="coerce").dt.strftime(
|
|
47
|
+
"%Y-%m-%d"
|
|
48
|
+
)
|
|
49
|
+
df = df.replace([np.inf, -np.inf, np.nan, pd.NaT], None).replace({"None": None}).dropna(how="all")
|
|
50
|
+
for row in df.to_dict("records"):
|
|
51
|
+
exchanges = []
|
|
52
|
+
if exchanges_str := row.pop("exchanges", None):
|
|
53
|
+
exchanges = [get_exchange(e) for e in exchanges_str.split(" ")]
|
|
54
|
+
if exchange := row.pop("exchange", None):
|
|
55
|
+
exchanges.append(get_exchange(exchange))
|
|
56
|
+
row["instrument_type"] = get_instrument_type(row.get("instrument_type", None))
|
|
57
|
+
if ticker := row.get("ticker", None):
|
|
58
|
+
row["ticker"] = ticker.replace("'", "").split("-")[0]
|
|
59
|
+
|
|
60
|
+
row["exchanges"] = exchanges
|
|
61
|
+
row["country"] = get_country(row.get("country", None))
|
|
62
|
+
classifications = []
|
|
63
|
+
if gics_classification := row.pop("gics_classification", None):
|
|
64
|
+
classifications.append(
|
|
65
|
+
{
|
|
66
|
+
"code_aggregated": gics_classification,
|
|
67
|
+
"group": ClassificationGroup.objects.get(is_primary=True).id,
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
if trbc_classification := row.pop("trbc_classification", None):
|
|
71
|
+
group, created = ClassificationGroup.objects.get_or_create(name="TRBC")
|
|
72
|
+
classifications.append({"code_aggregated": trbc_classification, "group": group.id})
|
|
73
|
+
row["classification"] = classifications
|
|
74
|
+
data.append({k: v for k, v in row.items() if v})
|
|
75
|
+
return {"data": data}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from wbfdm.import_export.backends.refinitiv.instrument_price import DEFAULT_MAPPING
|
|
4
|
+
|
|
5
|
+
from .utils import _clean_and_return_dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse(import_source):
|
|
9
|
+
# Read Json as dataframe
|
|
10
|
+
df = pd.read_json(import_source.file, orient="records")
|
|
11
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
12
|
+
# Sanitize df and rename columns to model knowledge's
|
|
13
|
+
df = (
|
|
14
|
+
df.replace({"NA": None})
|
|
15
|
+
.rename(columns=DEFAULT_MAPPING)
|
|
16
|
+
.rename(columns={"Instrument": "instrument", "Dates": "date"})
|
|
17
|
+
)
|
|
18
|
+
# Sanitize
|
|
19
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
20
|
+
df = df.dropna(how="all", subset=df.columns.difference(["instrument"]))
|
|
21
|
+
|
|
22
|
+
df["date"] = pd.to_datetime(df["date"], utc=True, unit="ms")
|
|
23
|
+
df = df.sort_values(by="date", ascending=True)
|
|
24
|
+
df["date"] = df["date"].dt.strftime("%Y-%m-%d")
|
|
25
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
26
|
+
return {"data": _clean_and_return_dict(df)}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from wbcore.contrib.geography.models import Geography
|
|
6
|
+
|
|
7
|
+
REPORT_TYPE_MAP = {1: "Q", 2: "6M", 3: "4M", 4: "Y"}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_exchange(code):
|
|
11
|
+
return {"mic_code": code}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_country(code):
|
|
15
|
+
country = Geography.countries.filter(code_2=code).first()
|
|
16
|
+
return country.id if country else None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _clean_and_return_dict(df, extra_normalization_map=None):
|
|
20
|
+
# df = df.groupby(df.columns, axis=1).sum()
|
|
21
|
+
if extra_normalization_map:
|
|
22
|
+
for col, den in extra_normalization_map.items():
|
|
23
|
+
if col in df.columns:
|
|
24
|
+
df[col] *= den
|
|
25
|
+
if percent_fields_columns := list(filter(lambda x: "margin" in x, df.columns)):
|
|
26
|
+
df[percent_fields_columns] = (
|
|
27
|
+
df[percent_fields_columns] / 100
|
|
28
|
+
) # margin/percent type of data are given in base 100 by refinitiv
|
|
29
|
+
df["instrument"] = df["instrument"].str.replace("^<|>", "", regex=True)
|
|
30
|
+
df = df.rename(columns={"instrument": "instrument__provider_id"})
|
|
31
|
+
# df["instrument__refinitiv_identifier_code"] = df["instrument__provider_id"] # We add this for backward compatibility reason
|
|
32
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
33
|
+
return df.to_dict("records")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_daily_fundamental_data(import_source, default_mapping, extra_normalization_map=None):
|
|
37
|
+
df = pd.read_json(import_source.file, orient="records")
|
|
38
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
39
|
+
mapping = {**default_mapping, "Instrument": "instrument", "Dates": "date"}
|
|
40
|
+
df = df.rename(columns=mapping).dropna(how="all", axis=1)
|
|
41
|
+
df = df.drop(columns=df.columns.difference(mapping.values()))
|
|
42
|
+
data = list()
|
|
43
|
+
if not df.empty:
|
|
44
|
+
df["date"] = pd.to_datetime(df["date"], utc=True, unit="ms")
|
|
45
|
+
df = df.sort_values(by="date", ascending=True)
|
|
46
|
+
df.date = df.date.dt.strftime("%Y-%m-%d")
|
|
47
|
+
data = _clean_and_return_dict(df, extra_normalization_map=extra_normalization_map)
|
|
48
|
+
return {"data": data}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_periodic_fundamental_data(import_source, default_mapping, extra_normalization_map=None):
|
|
52
|
+
def _get_fiscal_period(_date, frequency):
|
|
53
|
+
fiscal_period = 0
|
|
54
|
+
for index, p in enumerate(
|
|
55
|
+
pd.period_range(start=date(_date.year, 1, 1), end=date(_date.year, 12, 31), freq=frequency)
|
|
56
|
+
):
|
|
57
|
+
if _date >= p.start_time.date() and _date <= p.end_time.date():
|
|
58
|
+
fiscal_period = index + 1
|
|
59
|
+
return fiscal_period
|
|
60
|
+
|
|
61
|
+
df = pd.read_json(import_source.file, orient="records")
|
|
62
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
63
|
+
mapping = {**default_mapping, "Instrument": "instrument", "Dates": "date"}
|
|
64
|
+
df = df.rename(columns=mapping).dropna(how="all", axis=1)
|
|
65
|
+
|
|
66
|
+
df = df.drop(columns=df.columns.difference([*mapping.values(), "period__period_interim"]))
|
|
67
|
+
df = df.dropna(
|
|
68
|
+
how="all",
|
|
69
|
+
subset=df.columns.difference(
|
|
70
|
+
["instrument", "date", "period__period_interim", "period__period_end_date", "period__period_type"]
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
df = df.dropna(
|
|
74
|
+
how="any",
|
|
75
|
+
subset=df.columns.intersection(["instrument", "date", "period__period_end_date", "period__period_type"]),
|
|
76
|
+
)
|
|
77
|
+
data = list()
|
|
78
|
+
if not df.empty:
|
|
79
|
+
df["date"] = pd.to_datetime(df["date"], utc=True, unit="ms")
|
|
80
|
+
df["period__period_end_date"] = pd.to_datetime(df["period__period_end_date"], utc=True)
|
|
81
|
+
df["period__period_year"] = df.date.dt.year
|
|
82
|
+
df["period__period_type"] = df["period__period_type"].map(REPORT_TYPE_MAP)
|
|
83
|
+
df["period__period_type"] = df["period__period_type"].fillna("Q") # Assuming empty period type is quaterly
|
|
84
|
+
df["period__period_index"] = df[["date", "period__period_type"]].apply(
|
|
85
|
+
lambda x: _get_fiscal_period(x["date"].date(), x["period__period_type"]), axis=1
|
|
86
|
+
)
|
|
87
|
+
del df["date"]
|
|
88
|
+
|
|
89
|
+
df["period__period_end_date"] = df["period__period_end_date"] + pd.offsets.MonthEnd(0)
|
|
90
|
+
df["period__period_end_date"] = df["period__period_end_date"].dt.strftime("%Y-%m-%d")
|
|
91
|
+
df = df.sort_values(by="period__period_end_date")
|
|
92
|
+
|
|
93
|
+
if "employee_count" in df.columns:
|
|
94
|
+
df["employee_count"] = df["employee_count"].bfill()
|
|
95
|
+
data = _clean_and_return_dict(df, extra_normalization_map=extra_normalization_map)
|
|
96
|
+
return {"data": data}
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from import_export.widgets import ManyToManyWidget
|
|
2
|
+
from wbfdm.models import Classification, ClassificationGroup
|
|
3
|
+
from wbfdm.preferences import get_default_classification_group
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ClassificationManyToManyWidget(ManyToManyWidget):
|
|
7
|
+
model = Classification
|
|
8
|
+
field = "code_aggregated"
|
|
9
|
+
|
|
10
|
+
def __init__(self, separator=None, field="code_aggregated", primary_classification_group=False, **kwargs):
|
|
11
|
+
super().__init__(Classification, separator=separator, field=field, **kwargs)
|
|
12
|
+
self.primary_classification_group = primary_classification_group
|
|
13
|
+
|
|
14
|
+
def clean(self, value, row=None, **kwargs):
|
|
15
|
+
if self.primary_classification_group:
|
|
16
|
+
primary_classification_group = ClassificationGroup.objects.get(is_primary=True)
|
|
17
|
+
else:
|
|
18
|
+
primary_classification_group = get_default_classification_group()
|
|
19
|
+
queryset = Classification.objects.filter(
|
|
20
|
+
**{f"{self.field}__icontains": value, "group": primary_classification_group}
|
|
21
|
+
)
|
|
22
|
+
if queryset.count() == 1:
|
|
23
|
+
return queryset
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from import_export import fields
|
|
2
|
+
from import_export.widgets import ForeignKeyWidget
|
|
3
|
+
from wbcore.contrib.io.resources import FilterModelResource
|
|
4
|
+
from wbfdm.models import Instrument, InstrumentPrice
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InstrumentPriceExportResource(FilterModelResource):
|
|
8
|
+
"""
|
|
9
|
+
Instrument Price Resource class to use to export instrument price from the viewset
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
instrument = fields.Field(
|
|
13
|
+
column_name="instrument",
|
|
14
|
+
attribute="instrument",
|
|
15
|
+
widget=ForeignKeyWidget(Instrument, field="isin"),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
class Meta:
|
|
19
|
+
fields = (
|
|
20
|
+
"date",
|
|
21
|
+
"net_value",
|
|
22
|
+
"gross_value",
|
|
23
|
+
"market_capitalization",
|
|
24
|
+
"sharpe_ratio",
|
|
25
|
+
"correlation",
|
|
26
|
+
"beta",
|
|
27
|
+
"outstanding_shares_consolidated",
|
|
28
|
+
"volume",
|
|
29
|
+
"volume_50d",
|
|
30
|
+
"instrument",
|
|
31
|
+
)
|
|
32
|
+
export_order = fields
|
|
33
|
+
model = InstrumentPrice
|