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,88 @@
|
|
|
1
|
+
# Generated by Django 5.0.6 on 2024-05-27 08:56
|
|
2
|
+
|
|
3
|
+
import django.core.serializers.json
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
initial = True
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
("contenttypes", "0002_remove_content_type_name"),
|
|
13
|
+
("wbfdm", "0021_delete_instrumentdailystatistics"),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name="InstrumentMetric",
|
|
19
|
+
fields=[
|
|
20
|
+
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
21
|
+
("basket_id", models.PositiveIntegerField()),
|
|
22
|
+
("basket_repr", models.CharField(max_length=256)),
|
|
23
|
+
(
|
|
24
|
+
"date",
|
|
25
|
+
models.DateField(
|
|
26
|
+
blank=True,
|
|
27
|
+
help_text="If date is null, the metric is considered static",
|
|
28
|
+
null=True,
|
|
29
|
+
verbose_name="Metric Date",
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
("key", models.CharField(max_length=255, verbose_name="Metric Key")),
|
|
33
|
+
(
|
|
34
|
+
"metrics",
|
|
35
|
+
models.JSONField(
|
|
36
|
+
default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder, verbose_name="Metrics"
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
(
|
|
40
|
+
"basket_content_type",
|
|
41
|
+
models.ForeignKey(
|
|
42
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
43
|
+
related_name="instrument_metrics",
|
|
44
|
+
to="contenttypes.contenttype",
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
"instrument",
|
|
49
|
+
models.ForeignKey(
|
|
50
|
+
blank=True,
|
|
51
|
+
help_text="Instrument where this metric belongs to. If null, the metric is applicable to all instruments related to the basket",
|
|
52
|
+
null=True,
|
|
53
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
54
|
+
related_name="metrics",
|
|
55
|
+
to="wbfdm.instrument",
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
(
|
|
59
|
+
"parent_metric",
|
|
60
|
+
models.ForeignKey(
|
|
61
|
+
blank=True,
|
|
62
|
+
null=True,
|
|
63
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
64
|
+
related_name="dependent_metrics",
|
|
65
|
+
to="metric.instrumentmetric",
|
|
66
|
+
verbose_name="Parent Metric",
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
options={
|
|
71
|
+
"verbose_name": "Instrument Metric",
|
|
72
|
+
"verbose_name_plural": "Instrument Metrics",
|
|
73
|
+
"indexes": [
|
|
74
|
+
models.Index(
|
|
75
|
+
fields=["basket_content_type", "basket_id", "key", "date"],
|
|
76
|
+
name="metric_inst_basket__30dcda_idx",
|
|
77
|
+
)
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
),
|
|
81
|
+
migrations.AddConstraint(
|
|
82
|
+
model_name="instrumentmetric",
|
|
83
|
+
constraint=models.UniqueConstraint(
|
|
84
|
+
fields=("basket_content_type", "basket_id", "instrument", "date", "key"),
|
|
85
|
+
name="unique_instrument_metric",
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
]
|
wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Generated by Django 5.0.9 on 2024-11-06 13:20
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("contenttypes", "0002_remove_content_type_name"),
|
|
9
|
+
("metric", "0001_initial"),
|
|
10
|
+
("wbfdm", "0027_remove_instrument_unique_ric_and_more"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.RemoveConstraint(
|
|
15
|
+
model_name="instrumentmetric",
|
|
16
|
+
name="unique_instrument_metric",
|
|
17
|
+
),
|
|
18
|
+
migrations.AddConstraint(
|
|
19
|
+
model_name="instrumentmetric",
|
|
20
|
+
constraint=models.UniqueConstraint(
|
|
21
|
+
fields=("basket_content_type", "basket_id", "instrument", "date", "key"),
|
|
22
|
+
name="unique_instrument_metric",
|
|
23
|
+
nulls_distinct=False,
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
from datetime import date as date_lib
|
|
2
|
+
from typing import Self, Type
|
|
3
|
+
|
|
4
|
+
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
from django.core.serializers.json import DjangoJSONEncoder
|
|
7
|
+
from django.db import models
|
|
8
|
+
from django.db.models.fields.json import KeyTextTransform
|
|
9
|
+
from django.db.models.functions import Cast, NullIf
|
|
10
|
+
from wbcore.models import WBModel
|
|
11
|
+
|
|
12
|
+
from .backends.base import AbstractBackend
|
|
13
|
+
from .dto import Metric, MetricKey
|
|
14
|
+
from .registry import backend_registry
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InstrumentMetric(models.Model):
|
|
18
|
+
basket_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name="instrument_metrics")
|
|
19
|
+
basket_id = models.PositiveIntegerField()
|
|
20
|
+
basket_repr = models.CharField(max_length=256)
|
|
21
|
+
|
|
22
|
+
basket = GenericForeignKey("basket_content_type", "basket_id")
|
|
23
|
+
|
|
24
|
+
instrument = models.ForeignKey(
|
|
25
|
+
"wbfdm.Instrument",
|
|
26
|
+
on_delete=models.CASCADE,
|
|
27
|
+
related_name="metrics",
|
|
28
|
+
null=True,
|
|
29
|
+
blank=True,
|
|
30
|
+
help_text="Instrument where this metric belongs to. If null, the metric is applicable to all instruments related to the basket",
|
|
31
|
+
)
|
|
32
|
+
date = models.DateField(
|
|
33
|
+
verbose_name="Metric Date", null=True, blank=True, help_text="If date is null, the metric is considered static"
|
|
34
|
+
)
|
|
35
|
+
key = models.CharField(max_length=255, verbose_name="Metric Key")
|
|
36
|
+
metrics = models.JSONField(default=dict, verbose_name="Metrics", encoder=DjangoJSONEncoder)
|
|
37
|
+
|
|
38
|
+
parent_metric = models.ForeignKey(
|
|
39
|
+
"self",
|
|
40
|
+
related_name="dependent_metrics",
|
|
41
|
+
blank=True,
|
|
42
|
+
null=True,
|
|
43
|
+
on_delete=models.SET_NULL,
|
|
44
|
+
verbose_name="Parent Metric",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def save(self, *args, **kwargs):
|
|
48
|
+
self.basket_repr = str(self.basket)
|
|
49
|
+
super().save(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def update_or_create_from_metric(cls, metric: Metric, parent_instrument_metric: Self | None = None):
|
|
53
|
+
"""
|
|
54
|
+
Update or create an InstrumentMetric instance based on a given Metric DTO object.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
metric (Metric): The DTO to base the creation or update on.
|
|
58
|
+
parent_instrument_metric (Optional[InstrumentMetric]): The parent of the created/updated InstrumentMetric instance, if any. Defaults to None.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
None
|
|
62
|
+
|
|
63
|
+
Side Effects:
|
|
64
|
+
- Creates or updates an InstrumentMetric instance in the database.
|
|
65
|
+
- Recursively creates or updates dependent InstrumentMetric instances.
|
|
66
|
+
"""
|
|
67
|
+
base_metric, _ = InstrumentMetric.objects.update_or_create(
|
|
68
|
+
basket_id=metric.basket_id,
|
|
69
|
+
basket_content_type_id=metric.basket_content_type_id,
|
|
70
|
+
instrument_id=metric.instrument_id,
|
|
71
|
+
date=metric.date,
|
|
72
|
+
key=metric.key,
|
|
73
|
+
defaults={
|
|
74
|
+
"metrics": metric.metrics,
|
|
75
|
+
"parent_metric": parent_instrument_metric,
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
for dependency_metric in metric.dependency_metrics:
|
|
79
|
+
cls.update_or_create_from_metric(dependency_metric, parent_instrument_metric=base_metric)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def annotate_with_metrics(
|
|
83
|
+
cls,
|
|
84
|
+
queryset: models.QuerySet,
|
|
85
|
+
metric_key: MetricKey,
|
|
86
|
+
metric_basket_class: Type[WBModel],
|
|
87
|
+
val_date: date_lib | None = None,
|
|
88
|
+
basket_label: str = "id",
|
|
89
|
+
instrument_label: str | None = None,
|
|
90
|
+
) -> models.QuerySet:
|
|
91
|
+
"""
|
|
92
|
+
Annotate metrics to a queryset related to the given metric key and basket class.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
queryset (models.QuerySet): The queryset to be annotated.
|
|
96
|
+
metric_key (MetricKey): The metric key or MetricKey instance to use for annotation.
|
|
97
|
+
metric_basket_class (Type[WBModel]): The basket class associated with the metric.
|
|
98
|
+
val_date (date, optional): The date for the metrics. Defaults to None.
|
|
99
|
+
basket_label (str, optional): The label to identify the basket in the subquery queryset. Defaults to "id".
|
|
100
|
+
instrument_label (Union[str, None], optional): The label to identify the instrument in the subquery queryset, if any. Defaults to None.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
models.QuerySet: The annotated queryset with the metrics included. (with field keys as returned by MetricField.get_fields)
|
|
104
|
+
|
|
105
|
+
Side Effects:
|
|
106
|
+
- Annotates the given queryset with subqueries for the specified metrics.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
backend_class: AbstractBackend = backend_registry[metric_key, metric_basket_class]
|
|
110
|
+
|
|
111
|
+
content_type = ContentType.objects.get_for_model(backend_class.BASKET_MODEL_CLASS)
|
|
112
|
+
subquery = InstrumentMetric.objects.filter(
|
|
113
|
+
basket_content_type=content_type,
|
|
114
|
+
basket_id=models.OuterRef(basket_label),
|
|
115
|
+
key=metric_key.key,
|
|
116
|
+
date=val_date,
|
|
117
|
+
)
|
|
118
|
+
if instrument_label:
|
|
119
|
+
subquery = subquery.filter(instrument=models.OuterRef(instrument_label))
|
|
120
|
+
queryset = queryset.annotate(
|
|
121
|
+
**{
|
|
122
|
+
k: models.Subquery(
|
|
123
|
+
subquery.annotate(
|
|
124
|
+
casted_metric=Cast(
|
|
125
|
+
NullIf(models.F("metrics__" + v), models.Value("null")),
|
|
126
|
+
output_field=models.FloatField(),
|
|
127
|
+
)
|
|
128
|
+
).values("casted_metric")[:1]
|
|
129
|
+
)
|
|
130
|
+
for k, v in metric_key.subfields_filter_map.items()
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# annotate extra subfields
|
|
135
|
+
queryset = queryset.annotate(
|
|
136
|
+
**{
|
|
137
|
+
f"{metric_key.key}_{extra_subfield.key}": models.Subquery(
|
|
138
|
+
subquery.annotate(
|
|
139
|
+
casted_metric=Cast(
|
|
140
|
+
KeyTextTransform(extra_subfield.key, "metrics"), output_field=extra_subfield.field_type()
|
|
141
|
+
),
|
|
142
|
+
).values("casted_metric")[:1]
|
|
143
|
+
)
|
|
144
|
+
for extra_subfield in metric_key.extra_subfields
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return queryset
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def get_endpoint_basename(cls) -> str:
|
|
152
|
+
return "metric:instrumentmetric"
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def get_representation_endpoint(cls) -> str:
|
|
156
|
+
return "metric:instrumentmetricrepresentation-list"
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def get_representation_value_key(cls) -> str:
|
|
160
|
+
return "id"
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def get_representation_label_key(cls) -> str:
|
|
164
|
+
return "{{key}} - {{date}} - {{basket_repr}}"
|
|
165
|
+
|
|
166
|
+
class Meta:
|
|
167
|
+
verbose_name = "Instrument Metric"
|
|
168
|
+
verbose_name_plural = "Instrument Metrics"
|
|
169
|
+
constraints = [
|
|
170
|
+
models.UniqueConstraint(
|
|
171
|
+
name="unique_instrument_metric",
|
|
172
|
+
fields=["basket_content_type", "basket_id", "instrument", "date", "key"],
|
|
173
|
+
nulls_distinct=False,
|
|
174
|
+
)
|
|
175
|
+
]
|
|
176
|
+
indexes = [
|
|
177
|
+
models.Index(
|
|
178
|
+
fields=["basket_content_type", "basket_id", "key", "date"],
|
|
179
|
+
)
|
|
180
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Any, Generator
|
|
4
|
+
|
|
5
|
+
from django.db import models
|
|
6
|
+
from django.db.utils import DataError
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
from .backends.base import AbstractBackend
|
|
10
|
+
from .dto import Metric
|
|
11
|
+
from .exceptions import MetricInvalidParameterException
|
|
12
|
+
from .models import InstrumentMetric
|
|
13
|
+
from .registry import backend_registry
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MetricOrchestrator:
|
|
17
|
+
"""
|
|
18
|
+
Orchestrator class responsible to gather the proper metric backends and their respective queryset of baskets
|
|
19
|
+
|
|
20
|
+
For each parameters, the orchestrator is responsible to compute and store the generated metrics
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, val_date: date | None, key: str | None = None, basket: Any | None = None, **kwargs):
|
|
24
|
+
self.basket = basket
|
|
25
|
+
self.val_date = val_date
|
|
26
|
+
self.backend_classes = backend_registry.get(metric_key=key, model_class=basket.__class__ if basket else None)
|
|
27
|
+
|
|
28
|
+
def _iterate_baskets(self, backend: AbstractBackend) -> models.QuerySet:
|
|
29
|
+
"""
|
|
30
|
+
Retrieve and filter the queryset of baskets from the given backend.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
backend (AbstractBackend): The backend from which to retrieve the queryset of baskets.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
models.QuerySet: A queryset of baskets, filtered by the current basket if specified.
|
|
37
|
+
"""
|
|
38
|
+
qs = backend.get_queryset()
|
|
39
|
+
if self.basket:
|
|
40
|
+
qs = qs.filter(id=self.basket.id)
|
|
41
|
+
return qs
|
|
42
|
+
|
|
43
|
+
def _get_parameters(self) -> list[tuple[AbstractBackend, Any]]:
|
|
44
|
+
"""
|
|
45
|
+
Generate a list of parameters consisting of metric backend instances and baskets.
|
|
46
|
+
|
|
47
|
+
This method initializes backends for each backend class with the validation date,
|
|
48
|
+
iterates through the baskets, and creates a list of tuples containing the backend
|
|
49
|
+
and each basket.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List[Tuple[AbstractBackend, Any]]: A list of tuples where each tuple contains
|
|
53
|
+
an instance of a instantiated metric backend and a corresponding basket to compute metrics from.
|
|
54
|
+
"""
|
|
55
|
+
parameters = []
|
|
56
|
+
for backend_class in self.backend_classes:
|
|
57
|
+
backend = backend_class(self.val_date)
|
|
58
|
+
for basket in self._iterate_baskets(backend):
|
|
59
|
+
parameters.append((backend, basket))
|
|
60
|
+
return parameters
|
|
61
|
+
|
|
62
|
+
def get_results(self, debug: bool = False) -> Generator[Metric, None, None]:
|
|
63
|
+
"""
|
|
64
|
+
Compute and yield metrics based on the parameters obtained from `_get_parameters`
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
debug (bool, optional): If True, wraps the parameters list in a tqdm generator for progress tracking. Defaults to False.
|
|
68
|
+
|
|
69
|
+
Yields:
|
|
70
|
+
Metric: Each DTO metric computed by the backend for the given baskets.
|
|
71
|
+
"""
|
|
72
|
+
parameters = self._get_parameters()
|
|
73
|
+
if debug:
|
|
74
|
+
# if debug mode is enabled, we wrap the parameters list into a tqdm generator
|
|
75
|
+
parameters = tqdm(parameters)
|
|
76
|
+
for parameters in parameters:
|
|
77
|
+
with suppress(MetricInvalidParameterException):
|
|
78
|
+
yield from parameters[0].compute_metrics(parameters[1])
|
|
79
|
+
|
|
80
|
+
def process(self, debug: bool = False):
|
|
81
|
+
"""
|
|
82
|
+
Process the computation of metrics and update or create InstrumentMetric instances accordingly.
|
|
83
|
+
|
|
84
|
+
Side Effects:
|
|
85
|
+
- Updates or creates InstrumentMetric instances based on the computed metrics.
|
|
86
|
+
"""
|
|
87
|
+
# we need to see how one threaded loop is fast enough
|
|
88
|
+
errors = []
|
|
89
|
+
for metric in self.get_results(debug=debug):
|
|
90
|
+
try:
|
|
91
|
+
InstrumentMetric.update_or_create_from_metric(metric)
|
|
92
|
+
except DataError:
|
|
93
|
+
errors.append(metric)
|
|
94
|
+
return errors
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from collections import OrderedDict, defaultdict
|
|
2
|
+
from typing import TYPE_CHECKING, Type
|
|
3
|
+
|
|
4
|
+
from django.db.models import Model
|
|
5
|
+
|
|
6
|
+
from .dto import MetricKey
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from wbfdm.contrib.metric.backends.base import AbstractBackend
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# The cached metric backend registry as a dictionary of dictionary (e.g. {key: {model_class: {backend_class}})
|
|
13
|
+
class BackendRegistry:
|
|
14
|
+
_key_label_map: dict[str, str] = dict()
|
|
15
|
+
_metric_key_map: dict[str, MetricKey] = dict()
|
|
16
|
+
_overrode_model_class: dict[MetricKey, list[Type[Model]]] = defaultdict(list)
|
|
17
|
+
registry: dict[MetricKey, OrderedDict] = defaultdict(OrderedDict)
|
|
18
|
+
|
|
19
|
+
def __getitem__(self, index):
|
|
20
|
+
key = index[0]
|
|
21
|
+
if isinstance(key, str):
|
|
22
|
+
key = self._metric_key_map[key]
|
|
23
|
+
return self.registry[key][index[1]]
|
|
24
|
+
|
|
25
|
+
def get_choices(self, keys: list[MetricKey] | None = None) -> list[tuple[str, str]]:
|
|
26
|
+
if not keys:
|
|
27
|
+
keys = list(self.registry.keys())
|
|
28
|
+
return [(key.key, key.label) for key in keys]
|
|
29
|
+
|
|
30
|
+
def set(
|
|
31
|
+
self,
|
|
32
|
+
metric_key: MetricKey,
|
|
33
|
+
model_class: Type[Model],
|
|
34
|
+
backend: Type,
|
|
35
|
+
move_first: bool = False,
|
|
36
|
+
override_backend: bool = False,
|
|
37
|
+
):
|
|
38
|
+
self._metric_key_map[metric_key.key] = metric_key
|
|
39
|
+
self._key_label_map[metric_key.key] = metric_key.label
|
|
40
|
+
# we ensure that the registered key and model class pair are not already registered and lock as override
|
|
41
|
+
if model_class not in self._overrode_model_class[metric_key]:
|
|
42
|
+
self.registry[metric_key][model_class] = backend
|
|
43
|
+
if move_first:
|
|
44
|
+
self.registry[metric_key].move_to_end(model_class, last=False)
|
|
45
|
+
if override_backend:
|
|
46
|
+
self._overrode_model_class[metric_key].append(model_class)
|
|
47
|
+
|
|
48
|
+
def get(
|
|
49
|
+
self, metric_key: MetricKey | str | None = None, model_class: Type[Model] | None = None
|
|
50
|
+
) -> list[Type["AbstractBackend"]]:
|
|
51
|
+
# Initialize the backend classes list to iterate over
|
|
52
|
+
registry = self.registry
|
|
53
|
+
if isinstance(metric_key, str):
|
|
54
|
+
metric_key = self._metric_key_map[metric_key]
|
|
55
|
+
if metric_key:
|
|
56
|
+
# if key is provided, we return only the backends associated with that metric key
|
|
57
|
+
if metric_key not in self.registry:
|
|
58
|
+
raise ValueError(f"key {metric_key.key} does not belong to a registered backend")
|
|
59
|
+
registry = {metric_key: registry[metric_key]}
|
|
60
|
+
|
|
61
|
+
if model_class:
|
|
62
|
+
# if the metric needs to be computed only for a specific basket, we filter out the backend classes related to his basket class
|
|
63
|
+
registry = {
|
|
64
|
+
key: {_class: _dataclass}
|
|
65
|
+
for key, d in registry.items()
|
|
66
|
+
for _class, _dataclass in d.items()
|
|
67
|
+
if _class == model_class
|
|
68
|
+
}
|
|
69
|
+
backends = []
|
|
70
|
+
for _, ordered_dict in registry.items():
|
|
71
|
+
for _, backend in ordered_dict.items():
|
|
72
|
+
if backend not in backends:
|
|
73
|
+
backends.append(backend)
|
|
74
|
+
return backends
|
|
75
|
+
|
|
76
|
+
def keys(self):
|
|
77
|
+
return self._metric_key_map.keys()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
backend_registry = BackendRegistry()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from rest_framework.reverse import reverse
|
|
2
|
+
from wbcore import serializers as wb_serializers
|
|
3
|
+
from wbcore.content_type.serializers import ContentTypeRepresentationSerializer
|
|
4
|
+
from wbfdm.contrib.metric.models import InstrumentMetric
|
|
5
|
+
from wbfdm.serializers.instruments import InstrumentRepresentationSerializer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InstrumentMetricRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
9
|
+
class Meta:
|
|
10
|
+
model = InstrumentMetric
|
|
11
|
+
fields = ("id", "key", "date", "basket_repr")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InstrumentMetricModelSerializer(wb_serializers.ModelSerializer):
|
|
15
|
+
_basket_content_type = ContentTypeRepresentationSerializer(source="basket_content_type")
|
|
16
|
+
_instrument = InstrumentRepresentationSerializer(source="instrument")
|
|
17
|
+
_parent_metric = InstrumentMetricRepresentationSerializer(source="parent_metric")
|
|
18
|
+
_group_key = wb_serializers.CharField(read_only=True)
|
|
19
|
+
|
|
20
|
+
@wb_serializers.register_resource()
|
|
21
|
+
def additional_resources(self, instance, request, user):
|
|
22
|
+
return {
|
|
23
|
+
"children_metrics": f'{reverse("metric:instrumentmetric-list", args=[], request=request)}?parent_metric={instance.id}'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class Meta:
|
|
27
|
+
model = InstrumentMetric
|
|
28
|
+
fields = (
|
|
29
|
+
"id",
|
|
30
|
+
"basket_content_type",
|
|
31
|
+
"_basket_content_type",
|
|
32
|
+
"basket_repr",
|
|
33
|
+
"basket_id",
|
|
34
|
+
"instrument",
|
|
35
|
+
"_instrument",
|
|
36
|
+
"date",
|
|
37
|
+
"key",
|
|
38
|
+
"metrics",
|
|
39
|
+
"parent_metric",
|
|
40
|
+
"_parent_metric",
|
|
41
|
+
"_group_key",
|
|
42
|
+
"_additional_resources",
|
|
43
|
+
)
|
|
44
|
+
read_only_fields = fields
|
|
@@ -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
|