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,106 @@
|
|
|
1
|
+
from django.db.models import Case, Exists, F, IntegerField, OuterRef, When
|
|
2
|
+
from rest_framework.filters import OrderingFilter
|
|
3
|
+
from wbcore import viewsets
|
|
4
|
+
from wbcore.contrib.guardian.filters import ObjectPermissionsFilter
|
|
5
|
+
from wbcore.viewsets.mixins import DjangoFilterBackend
|
|
6
|
+
from wbfdm.contrib.metric.backends.performances import PERFORMANCE_METRIC
|
|
7
|
+
from wbfdm.contrib.metric.backends.statistics import STATISTICS_METRIC
|
|
8
|
+
from wbfdm.contrib.metric.viewsets.mixins import InstrumentMetricMixin
|
|
9
|
+
from wbfdm.filters import InstrumentFilterSet
|
|
10
|
+
from wbfdm.import_export.resources.instruments import InstrumentResource
|
|
11
|
+
from wbfdm.models import Instrument, InstrumentType
|
|
12
|
+
from wbfdm.serializers import (
|
|
13
|
+
InstrumentModelListSerializer,
|
|
14
|
+
InstrumentModelSerializer,
|
|
15
|
+
InstrumentRepresentationSerializer,
|
|
16
|
+
InstrumentTypeRepresentationSerializer,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from ..configs import (
|
|
20
|
+
ChildrenInstrumentModelViewConfig,
|
|
21
|
+
InstrumentButtonViewConfig,
|
|
22
|
+
InstrumentDisplayConfig,
|
|
23
|
+
InstrumentEndpointConfig,
|
|
24
|
+
InstrumentTitleConfig,
|
|
25
|
+
)
|
|
26
|
+
from ..mixins import InstrumentMixin
|
|
27
|
+
from .utils import InstrumentSearchFilter
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InstrumentTypeRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
31
|
+
queryset = InstrumentType.objects.all()
|
|
32
|
+
serializer_class = InstrumentTypeRepresentationSerializer
|
|
33
|
+
search_fields = ("name", "key")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class InstrumentRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
37
|
+
filter_backends = (InstrumentSearchFilter, ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter)
|
|
38
|
+
|
|
39
|
+
queryset = Instrument.objects.annotate_base_data().exclude(name="")
|
|
40
|
+
serializer_class = InstrumentRepresentationSerializer
|
|
41
|
+
search_fields = ("name", "name_repr", "isin", "ticker")
|
|
42
|
+
|
|
43
|
+
filterset_class = InstrumentFilterSet
|
|
44
|
+
ordering_fields = ("title", "ticker")
|
|
45
|
+
ordering = ["-search_rank"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InstrumentModelViewSet(InstrumentMetricMixin, viewsets.ModelViewSet):
|
|
49
|
+
METRIC_KEYS = (PERFORMANCE_METRIC, STATISTICS_METRIC)
|
|
50
|
+
METRIC_SHOW_AGGREGATES = False
|
|
51
|
+
filter_backends = (InstrumentSearchFilter, ObjectPermissionsFilter, DjangoFilterBackend, OrderingFilter)
|
|
52
|
+
|
|
53
|
+
queryset = Instrument.objects.annotate_all()
|
|
54
|
+
serializer_class = InstrumentModelListSerializer
|
|
55
|
+
ordering_fields = (
|
|
56
|
+
"instrument_type",
|
|
57
|
+
"name_repr",
|
|
58
|
+
"ticker",
|
|
59
|
+
"isin",
|
|
60
|
+
"country__name",
|
|
61
|
+
"currency__key",
|
|
62
|
+
)
|
|
63
|
+
search_fields = (
|
|
64
|
+
"name_repr",
|
|
65
|
+
"name",
|
|
66
|
+
"isin",
|
|
67
|
+
"ticker",
|
|
68
|
+
"computed_str",
|
|
69
|
+
"refinitiv_identifier_code",
|
|
70
|
+
"refinitiv_mnemonic_code",
|
|
71
|
+
)
|
|
72
|
+
ordering = ["-search_rank"]
|
|
73
|
+
|
|
74
|
+
def get_serializer_class(self):
|
|
75
|
+
if self.get_action() in ["list", "list-metadata"]:
|
|
76
|
+
return InstrumentModelListSerializer
|
|
77
|
+
return InstrumentModelSerializer
|
|
78
|
+
|
|
79
|
+
display_config_class = InstrumentDisplayConfig
|
|
80
|
+
title_config_class = InstrumentTitleConfig
|
|
81
|
+
button_config_class = InstrumentButtonViewConfig
|
|
82
|
+
endpoint_config_class = InstrumentEndpointConfig
|
|
83
|
+
filterset_class = InstrumentFilterSet
|
|
84
|
+
|
|
85
|
+
def get_resource_class(self):
|
|
86
|
+
return InstrumentResource
|
|
87
|
+
|
|
88
|
+
def get_queryset(self):
|
|
89
|
+
qs = (
|
|
90
|
+
super()
|
|
91
|
+
.get_queryset()
|
|
92
|
+
.select_related("currency", "country", "instrument_type", "parent", "exchange")
|
|
93
|
+
.prefetch_related("tags", "classifications")
|
|
94
|
+
).annotate(
|
|
95
|
+
has_children=Exists(Instrument.objects.filter(parent=OuterRef("pk"))),
|
|
96
|
+
_group_key=Case(When(has_children=True, then=F("id")), default=None, output_field=IntegerField()),
|
|
97
|
+
currency_symbol=F("currency__symbol"),
|
|
98
|
+
)
|
|
99
|
+
return qs
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ChildrenInstrumentModelViewSet(InstrumentMixin, InstrumentModelViewSet):
|
|
103
|
+
button_config_class = ChildrenInstrumentModelViewConfig
|
|
104
|
+
|
|
105
|
+
def get_queryset(self):
|
|
106
|
+
return super().get_queryset().filter(parent=self.instrument)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from django.db.models import Prefetch, Q
|
|
2
|
+
from django.db.models.expressions import F
|
|
3
|
+
from django.db.models.query import QuerySet
|
|
4
|
+
from django.utils.functional import cached_property
|
|
5
|
+
from rest_framework import filters
|
|
6
|
+
from wbcore import filters as wb_filters
|
|
7
|
+
from wbcore import serializers as wb_serializers
|
|
8
|
+
from wbcore import viewsets
|
|
9
|
+
from wbcore.contrib.tags.serializers import TagSerializerMixin
|
|
10
|
+
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
11
|
+
from wbfdm.filters.instruments import BaseClassifiedInstrumentFilterSet
|
|
12
|
+
from wbfdm.models import (
|
|
13
|
+
Classification,
|
|
14
|
+
ClassificationGroup,
|
|
15
|
+
Instrument,
|
|
16
|
+
InstrumentFavoriteGroup,
|
|
17
|
+
RelatedInstrumentThroughModel,
|
|
18
|
+
)
|
|
19
|
+
from wbfdm.models.instruments import (
|
|
20
|
+
InstrumentClassificationRelatedInstrument,
|
|
21
|
+
InstrumentClassificationThroughModel,
|
|
22
|
+
)
|
|
23
|
+
from wbfdm.preferences import get_default_classification_group
|
|
24
|
+
from wbfdm.serializers import (
|
|
25
|
+
ClassifiableInstrumentRepresentationSerializer,
|
|
26
|
+
ClassificationRepresentationSerializer,
|
|
27
|
+
InstrumentClassificationRelatedInstrumentModelSerializer,
|
|
28
|
+
InstrumentClassificationRelatedInstrumentRepresentationSerializer,
|
|
29
|
+
InstrumentFavoriteGroupModelSerializer,
|
|
30
|
+
InstrumentFavoriteGroupRepresentationSerializer,
|
|
31
|
+
RelatedInstrumentThroughInstrumentModelSerializer,
|
|
32
|
+
)
|
|
33
|
+
from wbfdm.viewsets.configs.display import ClassifiedInstrumentDisplayConfig
|
|
34
|
+
from wbfdm.viewsets.configs.titles import ClassifiedInstrumentTitleConfig
|
|
35
|
+
|
|
36
|
+
from ..configs import (
|
|
37
|
+
ClassificationInstrumentRelatedInstrumentDisplayConfig,
|
|
38
|
+
ClassificationInstrumentRelatedInstrumentEndpointConfig,
|
|
39
|
+
ClassifiedInstrumentEndpointConfig,
|
|
40
|
+
InstrumentFavoriteGroupDisplayConfig,
|
|
41
|
+
InstrumentFavoriteGroupTitleConfig,
|
|
42
|
+
RelatedInstrumentThroughInstrumentDisplayConfig,
|
|
43
|
+
RelatedInstrumentThroughInstrumentEndpointConfig,
|
|
44
|
+
)
|
|
45
|
+
from ..mixins import InstrumentMixin
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InstrumentClassificationRelatedInstrumentRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
49
|
+
queryset = InstrumentClassificationRelatedInstrument.objects.all()
|
|
50
|
+
serializer_class = InstrumentClassificationRelatedInstrumentRepresentationSerializer
|
|
51
|
+
|
|
52
|
+
def get_queryset(self):
|
|
53
|
+
queryset = super().get_queryset()
|
|
54
|
+
if pk := self.kwargs.get("classified_instrument_id", None):
|
|
55
|
+
queryset = queryset.filter(classified_instrument_id=pk)
|
|
56
|
+
return queryset
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InstrumentClassificationRelatedInstrumentModelViewSet(viewsets.ModelViewSet):
|
|
60
|
+
queryset = InstrumentClassificationRelatedInstrument.objects.all()
|
|
61
|
+
serializer_class = InstrumentClassificationRelatedInstrumentModelSerializer
|
|
62
|
+
display_config_class = ClassificationInstrumentRelatedInstrumentDisplayConfig
|
|
63
|
+
endpoint_config_class = ClassificationInstrumentRelatedInstrumentEndpointConfig
|
|
64
|
+
|
|
65
|
+
def get_queryset(self):
|
|
66
|
+
queryset = super().get_queryset()
|
|
67
|
+
if pk := self.kwargs.get("classified_instrument_id", None):
|
|
68
|
+
queryset = queryset.filter(classified_instrument_id=pk)
|
|
69
|
+
return queryset
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class InstrumentFavoriteGroupRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
73
|
+
IDENTIFIER = "wbfdm:favoritegroup"
|
|
74
|
+
filter_backends = (filters.OrderingFilter, filters.SearchFilter)
|
|
75
|
+
ordering_fields = ordering = ("name",)
|
|
76
|
+
search_fields = ("name", "instruments__name", "owner__computed_str")
|
|
77
|
+
queryset = InstrumentFavoriteGroup.objects.all()
|
|
78
|
+
serializer_class = InstrumentFavoriteGroupRepresentationSerializer
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class InstrumentFavoriteGroupModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
|
|
82
|
+
queryset = InstrumentFavoriteGroup.objects.all()
|
|
83
|
+
serializer_class = InstrumentFavoriteGroupModelSerializer
|
|
84
|
+
|
|
85
|
+
ordering_fields = ("name", "owner__computed_str", "public")
|
|
86
|
+
ordering = ("name",)
|
|
87
|
+
search_fields = ("name", "instruments__name", "owner__computed_str")
|
|
88
|
+
|
|
89
|
+
filterset_fields = {"instruments": ["exact"], "owner": ["exact"], "public": ["exact"]}
|
|
90
|
+
|
|
91
|
+
display_config_class = InstrumentFavoriteGroupDisplayConfig
|
|
92
|
+
title_config_class = InstrumentFavoriteGroupTitleConfig
|
|
93
|
+
|
|
94
|
+
def get_queryset(self):
|
|
95
|
+
qs = InstrumentFavoriteGroup.objects.all()
|
|
96
|
+
if not self.request.user.is_superuser:
|
|
97
|
+
qs = qs.filter(Q(owner=self.request.user.profile) | Q(public=True))
|
|
98
|
+
return qs.select_related("owner").prefetch_related(
|
|
99
|
+
Prefetch("instruments", queryset=Instrument.objects.filter(favorite_groups__isnull=False).distinct())
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class RelatedInstrumentThroughInstrumentModelViewSet(
|
|
104
|
+
InstrumentMixin, InternalUserPermissionMixin, viewsets.ModelViewSet
|
|
105
|
+
):
|
|
106
|
+
serializer_class = RelatedInstrumentThroughInstrumentModelSerializer
|
|
107
|
+
queryset = RelatedInstrumentThroughModel.objects.select_related(
|
|
108
|
+
"related_instrument",
|
|
109
|
+
"instrument",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
search_fields = ("related_instrument__computed_str",)
|
|
113
|
+
ordering_fields = ["is_primary"]
|
|
114
|
+
ordering = ["-is_primary"]
|
|
115
|
+
|
|
116
|
+
filterset_fields = {"is_primary": ["exact"], "related_instrument": ["exact"], "related_type": ["exact"]}
|
|
117
|
+
display_config_class = RelatedInstrumentThroughInstrumentDisplayConfig
|
|
118
|
+
endpoint_config_class = RelatedInstrumentThroughInstrumentEndpointConfig
|
|
119
|
+
|
|
120
|
+
def get_queryset(self):
|
|
121
|
+
return super().get_queryset().filter(instrument=self.instrument)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ClassifiedInstrumentModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
|
|
125
|
+
queryset = InstrumentClassificationThroughModel.objects.all()
|
|
126
|
+
display_config_class = ClassifiedInstrumentDisplayConfig
|
|
127
|
+
title_config_class = ClassifiedInstrumentTitleConfig
|
|
128
|
+
endpoint_config_class = ClassifiedInstrumentEndpointConfig
|
|
129
|
+
|
|
130
|
+
search_fields = ("instrument__computed_str",)
|
|
131
|
+
|
|
132
|
+
def get_ordering_fields(self) -> list[str]:
|
|
133
|
+
ordering_fields = ["instrument"]
|
|
134
|
+
for field_name in self.classification_group.get_fields_names(sep="_"):
|
|
135
|
+
ordering_fields.append(f"classification_{field_name}")
|
|
136
|
+
return ordering_fields
|
|
137
|
+
|
|
138
|
+
def get_serializer_class(self):
|
|
139
|
+
"""
|
|
140
|
+
Unwrap defined serializer class and inject the metric fields into a new class
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
group = self.classification_group
|
|
144
|
+
|
|
145
|
+
attrs = dict()
|
|
146
|
+
serializer_fields = [
|
|
147
|
+
"id",
|
|
148
|
+
"instrument",
|
|
149
|
+
"_instrument",
|
|
150
|
+
"classification",
|
|
151
|
+
"_classification",
|
|
152
|
+
"is_favorite",
|
|
153
|
+
"tags",
|
|
154
|
+
"_tags",
|
|
155
|
+
]
|
|
156
|
+
for field_name in group.get_fields_names(sep="_"):
|
|
157
|
+
base_field_name = f"classification_{field_name}"
|
|
158
|
+
# representation_field_name = f"_classification_{field_name}"
|
|
159
|
+
attrs[base_field_name] = wb_serializers.CharField(read_only=True)
|
|
160
|
+
serializer_fields.append(base_field_name)
|
|
161
|
+
|
|
162
|
+
class BaseClassifiedInstrumentModelSerializer(TagSerializerMixin, wb_serializers.ModelSerializer):
|
|
163
|
+
_instrument = ClassifiableInstrumentRepresentationSerializer(source="instrument")
|
|
164
|
+
_classification = ClassificationRepresentationSerializer(source="classification", label_key="{{name}}")
|
|
165
|
+
|
|
166
|
+
class Meta:
|
|
167
|
+
model = InstrumentClassificationThroughModel
|
|
168
|
+
fields = read_only_fields = serializer_fields
|
|
169
|
+
|
|
170
|
+
serializer_class = type(
|
|
171
|
+
"ClassifiedInstrumentModelSerializer", (BaseClassifiedInstrumentModelSerializer,), attrs
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return serializer_class
|
|
175
|
+
|
|
176
|
+
def get_filterset_class(self, request):
|
|
177
|
+
group = self.classification_group
|
|
178
|
+
|
|
179
|
+
attrs = {
|
|
180
|
+
"classification": wb_filters.ModelChoiceFilter(
|
|
181
|
+
label="Height 0",
|
|
182
|
+
queryset=Classification.objects.filter(group=group, height=0),
|
|
183
|
+
endpoint=Classification.get_representation_endpoint(),
|
|
184
|
+
filter_params={"height": 0, "group": group.id},
|
|
185
|
+
value_key=Classification.get_representation_value_key(),
|
|
186
|
+
label_key="{{name}}",
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
for index, field_name in enumerate(group.get_fields_names(sep="_"), start=1):
|
|
190
|
+
attrs[f"classification_{field_name}"] = wb_filters.ModelChoiceFilter(
|
|
191
|
+
label=f"Height {index}",
|
|
192
|
+
queryset=Classification.objects.filter(group=group, height=index),
|
|
193
|
+
endpoint=Classification.get_representation_endpoint(),
|
|
194
|
+
filter_params={"height": index, "group": group.id},
|
|
195
|
+
value_key=Classification.get_representation_value_key(),
|
|
196
|
+
method="query_classification",
|
|
197
|
+
label_key="{{name}}",
|
|
198
|
+
)
|
|
199
|
+
filter_class = type("ClassifiedInstrumentFilterSet", (BaseClassifiedInstrumentFilterSet,), attrs)
|
|
200
|
+
|
|
201
|
+
def _get_filter_class_for_remote_filter(cls):
|
|
202
|
+
"""
|
|
203
|
+
Define which filterset class sender to user for remote filter registration
|
|
204
|
+
"""
|
|
205
|
+
return BaseClassifiedInstrumentFilterSet
|
|
206
|
+
|
|
207
|
+
filter_class.get_filter_class_for_remote_filter = classmethod(_get_filter_class_for_remote_filter)
|
|
208
|
+
return filter_class
|
|
209
|
+
|
|
210
|
+
@cached_property
|
|
211
|
+
def classification_group(self):
|
|
212
|
+
try:
|
|
213
|
+
return ClassificationGroup.objects.get(id=self.request.GET.get("classification_group"))
|
|
214
|
+
except ClassificationGroup.DoesNotExist:
|
|
215
|
+
return get_default_classification_group()
|
|
216
|
+
|
|
217
|
+
def get_queryset(self) -> QuerySet[InstrumentClassificationThroughModel]:
|
|
218
|
+
return (
|
|
219
|
+
super()
|
|
220
|
+
.get_queryset()
|
|
221
|
+
.filter(classification__group=self.classification_group)
|
|
222
|
+
.annotate(
|
|
223
|
+
**{
|
|
224
|
+
f"classification_{field_name}".replace("__", "_"): F(f"classification__{field_name}__name")
|
|
225
|
+
for field_name in self.classification_group.get_fields_names()
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
.select_related(
|
|
229
|
+
*[f"classification__{field_name}" for field_name in self.classification_group.get_fields_names()]
|
|
230
|
+
)
|
|
231
|
+
.prefetch_related(
|
|
232
|
+
"tags",
|
|
233
|
+
Prefetch("instrument", queryset=Instrument.objects.filter(classifications_through__isnull=False)),
|
|
234
|
+
)
|
|
235
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from django.contrib.postgres.search import SearchQuery, SearchRank
|
|
2
|
+
from django.db.models import F, Q
|
|
3
|
+
from django.db.models.expressions import Value
|
|
4
|
+
from django.db.models.functions import Coalesce
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InstrumentSearchFilter:
|
|
8
|
+
def get_min_search_rank(self, view) -> float:
|
|
9
|
+
return getattr(view, "SEARCH_MIN_RANK", 0.3)
|
|
10
|
+
|
|
11
|
+
def filter_queryset(self, request, queryset, view):
|
|
12
|
+
if search := request.GET.get("search", None):
|
|
13
|
+
min_search_rank = self.get_min_search_rank(view)
|
|
14
|
+
query = SearchQuery(search, search_type="phrase")
|
|
15
|
+
queryset = (
|
|
16
|
+
queryset.annotate(search_rank=Coalesce(SearchRank(F("search_vector"), query), Value(-1.0)))
|
|
17
|
+
.filter(
|
|
18
|
+
(Q(search_vector=query) & Q(search_rank__gte=min_search_rank))
|
|
19
|
+
| Q(name__icontains=search)
|
|
20
|
+
| Q(name_repr__icontains=search)
|
|
21
|
+
| Q(isin__icontains=search)
|
|
22
|
+
)
|
|
23
|
+
.order_by("-search_rank")
|
|
24
|
+
).distinct()
|
|
25
|
+
else:
|
|
26
|
+
queryset = queryset.annotate(search_rank=Value(-1.0))
|
|
27
|
+
return queryset
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import date
|
|
3
|
+
from itertools import cycle
|
|
4
|
+
|
|
5
|
+
import plotly.express as px
|
|
6
|
+
from django.utils.functional import cached_property
|
|
7
|
+
from plotly import graph_objects as go
|
|
8
|
+
from plotly.subplots import make_subplots
|
|
9
|
+
from wbcore import viewsets
|
|
10
|
+
from wbcore.utils.date import get_date_interval_from_request
|
|
11
|
+
from wbfdm.enums import Indicator, MarketDataChartType
|
|
12
|
+
from wbfdm.filters import MarketDataChartFilterSet
|
|
13
|
+
from wbfdm.models.instruments import Instrument
|
|
14
|
+
|
|
15
|
+
from .configs import MarketDataChartTitleConfig, PerformanceSummaryChartTitleConfig
|
|
16
|
+
from .mixins import InstrumentMixin
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PerformanceSummaryChartViewSet(InstrumentMixin, viewsets.ChartViewSet):
|
|
20
|
+
queryset = Instrument.objects.all()
|
|
21
|
+
title_config_class = PerformanceSummaryChartTitleConfig
|
|
22
|
+
|
|
23
|
+
def get_queryset(self):
|
|
24
|
+
return super().get_queryset().filter(id=self.instrument.id)
|
|
25
|
+
|
|
26
|
+
def get_plotly(self, queryset):
|
|
27
|
+
today = date.today()
|
|
28
|
+
trace_factory = (
|
|
29
|
+
queryset.first().technical_analysis(from_date=today.replace(year=today.year - 4)).trace_factory()
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
fig = go.Figure()
|
|
33
|
+
for trace in trace_factory.performance_summary_trace(bar_options={"color": px.colors.qualitative.T10[0]}):
|
|
34
|
+
fig.add_trace(trace)
|
|
35
|
+
fig.update_layout(
|
|
36
|
+
template="plotly_white",
|
|
37
|
+
yaxis_tickformat="%",
|
|
38
|
+
)
|
|
39
|
+
return fig
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MarketDataChartViewSet(InstrumentMixin, viewsets.ChartViewSet):
|
|
43
|
+
queryset = Instrument.objects.all()
|
|
44
|
+
filterset_class = MarketDataChartFilterSet
|
|
45
|
+
title_config_class = MarketDataChartTitleConfig
|
|
46
|
+
|
|
47
|
+
CHART_MAPPING = {
|
|
48
|
+
MarketDataChartType.CLOSE: "close_trace",
|
|
49
|
+
MarketDataChartType.RETURN: "return_trace",
|
|
50
|
+
MarketDataChartType.LOG_RETURN: "log_return_trace",
|
|
51
|
+
MarketDataChartType.DRAWDOWN: "drawdown_trace",
|
|
52
|
+
MarketDataChartType.CANDLESTICK: "candlestick_trace",
|
|
53
|
+
MarketDataChartType.OHLC: "ohlc_trace",
|
|
54
|
+
}
|
|
55
|
+
BENCHMARK_CHART_MAPPING = {
|
|
56
|
+
MarketDataChartType.CLOSE: "close_trace",
|
|
57
|
+
MarketDataChartType.RETURN: "return_trace",
|
|
58
|
+
MarketDataChartType.LOG_RETURN: "log_return_trace",
|
|
59
|
+
MarketDataChartType.DRAWDOWN: "drawdown_trace",
|
|
60
|
+
MarketDataChartType.CANDLESTICK: "close_trace",
|
|
61
|
+
MarketDataChartType.OHLC: "close_trace",
|
|
62
|
+
}
|
|
63
|
+
STATISTIC_MAPPING = {
|
|
64
|
+
MarketDataChartType.CLOSE: "close",
|
|
65
|
+
MarketDataChartType.RETURN: "cum-ret",
|
|
66
|
+
MarketDataChartType.LOG_RETURN: "cum-log-ret",
|
|
67
|
+
MarketDataChartType.DRAWDOWN: "drawdown",
|
|
68
|
+
MarketDataChartType.CANDLESTICK: "close",
|
|
69
|
+
MarketDataChartType.OHLC: "close",
|
|
70
|
+
}
|
|
71
|
+
INDICATOR_MAPPING = {
|
|
72
|
+
Indicator.SMA_50: 50,
|
|
73
|
+
Indicator.SMA_100: 100,
|
|
74
|
+
Indicator.SMA_120: 120,
|
|
75
|
+
Indicator.SMA_200: 200,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
def get_queryset(self):
|
|
79
|
+
return super().get_queryset().filter(id=self.instrument.id)
|
|
80
|
+
|
|
81
|
+
def get_benchmark_queryset(self):
|
|
82
|
+
return Instrument.objects.filter(id__in=self.benchmarks_ids)
|
|
83
|
+
|
|
84
|
+
@cached_property
|
|
85
|
+
def benchmarks_ids(self) -> list[int]:
|
|
86
|
+
if benchmarks_str := self.request.GET.get("benchmarks", None):
|
|
87
|
+
return benchmarks_str.split(",")
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
def get_parameters(self) -> list:
|
|
91
|
+
return self.request.GET.get("parameters", "").split(";")
|
|
92
|
+
|
|
93
|
+
def get_plotly(self, queryset):
|
|
94
|
+
# Parametrization
|
|
95
|
+
chart_type = MarketDataChartType(self.request.GET.get("chart_type", "close"))
|
|
96
|
+
volume = self.request.GET.get("volume", "false") == "true"
|
|
97
|
+
show_estimates = self.request.GET.get("show_estimates", "false") == "true"
|
|
98
|
+
from_date, to_date = get_date_interval_from_request(self.request, date_range_fieldname="period")
|
|
99
|
+
colors = cycle(px.colors.qualitative.T10)
|
|
100
|
+
|
|
101
|
+
# Bootstrap chart
|
|
102
|
+
fig = make_subplots(
|
|
103
|
+
rows=2 if volume else 1,
|
|
104
|
+
cols=1,
|
|
105
|
+
shared_xaxes=True,
|
|
106
|
+
vertical_spacing=0.03,
|
|
107
|
+
row_width=[0.2, 0.7] if volume else [1],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Generate Chart for main timeseries
|
|
111
|
+
ta = queryset.first().technical_analysis(from_date, to_date)
|
|
112
|
+
factory = ta.trace_factory()
|
|
113
|
+
for trace in getattr(factory, self.CHART_MAPPING[chart_type])(
|
|
114
|
+
line_options=dict(color=next(colors)), show_estimates=show_estimates
|
|
115
|
+
):
|
|
116
|
+
fig.add_trace(trace)
|
|
117
|
+
|
|
118
|
+
# Generate Charts for all added benchmarks
|
|
119
|
+
for benchmark in self.get_benchmark_queryset():
|
|
120
|
+
_factory = benchmark.technical_benchmark_analysis(ta.df.index.min(), to_date).trace_factory()
|
|
121
|
+
for trace in getattr(_factory, self.BENCHMARK_CHART_MAPPING[chart_type])(
|
|
122
|
+
base_series=ta.df[self.STATISTIC_MAPPING[chart_type]],
|
|
123
|
+
line_options=dict(color=next(colors)),
|
|
124
|
+
):
|
|
125
|
+
fig.add_trace(trace)
|
|
126
|
+
|
|
127
|
+
# Generate Charts for any selected indicators
|
|
128
|
+
if indicators := self.request.GET.get("indicators", None):
|
|
129
|
+
for indicator in indicators.split(","):
|
|
130
|
+
for trace in factory.sma_trace(
|
|
131
|
+
self.INDICATOR_MAPPING[Indicator(indicator)], line_options=dict(color=next(colors))
|
|
132
|
+
):
|
|
133
|
+
fig.add_trace(trace)
|
|
134
|
+
|
|
135
|
+
if volume:
|
|
136
|
+
fig.add_trace(
|
|
137
|
+
factory.volume_trace(),
|
|
138
|
+
row=2,
|
|
139
|
+
col=1,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
fig.update_layout(
|
|
143
|
+
template="plotly_white",
|
|
144
|
+
legend=dict(x=0.02, y=0.98, bgcolor="rgba(0,0,0,0)"),
|
|
145
|
+
margin=dict(l=0, r=0, t=0, b=40),
|
|
146
|
+
xaxis_rangeslider_visible=False,
|
|
147
|
+
showlegend=True,
|
|
148
|
+
hovermode="x",
|
|
149
|
+
)
|
|
150
|
+
fig.update_xaxes(rangebreaks=[{"pattern": "day of week", "bounds": [6, 1]}])
|
|
151
|
+
|
|
152
|
+
for i, d in enumerate(fig.data):
|
|
153
|
+
with suppress(AttributeError, IndexError, ValueError): # Either Candlestick or OHCL
|
|
154
|
+
if (y := d.y[-1]) is not None:
|
|
155
|
+
text_value = (
|
|
156
|
+
f"{y:.2%}"
|
|
157
|
+
if chart_type in [MarketDataChartType.RETURN, MarketDataChartType.LOG_RETURN]
|
|
158
|
+
else f"{y:.1f}"
|
|
159
|
+
)
|
|
160
|
+
fig.add_scatter(
|
|
161
|
+
x=[d.x[-1]],
|
|
162
|
+
y=[y],
|
|
163
|
+
name=d.name,
|
|
164
|
+
mode="markers+text",
|
|
165
|
+
text=text_value,
|
|
166
|
+
textfont=dict(color=d.line.color),
|
|
167
|
+
textposition="middle right",
|
|
168
|
+
marker=dict(color=d.line.color, size=12, symbol="circle"),
|
|
169
|
+
legendgroup=d.name,
|
|
170
|
+
showlegend=False,
|
|
171
|
+
)
|
|
172
|
+
return fig
|
wbfdm/viewsets/mixins.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from django.utils.functional import cached_property
|
|
2
|
+
from rest_framework.generics import get_object_or_404
|
|
3
|
+
from wbfdm.models import Instrument
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InstrumentMixin:
|
|
7
|
+
@cached_property
|
|
8
|
+
def instrument(self) -> Instrument:
|
|
9
|
+
return get_object_or_404(Instrument, pk=self.kwargs["instrument_id"])
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from rest_framework.response import Response
|
|
2
|
+
from wbcore import viewsets
|
|
3
|
+
from wbfdm.models.instruments import Instrument
|
|
4
|
+
from wbfdm.serializers import OfficerSerializer
|
|
5
|
+
from wbfdm.viewsets.configs.display.officers import OfficerDisplayViewConfig
|
|
6
|
+
from wbfdm.viewsets.configs.titles.instruments import SubviewInstrumentTitleViewConfig
|
|
7
|
+
|
|
8
|
+
from .mixins import InstrumentMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OfficerViewSet(InstrumentMixin, viewsets.ViewSet):
|
|
12
|
+
IDENTIFIER = "wbfdm:instrument-officers"
|
|
13
|
+
SUBVIEW_NAME = "Officers"
|
|
14
|
+
display_config_class = OfficerDisplayViewConfig
|
|
15
|
+
title_config_class = SubviewInstrumentTitleViewConfig
|
|
16
|
+
serializer_class = OfficerSerializer
|
|
17
|
+
permission_classes = []
|
|
18
|
+
|
|
19
|
+
def list(self, request, instrument_id):
|
|
20
|
+
queryset = self.get_queryset()
|
|
21
|
+
serializer = self.get_serializer_class()
|
|
22
|
+
serializer = serializer(queryset, many=True)
|
|
23
|
+
return Response({"results": serializer.data})
|
|
24
|
+
|
|
25
|
+
def get_queryset(self):
|
|
26
|
+
queryset = Instrument.objects.filter(id=self.instrument.id)
|
|
27
|
+
return queryset.dl.officers()
|
wbfdm/viewsets/prices.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
5
|
+
from wbcore.pandas import fields as pf
|
|
6
|
+
from wbcore.serializers.fields.types import DisplayMode
|
|
7
|
+
from wbcore.utils.date import get_date_interval_from_request
|
|
8
|
+
from wbfdm.filters.instrument_prices import FakeDateRange
|
|
9
|
+
from wbfdm.models.instruments import Instrument
|
|
10
|
+
from wbfdm.viewsets.configs.display.prices import InstrumentPriceDisplayConfig
|
|
11
|
+
from wbfdm.viewsets.configs.titles.prices import InstrumentPriceTitleViewConfig
|
|
12
|
+
|
|
13
|
+
from .mixins import InstrumentMixin
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InstrumentPriceViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
17
|
+
IDENTIFIER = "wbfdm:instrument-price"
|
|
18
|
+
display_config_class = InstrumentPriceDisplayConfig
|
|
19
|
+
title_config_class = InstrumentPriceTitleViewConfig
|
|
20
|
+
pandas_fields = pf.PandasFields(
|
|
21
|
+
fields=(
|
|
22
|
+
pf.PKField(key="id", label="ID"),
|
|
23
|
+
pf.DateField(
|
|
24
|
+
key="valuation_date",
|
|
25
|
+
label="valuation_date",
|
|
26
|
+
),
|
|
27
|
+
pf.FloatField(key="open", label="open"),
|
|
28
|
+
pf.FloatField(key="high", label="high"),
|
|
29
|
+
pf.FloatField(key="low", label="low"),
|
|
30
|
+
pf.FloatField(key="close", label="close"),
|
|
31
|
+
pf.FloatField(key="volume", label="volume", display_mode=DisplayMode.SHORTENED),
|
|
32
|
+
pf.FloatField(key="outstanding_shares", label="outstanding_shares", display_mode=DisplayMode.SHORTENED),
|
|
33
|
+
pf.FloatField(
|
|
34
|
+
key="market_capitalization", label="market_capitalization", display_mode=DisplayMode.SHORTENED
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
permission_classes = []
|
|
39
|
+
filterset_class = FakeDateRange
|
|
40
|
+
queryset = Instrument.objects.all()
|
|
41
|
+
ordering_fields = (
|
|
42
|
+
"valuation_date",
|
|
43
|
+
"open",
|
|
44
|
+
"high",
|
|
45
|
+
"low",
|
|
46
|
+
"close",
|
|
47
|
+
"volume",
|
|
48
|
+
"outstanding_shares",
|
|
49
|
+
"market_capitalization",
|
|
50
|
+
)
|
|
51
|
+
ordering = ["-valuation_date"]
|
|
52
|
+
|
|
53
|
+
def get_queryset(self):
|
|
54
|
+
return Instrument.objects.filter(id=self.instrument.id)
|
|
55
|
+
|
|
56
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
57
|
+
start, end = get_date_interval_from_request(request, date_range_fieldname="date")
|
|
58
|
+
if not end:
|
|
59
|
+
end = date.today()
|
|
60
|
+
if not start:
|
|
61
|
+
start = end - timedelta(days=365)
|
|
62
|
+
return pd.DataFrame(queryset.dl.market_data(from_date=start, to_date=end))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .statements import StatementPandasViewSet
|