wbfdm 1.43.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 +277 -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/backends/dto.py +36 -0
- wbfdm/contrib/__init__.py +0 -0
- wbfdm/contrib/dsws/__init__.py +0 -0
- wbfdm/contrib/dsws/client.py +285 -0
- wbfdm/contrib/dsws/dataloaders/market_data.py +130 -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 +248 -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/jinja2/qa/sql/companies.sql +100 -0
- wbfdm/contrib/qa/jinja2/qa/sql/ibes/base_estimates.sql +33 -0
- wbfdm/contrib/qa/jinja2/qa/sql/ibes/calendarized.sql +37 -0
- wbfdm/contrib/qa/jinja2/qa/sql/ibes/complete.sql +9 -0
- wbfdm/contrib/qa/jinja2/qa/sql/ibes/estimates.sql +3 -0
- wbfdm/contrib/qa/jinja2/qa/sql/ibes/financials.sql +79 -0
- wbfdm/contrib/qa/jinja2/qa/sql/instruments.sql +100 -0
- wbfdm/contrib/qa/jinja2/qa/sql/quotes.sql +98 -0
- wbfdm/contrib/qa/sync/exchanges.py +70 -0
- wbfdm/contrib/qa/sync/instruments.py +94 -0
- wbfdm/contrib/qa/sync/utils.py +241 -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/0028_instrumentprice_annualized_daily_volatility.py +17 -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 +544 -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 +297 -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 +340 -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-1.43.1.dist-info/METADATA +15 -0
- wbfdm-1.43.1.dist-info/RECORD +351 -0
- wbfdm-1.43.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from celery import shared_task
|
|
4
|
+
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
from wbfdm.contrib.metric.orchestrators import MetricOrchestrator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@shared_task(queue="portfolio")
|
|
9
|
+
def compute_metrics_as_task(
|
|
10
|
+
val_date: date | None = None,
|
|
11
|
+
key: str | None = None,
|
|
12
|
+
basket_content_type_id: int | None = None,
|
|
13
|
+
basket_id: int | None = None,
|
|
14
|
+
**kwargs
|
|
15
|
+
):
|
|
16
|
+
"""
|
|
17
|
+
Run the orchestrator as a async task periodically for all keys and baskets
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
val_date (date): Compute the metric for the given date (Default to None). If None, let the metric backends decide what is the last valide date to use
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
basket = None
|
|
24
|
+
if basket_content_type_id and basket_id:
|
|
25
|
+
basket = ContentType.objects.get(id=basket_content_type_id).get_object_for_this_type(pk=basket_id)
|
|
26
|
+
routine = MetricOrchestrator(val_date, key=key, basket=basket, **kwargs)
|
|
27
|
+
routine.process()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import pytest
|
|
6
|
+
from django.contrib.contenttypes.models import ContentType
|
|
7
|
+
from faker import Faker
|
|
8
|
+
from pandas.tseries.offsets import BDay
|
|
9
|
+
from wbfdm.models import Instrument, RelatedInstrumentThroughModel
|
|
10
|
+
|
|
11
|
+
fake = Faker()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.django_db
|
|
15
|
+
class TestInstrumentPerformanceMetricBackend:
|
|
16
|
+
def test_compute_metrics(self, weekday, instrument, instrument_price_factory):
|
|
17
|
+
from wbfdm.contrib.metric.backends.performances import ( # we import locally to avoid database access pytest error
|
|
18
|
+
InstrumentPerformanceMetricBackend,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
backend = InstrumentPerformanceMetricBackend(weekday)
|
|
22
|
+
|
|
23
|
+
price_today = instrument_price_factory.create(instrument=instrument, calculated=False, date=weekday)
|
|
24
|
+
price_yesterday = instrument_price_factory.create(
|
|
25
|
+
instrument=instrument, calculated=False, date=weekday - BDay(1)
|
|
26
|
+
)
|
|
27
|
+
price_last_week = instrument_price_factory.create(
|
|
28
|
+
instrument=instrument, calculated=False, date=(weekday - timedelta(days=7)) - BDay(0)
|
|
29
|
+
)
|
|
30
|
+
price_last_month = instrument_price_factory.create(
|
|
31
|
+
instrument=instrument, calculated=False, date=(weekday - timedelta(days=30)) - BDay(1)
|
|
32
|
+
)
|
|
33
|
+
price_last_quarter = instrument_price_factory.create(
|
|
34
|
+
instrument=instrument, calculated=False, date=(weekday - timedelta(days=120)) - BDay(1)
|
|
35
|
+
)
|
|
36
|
+
price_last_year = instrument_price_factory.create(
|
|
37
|
+
instrument=instrument, calculated=False, date=(weekday - timedelta(days=365)) - BDay(1)
|
|
38
|
+
)
|
|
39
|
+
price_end_last_year = instrument_price_factory.create(
|
|
40
|
+
instrument=instrument, calculated=False, date=weekday - pd.tseries.offsets.BYearEnd(1)
|
|
41
|
+
)
|
|
42
|
+
price_inception = instrument_price_factory.create(
|
|
43
|
+
instrument=instrument, calculated=False, date=price_last_year.date - BDay(1)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
instrument_price_factory.create(
|
|
47
|
+
instrument=instrument, calculated=True, date=price_inception.date - BDay(1)
|
|
48
|
+
) # add noise
|
|
49
|
+
instrument.inception_date = price_inception.date.date()
|
|
50
|
+
instrument.save()
|
|
51
|
+
instrument.update_last_valuation_date()
|
|
52
|
+
base_metric = next(backend.compute_metrics(instrument))
|
|
53
|
+
|
|
54
|
+
# Check base metric metadata
|
|
55
|
+
assert base_metric.date is None
|
|
56
|
+
assert base_metric.basket_id == instrument.id
|
|
57
|
+
assert base_metric.basket_content_type_id == ContentType.objects.get_for_model(Instrument).id
|
|
58
|
+
assert base_metric.key == "performance"
|
|
59
|
+
assert base_metric.instrument_id is None
|
|
60
|
+
|
|
61
|
+
assert base_metric.metrics["is_estimated"] is False
|
|
62
|
+
assert base_metric.metrics["daily"] == pytest.approx(
|
|
63
|
+
float(price_today.net_value / price_yesterday.net_value - Decimal(1)), abs=1e-5
|
|
64
|
+
)
|
|
65
|
+
assert base_metric.metrics["weekly"] == pytest.approx(
|
|
66
|
+
float(price_today.net_value / price_last_week.net_value - Decimal(1)), abs=1e-5
|
|
67
|
+
)
|
|
68
|
+
assert base_metric.metrics["monthly"] == pytest.approx(
|
|
69
|
+
float(price_today.net_value / price_last_month.net_value - Decimal(1)), abs=1e-5
|
|
70
|
+
)
|
|
71
|
+
assert base_metric.metrics["quarterly"] == pytest.approx(
|
|
72
|
+
float(price_today.net_value / price_last_quarter.net_value - Decimal(1)), abs=1e-5
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
assert base_metric.metrics["yearly"] == pytest.approx(
|
|
76
|
+
float(price_today.net_value / price_last_year.net_value - Decimal(1)), abs=1e-5
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
assert base_metric.metrics["year_to_date"] == pytest.approx(
|
|
80
|
+
float(price_today.net_value / price_end_last_year.net_value - Decimal(1)), abs=1e-5
|
|
81
|
+
)
|
|
82
|
+
assert base_metric.metrics["inception"] == pytest.approx(
|
|
83
|
+
float(price_today.net_value / price_inception.net_value - Decimal(1)), abs=1e-5
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def test_compute_metrics_estimated(self, weekday, instrument, instrument_price_factory):
|
|
87
|
+
from wbfdm.contrib.metric.backends.performances import ( # we import locally to avoid database access pytest error
|
|
88
|
+
InstrumentPerformanceMetricBackend,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
backend = InstrumentPerformanceMetricBackend(weekday)
|
|
92
|
+
|
|
93
|
+
price_today = instrument_price_factory.create(instrument=instrument, calculated=True, date=weekday)
|
|
94
|
+
price_yesterday = instrument_price_factory.create(
|
|
95
|
+
instrument=instrument, calculated=False, date=weekday - BDay(1)
|
|
96
|
+
)
|
|
97
|
+
instrument.inception_date = price_yesterday.date.date()
|
|
98
|
+
instrument.save()
|
|
99
|
+
instrument.update_last_valuation_date()
|
|
100
|
+
base_metric = next(backend.compute_metrics(instrument))
|
|
101
|
+
|
|
102
|
+
assert base_metric.metrics["is_estimated"] is True
|
|
103
|
+
assert base_metric.metrics["daily"] == pytest.approx(
|
|
104
|
+
float(price_today.net_value / price_yesterday.net_value - Decimal(1)), abs=1e-5
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def test_compute_metrics_with_peers(self, weekday, instrument_factory, instrument_price_factory):
|
|
108
|
+
from wbfdm.contrib.metric.backends.performances import ( # we import locally to avoid database access pytest error
|
|
109
|
+
InstrumentPerformanceMetricBackend,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
instrument = instrument_factory.create()
|
|
113
|
+
peer_1 = instrument_factory.create()
|
|
114
|
+
RelatedInstrumentThroughModel.objects.create(
|
|
115
|
+
related_type="PEER", instrument=instrument, related_instrument=peer_1
|
|
116
|
+
)
|
|
117
|
+
peer_2 = instrument_factory.create()
|
|
118
|
+
RelatedInstrumentThroughModel.objects.create(
|
|
119
|
+
related_type="PEER", instrument=instrument, related_instrument=peer_2
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
backend = InstrumentPerformanceMetricBackend(weekday)
|
|
123
|
+
|
|
124
|
+
price_today = instrument_price_factory.create(instrument=instrument, calculated=False, date=weekday)
|
|
125
|
+
price_yesterday = instrument_price_factory.create(
|
|
126
|
+
instrument=instrument, calculated=False, date=weekday - BDay(1)
|
|
127
|
+
)
|
|
128
|
+
instrument.inception_date = price_yesterday.date.date()
|
|
129
|
+
instrument.save()
|
|
130
|
+
peer_1_price_today = instrument_price_factory.create(instrument=peer_1, calculated=False, date=weekday)
|
|
131
|
+
peer_1_price_yesterday = instrument_price_factory.create(
|
|
132
|
+
instrument=peer_1, calculated=False, date=weekday - BDay(1)
|
|
133
|
+
)
|
|
134
|
+
peer_1.inception_date = peer_1_price_yesterday.date.date()
|
|
135
|
+
peer_1.save()
|
|
136
|
+
peer_2_price_today = instrument_price_factory.create(instrument=peer_2, calculated=False, date=weekday)
|
|
137
|
+
peer_2_price_yesterday = instrument_price_factory.create(
|
|
138
|
+
instrument=peer_2, calculated=False, date=weekday - BDay(1)
|
|
139
|
+
)
|
|
140
|
+
peer_2.inception_date = peer_2_price_yesterday.date.date()
|
|
141
|
+
peer_2.save()
|
|
142
|
+
instrument.update_last_valuation_date()
|
|
143
|
+
peer_1.update_last_valuation_date()
|
|
144
|
+
peer_2.update_last_valuation_date()
|
|
145
|
+
base_metric = next(backend.compute_metrics(instrument))
|
|
146
|
+
daily_instrument_perf = float(price_today.net_value / price_yesterday.net_value - Decimal(1))
|
|
147
|
+
assert base_metric.metrics["daily"] == pytest.approx(daily_instrument_perf, abs=1e-5)
|
|
148
|
+
daily_peer_1_perf = float(peer_1_price_today.net_value / peer_1_price_yesterday.net_value - Decimal(1))
|
|
149
|
+
daily_peer_2_perf = float(peer_2_price_today.net_value / peer_2_price_yesterday.net_value - Decimal(1))
|
|
150
|
+
avg_daily_peer_perf = (daily_peer_1_perf + daily_peer_2_perf) / 2
|
|
151
|
+
r = daily_instrument_perf - avg_daily_peer_perf
|
|
152
|
+
assert base_metric.metrics["peer_daily"] == pytest.approx(r, abs=0.00001)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from wbfdm.dataloaders.proxies import InstrumentDataloaderProxy
|
|
7
|
+
|
|
8
|
+
fake = Faker()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.django_db
|
|
12
|
+
class TestInstrumentFinancialStatisticsMetricBackend:
|
|
13
|
+
@patch.object(InstrumentDataloaderProxy, "financials")
|
|
14
|
+
def test_default_daily_statistics(self, mock_fct, weekday, instrument, instrument_price_factory):
|
|
15
|
+
from wbfdm.contrib.metric.backends.statistics import ( # we import locally to avoid database access pytest error
|
|
16
|
+
InstrumentFinancialStatisticsMetricBackend,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
backend = InstrumentFinancialStatisticsMetricBackend(weekday)
|
|
20
|
+
|
|
21
|
+
revenue_y_1 = fake.pyfloat()
|
|
22
|
+
revenue_y0 = fake.pyfloat()
|
|
23
|
+
revenue_y1 = fake.pyfloat()
|
|
24
|
+
market_capitalization = fake.pyfloat()
|
|
25
|
+
price = Decimal(150)
|
|
26
|
+
volume_50d = fake.pyfloat()
|
|
27
|
+
instrument_price_factory.create(
|
|
28
|
+
instrument=instrument,
|
|
29
|
+
calculated=False,
|
|
30
|
+
date=weekday,
|
|
31
|
+
net_value=price,
|
|
32
|
+
volume_50d=volume_50d,
|
|
33
|
+
market_capitalization=market_capitalization,
|
|
34
|
+
)
|
|
35
|
+
instrument.update_last_valuation_date()
|
|
36
|
+
mock_fct.return_value = [
|
|
37
|
+
{"year": weekday.year - 1, "value": revenue_y_1, "instrument_id": instrument.id},
|
|
38
|
+
{"year": weekday.year, "value": revenue_y0, "instrument_id": instrument.id},
|
|
39
|
+
{"year": weekday.year + 1, "value": revenue_y1, "instrument_id": instrument.id},
|
|
40
|
+
]
|
|
41
|
+
metrics = next(backend.compute_metrics(instrument)).metrics
|
|
42
|
+
|
|
43
|
+
assert metrics["revenue_y_1"] == revenue_y_1
|
|
44
|
+
assert metrics["revenue_y0"] == revenue_y0
|
|
45
|
+
assert metrics["revenue_y1"] == revenue_y1
|
|
46
|
+
assert metrics["market_capitalization"] == market_capitalization
|
|
47
|
+
assert metrics["price"] == price
|
|
48
|
+
assert metrics["volume_50d"] == volume_50d
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from wbcore.tests.conftest import * # type: ignore # 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
|
+
ParentClassificationFactory,
|
|
48
|
+
RelatedInstrumentThroughModelFactory,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
from ..factories import InstrumentMetricFactory
|
|
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
|
+
|
|
74
|
+
register(CurrencyFactory)
|
|
75
|
+
register(CityFactory)
|
|
76
|
+
register(StateFactory)
|
|
77
|
+
register(CountryFactory)
|
|
78
|
+
register(ContinentFactory)
|
|
79
|
+
|
|
80
|
+
register(CompanyFactory)
|
|
81
|
+
register(PersonFactory)
|
|
82
|
+
register(InternalUserFactory)
|
|
83
|
+
register(EntryFactory)
|
|
84
|
+
register(CustomerStatusFactory)
|
|
85
|
+
register(CompanyTypeFactory)
|
|
86
|
+
|
|
87
|
+
register(UserFactory)
|
|
88
|
+
register(SuperUserFactory, "superuser")
|
|
89
|
+
|
|
90
|
+
register(InstrumentMetricFactory)
|
|
91
|
+
|
|
92
|
+
pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("metric"))
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
|
|
4
|
+
from ..backends.performances import InstrumentMetricBaseBackend
|
|
5
|
+
from ..dto import MetricField, MetricKey
|
|
6
|
+
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.django_db
|
|
11
|
+
class TestMetricKey:
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def metric_key(self):
|
|
14
|
+
label = fake.word()
|
|
15
|
+
|
|
16
|
+
return MetricKey(
|
|
17
|
+
key=label.lower(),
|
|
18
|
+
label=label,
|
|
19
|
+
additional_prefixes=[fake.word()],
|
|
20
|
+
subfields=[
|
|
21
|
+
MetricField(
|
|
22
|
+
key=label.lower(),
|
|
23
|
+
label=label,
|
|
24
|
+
decorators=[{"position": "left", "value": "&", "type": "text"}],
|
|
25
|
+
serializer_kwargs={"percent": fake.boolean(), "precision": fake.pyint(min_value=1, max_value=4)},
|
|
26
|
+
)
|
|
27
|
+
for label in map(lambda x: fake.word(), range(3))
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def test_get_fields(self, metric_key):
|
|
32
|
+
fields = list(metric_key.get_fields())
|
|
33
|
+
assert fields[0] == (
|
|
34
|
+
f"{metric_key.key}_{metric_key.subfields[0].key}",
|
|
35
|
+
f"{metric_key.label} {metric_key.subfields[0].label}",
|
|
36
|
+
)
|
|
37
|
+
assert fields[1] == (
|
|
38
|
+
f"{metric_key.key}_{metric_key.subfields[1].key}",
|
|
39
|
+
f"{metric_key.label} {metric_key.subfields[1].label}",
|
|
40
|
+
)
|
|
41
|
+
assert fields[2] == (
|
|
42
|
+
f"{metric_key.key}_{metric_key.subfields[2].key}",
|
|
43
|
+
f"{metric_key.label} {metric_key.subfields[2].label}",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def test_get_fields_with_prefixed_key(self, metric_key):
|
|
47
|
+
fields = list(metric_key.get_fields(with_prefixed_key=True))
|
|
48
|
+
prefix = metric_key.additional_prefixes[0]
|
|
49
|
+
assert fields[1] == (
|
|
50
|
+
f"{metric_key.key}_{prefix}_{metric_key.subfields[0].key}",
|
|
51
|
+
f"{metric_key.label} {metric_key.subfields[0].label} ({prefix.title()})",
|
|
52
|
+
)
|
|
53
|
+
assert fields[3] == (
|
|
54
|
+
f"{metric_key.key}_{prefix}_{metric_key.subfields[1].key}",
|
|
55
|
+
f"{metric_key.label} {metric_key.subfields[1].label} ({prefix.title()})",
|
|
56
|
+
)
|
|
57
|
+
assert fields[5] == (
|
|
58
|
+
f"{metric_key.key}_{prefix}_{metric_key.subfields[2].key}",
|
|
59
|
+
f"{metric_key.label} {metric_key.subfields[2].label} ({prefix.title()})",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def test_fields_map(self, metric_key):
|
|
63
|
+
keys = set(map(lambda x: x[0], metric_key.get_fields(with_prefixed_key=True)))
|
|
64
|
+
assert keys == set(metric_key.subfields_filter_map.keys())
|
|
65
|
+
|
|
66
|
+
def test_get_serializer_field_attr(self, weekday, metric_key):
|
|
67
|
+
metric_field = metric_key.subfields[0]
|
|
68
|
+
backend = InstrumentMetricBaseBackend(weekday)
|
|
69
|
+
assert backend.get_serializer_field_attr(metric_field)["percent"] == metric_field.serializer_kwargs["percent"]
|
|
70
|
+
assert (
|
|
71
|
+
backend.get_serializer_field_attr(metric_field)["precision"] == metric_field.serializer_kwargs["precision"]
|
|
72
|
+
)
|
|
73
|
+
assert backend.get_serializer_field_attr(metric_field)["decorators"] == metric_field.decorators
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from django.contrib.contenttypes.models import ContentType
|
|
3
|
+
from wbfdm.models import Instrument
|
|
4
|
+
|
|
5
|
+
from ..dto import Metric
|
|
6
|
+
from ..models import InstrumentMetric
|
|
7
|
+
from ..registry import backend_registry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.django_db
|
|
11
|
+
class TestInstrumentMetric:
|
|
12
|
+
def test_save(self, instrument_metric):
|
|
13
|
+
assert instrument_metric.basket_repr == str(instrument_metric.basket)
|
|
14
|
+
|
|
15
|
+
def test_update_or_create_from_metric(self, weekday, instrument_factory):
|
|
16
|
+
i1 = instrument_factory.create()
|
|
17
|
+
i2 = instrument_factory.create()
|
|
18
|
+
i3 = instrument_factory.create()
|
|
19
|
+
key = "key"
|
|
20
|
+
basket_content_type_id = ContentType.objects.get_for_model(i1).id
|
|
21
|
+
level1_dto_metric = Metric(
|
|
22
|
+
basket_id=i1.id,
|
|
23
|
+
basket_content_type_id=basket_content_type_id,
|
|
24
|
+
key=key,
|
|
25
|
+
metrics={"a": "a"},
|
|
26
|
+
date=weekday,
|
|
27
|
+
)
|
|
28
|
+
level1_dto_metric = Metric(
|
|
29
|
+
basket_id=i2.id,
|
|
30
|
+
basket_content_type_id=basket_content_type_id,
|
|
31
|
+
key=key,
|
|
32
|
+
metrics={"b": "b"},
|
|
33
|
+
date=weekday,
|
|
34
|
+
dependency_metrics=[level1_dto_metric],
|
|
35
|
+
)
|
|
36
|
+
base_dto_metric = Metric(
|
|
37
|
+
basket_id=i3.id,
|
|
38
|
+
basket_content_type_id=basket_content_type_id,
|
|
39
|
+
key=key,
|
|
40
|
+
metrics={"c": "c"},
|
|
41
|
+
date=weekday,
|
|
42
|
+
dependency_metrics=[level1_dto_metric],
|
|
43
|
+
)
|
|
44
|
+
InstrumentMetric.update_or_create_from_metric(base_dto_metric)
|
|
45
|
+
|
|
46
|
+
level2_metric = InstrumentMetric.objects.get(
|
|
47
|
+
basket_id=i1.id, basket_content_type_id=basket_content_type_id, date=weekday, key=key
|
|
48
|
+
)
|
|
49
|
+
level1_metric = InstrumentMetric.objects.get(
|
|
50
|
+
basket_id=i2.id, basket_content_type_id=basket_content_type_id, date=weekday, key=key
|
|
51
|
+
)
|
|
52
|
+
base_metric = InstrumentMetric.objects.get(
|
|
53
|
+
basket_id=i3.id, basket_content_type_id=basket_content_type_id, date=weekday, key=key
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
assert level2_metric.metrics == {"a": "a"}
|
|
57
|
+
assert level2_metric.parent_metric == level1_metric
|
|
58
|
+
|
|
59
|
+
assert level1_metric.metrics == {"b": "b"}
|
|
60
|
+
assert level1_metric.parent_metric == base_metric
|
|
61
|
+
|
|
62
|
+
assert base_metric.metrics == {"c": "c"}
|
|
63
|
+
|
|
64
|
+
def test_annotate_with_metrics(self, instrument_metric):
|
|
65
|
+
qs = Instrument.objects.filter(id=instrument_metric.basket_id)
|
|
66
|
+
qs = InstrumentMetric.annotate_with_metrics(
|
|
67
|
+
qs, backend_registry._metric_key_map[instrument_metric.key], Instrument, val_date=instrument_metric.date
|
|
68
|
+
)
|
|
69
|
+
for field in instrument_metric.metrics.keys():
|
|
70
|
+
qs_field = instrument_metric.key + "_" + field
|
|
71
|
+
res = list(qs.values_list(qs_field, flat=True))[0]
|
|
72
|
+
assert res == instrument_metric.metrics[field]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from unittest.mock import patch
|
|
2
|
+
|
|
3
|
+
from faker import Faker
|
|
4
|
+
|
|
5
|
+
from ..dispatch import compute_metrics
|
|
6
|
+
from ..orchestrators import MetricOrchestrator
|
|
7
|
+
from ..tasks import compute_metrics_as_task
|
|
8
|
+
|
|
9
|
+
fake = Faker()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@patch.object(MetricOrchestrator, "process")
|
|
13
|
+
def test_dispatch_compute_metrics(mock_fct):
|
|
14
|
+
key = "performance"
|
|
15
|
+
basket = object()
|
|
16
|
+
val_date = fake.date_object()
|
|
17
|
+
compute_metrics(val_date, key=key, basket=basket)
|
|
18
|
+
mock_fct.assert_called_once()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@patch.object(MetricOrchestrator, "process")
|
|
22
|
+
def test_periodically_compute_all_metrics(mock_fct):
|
|
23
|
+
compute_metrics_as_task()
|
|
24
|
+
mock_fct.assert_called_once()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from rest_framework.test import APIRequestFactory
|
|
6
|
+
from wbcore import viewsets
|
|
7
|
+
from wbcore.utils.strings import get_aggregate_symbol
|
|
8
|
+
from wbfdm.contrib.metric.backends.performances import (
|
|
9
|
+
PERFORMANCE_METRIC,
|
|
10
|
+
InstrumentPerformanceMetricBackend,
|
|
11
|
+
)
|
|
12
|
+
from wbfdm.contrib.metric.models import InstrumentMetric
|
|
13
|
+
from wbfdm.models import Instrument
|
|
14
|
+
from wbfdm.serializers import InstrumentModelSerializer
|
|
15
|
+
|
|
16
|
+
from ..viewsets.mixins import InstrumentMetricMixin
|
|
17
|
+
|
|
18
|
+
fake = Faker()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.django_db
|
|
22
|
+
class TestInstrumentMetricMixin:
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def viewset(self, instrument_metric_factory):
|
|
25
|
+
metric = instrument_metric_factory.create(key="performance")
|
|
26
|
+
|
|
27
|
+
class InstrumentWithMetricModelViewSet(InstrumentMetricMixin, viewsets.ModelViewSet):
|
|
28
|
+
METRIC_KEYS = (PERFORMANCE_METRIC,)
|
|
29
|
+
METRIC_WITH_PREFIXED_KEYS = fake.boolean()
|
|
30
|
+
serializer_class = InstrumentModelSerializer
|
|
31
|
+
queryset = Instrument.objects.all()
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def metric_basket(self):
|
|
35
|
+
return metric.basket
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def metric_date(self) -> date | None:
|
|
39
|
+
return metric.date
|
|
40
|
+
|
|
41
|
+
request = APIRequestFactory().get("")
|
|
42
|
+
return InstrumentWithMetricModelViewSet(request=request, format_kwarg={})
|
|
43
|
+
|
|
44
|
+
def test_metric_serializer_fields(self, weekday, viewset):
|
|
45
|
+
assert (
|
|
46
|
+
viewset._metric_serializer_fields.keys()
|
|
47
|
+
== InstrumentPerformanceMetricBackend(weekday)
|
|
48
|
+
.get_serializer_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS)
|
|
49
|
+
.keys()
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def test_get_ordering_fields(self, viewset):
|
|
53
|
+
ordering_fields = viewset.get_ordering_fields()
|
|
54
|
+
for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
|
|
55
|
+
assert key in ordering_fields
|
|
56
|
+
|
|
57
|
+
def test_get_queryset(self, viewset):
|
|
58
|
+
queryset = viewset.get_queryset()
|
|
59
|
+
annotations = list(queryset.query.annotations.keys())
|
|
60
|
+
for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
|
|
61
|
+
assert key in annotations
|
|
62
|
+
|
|
63
|
+
def test_get_serializer(self, viewset):
|
|
64
|
+
serializer = viewset.get_serializer()
|
|
65
|
+
for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
|
|
66
|
+
assert key in serializer.fields
|
|
67
|
+
|
|
68
|
+
def test_get_aggregates(self, viewset):
|
|
69
|
+
queryset = viewset.get_queryset()
|
|
70
|
+
aggregates = viewset.get_aggregates(queryset, queryset)
|
|
71
|
+
metric = InstrumentMetric.objects.first()
|
|
72
|
+
assert metric is not None
|
|
73
|
+
for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
|
|
74
|
+
key_filter = PERFORMANCE_METRIC.subfields_filter_map[key]
|
|
75
|
+
subfield = PERFORMANCE_METRIC.subfields_map[key]
|
|
76
|
+
assert subfield.aggregate is not None
|
|
77
|
+
|
|
78
|
+
if key_filter in metric.metrics:
|
|
79
|
+
assert aggregates[key] == {get_aggregate_symbol(subfield.aggregate.name): metric.metrics[key_filter]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from django.urls import include, path
|
|
2
|
+
from wbcore.routers import WBCoreRouter
|
|
3
|
+
|
|
4
|
+
from . import viewsets
|
|
5
|
+
|
|
6
|
+
router = WBCoreRouter()
|
|
7
|
+
|
|
8
|
+
# Representations
|
|
9
|
+
router.register(
|
|
10
|
+
r"instrumentmetricrepresentation",
|
|
11
|
+
viewsets.InstrumentMetricRepresentationViewSet,
|
|
12
|
+
basename="instrumentmetricrepresentation",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
router.register(r"instrumentmetric", viewsets.InstrumentMetricViewSet, basename="instrumentmetric")
|
|
16
|
+
|
|
17
|
+
urlpatterns = [
|
|
18
|
+
path("", include(router.urls)),
|
|
19
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .viewsets import InstrumentMetricRepresentationViewSet, InstrumentMetricViewSet
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .display import InstrumentMetricDisplayConfig
|