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,50 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
from wbfdm.models import Instrument
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SummaryTableInstrumentChartTitle(TitleViewConfig):
|
|
6
|
+
def get_list_title(self) -> str:
|
|
7
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
8
|
+
return f"{instrument.name_repr} - Financial Summary Table"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FinancialsGraphInstrumentChartTitle(TitleViewConfig):
|
|
12
|
+
def get_list_title(self) -> str:
|
|
13
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
14
|
+
return f"{instrument.name_repr} - Financials Graph Chart"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProfitabilityRatiosInstrumentChartTitle(TitleViewConfig):
|
|
18
|
+
def get_list_title(self) -> str:
|
|
19
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
20
|
+
return f"{instrument.name_repr} - Profitability Ratios Chart"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CashFlowAnalysisInstrumentTableChartTitle(TitleViewConfig):
|
|
24
|
+
def get_list_title(self) -> str:
|
|
25
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
26
|
+
return f"{instrument.name_repr} - Cash Flow Analysis Table"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CashFlowAnalysisInstrumentBarChartTitle(TitleViewConfig):
|
|
30
|
+
def get_list_title(self) -> str:
|
|
31
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
32
|
+
return f"{instrument.name_repr} - Cash Flow Analysis Bar Chart"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NetDebtAndEbitdaInstrumentChartTitle(TitleViewConfig):
|
|
36
|
+
def get_list_title(self) -> str:
|
|
37
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
38
|
+
return f"{instrument.name_repr} - Net debt and EBITDA Chart"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class EarningsInstrumentChartTitle(TitleViewConfig):
|
|
42
|
+
def get_list_title(self) -> str:
|
|
43
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
44
|
+
return f"{instrument.name_repr} - Earnings Analysis Chart"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FinancialAnalysisGeneratorTitleConfig(TitleViewConfig):
|
|
48
|
+
def get_list_title(self):
|
|
49
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
50
|
+
return f"Ratios for {str(instrument)}"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
from wbfdm.models import Instrument
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InstrumentPriceTitleConfig(TitleViewConfig):
|
|
6
|
+
def get_list_title(self):
|
|
7
|
+
return "Instrument Prices"
|
|
8
|
+
|
|
9
|
+
def get_instance_title(self):
|
|
10
|
+
return "Price: {{_instrument.name}} {{date}}"
|
|
11
|
+
|
|
12
|
+
def get_create_title(self):
|
|
13
|
+
return "New Price"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InstrumentPriceInstrumentTitleConfig(InstrumentPriceTitleConfig):
|
|
17
|
+
def get_list_title(self):
|
|
18
|
+
return f"{str(self.view.instrument)} - Prices"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InstrumentPriceStatisticsInstrumentTitleConfig(TitleViewConfig):
|
|
22
|
+
def get_list_title(self):
|
|
23
|
+
return f"{str(self.view.instrument)} - Statistics Chart"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MonthlyPerformancesInstrumentTitleConfig(TitleViewConfig):
|
|
27
|
+
def get_list_title(self):
|
|
28
|
+
return f"{str(self.view.instrument)} - Monthly Returns Table"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class InstrumentTitleConfigMixin(TitleViewConfig):
|
|
32
|
+
def get_list_title(self):
|
|
33
|
+
assert self.message, "No message has been set"
|
|
34
|
+
instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
|
|
35
|
+
return f"{self.message} {str(instrument)}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FinancialStatisticsInstrumentTitleConfig(InstrumentTitleConfigMixin):
|
|
39
|
+
def get_list_title(self):
|
|
40
|
+
return f"{str(self.view.instrument)} - Financial Statistics Chart"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InstrumentPriceInstrumentDistributionReturnsChartTitleConfig(InstrumentTitleConfigMixin):
|
|
44
|
+
def get_list_title(self):
|
|
45
|
+
return f"{str(self.view.instrument)} - Distribution Returns Chart"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BestAndWorstReturnsInstrumentTitleConfig(InstrumentTitleConfigMixin):
|
|
49
|
+
def get_list_title(self):
|
|
50
|
+
return f"{str(self.view.instrument)} - Best and Worst returns Table "
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InstrumentRequestTitleConfig(TitleViewConfig):
|
|
5
|
+
def get_list_title(self):
|
|
6
|
+
return "Instrument Requests"
|
|
7
|
+
|
|
8
|
+
def get_instance_title(self):
|
|
9
|
+
try:
|
|
10
|
+
obj = self.view.get_object()
|
|
11
|
+
return f"Instrument Request: {str(obj)}"
|
|
12
|
+
except AssertionError:
|
|
13
|
+
return "Instrument Request: {{status}}"
|
|
14
|
+
|
|
15
|
+
def get_create_title(self):
|
|
16
|
+
return "New Instrument Request"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InstrumentTitleConfig(TitleViewConfig):
|
|
5
|
+
def get_list_title(self):
|
|
6
|
+
return "Instruments"
|
|
7
|
+
|
|
8
|
+
def get_instance_title(self):
|
|
9
|
+
try:
|
|
10
|
+
obj = self.view.get_object()
|
|
11
|
+
instrument_type = obj.instrument_type.name if obj.instrument_type else "Instrument"
|
|
12
|
+
return f"{instrument_type}: {str(obj)}"
|
|
13
|
+
except AssertionError:
|
|
14
|
+
return "Instrument: {{name}}"
|
|
15
|
+
|
|
16
|
+
def get_create_title(self):
|
|
17
|
+
return "New Instrument"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SubviewInstrumentTitleViewConfig(TitleViewConfig):
|
|
21
|
+
def get_list_title(self):
|
|
22
|
+
return f"{str(self.view.instrument)} - {self.view.SUBVIEW_NAME} "
|
|
23
|
+
|
|
24
|
+
def get_delete_title(self) -> str:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
def get_instance_title(self) -> str:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
def get_create_title(self) -> str:
|
|
31
|
+
return None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InstrumentFavoriteGroupTitleConfig(TitleViewConfig):
|
|
5
|
+
def get_list_title(self):
|
|
6
|
+
return "Instrument Favorite Groups"
|
|
7
|
+
|
|
8
|
+
def get_instance_title(self):
|
|
9
|
+
try:
|
|
10
|
+
obj = self.view.get_object()
|
|
11
|
+
return f"Favorite Group: {str(obj)}"
|
|
12
|
+
except AssertionError:
|
|
13
|
+
return "Instrument: {{name}}"
|
|
14
|
+
|
|
15
|
+
def get_create_title(self):
|
|
16
|
+
return "New Favorite Group"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClassifiedInstrumentTitleConfig(TitleViewConfig):
|
|
20
|
+
def get_list_title(self) -> str:
|
|
21
|
+
return "Classified Instruments"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
from wbfdm.enums import MarketDataChartType
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PerformanceSummaryChartTitleConfig(TitleViewConfig):
|
|
6
|
+
def get_list_title(self):
|
|
7
|
+
return f"{self.view.instrument} - Performance Summary"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MarketDataChartTitleConfig(TitleViewConfig):
|
|
11
|
+
def get_list_title(self):
|
|
12
|
+
market_data_chart_type = MarketDataChartType(self.request.GET.get("chart_type", "close"))
|
|
13
|
+
return f"{self.view.instrument} - {market_data_chart_type.label} Chart"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InstrumentPriceTitleViewConfig(TitleViewConfig):
|
|
5
|
+
def get_list_title(self):
|
|
6
|
+
return f"{self.view.instrument} - Prices"
|
|
7
|
+
|
|
8
|
+
def get_delete_title(self) -> str:
|
|
9
|
+
return None
|
|
10
|
+
|
|
11
|
+
# def get_instance_title(self) -> str:
|
|
12
|
+
# return None
|
|
13
|
+
|
|
14
|
+
def get_create_title(self) -> str:
|
|
15
|
+
return None
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StatementTitleViewConfig(TitleViewConfig):
|
|
5
|
+
def get_list_title(self):
|
|
6
|
+
try:
|
|
7
|
+
statement_label = self.view.financial_analysis_mapping[self.view.kwargs["statement"]][1]
|
|
8
|
+
return f"{self.view.instrument} - {statement_label} Table"
|
|
9
|
+
except KeyError:
|
|
10
|
+
return f"{self.view.instrument} - Statement"
|
wbfdm/viewsets/esg.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from rest_framework.response import Response
|
|
3
|
+
from wbcore import viewsets
|
|
4
|
+
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
5
|
+
from wbcore.pandas import fields as pf
|
|
6
|
+
from wbfdm.enums import ESG
|
|
7
|
+
from wbfdm.models.instruments import Instrument
|
|
8
|
+
from wbfdm.serializers import InstrumentControversySerializer
|
|
9
|
+
from wbfdm.viewsets.configs import (
|
|
10
|
+
InstrumentESGControversiesEndpointViewConfig,
|
|
11
|
+
InstrumentESGControversiesTitleViewConfig,
|
|
12
|
+
InstrumentESGControversyDisplayViewConfig,
|
|
13
|
+
InstrumentESGPAIDisplayViewConfig,
|
|
14
|
+
InstrumentESGPAIEndpointViewConfig,
|
|
15
|
+
InstrumentESGPAITitleViewConfig,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .mixins import InstrumentMixin
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InstrumentESGControversiesViewSet(InstrumentMixin, viewsets.ViewSet):
|
|
22
|
+
IDENTIFIER = "wbfdm:instrument-controversy"
|
|
23
|
+
display_config_class = InstrumentESGControversyDisplayViewConfig
|
|
24
|
+
title_config_class = InstrumentESGControversiesTitleViewConfig
|
|
25
|
+
endpoint_config_class = InstrumentESGControversiesEndpointViewConfig
|
|
26
|
+
serializer_class = InstrumentControversySerializer
|
|
27
|
+
queryset = Instrument.objects.none()
|
|
28
|
+
permission_classes = []
|
|
29
|
+
|
|
30
|
+
def list(self, request, instrument_id):
|
|
31
|
+
queryset = self.get_queryset()
|
|
32
|
+
serializer = self.get_serializer_class()
|
|
33
|
+
serializer = serializer(queryset, many=True)
|
|
34
|
+
return Response({"results": serializer.data})
|
|
35
|
+
|
|
36
|
+
def get_queryset(self):
|
|
37
|
+
return sorted(
|
|
38
|
+
Instrument.objects.filter(id=self.instrument.id).dl.esg_controversies(),
|
|
39
|
+
key=lambda x: (x["review"] is None, x["review"]),
|
|
40
|
+
reverse=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InstrumentESGPAIViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
45
|
+
queryset = Instrument.objects.none()
|
|
46
|
+
display_config_class = InstrumentESGPAIDisplayViewConfig
|
|
47
|
+
endpoint_config_class = InstrumentESGPAIEndpointViewConfig
|
|
48
|
+
title_config_class = InstrumentESGPAITitleViewConfig
|
|
49
|
+
|
|
50
|
+
def get_queryset(self):
|
|
51
|
+
return Instrument.objects.filter(id=self.instrument.id)
|
|
52
|
+
|
|
53
|
+
def get_pandas_fields(self, request):
|
|
54
|
+
return pf.PandasFields(
|
|
55
|
+
fields=[
|
|
56
|
+
pf.PKField(key="index", label="ID"),
|
|
57
|
+
pf.IntegerField(key="section", label="Section"),
|
|
58
|
+
pf.TextField(key="asi", label="Adverse sustainability indicator"),
|
|
59
|
+
pf.TextField(key="metric", label="Metric"),
|
|
60
|
+
pf.TextField(key="factor", label="Factor"),
|
|
61
|
+
pf.FloatField(key="value", label="Value"),
|
|
62
|
+
]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
66
|
+
df = pd.DataFrame(queryset.dl.esg(values=list(ESG))).reset_index()
|
|
67
|
+
if not df.empty:
|
|
68
|
+
ESG_mapping = ESG.mapping()
|
|
69
|
+
df[["section", "asi", "metric", "factor"]] = pd.DataFrame(
|
|
70
|
+
df.factor_code.map(ESG_mapping).tolist(), index=df.index
|
|
71
|
+
)
|
|
72
|
+
return df
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from wbcore import viewsets
|
|
2
|
+
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
3
|
+
from wbfdm.filters import ExchangeFilterSet
|
|
4
|
+
from wbfdm.models import Exchange
|
|
5
|
+
from wbfdm.serializers import ExchangeModelSerializer, ExchangeRepresentationSerializer
|
|
6
|
+
|
|
7
|
+
from .configs import (
|
|
8
|
+
ExchangeButtonConfig,
|
|
9
|
+
ExchangeDisplayConfig,
|
|
10
|
+
ExchangeEndpointConfig,
|
|
11
|
+
ExchangeTitleConfig,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExchangeRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
16
|
+
queryset = Exchange.objects.all()
|
|
17
|
+
serializer_class = ExchangeRepresentationSerializer
|
|
18
|
+
|
|
19
|
+
search_fields = (
|
|
20
|
+
"name",
|
|
21
|
+
"mic_code",
|
|
22
|
+
"operating_mic_code",
|
|
23
|
+
"bbg_exchange_codes",
|
|
24
|
+
"bbg_composite_primary",
|
|
25
|
+
"bbg_composite",
|
|
26
|
+
"refinitiv_mnemonic",
|
|
27
|
+
"refinitiv_identifier_code",
|
|
28
|
+
"city__name",
|
|
29
|
+
"website",
|
|
30
|
+
"comments",
|
|
31
|
+
)
|
|
32
|
+
ordering_fields = search_fields
|
|
33
|
+
ordering = ["name"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ExchangeModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
|
|
37
|
+
filterset_class = ExchangeFilterSet
|
|
38
|
+
serializer_class = ExchangeModelSerializer
|
|
39
|
+
queryset = Exchange.objects.select_related(
|
|
40
|
+
"country",
|
|
41
|
+
"city",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
search_fields = (
|
|
45
|
+
"name",
|
|
46
|
+
"mic_code",
|
|
47
|
+
"operating_mic_code",
|
|
48
|
+
"bbg_exchange_codes",
|
|
49
|
+
"bbg_composite_primary",
|
|
50
|
+
"refinitiv_mnemonic",
|
|
51
|
+
"bbg_composite",
|
|
52
|
+
"refinitiv_identifier_code",
|
|
53
|
+
"city__name",
|
|
54
|
+
"website",
|
|
55
|
+
"comments",
|
|
56
|
+
)
|
|
57
|
+
ordering_fields = search_fields
|
|
58
|
+
ordering = ["name"]
|
|
59
|
+
|
|
60
|
+
display_config_class = ExchangeDisplayConfig
|
|
61
|
+
button_config_class = ExchangeButtonConfig
|
|
62
|
+
title_config_class = ExchangeTitleConfig
|
|
63
|
+
endpoint_config_class = ExchangeEndpointConfig
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from django.utils.functional import cached_property
|
|
3
|
+
from rest_framework.exceptions import ParseError
|
|
4
|
+
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
5
|
+
from wbcore.pandas import fields as pf
|
|
6
|
+
from wbcore.pandas.utils import override_number_to_percent
|
|
7
|
+
from wbcore.serializers.fields.types import DisplayMode
|
|
8
|
+
from wbfdm.analysis.financial_analysis.financial_metric_analysis import (
|
|
9
|
+
financial_metric_estimate_analysis,
|
|
10
|
+
financial_metric_growths,
|
|
11
|
+
)
|
|
12
|
+
from wbfdm.enums import Financial
|
|
13
|
+
from wbfdm.filters import GroupKeyFinancialsFilterSet
|
|
14
|
+
from wbfdm.models.instruments import Instrument
|
|
15
|
+
from wbfdm.viewsets.configs.display.statement_with_estimates import (
|
|
16
|
+
StatementWithEstimatesDisplayViewConfig,
|
|
17
|
+
)
|
|
18
|
+
from wbfdm.viewsets.configs.endpoints.statements import StatementsEndpointViewConfig
|
|
19
|
+
from wbfdm.viewsets.configs.titles.statement_with_estimates import (
|
|
20
|
+
StatementTitleViewConfig,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from ..mixins import InstrumentMixin
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FinancialMetricAnalysisPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
27
|
+
queryset = Instrument.objects.none()
|
|
28
|
+
display_config_class = StatementWithEstimatesDisplayViewConfig
|
|
29
|
+
endpoint_config_class = StatementsEndpointViewConfig
|
|
30
|
+
title_config_class = StatementTitleViewConfig
|
|
31
|
+
filterset_class = GroupKeyFinancialsFilterSet
|
|
32
|
+
|
|
33
|
+
def get_queryset(self):
|
|
34
|
+
return Instrument.objects.filter(id=self.instrument.id)
|
|
35
|
+
|
|
36
|
+
def get_pandas_fields(self, request):
|
|
37
|
+
return pf.PandasFields(
|
|
38
|
+
fields=[
|
|
39
|
+
*[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
|
|
40
|
+
pf.PKField(key="id", label="ID"),
|
|
41
|
+
pf.CharField(key="financial", label="Financial"),
|
|
42
|
+
pf.JsonField(key="_overwrites", label="Overwrites"),
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
47
|
+
if group_keys := request.GET.get("group_keys"):
|
|
48
|
+
try:
|
|
49
|
+
financial = Financial(group_keys.lower())
|
|
50
|
+
except ValueError:
|
|
51
|
+
raise ParseError()
|
|
52
|
+
df, self._estimate_mapping, self._columns = financial_metric_estimate_analysis(
|
|
53
|
+
queryset.first().id, financial
|
|
54
|
+
)
|
|
55
|
+
empty_row = pd.Series([None], dtype="float64", name="empty_row")
|
|
56
|
+
df_growth = financial_metric_growths(queryset.first().id, financial)
|
|
57
|
+
|
|
58
|
+
df = pd.concat([df_growth, df, empty_row]).dropna(how="all")
|
|
59
|
+
if "financial" in df.columns:
|
|
60
|
+
override_number_to_percent(df, df["financial"].str.contains("(%)"))
|
|
61
|
+
|
|
62
|
+
df = df.rename(columns={"index": "id"})
|
|
63
|
+
return df
|
|
64
|
+
|
|
65
|
+
return pd.DataFrame()
|
|
66
|
+
|
|
67
|
+
@cached_property
|
|
68
|
+
def columns(self):
|
|
69
|
+
if not hasattr(self, "_columns"):
|
|
70
|
+
self.get_dataframe(self.request, self.get_queryset())
|
|
71
|
+
return self._columns
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def year_columns(self):
|
|
75
|
+
yield from filter(lambda col: "Y" in col, self.columns)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def interim_columns(self):
|
|
79
|
+
yield from filter(lambda col: "Y" not in col, self.columns)
|
|
80
|
+
|
|
81
|
+
@cached_property
|
|
82
|
+
def estimate_mapping(self):
|
|
83
|
+
if not hasattr(self, "_estimate_mapping"):
|
|
84
|
+
self.get_dataframe(self.request, self.get_queryset())
|
|
85
|
+
return self._estimate_mapping
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import plotly.express as px
|
|
6
|
+
import plotly.graph_objects as go
|
|
7
|
+
from wbcore import viewsets
|
|
8
|
+
from wbcore.utils.date import get_date_interval_from_request
|
|
9
|
+
from wbfdm.analysis.financial_analysis.financial_ratio_analysis import (
|
|
10
|
+
FinancialRatio,
|
|
11
|
+
get_financial_ratios,
|
|
12
|
+
)
|
|
13
|
+
from wbfdm.filters import FinancialRatioFilterSet
|
|
14
|
+
from wbfdm.models.instruments import Instrument
|
|
15
|
+
|
|
16
|
+
from ..configs import ValuationRatioChartTitleConfig
|
|
17
|
+
from ..mixins import InstrumentMixin
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ValuationRatioChartViewSet(InstrumentMixin, viewsets.ChartViewSet):
|
|
21
|
+
queryset = Instrument.objects.all()
|
|
22
|
+
filterset_class = FinancialRatioFilterSet
|
|
23
|
+
title_config_class = ValuationRatioChartTitleConfig
|
|
24
|
+
|
|
25
|
+
def get_queryset(self):
|
|
26
|
+
return super().get_queryset().filter(id=self.instrument.id)
|
|
27
|
+
|
|
28
|
+
def get_plotly(self, queryset):
|
|
29
|
+
pd.options.plotting.backend = "plotly"
|
|
30
|
+
|
|
31
|
+
ratios = [FinancialRatio.PE, FinancialRatio.PS, FinancialRatio.PB, FinancialRatio.PFCF]
|
|
32
|
+
ttm = False if self.request.GET.get("ttm", "true") == "false" else True
|
|
33
|
+
start, end = get_date_interval_from_request(self.request, date_range_fieldname="period")
|
|
34
|
+
|
|
35
|
+
df = get_financial_ratios(
|
|
36
|
+
self.instrument.id,
|
|
37
|
+
ratios,
|
|
38
|
+
from_date=start,
|
|
39
|
+
to_date=end,
|
|
40
|
+
ttm=ttm,
|
|
41
|
+
)
|
|
42
|
+
fig = go.Figure()
|
|
43
|
+
colors = iter(px.colors.qualitative.T10)
|
|
44
|
+
|
|
45
|
+
for ratio, color in zip(ratios, colors):
|
|
46
|
+
with suppress(AttributeError):
|
|
47
|
+
series = getattr(df, ratio.value)
|
|
48
|
+
|
|
49
|
+
fig.add_trace(
|
|
50
|
+
go.Scatter(
|
|
51
|
+
x=series.index,
|
|
52
|
+
y=series,
|
|
53
|
+
line=dict(color=color),
|
|
54
|
+
name=f"{ratio.label} {'TTM' if ttm else 'FTM'}",
|
|
55
|
+
legendgroup=f"{ratio.label} {'TTM' if ttm else 'FTM'}",
|
|
56
|
+
visible="legendonly" if ratio != FinancialRatio.PE else True,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# fig = df.plot()
|
|
61
|
+
fig.update_layout(
|
|
62
|
+
template="plotly_white",
|
|
63
|
+
legend=dict(x=0.02, y=0.98, bgcolor="rgba(0,0,0,0)"),
|
|
64
|
+
margin=dict(l=0, r=0, t=0, b=0),
|
|
65
|
+
showlegend=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
for i, d in enumerate(fig.data):
|
|
69
|
+
with suppress(AttributeError, IndexError, TypeError, OverflowError):
|
|
70
|
+
if (v := d.y[-1]) and not np.isnan(v):
|
|
71
|
+
fig.add_scatter(
|
|
72
|
+
x=[d.x[-1]],
|
|
73
|
+
y=[v],
|
|
74
|
+
name=d.name,
|
|
75
|
+
mode="markers+text",
|
|
76
|
+
text=round(v),
|
|
77
|
+
textfont=dict(color=d.line.color),
|
|
78
|
+
textposition="middle right",
|
|
79
|
+
marker=dict(color=d.line.color, size=12, symbol="circle"),
|
|
80
|
+
legendgroup=d.name,
|
|
81
|
+
showlegend=False,
|
|
82
|
+
visible="legendonly" if i != 0 else True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return fig
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.contrib.messages import warning
|
|
6
|
+
from django.utils.functional import cached_property
|
|
7
|
+
from wbcore.cache.decorators import cache_table
|
|
8
|
+
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
9
|
+
from wbcore.pandas import fields as pf
|
|
10
|
+
from wbcore.serializers.fields.types import DisplayMode
|
|
11
|
+
from wbcore.utils.date import get_next_day_timedelta
|
|
12
|
+
from wbfdm.analysis.financial_analysis.statement_with_estimates import (
|
|
13
|
+
StatementWithEstimates,
|
|
14
|
+
)
|
|
15
|
+
from wbfdm.enums import CalendarType
|
|
16
|
+
from wbfdm.filters import StatementWithEstimateFilter
|
|
17
|
+
from wbfdm.models.instruments import Instrument
|
|
18
|
+
from wbfdm.viewsets.configs.display.statement_with_estimates import (
|
|
19
|
+
StatementWithEstimatesDisplayViewConfig,
|
|
20
|
+
)
|
|
21
|
+
from wbfdm.viewsets.configs.endpoints.statements import StatementsEndpointViewConfig
|
|
22
|
+
from wbfdm.viewsets.configs.titles.statement_with_estimates import (
|
|
23
|
+
StatementTitleViewConfig,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from ..mixins import InstrumentMixin
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cache_table(
|
|
30
|
+
timeout=lambda view: get_next_day_timedelta(),
|
|
31
|
+
key_prefix=lambda view: f"_{view.instrument.id}_{view.financial_analysis_key}_{view.calendar_type}",
|
|
32
|
+
)
|
|
33
|
+
class StatementWithEstimatesPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
34
|
+
queryset = Instrument.objects.none()
|
|
35
|
+
display_config_class = StatementWithEstimatesDisplayViewConfig
|
|
36
|
+
endpoint_config_class = StatementsEndpointViewConfig
|
|
37
|
+
title_config_class = StatementTitleViewConfig
|
|
38
|
+
filterset_class = StatementWithEstimateFilter
|
|
39
|
+
|
|
40
|
+
financial_analysis_mapping = {
|
|
41
|
+
"income": ("income_statement_with_estimate", "Income Statement"),
|
|
42
|
+
"balancesheet": ("balance_sheet_with_estimate", "Balance Sheet"),
|
|
43
|
+
"cashflow": ("cash_flow_statement_with_estimate", "Cash Flow Statement"),
|
|
44
|
+
"ratios": ("ratios_with_estimate", "Ratios"),
|
|
45
|
+
"summary": ("summary_with_estimate", "Summary"),
|
|
46
|
+
"margins": ("margins_with_estimates", "Margins"),
|
|
47
|
+
"cashflow-ratios": ("cashflow_ratios_with_estimates", "Cashflow Ratios"),
|
|
48
|
+
"asset-turnover-ratios": ("asset_turnover_with_estimates", "Asset-Turnover Ratios"),
|
|
49
|
+
"credit": ("credit_with_estimates", "Credit"),
|
|
50
|
+
"long-term-solvency": ("long_term_solvency_with_estimates", "Long-term Solvency"),
|
|
51
|
+
"short-term-liquidity": ("short_term_liquidity_with_estimates", "Short-term Liquidity"),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def get_queryset(self):
|
|
55
|
+
return Instrument.objects.filter(id=self.instrument.id)
|
|
56
|
+
|
|
57
|
+
def get_pandas_fields(self, request):
|
|
58
|
+
return pf.PandasFields(
|
|
59
|
+
fields=[
|
|
60
|
+
*[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
|
|
61
|
+
pf.PKField(key="id", label="ID"),
|
|
62
|
+
pf.CharField(key="financial", label="Financial"),
|
|
63
|
+
pf.CharField(key="_group_key", label="Group Key"),
|
|
64
|
+
pf.JsonField(key="_overwrites", label="Overwrites"),
|
|
65
|
+
pf.SparklineField(key="progress", label="Yearly Trend"),
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
70
|
+
statement_with_estimate = StatementWithEstimates(self.instrument, calendar_type=self.calendar_type)
|
|
71
|
+
financial_analysis_result = getattr(
|
|
72
|
+
statement_with_estimate, self.financial_analysis_mapping[self.financial_analysis_key][0]
|
|
73
|
+
)
|
|
74
|
+
df = financial_analysis_result.formatted_df
|
|
75
|
+
|
|
76
|
+
year_columns = list(filter(lambda col: "Y" in col, df.columns))
|
|
77
|
+
if year_columns:
|
|
78
|
+
df["progress"] = df[year_columns].replace([np.inf, -np.inf, np.nan], None).apply(list, axis=1)
|
|
79
|
+
|
|
80
|
+
self.extra_cache_kwargs["_estimate_mapping"] = financial_analysis_result.estimated_mapping
|
|
81
|
+
self.extra_cache_kwargs["_columns"] = df.columns
|
|
82
|
+
self.extra_cache_kwargs["_errors"] = financial_analysis_result.errors
|
|
83
|
+
|
|
84
|
+
if financial_analysis_result.errors.get("duplicated_interims", []) and not settings.DEBUG:
|
|
85
|
+
with suppress(ModuleNotFoundError):
|
|
86
|
+
from sentry_sdk import capture_message
|
|
87
|
+
|
|
88
|
+
capture_message(
|
|
89
|
+
f"Duplicate index detected for instrument {self.instrument} (id: {self.instrument.id})",
|
|
90
|
+
level="error",
|
|
91
|
+
)
|
|
92
|
+
return df
|
|
93
|
+
|
|
94
|
+
def add_messages(self, request, instance=None, **kwargs):
|
|
95
|
+
if self.errors:
|
|
96
|
+
message = """
|
|
97
|
+
<p>While gathering the financial data, we detected the following issues from the data vendor:</p>
|
|
98
|
+
<ul>
|
|
99
|
+
"""
|
|
100
|
+
if duplicated_interim := self.errors.get("duplicated_interim", []):
|
|
101
|
+
message += f"""
|
|
102
|
+
<li>Duplicated data for for interim period <strong>{', '.join(duplicated_interim)}</strong>
|
|
103
|
+
<ul>
|
|
104
|
+
<li>First available data was used and the rest was ignored
|
|
105
|
+
</li>
|
|
106
|
+
</ul>
|
|
107
|
+
</li>
|
|
108
|
+
"""
|
|
109
|
+
if missing_data := self.errors.get("missing_data", []):
|
|
110
|
+
for error in missing_data:
|
|
111
|
+
message += f"<li>{error}</li>"
|
|
112
|
+
message += "</ul>"
|
|
113
|
+
warning(request, message, extra_tags="auto_close=0")
|
|
114
|
+
|
|
115
|
+
# Cached attributes as properties
|
|
116
|
+
@cached_property
|
|
117
|
+
def calendar_type(self):
|
|
118
|
+
return CalendarType(self.request.GET.get("calendar_type", CalendarType.FISCAL.value))
|
|
119
|
+
|
|
120
|
+
@cached_property
|
|
121
|
+
def financial_analysis_key(self):
|
|
122
|
+
return self.kwargs.get("statement", "income")
|
|
123
|
+
|
|
124
|
+
@cached_property
|
|
125
|
+
def estimate_mapping(self):
|
|
126
|
+
return getattr(self, "_estimate_mapping", {})
|
|
127
|
+
|
|
128
|
+
@cached_property
|
|
129
|
+
def errors(self):
|
|
130
|
+
return getattr(self, "_errors", {})
|
|
131
|
+
|
|
132
|
+
@cached_property
|
|
133
|
+
def columns(self):
|
|
134
|
+
return self.df.columns
|
|
135
|
+
|
|
136
|
+
def get_ordering_fields(self):
|
|
137
|
+
return self.columns
|
|
138
|
+
|
|
139
|
+
@cached_property
|
|
140
|
+
def year_columns(self):
|
|
141
|
+
return list(filter(lambda col: "Y" in col, self.columns))
|
|
142
|
+
|
|
143
|
+
@cached_property
|
|
144
|
+
def interim_columns(self):
|
|
145
|
+
return list(filter(lambda col: "Y" not in col, self.columns))
|