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,544 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from celery import shared_task
|
|
6
|
+
from django.db import models, transaction
|
|
7
|
+
from django.db.models import (
|
|
8
|
+
Case,
|
|
9
|
+
DecimalField,
|
|
10
|
+
Exists,
|
|
11
|
+
ExpressionWrapper,
|
|
12
|
+
F,
|
|
13
|
+
OuterRef,
|
|
14
|
+
Q,
|
|
15
|
+
QuerySet,
|
|
16
|
+
Subquery,
|
|
17
|
+
Value,
|
|
18
|
+
When,
|
|
19
|
+
)
|
|
20
|
+
from django.db.models.signals import post_save
|
|
21
|
+
from django.dispatch import receiver
|
|
22
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
23
|
+
from wbcore.contrib.io.mixins import ImportMixin
|
|
24
|
+
from wbcore.models import DynamicDecimalField, DynamicFloatField, DynamicModel, WBModel
|
|
25
|
+
from wbcore.signals import pre_merge
|
|
26
|
+
from wbfdm.analysis.financial_analysis.financial_statistics_analysis import (
|
|
27
|
+
FinancialStatistics,
|
|
28
|
+
)
|
|
29
|
+
from wbfdm.import_export.handlers.instrument_price import InstrumentPriceImportHandler
|
|
30
|
+
|
|
31
|
+
from .mixin.financials_computed import InstrumentPriceComputedMixin
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ValidPricesQueryset(QuerySet):
|
|
35
|
+
def filter_only_valid_prices(self) -> QuerySet:
|
|
36
|
+
"""
|
|
37
|
+
Filter the queryset to remove duplicate in case calculated and non-calculated prices are present for the same date/product/type
|
|
38
|
+
"""
|
|
39
|
+
return self.annotate(
|
|
40
|
+
real_price_exists=Exists(
|
|
41
|
+
self.filter(
|
|
42
|
+
instrument=OuterRef("instrument"),
|
|
43
|
+
date=OuterRef("date"),
|
|
44
|
+
calculated=False,
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
).filter(Q(calculated=False) | (Q(real_price_exists=False) & Q(calculated=True)))
|
|
48
|
+
|
|
49
|
+
def annotate_market_data(self):
|
|
50
|
+
base_qs = ValidPricesQueryset(self.model, using=self._db)
|
|
51
|
+
|
|
52
|
+
return self.annotate(
|
|
53
|
+
currency_fx_rate_to_usd_rate=F("currency_fx_rate_to_usd__value"),
|
|
54
|
+
calculated_outstanding_shares=Subquery(
|
|
55
|
+
base_qs.filter(instrument=OuterRef("instrument"), calculated=True, date=OuterRef("date")).values(
|
|
56
|
+
"outstanding_shares"
|
|
57
|
+
)[:1]
|
|
58
|
+
),
|
|
59
|
+
internal_outstanding_shares=ExpressionWrapper(
|
|
60
|
+
Case( # Annotate the parent security if exists
|
|
61
|
+
When(
|
|
62
|
+
outstanding_shares__isnull=False,
|
|
63
|
+
then=F("outstanding_shares"),
|
|
64
|
+
),
|
|
65
|
+
default=F("calculated_outstanding_shares"),
|
|
66
|
+
),
|
|
67
|
+
output_field=DecimalField(max_digits=4),
|
|
68
|
+
),
|
|
69
|
+
internal_market_capitalization=Case(
|
|
70
|
+
When(
|
|
71
|
+
market_capitalization__isnull=True,
|
|
72
|
+
then=ExpressionWrapper(
|
|
73
|
+
F("internal_outstanding_shares") * F("net_value"),
|
|
74
|
+
output_field=DecimalField(max_digits=4),
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
default=ExpressionWrapper(F("market_capitalization"), output_field=DecimalField(max_digits=4)),
|
|
78
|
+
),
|
|
79
|
+
internal_market_capitalization_usd=F("internal_market_capitalization") / F("currency_fx_rate_to_usd_rate"),
|
|
80
|
+
calculated_volume=Subquery(
|
|
81
|
+
base_qs.filter(instrument=OuterRef("instrument"), calculated=True, date=OuterRef("date")).values(
|
|
82
|
+
"volume"
|
|
83
|
+
)[:1]
|
|
84
|
+
),
|
|
85
|
+
internal_volume=ExpressionWrapper(
|
|
86
|
+
Case( # Annotate the parent security if exists
|
|
87
|
+
When(
|
|
88
|
+
volume__isnull=False,
|
|
89
|
+
then=F("volume"),
|
|
90
|
+
),
|
|
91
|
+
default=F("calculated_volume"),
|
|
92
|
+
),
|
|
93
|
+
output_field=models.FloatField(),
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def annotate_base_data(self):
|
|
98
|
+
return self.annotate(
|
|
99
|
+
currency_fx_rate_to_usd_rate=F("currency_fx_rate_to_usd__value"),
|
|
100
|
+
market_capitalization_usd=ExpressionWrapper(
|
|
101
|
+
F("market_capitalization") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField(max_digits=4)
|
|
102
|
+
),
|
|
103
|
+
net_value_usd=F("net_value") / F("currency_fx_rate_to_usd_rate"),
|
|
104
|
+
gross_value_usd=F("gross_value") / F("currency_fx_rate_to_usd_rate"),
|
|
105
|
+
volume_usd=ExpressionWrapper(
|
|
106
|
+
F("volume") * F("net_value") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField()
|
|
107
|
+
),
|
|
108
|
+
volume_50d_usd=ExpressionWrapper(
|
|
109
|
+
F("volume_50d") * F("net_value") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField()
|
|
110
|
+
),
|
|
111
|
+
volume_200d_usd=ExpressionWrapper(
|
|
112
|
+
F("volume_200d") * F("net_value") / F("currency_fx_rate_to_usd_rate"), output_field=DecimalField()
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def annotate_security_data(self):
|
|
117
|
+
return self.annotate(
|
|
118
|
+
security=Case( # Annotate the parent security if exists
|
|
119
|
+
When(instrument__parent__isnull=False, then=F("instrument__parent")),
|
|
120
|
+
default=F("instrument"),
|
|
121
|
+
),
|
|
122
|
+
security_instrument_type_key=Case( # Annotate the parent security if exists
|
|
123
|
+
When(
|
|
124
|
+
instrument__parent__isnull=False,
|
|
125
|
+
then=F("instrument__parent__instrument_type__key"),
|
|
126
|
+
),
|
|
127
|
+
default=F("instrument__instrument_type__key"),
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def annotate_all(self):
|
|
132
|
+
return self.annotate_market_data().annotate_security_data().annotate_base_data()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class InstrumentPriceManager(models.Manager):
|
|
136
|
+
def __init__(self, with_annotation: bool = False, *args, **kwargs):
|
|
137
|
+
self.with_annotation = with_annotation
|
|
138
|
+
super().__init__(*args, **kwargs)
|
|
139
|
+
|
|
140
|
+
def get_queryset(self) -> ValidPricesQueryset:
|
|
141
|
+
qs = ValidPricesQueryset(self.model)
|
|
142
|
+
if self.with_annotation:
|
|
143
|
+
qs = qs.annotate_all()
|
|
144
|
+
return qs
|
|
145
|
+
|
|
146
|
+
def filtered_by_instruments(self, instrument_queryset, *other_instruments):
|
|
147
|
+
return self.filter(models.Q(instrument__in=instrument_queryset) | models.Q(instrument__in=other_instruments))
|
|
148
|
+
|
|
149
|
+
def filter_only_valid_prices(self) -> QuerySet:
|
|
150
|
+
return self.get_queryset().filter_only_valid_prices()
|
|
151
|
+
|
|
152
|
+
def annotate_market_data(self) -> QuerySet:
|
|
153
|
+
return self.get_queryset().annotate_market_data()
|
|
154
|
+
|
|
155
|
+
def annotate_base_data(self) -> QuerySet:
|
|
156
|
+
return self.get_queryset().annotate_base_data()
|
|
157
|
+
|
|
158
|
+
def annotate_security_data(self) -> QuerySet:
|
|
159
|
+
return self.get_queryset().annotate_security_data()
|
|
160
|
+
|
|
161
|
+
def annotate_all(self) -> QuerySet:
|
|
162
|
+
return self.get_queryset().annotate_all()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class AnnotatedInstrumentPriceManager(InstrumentPriceManager):
|
|
166
|
+
def get_queryset(self):
|
|
167
|
+
return super().get_queryset().annotate_market_data().annotate_base_data().annotate_security_data()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class InstrumentPrice(
|
|
171
|
+
ImportMixin,
|
|
172
|
+
InstrumentPriceComputedMixin,
|
|
173
|
+
DynamicModel,
|
|
174
|
+
WBModel,
|
|
175
|
+
):
|
|
176
|
+
import_export_handler_class = InstrumentPriceImportHandler
|
|
177
|
+
# Base fields
|
|
178
|
+
instrument = models.ForeignKey(
|
|
179
|
+
to="wbfdm.Instrument",
|
|
180
|
+
related_name="prices",
|
|
181
|
+
on_delete=models.PROTECT,
|
|
182
|
+
limit_choices_to=models.Q(children__isnull=True),
|
|
183
|
+
verbose_name="Instrument",
|
|
184
|
+
blank=True,
|
|
185
|
+
null=True,
|
|
186
|
+
)
|
|
187
|
+
date = models.DateField(verbose_name="Date")
|
|
188
|
+
calculated = models.BooleanField(default=False, verbose_name="Is Calculated")
|
|
189
|
+
|
|
190
|
+
net_value = models.DecimalField(max_digits=16, decimal_places=6, verbose_name="Value (Net)")
|
|
191
|
+
gross_value = DynamicDecimalField(
|
|
192
|
+
max_digits=16,
|
|
193
|
+
decimal_places=6,
|
|
194
|
+
verbose_name="Value (Gross)",
|
|
195
|
+
) # TODO: I think we need to remove this field that is not really used here.
|
|
196
|
+
|
|
197
|
+
outstanding_shares = DynamicDecimalField(
|
|
198
|
+
decimal_places=4,
|
|
199
|
+
max_digits=16,
|
|
200
|
+
verbose_name="Outstanding Shares",
|
|
201
|
+
help_text="The amount of outstanding share for this instrument",
|
|
202
|
+
)
|
|
203
|
+
outstanding_shares_consolidated = DynamicDecimalField(
|
|
204
|
+
decimal_places=4,
|
|
205
|
+
max_digits=16,
|
|
206
|
+
verbose_name="Outstanding Shares (Consolidated)",
|
|
207
|
+
help_text="The amount of outstanding share for this instrument",
|
|
208
|
+
)
|
|
209
|
+
########################################################
|
|
210
|
+
# ASSET STATISTICS #
|
|
211
|
+
########################################################
|
|
212
|
+
|
|
213
|
+
volume = models.FloatField(
|
|
214
|
+
null=True,
|
|
215
|
+
blank=True,
|
|
216
|
+
verbose_name="Volume",
|
|
217
|
+
help_text="The Volume of the Asset on the price date of the Asset.",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
volume_50d = DynamicFloatField(
|
|
221
|
+
verbose_name="Average Volume (50 Days)",
|
|
222
|
+
help_text="The Average Volume of the Asset over the last 50 days from the price date of the Asset.",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
volume_200d = models.FloatField(
|
|
226
|
+
null=True,
|
|
227
|
+
blank=True,
|
|
228
|
+
verbose_name="Average Volume (200 Days)",
|
|
229
|
+
help_text="The Average Volume of the Asset over the last 200 days from the price date of the Asset.",
|
|
230
|
+
)
|
|
231
|
+
market_capitalization = models.FloatField(
|
|
232
|
+
null=True,
|
|
233
|
+
blank=True,
|
|
234
|
+
verbose_name="Market Capitalization",
|
|
235
|
+
help_text="The Market Capitalization of the Asset the price date of the Asset.",
|
|
236
|
+
)
|
|
237
|
+
market_capitalization_consolidated = models.FloatField(
|
|
238
|
+
null=True,
|
|
239
|
+
blank=True,
|
|
240
|
+
verbose_name="Market Capitalization (Consolidated)",
|
|
241
|
+
help_text="the consolidated market value of a company in local currency.",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
currency_fx_rate_to_usd = models.ForeignKey(
|
|
245
|
+
to="currency.CurrencyFXRates",
|
|
246
|
+
related_name="instrument_prices",
|
|
247
|
+
on_delete=models.PROTECT,
|
|
248
|
+
blank=True,
|
|
249
|
+
null=True,
|
|
250
|
+
verbose_name="Instrument Currency Rate",
|
|
251
|
+
help_text="Rate to between instrument currency and USD",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Statistics
|
|
255
|
+
lock_statistics = models.BooleanField(
|
|
256
|
+
default=False,
|
|
257
|
+
help_text="If True, a save will not override the beta, correlation and sharpe ratio",
|
|
258
|
+
)
|
|
259
|
+
sharpe_ratio = models.FloatField(blank=True, null=True, verbose_name="Sharpe Ratio")
|
|
260
|
+
correlation = models.FloatField(blank=True, null=True, verbose_name="Correlation")
|
|
261
|
+
beta = models.FloatField(blank=True, null=True, verbose_name="Beta")
|
|
262
|
+
annualized_daily_volatility = models.FloatField(blank=True, null=True, verbose_name="Annualized Volatility")
|
|
263
|
+
|
|
264
|
+
created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
|
|
265
|
+
modified = models.DateTimeField(
|
|
266
|
+
verbose_name="Modified",
|
|
267
|
+
auto_now=True,
|
|
268
|
+
)
|
|
269
|
+
# custom_beta_180d = DynamicFloatField(verbose_name="Custom Beta (180 days)")
|
|
270
|
+
# custom_beta_1y = DynamicFloatField(verbose_name="Custom Beta (1 Years)")
|
|
271
|
+
# custom_beta_2y = DynamicFloatField(verbose_name="Custom Beta (2 Years)")
|
|
272
|
+
# custom_beta_3y = DynamicFloatField(verbose_name="Custom Beta (3 Years)")
|
|
273
|
+
# custom_beta_5y = DynamicFloatField(verbose_name="Custom Beta (4 Years)")
|
|
274
|
+
#
|
|
275
|
+
# # Performances fields
|
|
276
|
+
# performance_1d = DynamicDecimalField(
|
|
277
|
+
# verbose_name="Performance 1D", help_text="Performance 1D", max_digits=16, decimal_places=6
|
|
278
|
+
# )
|
|
279
|
+
# performance_7d = DynamicDecimalField(
|
|
280
|
+
# verbose_name="Performance (1W)", help_text="Performance 7 days rolling", max_digits=16, decimal_places=6
|
|
281
|
+
# )
|
|
282
|
+
# performance_30d = DynamicDecimalField(
|
|
283
|
+
# verbose_name="Performance (1M)", help_text="Performance 30 days rolling", max_digits=16, decimal_places=6
|
|
284
|
+
# )
|
|
285
|
+
# performance_90d = DynamicDecimalField(
|
|
286
|
+
# verbose_name="Performance (1Q)", help_text="Performance 90 days rolling", max_digits=16, decimal_places=6
|
|
287
|
+
# )
|
|
288
|
+
# performance_365d = DynamicDecimalField(
|
|
289
|
+
# verbose_name="Performance (1Y)", help_text="Performance 365 days rolling", max_digits=16, decimal_places=6
|
|
290
|
+
# )
|
|
291
|
+
# performance_ytd = DynamicDecimalField(
|
|
292
|
+
# verbose_name="Performance (YTD)", help_text="Performance Year-to-date", max_digits=16, decimal_places=6
|
|
293
|
+
# )
|
|
294
|
+
# performance_inception = DynamicDecimalField(
|
|
295
|
+
# verbose_name="Performance (Inception)",
|
|
296
|
+
# help_text="Performance since inception",
|
|
297
|
+
# max_digits=16,
|
|
298
|
+
# decimal_places=6,
|
|
299
|
+
# )
|
|
300
|
+
|
|
301
|
+
objects = InstrumentPriceManager()
|
|
302
|
+
annotated_objects = InstrumentPriceManager(with_annotation=True)
|
|
303
|
+
|
|
304
|
+
class Meta:
|
|
305
|
+
verbose_name = "Instrument Price"
|
|
306
|
+
verbose_name_plural = "Instrument Prices"
|
|
307
|
+
constraints = [
|
|
308
|
+
models.CheckConstraint(
|
|
309
|
+
check=~models.Q(date__week_day__in=[1, 7]),
|
|
310
|
+
name="%(app_label)s_%(class)s_weekday_constraint",
|
|
311
|
+
),
|
|
312
|
+
models.UniqueConstraint(fields=["instrument", "date", "calculated"], name="unique_price"),
|
|
313
|
+
]
|
|
314
|
+
indexes = [
|
|
315
|
+
models.Index(
|
|
316
|
+
name="fdm_instrumentprice_base_idx",
|
|
317
|
+
fields=["calculated", "date", "instrument"],
|
|
318
|
+
),
|
|
319
|
+
models.Index(
|
|
320
|
+
name="fdm_instrumentprice_idx1",
|
|
321
|
+
fields=["calculated", "instrument"],
|
|
322
|
+
),
|
|
323
|
+
models.Index(
|
|
324
|
+
name="fdm_instrumentprice_idx2",
|
|
325
|
+
fields=["instrument"],
|
|
326
|
+
),
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def _net_value_usd(self):
|
|
331
|
+
if self.currency_fx_rate_to_usd:
|
|
332
|
+
return getattr(self, "net_value_usd", self.net_value * self.currency_fx_rate_to_usd.value)
|
|
333
|
+
|
|
334
|
+
def save(self, *args, **kwargs):
|
|
335
|
+
if not self.currency_fx_rate_to_usd:
|
|
336
|
+
with suppress(CurrencyFXRates.DoesNotExist):
|
|
337
|
+
self.currency_fx_rate_to_usd = CurrencyFXRates.objects.get(
|
|
338
|
+
date=self.date, currency=self.instrument.currency
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if self.market_capitalization_consolidated is None:
|
|
342
|
+
self.market_capitalization_consolidated = self.market_capitalization
|
|
343
|
+
|
|
344
|
+
super().save(*args, **kwargs)
|
|
345
|
+
|
|
346
|
+
def __str__(self):
|
|
347
|
+
return f"{self.instrument.name}: {self.net_value} {self.date:%d.%m.%Y}"
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def previous_price(self):
|
|
351
|
+
"""Returns the previous instrument prices if one exists or None
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
instrument.InstrumentPrice | None -- Previous InstrumentPrice
|
|
355
|
+
"""
|
|
356
|
+
try:
|
|
357
|
+
return InstrumentPrice.objects.filter(
|
|
358
|
+
instrument=self.instrument, date__lt=self.date, calculated=self.calculated
|
|
359
|
+
).latest("date")
|
|
360
|
+
except InstrumentPrice.DoesNotExist:
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def next_price(self):
|
|
365
|
+
"""Returns the next instrument prices if one exists or None
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
instrument.InstrumentPrice | None -- Next InstrumentPrice
|
|
369
|
+
"""
|
|
370
|
+
try:
|
|
371
|
+
return self.instrument.prices.filter(date__gt=self.date, calculated=self.calculated).earliest("date")
|
|
372
|
+
except InstrumentPrice.DoesNotExist:
|
|
373
|
+
return None
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def shares(self):
|
|
377
|
+
"""Returns the number of shares of a instrument
|
|
378
|
+
|
|
379
|
+
The number of shares are the sum of all customer trades
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
int -- Shares
|
|
383
|
+
"""
|
|
384
|
+
return self.instrument.total_shares(self.date)
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def valid_outstanding_shares(self):
|
|
388
|
+
prices = self.instrument.prices.filter(date=self.date, outstanding_shares__isnull=False).order_by("calculated")
|
|
389
|
+
return prices.last().outstanding_shares if prices.exists() else self.shares
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def nominal_value(self):
|
|
393
|
+
"""Returns the nominal value of a instrument
|
|
394
|
+
|
|
395
|
+
The nominal value is the number of current shares multiplied by the share price of a instrument
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
int -- Nominal Value
|
|
399
|
+
"""
|
|
400
|
+
return self.instrument.nominal_value(self.date)
|
|
401
|
+
|
|
402
|
+
def compute_and_update_statistics(self, min_period: int = 20):
|
|
403
|
+
prices_df = (
|
|
404
|
+
pd.DataFrame(
|
|
405
|
+
InstrumentPrice.objects.filter_only_valid_prices()
|
|
406
|
+
.filter(instrument=self.instrument, date__lte=self.date)
|
|
407
|
+
.values_list("date", "net_value"),
|
|
408
|
+
columns=["date", "net_value"],
|
|
409
|
+
)
|
|
410
|
+
.set_index("date")
|
|
411
|
+
.sort_index()["net_value"]
|
|
412
|
+
)
|
|
413
|
+
if not prices_df.empty and prices_df.shape[0] >= min_period:
|
|
414
|
+
financial_statistics = FinancialStatistics(prices_df)
|
|
415
|
+
min_date = prices_df.index.min()
|
|
416
|
+
if risk_free_rate := self.instrument.primary_risk_instrument:
|
|
417
|
+
risk_free_rate_df = (
|
|
418
|
+
pd.DataFrame(
|
|
419
|
+
risk_free_rate.valuations.filter(date__gte=min_date, date__lte=self.date).values_list(
|
|
420
|
+
"date", "net_value"
|
|
421
|
+
),
|
|
422
|
+
columns=["date", "net_value"],
|
|
423
|
+
)
|
|
424
|
+
.set_index("date")
|
|
425
|
+
.sort_index()["net_value"]
|
|
426
|
+
)
|
|
427
|
+
if sharpe_ratio := financial_statistics.get_sharpe_ratio(risk_free_rate_df):
|
|
428
|
+
self.sharpe_ratio = sharpe_ratio
|
|
429
|
+
if benchmark := self.instrument.primary_benchmark:
|
|
430
|
+
benchmark_df = (
|
|
431
|
+
pd.DataFrame(
|
|
432
|
+
benchmark.valuations.filter(date__gte=min_date, date__lte=self.date).values_list(
|
|
433
|
+
"date", "net_value"
|
|
434
|
+
),
|
|
435
|
+
columns=["date", "net_value"],
|
|
436
|
+
)
|
|
437
|
+
.set_index("date")
|
|
438
|
+
.sort_index()["net_value"]
|
|
439
|
+
)
|
|
440
|
+
if (beta := financial_statistics.get_beta(benchmark_df)) is not None:
|
|
441
|
+
self.beta = beta
|
|
442
|
+
if (correlation := financial_statistics.get_correlation(benchmark_df)) is not None:
|
|
443
|
+
self.correlation = correlation
|
|
444
|
+
|
|
445
|
+
self.annualized_daily_volatility = financial_statistics.get_annualized_daily_volatility()
|
|
446
|
+
|
|
447
|
+
@classmethod
|
|
448
|
+
def subquery_closest_value(
|
|
449
|
+
cls,
|
|
450
|
+
field_name,
|
|
451
|
+
val_date=None,
|
|
452
|
+
date_name="date",
|
|
453
|
+
instrument_pk_name="instrument__pk",
|
|
454
|
+
date_lookup="lte",
|
|
455
|
+
order_by="-date",
|
|
456
|
+
calculated_filter_value=False,
|
|
457
|
+
):
|
|
458
|
+
index_filter_params = {}
|
|
459
|
+
if calculated_filter_value is not None:
|
|
460
|
+
index_filter_params["calculated"] = calculated_filter_value
|
|
461
|
+
if not val_date and date_name:
|
|
462
|
+
index_filter_params[f"date__{date_lookup}"] = models.OuterRef(date_name)
|
|
463
|
+
elif val_date:
|
|
464
|
+
index_filter_params[f"date__{date_lookup}"] = val_date
|
|
465
|
+
index_filter_params["instrument"] = models.OuterRef(instrument_pk_name)
|
|
466
|
+
qs = cls.objects.filter(**index_filter_params).filter(**{f"{field_name}__isnull": False})
|
|
467
|
+
return models.Subquery(qs.order_by(order_by).values(field_name)[:1])
|
|
468
|
+
|
|
469
|
+
@classmethod
|
|
470
|
+
def annotate_sum_shares(cls, queryset, val_date, date_key="date"):
|
|
471
|
+
"""
|
|
472
|
+
Efficient way to annotate sum of shares without destroying indexing
|
|
473
|
+
"""
|
|
474
|
+
return queryset.annotate(
|
|
475
|
+
sum_shares_calculated=InstrumentPrice.subquery_closest_value(
|
|
476
|
+
"outstanding_shares",
|
|
477
|
+
date_name=date_key,
|
|
478
|
+
instrument_pk_name="pk",
|
|
479
|
+
date_lookup="lte",
|
|
480
|
+
order_by="-date",
|
|
481
|
+
calculated_filter_value=True,
|
|
482
|
+
), # We get the last price whose outstanding_shares is not none and date below date_key
|
|
483
|
+
sum_shares_real=InstrumentPrice.subquery_closest_value(
|
|
484
|
+
"outstanding_shares",
|
|
485
|
+
val_date=val_date,
|
|
486
|
+
date_name=date_key,
|
|
487
|
+
instrument_pk_name="pk",
|
|
488
|
+
date_lookup="exact",
|
|
489
|
+
calculated_filter_value=False,
|
|
490
|
+
),
|
|
491
|
+
sum_shares=Case(
|
|
492
|
+
When(sum_shares_real__isnull=False, then=F("sum_shares_real")),
|
|
493
|
+
When(sum_shares_calculated__isnull=False, then=F("sum_shares_calculated")),
|
|
494
|
+
default=Value(Decimal(0)),
|
|
495
|
+
output_field=DecimalField(),
|
|
496
|
+
),
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
@classmethod
|
|
500
|
+
def get_endpoint_basename(cls):
|
|
501
|
+
return "wbfdm:price"
|
|
502
|
+
|
|
503
|
+
@classmethod
|
|
504
|
+
def get_representation_value_key(cls):
|
|
505
|
+
return "id"
|
|
506
|
+
|
|
507
|
+
@classmethod
|
|
508
|
+
def get_representation_label_key(cls):
|
|
509
|
+
return "{{instrument} {{date}}"
|
|
510
|
+
|
|
511
|
+
@classmethod
|
|
512
|
+
def get_representation_endpoint(cls):
|
|
513
|
+
return "wbfdm:price-list"
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
@receiver(post_save, sender="currency.CurrencyFXRates")
|
|
517
|
+
def rate_creation(sender, instance, created, raw, **kwargs):
|
|
518
|
+
if not raw and created:
|
|
519
|
+
transaction.on_commit(lambda: update_currency_fx_rate_from_created_rate.delay(instance.id))
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
@receiver(pre_merge, sender="wbfdm.Instrument")
|
|
523
|
+
def pre_merge_instrument(sender: models.Model, merged_object, main_object, **kwargs):
|
|
524
|
+
"""
|
|
525
|
+
Simply reassign the prices of the merged instrument to the main instrument if they don't already exist for that day, otherwise simply delete them
|
|
526
|
+
"""
|
|
527
|
+
merged_object.prices.annotate(
|
|
528
|
+
already_exists=Exists(
|
|
529
|
+
InstrumentPrice.objects.filter(
|
|
530
|
+
calculated=OuterRef("calculated"), instrument=main_object, date=OuterRef("date")
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
).filter(already_exists=True).delete()
|
|
534
|
+
merged_object.prices.update(instrument=main_object)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@shared_task(queue="portfolio")
|
|
538
|
+
def update_currency_fx_rate_from_created_rate(rate_id):
|
|
539
|
+
currency_rate = CurrencyFXRates.objects.get(id=rate_id)
|
|
540
|
+
for price in InstrumentPrice.objects.filter(
|
|
541
|
+
instrument__currency=currency_rate.currency, date=currency_rate.date, currency_fx_rate_to_usd__isnull=True
|
|
542
|
+
):
|
|
543
|
+
price.currency_fx_rate_to_usd = currency_rate
|
|
544
|
+
price.save()
|