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,99 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from wbfdm.models.instruments import (
|
|
3
|
+
Classification,
|
|
4
|
+
ClassificationGroup,
|
|
5
|
+
InstrumentClassificationThroughModel,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.django_db
|
|
10
|
+
class TestClassificationGroupModel:
|
|
11
|
+
def test_init(self, classification_group):
|
|
12
|
+
assert classification_group.id is not None
|
|
13
|
+
|
|
14
|
+
def test_primary(self, classification_group_factory):
|
|
15
|
+
g1 = classification_group_factory.create()
|
|
16
|
+
assert g1.is_primary
|
|
17
|
+
g2 = ClassificationGroup.objects.create(is_primary=True, name="Other group")
|
|
18
|
+
g1.refresh_from_db()
|
|
19
|
+
assert g2.is_primary
|
|
20
|
+
assert not g1.is_primary
|
|
21
|
+
|
|
22
|
+
def test_get_levels_representation(self, classification_group_factory, classification_factory):
|
|
23
|
+
group = classification_group_factory.create(max_depth=2)
|
|
24
|
+
c0 = classification_factory.create(name="c0", group=group, level=0, parent=None)
|
|
25
|
+
c1, created = Classification.objects.update_or_create(group=group, level=1, parent=c0, defaults={"name": "c1"})
|
|
26
|
+
c2, created = Classification.objects.update_or_create(group=group, level=2, parent=c1, defaults={"name": "c2"})
|
|
27
|
+
assert set(group.get_levels_representation()) == {
|
|
28
|
+
c0.level_representation,
|
|
29
|
+
c1.level_representation,
|
|
30
|
+
c2.level_representation,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.django_db
|
|
35
|
+
class TestClassificationModel:
|
|
36
|
+
def test_init(self, classification_factory):
|
|
37
|
+
classification = classification_factory.create()
|
|
38
|
+
assert classification.id is not None
|
|
39
|
+
|
|
40
|
+
def test_str(self, classification_factory):
|
|
41
|
+
classification = classification_factory.create()
|
|
42
|
+
assert str(classification) == classification.computed_str
|
|
43
|
+
|
|
44
|
+
def test_get_instruments(self, classification_factory, instrument_factory, classification_group):
|
|
45
|
+
ind1 = classification_factory.create(group=classification_group)
|
|
46
|
+
subind1 = classification_factory.create(parent=ind1, group=classification_group)
|
|
47
|
+
subind2 = classification_factory.create(parent=ind1, group=classification_group)
|
|
48
|
+
|
|
49
|
+
e1 = instrument_factory.create(classifications=(subind1,))
|
|
50
|
+
e2 = instrument_factory.create(classifications=(subind2,))
|
|
51
|
+
InstrumentClassificationThroughModel.objects.filter(instrument=e2, classification=subind2).update(
|
|
52
|
+
is_favorite=True
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
assert ind1.get_classified_instruments().count() == 2
|
|
56
|
+
assert subind1.get_classified_instruments().first() == e1
|
|
57
|
+
assert subind2.get_classified_instruments().first() == e2
|
|
58
|
+
|
|
59
|
+
assert ind1.get_classified_instruments(only_favorites=True).count() == 1
|
|
60
|
+
assert subind1.get_classified_instruments(only_favorites=True).first() is None
|
|
61
|
+
assert subind2.get_classified_instruments(only_favorites=True).first() == e2
|
|
62
|
+
|
|
63
|
+
@pytest.mark.parametrize("classification_group__code_level_digits", [1, 2, 3, 4])
|
|
64
|
+
def test_get_next_valid_code(self, classification_factory, classification_group):
|
|
65
|
+
parent_classification = classification_factory.create(group=classification_group, code_aggregated=None)
|
|
66
|
+
assert parent_classification.code_aggregated == f"{1:0{classification_group.code_level_digits}}"
|
|
67
|
+
c1 = Classification.objects.filter(parent=parent_classification, group=classification_group).first()
|
|
68
|
+
assert (
|
|
69
|
+
c1.code_aggregated
|
|
70
|
+
== f"{1:0{classification_group.code_level_digits}}" + f"{1:0{classification_group.code_level_digits}}"
|
|
71
|
+
)
|
|
72
|
+
c2 = classification_factory.create(
|
|
73
|
+
parent=parent_classification, group=classification_group, code_aggregated=None
|
|
74
|
+
)
|
|
75
|
+
assert (
|
|
76
|
+
c2.code_aggregated
|
|
77
|
+
== f"{1:0{classification_group.code_level_digits}}" + f"{2:0{classification_group.code_level_digits}}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def test_get_default_level_representation(self, classification_factory, classification_group_factory):
|
|
81
|
+
classification_group = classification_group_factory.create(max_depth=2)
|
|
82
|
+
|
|
83
|
+
# Parent Classification has a default "Level 0" level representation.
|
|
84
|
+
parent_classification = classification_factory.create(group=classification_group)
|
|
85
|
+
assert parent_classification.level_representation == "Level 0"
|
|
86
|
+
|
|
87
|
+
# If no siblings, default is the roman style representation of parent level + 1.
|
|
88
|
+
c1 = Classification.objects.filter(parent=parent_classification, group=classification_group).first()
|
|
89
|
+
assert c1.level_representation == "Level I"
|
|
90
|
+
|
|
91
|
+
# C1 is the parent of C2, we write our own level representation for level 2 classifications.
|
|
92
|
+
c2 = Classification.objects.filter(parent=c1, group=classification_group).first()
|
|
93
|
+
assert c2.level_representation == "Level II"
|
|
94
|
+
c2.level_representation = "Level B"
|
|
95
|
+
c2.save()
|
|
96
|
+
|
|
97
|
+
# If we now create a new level 2 classification, it will take the same representation of its siblings.
|
|
98
|
+
c22 = classification_factory.create(parent=c1, group=classification_group)
|
|
99
|
+
assert c22.level_representation == "Level B"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from unittest.mock import patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from django.contrib.auth.models import Permission
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
7
|
+
from wbfdm.import_export.handlers.instrument_list import InstrumentListImportHandler
|
|
8
|
+
from wbfdm.models import InstrumentListThroughModel
|
|
9
|
+
|
|
10
|
+
fake = Faker()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.django_db
|
|
14
|
+
class TestInstrumentListModel:
|
|
15
|
+
def test_instrument_list_creation(self, instrument_list):
|
|
16
|
+
assert instrument_list.id is not None
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize(
|
|
19
|
+
"from_date,to_date,comment",
|
|
20
|
+
[
|
|
21
|
+
(fake.past_date(), fake.future_date(), fake.sentence()),
|
|
22
|
+
(fake.past_date(), fake.future_date(), None),
|
|
23
|
+
(fake.past_date(), None, fake.sentence()),
|
|
24
|
+
(None, fake.future_date(), fake.sentence()),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_handler_deserialization(self, instrument_list, instrument, import_source, from_date, to_date, comment):
|
|
28
|
+
data = dict()
|
|
29
|
+
if from_date:
|
|
30
|
+
data["from_date"] = from_date.strftime("%Y-%m-%d")
|
|
31
|
+
if to_date:
|
|
32
|
+
data["to_date"] = to_date.strftime("%Y-%m-%d")
|
|
33
|
+
if comment:
|
|
34
|
+
data["comment"] = comment
|
|
35
|
+
|
|
36
|
+
data["instrument"] = instrument.id
|
|
37
|
+
data["instrument_list"] = instrument_list.id
|
|
38
|
+
handler = InstrumentListImportHandler(import_source)
|
|
39
|
+
handler._deserialize(data)
|
|
40
|
+
assert data["instrument"] == instrument
|
|
41
|
+
assert data["instrument_list"] == instrument_list
|
|
42
|
+
if to_date:
|
|
43
|
+
assert data["to_date"] == to_date
|
|
44
|
+
if from_date:
|
|
45
|
+
assert data["from_date"] == from_date
|
|
46
|
+
if comment:
|
|
47
|
+
assert data["comment"] == comment
|
|
48
|
+
# try with a dictionary
|
|
49
|
+
data = {}
|
|
50
|
+
data["instrument"] = {"instrument_type": instrument.instrument_type, "isin": instrument.isin}
|
|
51
|
+
data["instrument_list"] = {
|
|
52
|
+
"identifier": instrument_list.identifier,
|
|
53
|
+
"name": instrument_list.name,
|
|
54
|
+
"instrument_list_type": instrument_list.instrument_list_type,
|
|
55
|
+
}
|
|
56
|
+
handler._deserialize(data)
|
|
57
|
+
assert data["instrument"] == instrument
|
|
58
|
+
assert data["instrument_list"] == instrument_list
|
|
59
|
+
|
|
60
|
+
# assert missing instrument raise error
|
|
61
|
+
data = {}
|
|
62
|
+
data["instrument"] = instrument.id
|
|
63
|
+
with pytest.raises((DeserializationError,)):
|
|
64
|
+
handler._deserialize(data)
|
|
65
|
+
raise DeserializationError
|
|
66
|
+
|
|
67
|
+
# automatically match the instrument if the string representation is provided and the match has already been done for that list
|
|
68
|
+
data = {}
|
|
69
|
+
data["instrument_list"] = instrument_list.id
|
|
70
|
+
data["instrument_str"] = instrument.name
|
|
71
|
+
InstrumentListThroughModel.objects.create(
|
|
72
|
+
instrument_list=instrument_list, instrument_str=instrument.name, instrument=instrument
|
|
73
|
+
)
|
|
74
|
+
handler._deserialize(data)
|
|
75
|
+
assert data["instrument"].id == instrument.id
|
|
76
|
+
|
|
77
|
+
@patch("wbfdm.import_export.handlers.instrument_list.send_notification")
|
|
78
|
+
def test_post_processing_objects(
|
|
79
|
+
self, mock_fct, import_source, instrument_list_factory, instrument_factory, user_factory
|
|
80
|
+
):
|
|
81
|
+
initial_call = mock_fct.call_count
|
|
82
|
+
pm = user_factory.create()
|
|
83
|
+
user_factory.create() # normal user
|
|
84
|
+
|
|
85
|
+
pm.user_permissions.add(
|
|
86
|
+
Permission.objects.get(content_type__app_label="wbfdm", codename="administrate_instrumentlist")
|
|
87
|
+
)
|
|
88
|
+
handler = InstrumentListImportHandler(import_source)
|
|
89
|
+
|
|
90
|
+
# Test if the leftover elements are removed on every imports. (e.g. instrument that are not in an exclusion list anymore)
|
|
91
|
+
list1 = instrument_list_factory.create()
|
|
92
|
+
list2 = instrument_list_factory.create()
|
|
93
|
+
|
|
94
|
+
leftover1 = InstrumentListThroughModel.objects.create(
|
|
95
|
+
instrument_list=list1, instrument=instrument_factory.create()
|
|
96
|
+
)
|
|
97
|
+
remaining_obj1 = InstrumentListThroughModel.objects.create(
|
|
98
|
+
instrument_list=list1, instrument=instrument_factory.create()
|
|
99
|
+
)
|
|
100
|
+
leftover2 = InstrumentListThroughModel.objects.create(
|
|
101
|
+
instrument_list=list2, instrument=instrument_factory.create()
|
|
102
|
+
)
|
|
103
|
+
remaining_obj2 = InstrumentListThroughModel.objects.create(
|
|
104
|
+
instrument_list=list2, instrument=instrument_factory.create()
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
handler._post_processing_objects([remaining_obj1, remaining_obj2], [], [])
|
|
108
|
+
with pytest.raises((InstrumentListThroughModel.DoesNotExist)):
|
|
109
|
+
leftover1.refresh_from_db()
|
|
110
|
+
with pytest.raises((InstrumentListThroughModel.DoesNotExist)):
|
|
111
|
+
leftover2.refresh_from_db()
|
|
112
|
+
remaining_obj2.refresh_from_db()
|
|
113
|
+
remaining_obj1.refresh_from_db()
|
|
114
|
+
assert remaining_obj1
|
|
115
|
+
assert remaining_obj2
|
|
116
|
+
|
|
117
|
+
assert mock_fct.call_count - initial_call == 2 # 2 because there are two instrument added to the list
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import pytest
|
|
6
|
+
from faker import Faker
|
|
7
|
+
from pandas.tseries.offsets import BDay
|
|
8
|
+
from wbcore.models import DynamicDecimalField, DynamicFloatField
|
|
9
|
+
from wbfdm.models import Instrument, InstrumentPrice, RelatedInstrumentThroughModel
|
|
10
|
+
|
|
11
|
+
fake = Faker()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.django_db
|
|
15
|
+
class TestInstrumentPriceModel:
|
|
16
|
+
def test_init(self, instrument_price):
|
|
17
|
+
assert instrument_price.id is not None
|
|
18
|
+
|
|
19
|
+
def test_str(self, instrument_price):
|
|
20
|
+
assert (
|
|
21
|
+
str(instrument_price)
|
|
22
|
+
== f"{instrument_price.instrument.name}: {instrument_price.net_value} {instrument_price.date:%d.%m.%Y}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def test_previous_price(self, instrument, instrument_price_factory):
|
|
26
|
+
previous_price = instrument_price_factory.create(instrument=instrument, date=date(2009, 12, 31))
|
|
27
|
+
price = instrument_price_factory.create(instrument=instrument, date=date(2010, 1, 1))
|
|
28
|
+
assert price.previous_price == previous_price
|
|
29
|
+
|
|
30
|
+
def test_previous_price_does_not_exist(self, instrument_price):
|
|
31
|
+
assert instrument_price.previous_price is None
|
|
32
|
+
|
|
33
|
+
def test_next_price_price(self, instrument, instrument_price_factory):
|
|
34
|
+
next_price = instrument_price_factory.create(instrument=instrument, date=date(2010, 1, 4))
|
|
35
|
+
price = instrument_price_factory.create(instrument=instrument, date=date(2010, 1, 1))
|
|
36
|
+
assert price.next_price == next_price
|
|
37
|
+
|
|
38
|
+
def test_next_price_does_not_exist(self, instrument_price):
|
|
39
|
+
assert instrument_price.next_price is None
|
|
40
|
+
|
|
41
|
+
def test_subquery_closest_value(self, instrument, instrument_price_factory):
|
|
42
|
+
instrument_price_factory.create_batch(5, instrument=instrument)
|
|
43
|
+
latest_valid_price = InstrumentPrice.objects.latest("date")
|
|
44
|
+
queryset = Instrument.objects.all().annotate(
|
|
45
|
+
net_value=InstrumentPrice.subquery_closest_value(
|
|
46
|
+
"net_value", val_date=latest_valid_price.date, instrument_pk_name="pk"
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
assert queryset.get(id=instrument.id).net_value == latest_valid_price.net_value
|
|
51
|
+
|
|
52
|
+
def test_compute_and_update_statistics(self, weekday, instrument_factory, instrument_price_factory):
|
|
53
|
+
product = instrument_factory.create()
|
|
54
|
+
benchmark = instrument_factory.create()
|
|
55
|
+
RelatedInstrumentThroughModel.objects.create(
|
|
56
|
+
instrument=product,
|
|
57
|
+
related_instrument=benchmark,
|
|
58
|
+
related_type=RelatedInstrumentThroughModel.RelatedTypeChoices.BENCHMARK,
|
|
59
|
+
)
|
|
60
|
+
risk_free_rate = instrument_factory.create()
|
|
61
|
+
RelatedInstrumentThroughModel.objects.create(
|
|
62
|
+
instrument=product,
|
|
63
|
+
related_instrument=risk_free_rate,
|
|
64
|
+
related_type=RelatedInstrumentThroughModel.RelatedTypeChoices.RISK_INSTRUMENT,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
for _d in pd.date_range(end=weekday, periods=5, freq="B"):
|
|
68
|
+
instrument_price_factory.create(
|
|
69
|
+
calculated=False, instrument=benchmark, date=_d.date(), sharpe_ratio=None, correlation=None, beta=None
|
|
70
|
+
)
|
|
71
|
+
instrument_price_factory.create(
|
|
72
|
+
calculated=False,
|
|
73
|
+
instrument=risk_free_rate,
|
|
74
|
+
date=_d.date(),
|
|
75
|
+
sharpe_ratio=None,
|
|
76
|
+
correlation=None,
|
|
77
|
+
beta=None,
|
|
78
|
+
)
|
|
79
|
+
instrument_price_factory.create(
|
|
80
|
+
calculated=False, instrument=product, date=_d.date(), sharpe_ratio=None, correlation=None, beta=None
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
p = product.valuations.get(date=weekday)
|
|
84
|
+
p.compute_and_update_statistics(min_period=5)
|
|
85
|
+
assert p.sharpe_ratio
|
|
86
|
+
assert p.correlation
|
|
87
|
+
assert p.beta
|
|
88
|
+
|
|
89
|
+
# Compute functions tests
|
|
90
|
+
#
|
|
91
|
+
# @pytest.mark.parametrize("instrument_price__custom_beta_180d", [None])
|
|
92
|
+
# def test_compute_custom_beta_180d(self, instrument_price):
|
|
93
|
+
# assert hasattr(instrument_price, "_compute_custom_beta_180d")
|
|
94
|
+
# assert isinstance(instrument_price._meta.get_field("custom_beta_180d"), DynamicFloatField)
|
|
95
|
+
#
|
|
96
|
+
# @pytest.mark.parametrize("instrument_price__custom_beta_1y", [None])
|
|
97
|
+
# def test_compute_custom_beta_1y(self, instrument_price):
|
|
98
|
+
# assert hasattr(instrument_price, "_compute_custom_beta_1y")
|
|
99
|
+
# assert isinstance(instrument_price._meta.get_field("custom_beta_1y"), DynamicFloatField)
|
|
100
|
+
#
|
|
101
|
+
# @pytest.mark.parametrize("instrument_price__custom_beta_2y", [None])
|
|
102
|
+
# def test_compute_custom_beta_2y(self, instrument_price):
|
|
103
|
+
# assert hasattr(instrument_price, "_compute_custom_beta_2y")
|
|
104
|
+
# assert isinstance(instrument_price._meta.get_field("custom_beta_2y"), DynamicFloatField)
|
|
105
|
+
#
|
|
106
|
+
# @pytest.mark.parametrize("instrument_price__custom_beta_3y", [None])
|
|
107
|
+
# def test_compute_custom_beta_3y(self, instrument_price):
|
|
108
|
+
# assert hasattr(instrument_price, "_compute_custom_beta_3y")
|
|
109
|
+
# assert isinstance(instrument_price._meta.get_field("custom_beta_3y"), DynamicFloatField)
|
|
110
|
+
#
|
|
111
|
+
# @pytest.mark.parametrize("instrument_price__custom_beta_5y", [None])
|
|
112
|
+
# def test_compute_custom_beta_5y(self, instrument_price):
|
|
113
|
+
# assert hasattr(instrument_price, "_compute_custom_beta_5y")
|
|
114
|
+
# assert isinstance(instrument_price._meta.get_field("custom_beta_5y"), DynamicFloatField)
|
|
115
|
+
#
|
|
116
|
+
# @pytest.mark.parametrize(
|
|
117
|
+
# "instrument_price__free_float_ratio, instrument_price__outstanding_shares", [(None, decimal.Decimal(10))]
|
|
118
|
+
# )
|
|
119
|
+
# def test_compute_free_float_ratio(self, instrument_price):
|
|
120
|
+
# assert hasattr(instrument_price, "_compute_free_float_ratio")
|
|
121
|
+
# assert isinstance(instrument_price._meta.get_field("free_float_ratio"), DynamicFloatField)
|
|
122
|
+
# assert instrument_price.free_float_ratio == instrument_price.free_float / float(
|
|
123
|
+
# instrument_price.outstanding_shares
|
|
124
|
+
# )
|
|
125
|
+
#
|
|
126
|
+
# @pytest.mark.parametrize(
|
|
127
|
+
# "instrument_price__short_interest_ratio, instrument_price__outstanding_shares", [(None, decimal.Decimal(10))]
|
|
128
|
+
# )
|
|
129
|
+
# def test_compute_short_interest_ratio(self, instrument_price):
|
|
130
|
+
# assert hasattr(instrument_price, "_compute_short_interest_ratio")
|
|
131
|
+
# assert isinstance(instrument_price._meta.get_field("short_interest_ratio"), DynamicFloatField)
|
|
132
|
+
# assert instrument_price.short_interest_ratio == instrument_price.short_interest / float(
|
|
133
|
+
# instrument_price.outstanding_shares
|
|
134
|
+
# )
|
|
135
|
+
#
|
|
136
|
+
# @pytest.mark.parametrize("instrument_price__net_value_usd", [None])
|
|
137
|
+
# def test_compute_net_value_usd(self, instrument_price):
|
|
138
|
+
# assert hasattr(instrument_price, "_compute_net_value_usd")
|
|
139
|
+
# assert isinstance(instrument_price._meta.get_field("net_value_usd"), DynamicFloatField)
|
|
140
|
+
# assert instrument_price.net_value_usd == instrument_price.currency_fx_usd * float(instrument_price.net_value)
|
|
141
|
+
#
|
|
142
|
+
# @pytest.mark.parametrize("instrument_price__volume_usd", [None])
|
|
143
|
+
# def test_compute_volume_usd(self, instrument_price):
|
|
144
|
+
# assert hasattr(instrument_price, "_compute_volume_usd")
|
|
145
|
+
# assert isinstance(instrument_price._meta.get_field("volume_usd"), DynamicFloatField)
|
|
146
|
+
# assert instrument_price.volume_usd == instrument_price.currency_fx_usd * instrument_price.volume * float(
|
|
147
|
+
# instrument_price.net_value
|
|
148
|
+
# )
|
|
149
|
+
#
|
|
150
|
+
# @pytest.mark.parametrize("instrument_price__volume_50d_usd", [None])
|
|
151
|
+
# def test_compute_volume_50d_usd(self, instrument_price):
|
|
152
|
+
# assert hasattr(instrument_price, "_compute_volume_50d_usd")
|
|
153
|
+
# assert isinstance(instrument_price._meta.get_field("volume_50d_usd"), DynamicFloatField)
|
|
154
|
+
# assert instrument_price.volume_50d_usd == instrument_price.net_value_usd * instrument_price.volume_50d
|
|
155
|
+
#
|
|
156
|
+
# @pytest.mark.parametrize("instrument_price__currency_fx_usd", [None])
|
|
157
|
+
# def test_compute_currency_fx_usd(self, instrument_price):
|
|
158
|
+
# fx_rate = instrument_price.instrument.currency.fx_rates.get(date=instrument_price.date)
|
|
159
|
+
# instrument_price.save(update_all_dynamic_fields=True)
|
|
160
|
+
# assert hasattr(instrument_price, "_compute_currency_fx_usd")
|
|
161
|
+
# assert isinstance(instrument_price._meta.get_field("currency_fx_usd"), DynamicFloatField)
|
|
162
|
+
# assert instrument_price.currency_fx_usd == 1 / float(fx_rate.value)
|
|
163
|
+
|
|
164
|
+
@pytest.mark.parametrize("instrument_price__gross_value", [None])
|
|
165
|
+
def test_compute_gross_value(self, instrument_price):
|
|
166
|
+
assert hasattr(instrument_price, "_compute_gross_value")
|
|
167
|
+
assert isinstance(instrument_price._meta.get_field("gross_value"), DynamicDecimalField)
|
|
168
|
+
assert instrument_price.gross_value == instrument_price.net_value
|
|
169
|
+
|
|
170
|
+
@pytest.mark.parametrize("instrument_price__outstanding_shares", [Decimal(10)])
|
|
171
|
+
def test_compute_outstanding_shares(self, instrument_price, instrument_price_factory):
|
|
172
|
+
next_price = instrument_price_factory.create(
|
|
173
|
+
instrument=instrument_price.instrument,
|
|
174
|
+
date=instrument_price.date + BDay(1),
|
|
175
|
+
outstanding_shares=None,
|
|
176
|
+
calculated=instrument_price.calculated,
|
|
177
|
+
)
|
|
178
|
+
assert hasattr(instrument_price, "_compute_outstanding_shares")
|
|
179
|
+
assert isinstance(instrument_price._meta.get_field("outstanding_shares"), DynamicDecimalField)
|
|
180
|
+
assert next_price.outstanding_shares == instrument_price.outstanding_shares
|
|
181
|
+
|
|
182
|
+
@pytest.mark.parametrize("instrument_price__volume_50d", [None])
|
|
183
|
+
def test_compute_volume_50d(self, instrument_price, instrument_price_factory):
|
|
184
|
+
assert hasattr(instrument_price, "_compute_volume_50d")
|
|
185
|
+
assert isinstance(instrument_price._meta.get_field("volume_50d"), DynamicFloatField)
|
|
186
|
+
|
|
187
|
+
sum_volume_50d = instrument_price.volume
|
|
188
|
+
for _d in pd.date_range(end=instrument_price.date, periods=50, freq="B", inclusive="left"):
|
|
189
|
+
p = instrument_price_factory.create(date=_d, instrument=instrument_price.instrument)
|
|
190
|
+
sum_volume_50d += p.volume
|
|
191
|
+
instrument_price.save(update_all_dynamic_fields=True)
|
|
192
|
+
assert instrument_price.volume_50d == sum_volume_50d / 50
|
|
193
|
+
|
|
194
|
+
# @pytest.mark.parametrize("instrument_price__volume_200d", [None])
|
|
195
|
+
# def test_compute_volume_200d(self, instrument_price, instrument_price_factory):
|
|
196
|
+
# assert hasattr(instrument_price, "_compute_volume_200d")
|
|
197
|
+
# assert isinstance(instrument_price._meta.get_field("volume_200d"), DynamicFloatField)
|
|
198
|
+
#
|
|
199
|
+
# sum_volume_200d = instrument_price.volume
|
|
200
|
+
# for _d in pd.date_range(end=instrument_price.date, periods=200, freq="B", inclusive="left"):
|
|
201
|
+
# p = instrument_price_factory.create(date=_d, instrument=instrument_price.instrument)
|
|
202
|
+
# sum_volume_200d += p.volume
|
|
203
|
+
# instrument_price.save(update_all_dynamic_fields=True)
|
|
204
|
+
# assert instrument_price.volume_200d == sum_volume_200d / 200
|
|
205
|
+
|
|
206
|
+
# @pytest.mark.parametrize("instrument_price__market_capitalization_usd", [None])
|
|
207
|
+
# def test_compute_market_capitalization_usd(self, instrument_price):
|
|
208
|
+
# assert hasattr(instrument_price, "market_capitalization_usd")
|
|
209
|
+
# assert isinstance(instrument_price._meta.get_field("market_capitalization_usd"), DynamicFloatField)
|
|
210
|
+
# assert (
|
|
211
|
+
# instrument_price.market_capitalization_usd
|
|
212
|
+
# == instrument_price.market_capitalization * instrument_price.currency_fx_usd
|
|
213
|
+
# )
|
|
214
|
+
|
|
215
|
+
# def test_compute_performance_1d(self, instrument_price_factory):
|
|
216
|
+
# previous_price = instrument_price_factory.create()
|
|
217
|
+
# instrument_price = instrument_price_factory.create(
|
|
218
|
+
# instrument=previous_price.instrument,
|
|
219
|
+
# calculated=previous_price.calculated,
|
|
220
|
+
# date=previous_price.date + BDay(1),
|
|
221
|
+
# performance_1d=None,
|
|
222
|
+
# )
|
|
223
|
+
# assert hasattr(instrument_price, "performance_1d")
|
|
224
|
+
# assert isinstance(instrument_price._meta.get_field("performance_1d"), DynamicDecimalField)
|
|
225
|
+
# assert instrument_price.performance_1d == instrument_price.net_value / previous_price.net_value - 1
|
|
226
|
+
#
|
|
227
|
+
# def test_compute_performance_7d(self, instrument_price_factory):
|
|
228
|
+
# previous_price = instrument_price_factory.create()
|
|
229
|
+
# instrument_price = instrument_price_factory.create(
|
|
230
|
+
# instrument=previous_price.instrument,
|
|
231
|
+
# calculated=previous_price.calculated,
|
|
232
|
+
# date=previous_price.date + BDay(7),
|
|
233
|
+
# performance_7d=None,
|
|
234
|
+
# )
|
|
235
|
+
# assert hasattr(instrument_price, "performance_7d")
|
|
236
|
+
# assert isinstance(instrument_price._meta.get_field("performance_7d"), DynamicDecimalField)
|
|
237
|
+
# assert instrument_price.performance_7d == instrument_price.net_value / previous_price.net_value - 1
|
|
238
|
+
#
|
|
239
|
+
# def test_compute_performance_30d(self, instrument_price_factory):
|
|
240
|
+
# previous_price = instrument_price_factory.create()
|
|
241
|
+
# instrument_price = instrument_price_factory.create(
|
|
242
|
+
# instrument=previous_price.instrument,
|
|
243
|
+
# calculated=previous_price.calculated,
|
|
244
|
+
# date=previous_price.date + BDay(30),
|
|
245
|
+
# performance_30d=None,
|
|
246
|
+
# )
|
|
247
|
+
# assert hasattr(instrument_price, "performance_30d")
|
|
248
|
+
# assert isinstance(instrument_price._meta.get_field("performance_30d"), DynamicDecimalField)
|
|
249
|
+
# assert instrument_price.performance_30d == instrument_price.net_value / previous_price.net_value - 1
|
|
250
|
+
#
|
|
251
|
+
# def test_compute_performance_90d(self, instrument_price_factory):
|
|
252
|
+
# previous_price = instrument_price_factory.create()
|
|
253
|
+
# instrument_price = instrument_price_factory.create(
|
|
254
|
+
# instrument=previous_price.instrument,
|
|
255
|
+
# calculated=previous_price.calculated,
|
|
256
|
+
# date=previous_price.date + BDay(90),
|
|
257
|
+
# performance_90d=None,
|
|
258
|
+
# )
|
|
259
|
+
# assert hasattr(instrument_price, "performance_90d")
|
|
260
|
+
# assert isinstance(instrument_price._meta.get_field("performance_90d"), DynamicDecimalField)
|
|
261
|
+
# assert instrument_price.performance_90d == instrument_price.net_value / previous_price.net_value - 1
|
|
262
|
+
#
|
|
263
|
+
# def test_compute_performance_365d(self, instrument_price_factory):
|
|
264
|
+
# previous_price = instrument_price_factory.create()
|
|
265
|
+
# instrument_price = instrument_price_factory.create(
|
|
266
|
+
# instrument=previous_price.instrument,
|
|
267
|
+
# calculated=previous_price.calculated,
|
|
268
|
+
# date=previous_price.date + BDay(365),
|
|
269
|
+
# performance_365d=None,
|
|
270
|
+
# )
|
|
271
|
+
# assert hasattr(instrument_price, "performance_365d")
|
|
272
|
+
# assert isinstance(instrument_price._meta.get_field("performance_365d"), DynamicDecimalField)
|
|
273
|
+
# assert instrument_price.performance_365d == instrument_price.net_value / previous_price.net_value - 1
|
|
274
|
+
#
|
|
275
|
+
# @pytest.mark.parametrize("instrument_price__performance_ytd", [None])
|
|
276
|
+
# def test_compute_performance_ytd(self, instrument_price, instrument_price_factory):
|
|
277
|
+
# previous_price = instrument_price_factory.create(
|
|
278
|
+
# instrument=instrument_price.instrument,
|
|
279
|
+
# calculated=instrument_price.calculated,
|
|
280
|
+
# date=(date(instrument_price.date.year, 1, 1) - BDay(0)).date(),
|
|
281
|
+
# performance_ytd=None,
|
|
282
|
+
# )
|
|
283
|
+
# instrument_price.save(update_all_dynamic_fields=True)
|
|
284
|
+
# assert hasattr(instrument_price, "performance_ytd")
|
|
285
|
+
# assert isinstance(instrument_price._meta.get_field("performance_ytd"), DynamicDecimalField)
|
|
286
|
+
# assert instrument_price.performance_ytd == instrument_price.net_value / previous_price.net_value - 1
|
|
287
|
+
#
|
|
288
|
+
# def test_compute_performance_inception(self, instrument_price_factory):
|
|
289
|
+
# inception_price = instrument_price_factory.create()
|
|
290
|
+
# inception_price.instrument.inception_date = inception_price.date
|
|
291
|
+
# inception_price.instrument.save()
|
|
292
|
+
# instrument_price_factory.create(
|
|
293
|
+
# instrument=inception_price.instrument,
|
|
294
|
+
# calculated=inception_price.calculated,
|
|
295
|
+
# date=inception_price.date + BDay(1),
|
|
296
|
+
# performance_inception=None,
|
|
297
|
+
# )
|
|
298
|
+
# instrument_price = instrument_price_factory.create(
|
|
299
|
+
# instrument=inception_price.instrument,
|
|
300
|
+
# calculated=inception_price.calculated,
|
|
301
|
+
# date=inception_price.date + BDay(2),
|
|
302
|
+
# performance_inception=None,
|
|
303
|
+
# )
|
|
304
|
+
# assert hasattr(instrument_price, "performance_inception")
|
|
305
|
+
# assert isinstance(instrument_price._meta.get_field("performance_inception"), DynamicDecimalField)
|
|
306
|
+
# assert instrument_price.performance_inception == instrument_price.net_value / inception_price.net_value - 1
|