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,265 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import date, timedelta
|
|
3
|
+
from typing import Any, Generator, Iterable
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from dateutil.relativedelta import relativedelta
|
|
8
|
+
from django.db.models import Avg, BooleanField, DateField, F, QuerySet
|
|
9
|
+
from wbfdm.models import Instrument, InstrumentPrice, RelatedInstrumentThroughModel
|
|
10
|
+
|
|
11
|
+
from ..decorators import register
|
|
12
|
+
from ..dto import Metric, MetricField, MetricKey
|
|
13
|
+
from .base import BaseDataloader, InstrumentMetricBaseBackend
|
|
14
|
+
|
|
15
|
+
PERFORMANCE_METRIC = MetricKey(
|
|
16
|
+
key="performance",
|
|
17
|
+
label="Performance",
|
|
18
|
+
subfields=[
|
|
19
|
+
MetricField(key="daily", label="Daily", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg),
|
|
20
|
+
MetricField(key="weekly", label="Weekly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg),
|
|
21
|
+
MetricField(
|
|
22
|
+
key="monthly", label="Monthly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
|
|
23
|
+
),
|
|
24
|
+
MetricField(
|
|
25
|
+
key="quarterly", label="Quarterly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
|
|
26
|
+
),
|
|
27
|
+
MetricField(key="yearly", label="Yearly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg),
|
|
28
|
+
MetricField(
|
|
29
|
+
key="week_to_date", label="WTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
|
|
30
|
+
),
|
|
31
|
+
MetricField(
|
|
32
|
+
key="month_to_date", label="MTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
|
|
33
|
+
),
|
|
34
|
+
MetricField(
|
|
35
|
+
key="quarter_to_date", label="QTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
|
|
36
|
+
),
|
|
37
|
+
MetricField(
|
|
38
|
+
key="year_to_date", label="YTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
|
|
39
|
+
),
|
|
40
|
+
MetricField(
|
|
41
|
+
key="previous_week_to_date",
|
|
42
|
+
label="Previous Week",
|
|
43
|
+
serializer_kwargs={"percent": True, "precision": 4},
|
|
44
|
+
aggregate=Avg,
|
|
45
|
+
),
|
|
46
|
+
MetricField(
|
|
47
|
+
key="previous_month_to_date",
|
|
48
|
+
label="Previous Month",
|
|
49
|
+
serializer_kwargs={"percent": True, "precision": 4},
|
|
50
|
+
aggregate=Avg,
|
|
51
|
+
),
|
|
52
|
+
MetricField(
|
|
53
|
+
key="previous_quarter_to_date",
|
|
54
|
+
label="Previous Quarter",
|
|
55
|
+
serializer_kwargs={"percent": True, "precision": 4},
|
|
56
|
+
aggregate=Avg,
|
|
57
|
+
),
|
|
58
|
+
MetricField(
|
|
59
|
+
key="previous_year_to_date",
|
|
60
|
+
label="Previous Year",
|
|
61
|
+
serializer_kwargs={"percent": True, "precision": 4},
|
|
62
|
+
aggregate=Avg,
|
|
63
|
+
),
|
|
64
|
+
MetricField(
|
|
65
|
+
key="inception", label="Inception", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
|
|
66
|
+
),
|
|
67
|
+
],
|
|
68
|
+
extra_subfields=[
|
|
69
|
+
MetricField(
|
|
70
|
+
key="is_estimated",
|
|
71
|
+
label="Estimated",
|
|
72
|
+
help_text="True if the performance used a estimated price",
|
|
73
|
+
field_type=BooleanField,
|
|
74
|
+
aggregate=None,
|
|
75
|
+
),
|
|
76
|
+
MetricField(
|
|
77
|
+
key="date",
|
|
78
|
+
label="Performance Date",
|
|
79
|
+
help_text="The date at which the performances were computed",
|
|
80
|
+
field_type=DateField,
|
|
81
|
+
aggregate=None,
|
|
82
|
+
),
|
|
83
|
+
],
|
|
84
|
+
additional_prefixes=["benchmark", "peer"],
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
PERFORMANCE_METRIC_USD = MetricKey(
|
|
88
|
+
key="performance_usd",
|
|
89
|
+
label=PERFORMANCE_METRIC.label,
|
|
90
|
+
subfields=PERFORMANCE_METRIC.subfields,
|
|
91
|
+
additional_prefixes=PERFORMANCE_METRIC.additional_prefixes,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Dataloader(BaseDataloader):
|
|
96
|
+
METRIC_KEY = "performance"
|
|
97
|
+
|
|
98
|
+
PERFORMANCE_MAP = {
|
|
99
|
+
"weekly": 7,
|
|
100
|
+
"monthly": 30,
|
|
101
|
+
"quarterly": 120,
|
|
102
|
+
"yearly": 365,
|
|
103
|
+
"daily": "B",
|
|
104
|
+
"week_to_date": "W-FRI",
|
|
105
|
+
"month_to_date": "BME",
|
|
106
|
+
"quarter_to_date": "BQE",
|
|
107
|
+
"year_to_date": "BYE",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
def __init__(self, *args, **kwargs):
|
|
111
|
+
super().__init__(*args, **kwargs)
|
|
112
|
+
self.aggregate_callback = (
|
|
113
|
+
lambda df: df.min() if df.name == "date" else df.mean()
|
|
114
|
+
) # we need to not sum the "date" column otherwise pandas crashes
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def get_performance_date_map(cls, pivot_date) -> dict[str, date]:
|
|
118
|
+
pivot_date = pivot_date + timedelta(days=1) - pd.tseries.offsets.BDay(1)
|
|
119
|
+
return {
|
|
120
|
+
"weekly": (pivot_date - relativedelta(weeks=1) + timedelta(days=1) - pd.tseries.offsets.BDay(1)).date(),
|
|
121
|
+
"monthly": (pivot_date - relativedelta(months=1) + timedelta(days=1) - pd.tseries.offsets.BDay(1)).date(),
|
|
122
|
+
"quarterly": (
|
|
123
|
+
pivot_date - relativedelta(months=3) + timedelta(days=1) - pd.tseries.offsets.BDay(1)
|
|
124
|
+
).date(),
|
|
125
|
+
"yearly": (pivot_date - relativedelta(years=1) + timedelta(days=1) - pd.tseries.offsets.BDay(1)).date(),
|
|
126
|
+
"daily": (pivot_date - pd.tseries.offsets.BDay(1)).date(),
|
|
127
|
+
"week_to_date": (pivot_date - pd.tseries.offsets.Week(1, weekday=4)).date(),
|
|
128
|
+
"month_to_date": (pivot_date - pd.tseries.offsets.BMonthEnd(1)).date(),
|
|
129
|
+
"quarter_to_date": (pivot_date - pd.tseries.offsets.BQuarterEnd(1)).date(),
|
|
130
|
+
"year_to_date": (pivot_date - pd.tseries.offsets.BYearEnd(1)).date(),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
def get_data(self) -> Iterable[tuple[Instrument, pd.Series, pd.Series]]:
|
|
134
|
+
"""
|
|
135
|
+
Helper method to return the instrument prices as a pandas Series
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
instrument: The instrument to get the prices from
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
a tuple of the prices as Series and the calculated mask (as series as well)
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
qs = (
|
|
145
|
+
InstrumentPrice.objects.filter(instrument__in=self.basket_objects, date__lte=self.val_date)
|
|
146
|
+
.annotate_base_data()
|
|
147
|
+
.annotate(fx_rate=self.fx_rate_expression, price=F("net_value") / F("fx_rate"))
|
|
148
|
+
)
|
|
149
|
+
fields = ["instrument", "date", "price", "fx_rate", "calculated"]
|
|
150
|
+
instruments_map = {i.id: i for i in self.basket_objects}
|
|
151
|
+
|
|
152
|
+
if self.min_date:
|
|
153
|
+
qs = qs.filter(date__gte=self.min_date)
|
|
154
|
+
else:
|
|
155
|
+
qs = qs.filter(date__gte=F("instrument__inception_date"))
|
|
156
|
+
recs = qs.values_list(*fields)
|
|
157
|
+
df = (
|
|
158
|
+
pd.DataFrame.from_records(recs, columns=fields)
|
|
159
|
+
.sort_values(by="calculated")
|
|
160
|
+
.groupby(["instrument", "date"])
|
|
161
|
+
.agg("first")
|
|
162
|
+
.sort_index()
|
|
163
|
+
)
|
|
164
|
+
for instrument_id, dff in df.groupby(level=0):
|
|
165
|
+
dff = dff.droplevel(0)
|
|
166
|
+
dff = dff.reindex(pd.date_range(dff.index.min(), dff.index.max()), method="ffill")
|
|
167
|
+
dff.index = pd.to_datetime(dff.index).date
|
|
168
|
+
yield instruments_map[instrument_id], dff["price"].astype(float), dff["calculated"]
|
|
169
|
+
|
|
170
|
+
def _compute(self) -> dict[str, float]:
|
|
171
|
+
"""
|
|
172
|
+
Compute the performance metrics for all the PERFORMANCE_MAP keys. If the basket is constituted of multiple instrument, take the average of each performance
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
The metrics as dictionary
|
|
176
|
+
"""
|
|
177
|
+
res = {}
|
|
178
|
+
if self.val_date:
|
|
179
|
+
agg_metrics = []
|
|
180
|
+
is_estimated = False
|
|
181
|
+
for _, prices_df, calculated_df in self.get_data():
|
|
182
|
+
if not prices_df.empty and not calculated_df.empty:
|
|
183
|
+
metrics = {}
|
|
184
|
+
is_estimated = is_estimated or bool(calculated_df.iloc[-1])
|
|
185
|
+
for performance, start_date in self.get_performance_date_map(self.val_date).items():
|
|
186
|
+
with suppress(KeyError):
|
|
187
|
+
if start_price := prices_df.loc[start_date]:
|
|
188
|
+
metrics[performance] = round(prices_df.loc[self.val_date] / start_price - 1, 6)
|
|
189
|
+
previous_start_date = self.get_performance_date_map(start_date)[performance]
|
|
190
|
+
if previous_start_price := prices_df.loc[previous_start_date]:
|
|
191
|
+
metrics[f"previous_{performance}"] = round(
|
|
192
|
+
prices_df.loc[start_date] / previous_start_price - 1, 6
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if not prices_df.empty and prices_df.iloc[0]:
|
|
196
|
+
metrics["inception"] = round(float(prices_df.iloc[-1] / prices_df.iloc[0] - 1), 6)
|
|
197
|
+
agg_metrics.append(metrics)
|
|
198
|
+
res = (
|
|
199
|
+
pd.DataFrame(agg_metrics).astype(float).mean(axis=0).replace([np.inf, -np.inf, np.nan], None).to_dict()
|
|
200
|
+
)
|
|
201
|
+
res["is_estimated"] = is_estimated
|
|
202
|
+
return res
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@register(move_first=True)
|
|
206
|
+
class InstrumentPerformanceMetricBackend(InstrumentMetricBaseBackend):
|
|
207
|
+
performance = PERFORMANCE_METRIC
|
|
208
|
+
keys = [PERFORMANCE_METRIC]
|
|
209
|
+
|
|
210
|
+
def get_related_instrument_relationships(self, basket) -> QuerySet[RelatedInstrumentThroughModel]:
|
|
211
|
+
if issubclass(basket.__class__, Instrument):
|
|
212
|
+
return RelatedInstrumentThroughModel.objects.filter(instrument=basket)
|
|
213
|
+
return RelatedInstrumentThroughModel.objects.none()
|
|
214
|
+
|
|
215
|
+
def compute_metrics(self, basket: Instrument) -> Generator[Metric, None, None]:
|
|
216
|
+
val_date = self._get_valid_date(basket)
|
|
217
|
+
metrics = Dataloader(basket, val_date, target_currency_key=self.TARGET_CURRENCY_KEY).compute()
|
|
218
|
+
instrument_relationships = self.get_related_instrument_relationships(basket)
|
|
219
|
+
if instrument_relationships.exists():
|
|
220
|
+
for related_type in [
|
|
221
|
+
RelatedInstrumentThroughModel.RelatedTypeChoices.BENCHMARK,
|
|
222
|
+
RelatedInstrumentThroughModel.RelatedTypeChoices.PEER,
|
|
223
|
+
]:
|
|
224
|
+
type_metrics = []
|
|
225
|
+
for rel in instrument_relationships.filter(related_type=related_type):
|
|
226
|
+
type_metrics.append(
|
|
227
|
+
Dataloader(
|
|
228
|
+
rel.related_instrument, val_date, target_currency_key=self.TARGET_CURRENCY_KEY
|
|
229
|
+
).compute()
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
type_metrics = pd.DataFrame(type_metrics).mean(axis=0).round(6)
|
|
233
|
+
for subfield in self.performance.subfields:
|
|
234
|
+
if (base_value := metrics.get(subfield.key)) and (type_value := type_metrics.get(subfield.key)):
|
|
235
|
+
metrics[f"{related_type.value.lower()}_{subfield.key}"] = round(base_value - type_value, 6)
|
|
236
|
+
metrics["date"] = val_date
|
|
237
|
+
yield Metric(
|
|
238
|
+
metrics=metrics,
|
|
239
|
+
basket_id=basket.id,
|
|
240
|
+
basket_content_type_id=self.content_type.id,
|
|
241
|
+
key=self.performance.key,
|
|
242
|
+
date=None,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def get_serializer_field_attr(self, metric_field: MetricField) -> dict[str, Any]:
|
|
246
|
+
attrs = super().get_serializer_field_attr(metric_field)
|
|
247
|
+
pivot_date = self.val_date
|
|
248
|
+
if not pivot_date:
|
|
249
|
+
pivot_date = (date.today() - pd.tseries.offsets.BDay(1)).date()
|
|
250
|
+
if "previous" in metric_field.key:
|
|
251
|
+
pivot_date = Dataloader.get_performance_date_map(pivot_date)[metric_field.key.replace("previous_", "")]
|
|
252
|
+
|
|
253
|
+
with suppress(KeyError):
|
|
254
|
+
start_date = Dataloader.get_performance_date_map(pivot_date)[metric_field.key.replace("previous_", "")]
|
|
255
|
+
attrs[
|
|
256
|
+
"help_text"
|
|
257
|
+
] = f"The {metric_field.label} performance is computed from {start_date:%Y-%m-%d} to {pivot_date:%Y-%m-%d}"
|
|
258
|
+
return attrs
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@register(move_first=True)
|
|
262
|
+
class InstrumentPerformanceUSDMetricBackend(InstrumentPerformanceMetricBackend):
|
|
263
|
+
performance = PERFORMANCE_METRIC_USD
|
|
264
|
+
keys = [PERFORMANCE_METRIC_USD]
|
|
265
|
+
TARGET_CURRENCY_KEY = "USD"
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Generator
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from django.db.models import Avg, Sum
|
|
7
|
+
from wbcore.serializers.fields.number import DisplayMode
|
|
8
|
+
from wbfdm.enums import Financial, PeriodType, SeriesType
|
|
9
|
+
from wbfdm.models import Instrument, InstrumentPrice
|
|
10
|
+
|
|
11
|
+
from ..decorators import register
|
|
12
|
+
from ..dto import Metric, MetricField, MetricKey
|
|
13
|
+
from ..exceptions import MetricInvalidParameterException
|
|
14
|
+
from .base import BaseDataloader, InstrumentMetricBaseBackend
|
|
15
|
+
|
|
16
|
+
STATISTICS_METRIC = MetricKey(
|
|
17
|
+
key="statistic",
|
|
18
|
+
label="Statistic",
|
|
19
|
+
subfields=[
|
|
20
|
+
MetricField(
|
|
21
|
+
key="revenue_y_1",
|
|
22
|
+
label="Revenue Y-1",
|
|
23
|
+
aggregate=Sum,
|
|
24
|
+
list_display_kwargs={"show": "open"},
|
|
25
|
+
decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
|
|
26
|
+
serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
|
|
27
|
+
),
|
|
28
|
+
MetricField(
|
|
29
|
+
key="revenue_y0",
|
|
30
|
+
label="Revenue Y0",
|
|
31
|
+
aggregate=Sum,
|
|
32
|
+
decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
|
|
33
|
+
serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
|
|
34
|
+
),
|
|
35
|
+
MetricField(
|
|
36
|
+
key="revenue_y1",
|
|
37
|
+
label="Revenue Y1",
|
|
38
|
+
aggregate=Sum,
|
|
39
|
+
list_display_kwargs={"show": "open"},
|
|
40
|
+
decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
|
|
41
|
+
serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
|
|
42
|
+
),
|
|
43
|
+
MetricField(
|
|
44
|
+
key="market_capitalization",
|
|
45
|
+
label="Market Capitalization",
|
|
46
|
+
aggregate=Sum,
|
|
47
|
+
decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
|
|
48
|
+
serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
|
|
49
|
+
),
|
|
50
|
+
MetricField(
|
|
51
|
+
key="price",
|
|
52
|
+
label="Price",
|
|
53
|
+
aggregate=Avg,
|
|
54
|
+
decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
|
|
55
|
+
),
|
|
56
|
+
MetricField(
|
|
57
|
+
key="volume_50d",
|
|
58
|
+
label="Volume 50D",
|
|
59
|
+
aggregate=Avg,
|
|
60
|
+
serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
|
|
61
|
+
),
|
|
62
|
+
],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
STATISTICS_METRIC_USD = MetricKey(
|
|
66
|
+
key="statistic_usd",
|
|
67
|
+
label=STATISTICS_METRIC.label,
|
|
68
|
+
subfields=STATISTICS_METRIC.subfields,
|
|
69
|
+
additional_prefixes=STATISTICS_METRIC.additional_prefixes,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Dataloader(BaseDataloader):
|
|
74
|
+
METRIC_KEY = "statistic"
|
|
75
|
+
|
|
76
|
+
def __init__(self, *args, **kwargs):
|
|
77
|
+
super().__init__(*args, **kwargs)
|
|
78
|
+
self.aggregate_callback = {
|
|
79
|
+
"revenue_y_1": "sum",
|
|
80
|
+
"revenue_y0": "sum",
|
|
81
|
+
"revenue_y1": "sum",
|
|
82
|
+
"market_capitalization": "sum",
|
|
83
|
+
"price": "mean",
|
|
84
|
+
"volume_50d": "mean",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def _compute(self) -> dict[str, float]:
|
|
88
|
+
"""
|
|
89
|
+
Compute/fetch the statistics metrics. If the basket is constituted of multiple instrument, take the average of each performance
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The metrics as dictionary
|
|
93
|
+
"""
|
|
94
|
+
if self.val_date:
|
|
95
|
+
pivot_year = self.val_date.year
|
|
96
|
+
instruments = self.basket_objects
|
|
97
|
+
df_revenue = pd.DataFrame(
|
|
98
|
+
instruments.dl.financials(
|
|
99
|
+
values=[Financial.REVENUE],
|
|
100
|
+
series_type=SeriesType.COMPLETE,
|
|
101
|
+
period_type=PeriodType.ANNUAL,
|
|
102
|
+
from_year=pivot_year - 1,
|
|
103
|
+
to_year=pivot_year + 1,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
if not df_revenue.empty:
|
|
107
|
+
df_revenue = (
|
|
108
|
+
df_revenue.pivot_table(index="instrument_id", columns="year", values="value")
|
|
109
|
+
.rename(
|
|
110
|
+
columns={pivot_year - 1: "revenue_y_1", pivot_year: "revenue_y0", pivot_year + 1: "revenue_y1"}
|
|
111
|
+
)
|
|
112
|
+
.astype(float)
|
|
113
|
+
)
|
|
114
|
+
df_price = (
|
|
115
|
+
pd.DataFrame(
|
|
116
|
+
InstrumentPrice.objects.filter(instrument__in=instruments, calculated=False, date=self.val_date)
|
|
117
|
+
.annotate(fx_rate=self.fx_rate_expression)
|
|
118
|
+
.annotate_market_data()
|
|
119
|
+
.values_list(
|
|
120
|
+
"instrument",
|
|
121
|
+
"internal_market_capitalization",
|
|
122
|
+
"net_value",
|
|
123
|
+
"volume_50d",
|
|
124
|
+
"fx_rate",
|
|
125
|
+
),
|
|
126
|
+
columns=[
|
|
127
|
+
"instrument",
|
|
128
|
+
"market_capitalization",
|
|
129
|
+
"price",
|
|
130
|
+
"volume_50d",
|
|
131
|
+
"fx_rate",
|
|
132
|
+
],
|
|
133
|
+
)
|
|
134
|
+
.set_index("instrument")
|
|
135
|
+
.astype(float)
|
|
136
|
+
)
|
|
137
|
+
fx_rate = df_price["fx_rate"]
|
|
138
|
+
df = pd.concat([df_revenue, df_price.drop("fx_rate", axis=1)], axis=1)
|
|
139
|
+
for key in ["revenue_y_1", "revenue_y0", "revenue_y1", "market_capitalization", "price"]:
|
|
140
|
+
if key in df.columns:
|
|
141
|
+
df[key] = df[key] / fx_rate
|
|
142
|
+
|
|
143
|
+
if not df.empty:
|
|
144
|
+
return (
|
|
145
|
+
df.reset_index()
|
|
146
|
+
.agg({k: v for k, v in self.aggregate_callback.items() if k in df.columns})
|
|
147
|
+
.dropna()
|
|
148
|
+
.to_dict()
|
|
149
|
+
)
|
|
150
|
+
return dict()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@register(move_first=True)
|
|
154
|
+
class InstrumentFinancialStatisticsMetricBackend(InstrumentMetricBaseBackend):
|
|
155
|
+
statistic = STATISTICS_METRIC
|
|
156
|
+
keys = [STATISTICS_METRIC]
|
|
157
|
+
|
|
158
|
+
def compute_metrics(self, basket: Instrument) -> Generator[Metric, None, None]:
|
|
159
|
+
val_date = self._get_valid_date(basket)
|
|
160
|
+
metrics = Dataloader(basket, val_date, target_currency_key=self.TARGET_CURRENCY_KEY).compute()
|
|
161
|
+
yield Metric(
|
|
162
|
+
metrics=metrics,
|
|
163
|
+
basket_id=basket.id,
|
|
164
|
+
basket_content_type_id=self.content_type.id,
|
|
165
|
+
key=self.statistic.key,
|
|
166
|
+
date=None,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _get_valid_date(self, instrument: Instrument) -> date:
|
|
170
|
+
if self.val_date is None and instrument.last_valuation_date:
|
|
171
|
+
return instrument.last_valuation_date
|
|
172
|
+
elif self.val_date:
|
|
173
|
+
with suppress(InstrumentPrice.DoesNotExist):
|
|
174
|
+
return instrument.valuations.filter(date__lte=self.val_date).latest("date").date
|
|
175
|
+
raise MetricInvalidParameterException()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@register(move_first=True)
|
|
179
|
+
class InstrumentFinancialStatisticsUSDMetricBackend(InstrumentFinancialStatisticsMetricBackend):
|
|
180
|
+
statistic = STATISTICS_METRIC_USD
|
|
181
|
+
keys = [STATISTICS_METRIC_USD]
|
|
182
|
+
TARGET_CURRENCY_KEY = "USD"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
def register(move_first: bool = False, override_backend: bool = False):
|
|
2
|
+
"""
|
|
3
|
+
Decorator to register the metric backend
|
|
4
|
+
"""
|
|
5
|
+
from wbfdm.contrib.metric.registry import backend_registry
|
|
6
|
+
|
|
7
|
+
def _model_wrapper(backend):
|
|
8
|
+
for key in backend.keys:
|
|
9
|
+
backend_registry.set(
|
|
10
|
+
key, backend.BASKET_MODEL_CLASS, backend, move_first=move_first, override_backend=override_backend
|
|
11
|
+
)
|
|
12
|
+
return backend
|
|
13
|
+
|
|
14
|
+
return _model_wrapper
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute_metrics(val_date: date, key: str | None = None, basket: Any | None = None, **kwargs):
|
|
7
|
+
"""
|
|
8
|
+
Compute and process metrics for a given date using the MetricOrchestrator.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
val_date (date): The validation date for the metrics computation.
|
|
12
|
+
key (Optional[str]): The optional metric backend key to narrow down the set of backends to use. Defaults to None.
|
|
13
|
+
basket (Optional[Any]): An optional basket to narrow down the backend queryset. Defaults to None.
|
|
14
|
+
**kwargs: Additional keyword arguments to pass to the MetricOrchestrator.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
None
|
|
18
|
+
"""
|
|
19
|
+
from wbfdm.contrib.metric.orchestrators import MetricOrchestrator
|
|
20
|
+
|
|
21
|
+
with suppress(KeyError):
|
|
22
|
+
orchestrator = MetricOrchestrator(val_date, key=key, basket=basket, **kwargs)
|
|
23
|
+
orchestrator.process()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Any, Optional, Type
|
|
4
|
+
|
|
5
|
+
from django.db.models import Aggregate, FloatField, Sum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class MetricField:
|
|
10
|
+
"""
|
|
11
|
+
A DTO Class to represent the metric subfield computed by metric backend for a given Metric Key.
|
|
12
|
+
|
|
13
|
+
list_display_kwargs: Contains keyword argument to be inserted in a dp.Field
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
key: str
|
|
17
|
+
label: str
|
|
18
|
+
list_display_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
aggregate: Type[Aggregate] | None = Sum
|
|
20
|
+
serializer_kwargs: dict[str, Any] = field(default_factory=dict)
|
|
21
|
+
field_type: type = FloatField
|
|
22
|
+
decorators: list[dict[str, str]] = field(
|
|
23
|
+
default_factory=list
|
|
24
|
+
) # Set into a wbcore decorator accordingly (e.g. to show the metric into a specific currency)
|
|
25
|
+
help_text: str | None = None # define if the field needs to show a particular help text
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class MetricKey:
|
|
30
|
+
"""
|
|
31
|
+
a DTO class to represent the metric generated by a metric backend. It contains general information regarding the type of field the metric stored (e.g. Is percent? precision? etc..)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
key: str
|
|
35
|
+
label: str
|
|
36
|
+
subfields: list[MetricField]
|
|
37
|
+
extra_subfields: list[MetricField] = field(default_factory=list)
|
|
38
|
+
additional_prefixes: list[str] = field(
|
|
39
|
+
default_factory=list
|
|
40
|
+
) # Define the optional category/groups stored in the metric json (e.g if a variable "x" is stored as well as "past_x", a prefix would be "past")
|
|
41
|
+
subfields_map: dict[str, MetricField] = field(init=False)
|
|
42
|
+
subfields_filter_map: dict[str, str] = field(init=False)
|
|
43
|
+
|
|
44
|
+
def __post_init__(self):
|
|
45
|
+
subfields_map = dict()
|
|
46
|
+
subfields_filter_map = dict()
|
|
47
|
+
for subfield in self.subfields:
|
|
48
|
+
subfields_map[f"{self.key}_{subfield.key}"] = subfield
|
|
49
|
+
subfields_filter_map[f"{self.key}_{subfield.key}"] = subfield.key
|
|
50
|
+
for prefix in self.additional_prefixes:
|
|
51
|
+
subfields_map[f"{self.key}_{prefix}_{subfield.key}"] = subfield
|
|
52
|
+
subfields_filter_map[f"{self.key}_{prefix}_{subfield.key}"] = f"{prefix}_{subfield.key}"
|
|
53
|
+
object.__setattr__(self, "subfields_map", subfields_map)
|
|
54
|
+
object.__setattr__(self, "subfields_filter_map", subfields_filter_map)
|
|
55
|
+
|
|
56
|
+
def __hash__(self):
|
|
57
|
+
return hash(self.key)
|
|
58
|
+
|
|
59
|
+
def get_fields(self, with_prefixed_key: bool = False):
|
|
60
|
+
"""
|
|
61
|
+
Returns a generator of key identifier and metric label containing in that metric
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
with_prefixed_key: Default to False. If True yield also the prefixed fields
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
a generator of tuple of strings
|
|
68
|
+
"""
|
|
69
|
+
for subfield in self.subfields:
|
|
70
|
+
yield f"{self.key}_{subfield.key}", f"{self.label} {subfield.label}"
|
|
71
|
+
if with_prefixed_key:
|
|
72
|
+
for prefix in self.additional_prefixes:
|
|
73
|
+
yield f"{self.key}_{prefix}_{subfield.key}", f"{self.label} {subfield.label} ({prefix.title()})"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class Metric:
|
|
78
|
+
"""
|
|
79
|
+
A DTO class to hold the metrics values computed by a metric backend. The object will be parsed by an orchestrator into a InstrumentMetric object
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
basket_id: int
|
|
83
|
+
basket_content_type_id: int
|
|
84
|
+
key: str
|
|
85
|
+
metrics: dict
|
|
86
|
+
date: Optional[date] = None
|
|
87
|
+
instrument_id: int | None = None
|
|
88
|
+
dependency_metrics: list["Metric"] = field(default_factory=list)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from django.contrib.contenttypes.models import ContentType
|
|
3
|
+
from faker import Faker
|
|
4
|
+
from wbfdm.contrib.metric.models import InstrumentMetric
|
|
5
|
+
from wbfdm.factories import InstrumentFactory
|
|
6
|
+
from wbfdm.models import Instrument
|
|
7
|
+
|
|
8
|
+
from .registry import backend_registry
|
|
9
|
+
|
|
10
|
+
fake = Faker()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_metrics(key: str):
|
|
14
|
+
backend = backend_registry[key, Instrument]
|
|
15
|
+
metrics = {}
|
|
16
|
+
for sub_field in backend.keys[0].subfields:
|
|
17
|
+
metrics[sub_field.key] = fake.pyfloat()
|
|
18
|
+
return metrics
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InstrumentMetricFactory(factory.django.DjangoModelFactory):
|
|
22
|
+
class Meta:
|
|
23
|
+
model = InstrumentMetric
|
|
24
|
+
# django_get_or_create = ["basket_content_type", "basket_id", "instrument", "date", "key"]
|
|
25
|
+
|
|
26
|
+
basket = factory.SubFactory(InstrumentFactory)
|
|
27
|
+
basket_content_type = factory.LazyAttribute(lambda o: ContentType.objects.get_for_model(Instrument))
|
|
28
|
+
basket_id = factory.LazyAttribute(lambda o: o.basket.id)
|
|
29
|
+
|
|
30
|
+
date = factory.Faker("date_object")
|
|
31
|
+
key = factory.Iterator(backend_registry.keys())
|
|
32
|
+
metrics = factory.LazyAttribute(lambda o: _get_metrics(o.key))
|
|
33
|
+
parent_metric = None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from django.utils.translation import gettext as _
|
|
3
|
+
from wbcore import filters
|
|
4
|
+
from wbfdm.contrib.metric.models import InstrumentMetric
|
|
5
|
+
|
|
6
|
+
from .registry import backend_registry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InstrumentMetricFilterSet(filters.FilterSet):
|
|
10
|
+
key = filters.ChoiceFilter(choices=backend_registry.get_choices(), label="Key")
|
|
11
|
+
basket_content_type = filters.ModelChoiceFilter(
|
|
12
|
+
queryset=ContentType.objects.all(),
|
|
13
|
+
endpoint="wbcore:contenttyperepresentation-list",
|
|
14
|
+
value_key="id",
|
|
15
|
+
label_key="{{app_label}} | {{model}}",
|
|
16
|
+
label=_("Basket Content Type"),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
class Meta:
|
|
20
|
+
model = InstrumentMetric
|
|
21
|
+
fields = {
|
|
22
|
+
"basket_content_type": ["exact"],
|
|
23
|
+
"basket_id": ["exact"],
|
|
24
|
+
"instrument": ["exact"],
|
|
25
|
+
"key": ["exact"],
|
|
26
|
+
"date": ["lte", "gte", "exact"],
|
|
27
|
+
"parent_metric": ["exact", "isnull"],
|
|
28
|
+
}
|