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,294 @@
|
|
|
1
|
+
import calendar
|
|
2
|
+
import math
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from datetime import date, timedelta
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from pandas.tseries.offsets import BDay
|
|
12
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
13
|
+
from wbfdm.analysis.financial_analysis.financial_statistics_analysis import (
|
|
14
|
+
FinancialStatistics,
|
|
15
|
+
)
|
|
16
|
+
from wbfdm.backends.dto import PriceDTO
|
|
17
|
+
from wbfdm.enums import MarketData
|
|
18
|
+
from wbfdm.models.instruments.instrument_prices import InstrumentPrice
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InstrumentPMSMixin:
|
|
22
|
+
def get_prices_df_with_calculated(self, market_data: MarketData = MarketData.CLOSE, **kwargs) -> pd.DataFrame:
|
|
23
|
+
prices = pd.DataFrame(self.get_prices(values=[market_data], **kwargs)).rename(
|
|
24
|
+
columns={"valuation_date": "date"}
|
|
25
|
+
)
|
|
26
|
+
if "calculated" not in prices.columns:
|
|
27
|
+
prices["calculated"] = False
|
|
28
|
+
|
|
29
|
+
if not prices.empty and market_data.value in prices.columns:
|
|
30
|
+
prices = prices[[market_data.value, "calculated", "date"]].sort_values(by="calculated")
|
|
31
|
+
prices = prices.groupby("date").first()
|
|
32
|
+
prices.index = pd.to_datetime(prices.index)
|
|
33
|
+
prices = prices.replace([np.inf, -np.inf, np.nan], None)
|
|
34
|
+
return prices.sort_index()
|
|
35
|
+
return pd.DataFrame()
|
|
36
|
+
|
|
37
|
+
def get_prices_df(self, market_data: MarketData = MarketData.CLOSE, **kwargs) -> pd.Series:
|
|
38
|
+
prices = self.get_prices_df_with_calculated(market_data=market_data, **kwargs)
|
|
39
|
+
|
|
40
|
+
if market_data.value in prices.columns:
|
|
41
|
+
return prices[market_data.value].astype(float)
|
|
42
|
+
return pd.Series(dtype="float64")
|
|
43
|
+
|
|
44
|
+
def get_price(self, val_date: date, price_date_timedelta: int = 3) -> Decimal:
|
|
45
|
+
if self.is_cash:
|
|
46
|
+
return Decimal(1)
|
|
47
|
+
return self._build_dto(val_date, price_date_timedelta=price_date_timedelta).close
|
|
48
|
+
|
|
49
|
+
def _build_dto(self, val_date: date, price_date_timedelta: int = 3) -> PriceDTO: # for backward compatibility
|
|
50
|
+
try:
|
|
51
|
+
price = self.valuations.get(date=val_date)
|
|
52
|
+
close = float(price.net_value)
|
|
53
|
+
return PriceDTO(
|
|
54
|
+
pk=price.id,
|
|
55
|
+
instrument=self.id,
|
|
56
|
+
date=val_date,
|
|
57
|
+
open=close,
|
|
58
|
+
close=close,
|
|
59
|
+
high=close,
|
|
60
|
+
low=close,
|
|
61
|
+
volume=close,
|
|
62
|
+
market_capitalization=price.market_capitalization,
|
|
63
|
+
outstanding_shares=float(price.outstanding_shares) if price.outstanding_shares else None,
|
|
64
|
+
)
|
|
65
|
+
except InstrumentPrice.DoesNotExist:
|
|
66
|
+
prices = sorted(
|
|
67
|
+
self.get_prices(from_date=(val_date - BDay(price_date_timedelta)).date(), to_date=val_date),
|
|
68
|
+
key=lambda x: x["valuation_date"],
|
|
69
|
+
reverse=True,
|
|
70
|
+
)
|
|
71
|
+
if (
|
|
72
|
+
prices
|
|
73
|
+
and (p := prices[0])
|
|
74
|
+
and (close := p.get("close", None))
|
|
75
|
+
and (p_date := p.get("valuation_date", None))
|
|
76
|
+
):
|
|
77
|
+
return PriceDTO(
|
|
78
|
+
pk=p["id"],
|
|
79
|
+
instrument=self.id,
|
|
80
|
+
date=p_date,
|
|
81
|
+
open=p.get("open", None),
|
|
82
|
+
close=close,
|
|
83
|
+
high=p.get("high", None),
|
|
84
|
+
low=p.get("low", None),
|
|
85
|
+
volume=p.get("volume", None),
|
|
86
|
+
market_capitalization=p.get("market_capitalization", None),
|
|
87
|
+
outstanding_shares=p.get("outstanding_shares", None),
|
|
88
|
+
)
|
|
89
|
+
raise ValueError("Not price was found")
|
|
90
|
+
|
|
91
|
+
# Instrument Prices Utility Functions
|
|
92
|
+
@classmethod
|
|
93
|
+
def _compute_performance(cls, prices: pd.Series, freq: str = "BME") -> pd.DataFrame:
|
|
94
|
+
if prices.empty:
|
|
95
|
+
raise ValueError("Price series cannot be empty")
|
|
96
|
+
performance = FinancialStatistics(prices).compute_performance(freq=freq) # For backward compatibility
|
|
97
|
+
return pd.concat([prices, performance], axis=1).dropna(
|
|
98
|
+
how="any", subset=["performance"]
|
|
99
|
+
) # For backward compatibility
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def extract_monthly_performance_df(cls, prices: pd.Series) -> pd.DataFrame:
|
|
103
|
+
if prices.empty:
|
|
104
|
+
raise ValueError("Price series cannot be empty")
|
|
105
|
+
performance = FinancialStatistics(prices).extract_monthly_performance_df() # For backward compatibility
|
|
106
|
+
df = pd.concat([performance], axis=1, keys=["performance"])
|
|
107
|
+
df["year"] = df.index.year
|
|
108
|
+
df["month"] = df.index.month
|
|
109
|
+
return df.dropna(how="any", subset=["performance"]).reset_index(drop=True) # For backward compatibility
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def extract_annual_performance_df(cls, prices: pd.Series) -> pd.DataFrame:
|
|
113
|
+
if prices.empty:
|
|
114
|
+
raise ValueError("Price series cannot be empty")
|
|
115
|
+
performance = FinancialStatistics(prices).extract_annual_performance_df() # For backward compatibility
|
|
116
|
+
df = pd.concat([performance], axis=1, keys=["performance"])
|
|
117
|
+
df["year"] = df.index.year
|
|
118
|
+
return df.dropna(how="any", subset=["performance"]).reset_index(drop=True) # For backward compatibility
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def extract_inception_performance_df(cls, prices: pd.Series) -> float:
|
|
122
|
+
if prices.empty:
|
|
123
|
+
raise ValueError("Price series cannot be empty")
|
|
124
|
+
return FinancialStatistics(prices).extract_inception_performance_df() # For backward compatibility
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def extract_daily_performance_df(cls, prices: pd.Series) -> pd.DataFrame:
|
|
128
|
+
if prices.empty:
|
|
129
|
+
raise ValueError("Price series cannot be empty")
|
|
130
|
+
performance = FinancialStatistics(prices).extract_daily_performance_df()
|
|
131
|
+
df = pd.concat([performance], axis=1, keys=["performance"])
|
|
132
|
+
df["year"] = df.index.year
|
|
133
|
+
return df.dropna(how="any", subset=["performance"]) # For backward compatibility
|
|
134
|
+
|
|
135
|
+
def get_monthly_return_summary(
|
|
136
|
+
self, start: Optional[date] = None, end: Optional[date] = None, **kwargs
|
|
137
|
+
) -> pd.DataFrame:
|
|
138
|
+
if not (prices := self.get_prices_df_with_calculated(from_date=start, to_date=end, **kwargs)).empty:
|
|
139
|
+
calculated_mask = prices[["calculated"]].copy().asfreq("BME", method="ffill")
|
|
140
|
+
calculated_mask["year"] = calculated_mask.index.year
|
|
141
|
+
calculated_mask["month"] = calculated_mask.index.month
|
|
142
|
+
calculated_mask = (
|
|
143
|
+
calculated_mask[["year", "month", "calculated"]]
|
|
144
|
+
.reset_index(drop=True)
|
|
145
|
+
.groupby(["year", "month"])
|
|
146
|
+
.any()
|
|
147
|
+
)
|
|
148
|
+
monthly_perfs = self.extract_monthly_performance_df(prices.close)
|
|
149
|
+
annual_perfs = self.extract_annual_performance_df(prices.close)
|
|
150
|
+
annual_perfs["month"] = "annual"
|
|
151
|
+
perfs = pd.concat([monthly_perfs, annual_perfs], axis=0, ignore_index=True)
|
|
152
|
+
|
|
153
|
+
return perfs.replace([np.inf, -np.inf, np.nan], None), calculated_mask
|
|
154
|
+
return pd.DataFrame(), pd.DataFrame()
|
|
155
|
+
|
|
156
|
+
def get_monthly_return_summary_dict(
|
|
157
|
+
self, start: Optional[date] = None, end: Optional[date] = None, **kwargs
|
|
158
|
+
) -> Dict:
|
|
159
|
+
perfs, calculated_mask = self.get_monthly_return_summary(start, end, **kwargs)
|
|
160
|
+
res = defaultdict(dict)
|
|
161
|
+
if not perfs.empty:
|
|
162
|
+
for year, df in perfs.sort_values(by="year", ascending=False).groupby("year", sort=False):
|
|
163
|
+
df = df.set_index("month")
|
|
164
|
+
for i in range(1, 13):
|
|
165
|
+
try:
|
|
166
|
+
perf = float(df.loc[i, "performance"])
|
|
167
|
+
except (IndexError, KeyError):
|
|
168
|
+
perf = None
|
|
169
|
+
try:
|
|
170
|
+
calculated = bool(calculated_mask.loc[(year, i), "calculated"])
|
|
171
|
+
except (IndexError, KeyError):
|
|
172
|
+
calculated = False
|
|
173
|
+
res[year][calendar.month_abbr[i]] = {"performance": perf, "calculated": calculated}
|
|
174
|
+
try:
|
|
175
|
+
res[year]["annual"] = {
|
|
176
|
+
"performance": float(df.loc["annual", "performance"]),
|
|
177
|
+
"calculated": bool(calculated_mask.loc[(year, slice(None)), "calculated"].any()),
|
|
178
|
+
}
|
|
179
|
+
except (IndexError, KeyError):
|
|
180
|
+
res[year]["annual"] = {"performance": None, "calculated": False}
|
|
181
|
+
|
|
182
|
+
return dict(res)
|
|
183
|
+
|
|
184
|
+
def build_benchmark_df(self, end_date: Optional[date] = None, **kwargs) -> pd.Series:
|
|
185
|
+
df = pd.Series(dtype="float64")
|
|
186
|
+
prices_df = self.get_prices_df(to_date=end_date).rename("net_value")
|
|
187
|
+
if not prices_df.empty and (benchmark := self.primary_benchmark) and self.primary_risk_instrument:
|
|
188
|
+
start_date = prices_df.index[0]
|
|
189
|
+
end_date = prices_df.index[-1]
|
|
190
|
+
kwargs = {"from_date": start_date, "to_date": end_date}
|
|
191
|
+
# Get and prepare Risk free rate dataframe from stainly
|
|
192
|
+
risk_df = self.primary_risk_instrument.get_prices_df(**kwargs).rename("rate")
|
|
193
|
+
|
|
194
|
+
benchmark_df = benchmark.get_prices_df(**kwargs).rename("benchmark_net_value")
|
|
195
|
+
# Prepare final dataframe, fill the NAN with backward index
|
|
196
|
+
df = pd.concat([risk_df, benchmark_df, prices_df], axis=1).astype("float64").ffill(axis=0).sort_index()
|
|
197
|
+
df.index = pd.to_datetime(df.index)
|
|
198
|
+
|
|
199
|
+
return df
|
|
200
|
+
|
|
201
|
+
def save_prices_in_db(self, from_date: date, to_date: date, clear: bool = False):
|
|
202
|
+
df = pd.DataFrame(
|
|
203
|
+
self.__class__.objects.filter(id=self.id).dl.market_data(
|
|
204
|
+
from_date=from_date
|
|
205
|
+
- timedelta(
|
|
206
|
+
days=90
|
|
207
|
+
), # we make sure to at least import the last 80 days to be sure to be able to compute the volume 50d
|
|
208
|
+
to_date=to_date,
|
|
209
|
+
values=[MarketData.CLOSE, MarketData.VOLUME, MarketData.MARKET_CAPITALIZATION],
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
if not df.empty:
|
|
213
|
+
df["calculated"] = False
|
|
214
|
+
df = df.set_index("valuation_date").sort_index()
|
|
215
|
+
|
|
216
|
+
# # if market cap is not found, maybe we have better chance on the primary exhange
|
|
217
|
+
if not df.market_capitalization.notnull().any() and self.parent and (company := self.parent.get_root()):
|
|
218
|
+
with suppress(KeyError):
|
|
219
|
+
df["market_capitalization"] = pd.DataFrame(
|
|
220
|
+
self.__class__.objects.filter(id=company.id).dl.market_data(
|
|
221
|
+
from_date=from_date,
|
|
222
|
+
to_date=to_date,
|
|
223
|
+
values=[MarketData.MARKET_CAPITALIZATION],
|
|
224
|
+
)
|
|
225
|
+
).set_index("valuation_date")["market_capitalization"]
|
|
226
|
+
|
|
227
|
+
ts = pd.date_range(df.index.min(), df.index.max(), freq="B")
|
|
228
|
+
# fill forward missing data
|
|
229
|
+
df = df.reindex(ts)
|
|
230
|
+
df[["close", "market_capitalization"]] = df[["close", "market_capitalization"]].ffill()
|
|
231
|
+
df.volume = df.volume.fillna(0)
|
|
232
|
+
df.calculated = df.calculated.astype(bool).fillna(
|
|
233
|
+
True
|
|
234
|
+
) # we do not ffill calculated but set the to True to mark them as "estimated"/"not real"
|
|
235
|
+
df["volume_50d"] = df["volume"].rolling(50).mean()
|
|
236
|
+
df = df[df.index.date >= from_date] # we import only from the requested from_date
|
|
237
|
+
df = df.reset_index().dropna(subset=["index", "close"])
|
|
238
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
239
|
+
update_objs = []
|
|
240
|
+
create_objs = []
|
|
241
|
+
for row in df.to_dict("records"):
|
|
242
|
+
if (_date := row.get("index")) and (close := row.get("close", None)):
|
|
243
|
+
# we make sure data does not exist 10 digits (for db constraint)
|
|
244
|
+
if int(math.log10(close)) + 1 < 10:
|
|
245
|
+
try:
|
|
246
|
+
p = InstrumentPrice.objects.get(instrument=self, date=_date)
|
|
247
|
+
|
|
248
|
+
# update only if net value is different with existing instrument price
|
|
249
|
+
if (
|
|
250
|
+
round(p.net_value, 2) != round(close, 2)
|
|
251
|
+
or (p.market_capitalization != row.get("market_capitalization"))
|
|
252
|
+
or (p.volume != row.get("volume"))
|
|
253
|
+
or (p.calculated != row.get("calculated"))
|
|
254
|
+
or clear
|
|
255
|
+
):
|
|
256
|
+
p.net_value = close
|
|
257
|
+
p.gross_value = close
|
|
258
|
+
p.calculated = row["calculated"]
|
|
259
|
+
p.volume = row.get("volume", p.volume)
|
|
260
|
+
p.volume_50d = row.get("volume_50d", p.volume_50d)
|
|
261
|
+
p.market_capitalization = row.get("market_capitalization", p.market_capitalization)
|
|
262
|
+
p.market_capitalization_consolidated = p.market_capitalization
|
|
263
|
+
p.set_dynamic_field(False)
|
|
264
|
+
update_objs.append(p)
|
|
265
|
+
except InstrumentPrice.DoesNotExist:
|
|
266
|
+
with suppress(CurrencyFXRates.DoesNotExist):
|
|
267
|
+
p = InstrumentPrice(
|
|
268
|
+
currency_fx_rate_to_usd=CurrencyFXRates.objects.get( # we need to get the currency rate because we bulk create the object, and thus save is not called
|
|
269
|
+
date=_date, currency=self.currency
|
|
270
|
+
),
|
|
271
|
+
instrument=self,
|
|
272
|
+
date=_date,
|
|
273
|
+
calculated=row["calculated"],
|
|
274
|
+
net_value=close,
|
|
275
|
+
gross_value=close,
|
|
276
|
+
volume=row.get("volume", None),
|
|
277
|
+
market_capitalization=row.get("market_capitalization", None),
|
|
278
|
+
volume_50d=row.get("volume_50d", None),
|
|
279
|
+
)
|
|
280
|
+
p.set_dynamic_field(False)
|
|
281
|
+
create_objs.append(p)
|
|
282
|
+
InstrumentPrice.objects.bulk_update(
|
|
283
|
+
update_objs,
|
|
284
|
+
fields=[
|
|
285
|
+
"net_value",
|
|
286
|
+
"gross_value",
|
|
287
|
+
"volume",
|
|
288
|
+
"market_capitalization",
|
|
289
|
+
"market_capitalization_consolidated",
|
|
290
|
+
"calculated",
|
|
291
|
+
"volume_50d",
|
|
292
|
+
],
|
|
293
|
+
)
|
|
294
|
+
InstrumentPrice.objects.bulk_create(create_objs, ignore_conflicts=True)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from wbcore.contrib.io.mixins import ImportMixin
|
|
3
|
+
from wbfdm.import_export.handlers.option import (
|
|
4
|
+
OptionAggregateImportHandler,
|
|
5
|
+
OptionImportHandler,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseOptionAbstractModel(ImportMixin, models.Model):
|
|
10
|
+
class Type(models.TextChoices):
|
|
11
|
+
PUT = "PUT", "Put"
|
|
12
|
+
CALL = "CALL", "Call"
|
|
13
|
+
|
|
14
|
+
type = models.CharField(choices=Type.choices, max_length=6)
|
|
15
|
+
date = models.DateField()
|
|
16
|
+
instrument = models.ForeignKey(
|
|
17
|
+
to="wbfdm.Instrument",
|
|
18
|
+
related_name="%(class)s",
|
|
19
|
+
on_delete=models.PROTECT,
|
|
20
|
+
limit_choices_to=models.Q(children__isnull=True),
|
|
21
|
+
verbose_name="Instrument",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Option Metrics
|
|
25
|
+
|
|
26
|
+
volume = models.FloatField(
|
|
27
|
+
null=True,
|
|
28
|
+
blank=True,
|
|
29
|
+
verbose_name="Volume",
|
|
30
|
+
help_text="Option Volume",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
volume_5d = models.FloatField(
|
|
34
|
+
null=True,
|
|
35
|
+
blank=True,
|
|
36
|
+
verbose_name="Volume 5D",
|
|
37
|
+
help_text="Option Volume (5D)",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
volume_10d = models.FloatField(
|
|
41
|
+
null=True,
|
|
42
|
+
blank=True,
|
|
43
|
+
verbose_name="Volume 10D",
|
|
44
|
+
help_text="Option Volume (10D)",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
volume_20d = models.FloatField(
|
|
48
|
+
null=True,
|
|
49
|
+
blank=True,
|
|
50
|
+
verbose_name="Volume 20D",
|
|
51
|
+
help_text="Option Volume (20D)",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
volume_50d = models.FloatField(
|
|
55
|
+
null=True,
|
|
56
|
+
blank=True,
|
|
57
|
+
verbose_name="Volume 50D",
|
|
58
|
+
help_text="Option Volume (50D)",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
open_interest = models.FloatField(
|
|
62
|
+
null=True,
|
|
63
|
+
blank=True,
|
|
64
|
+
verbose_name="Open Interest",
|
|
65
|
+
help_text="Open Interest",
|
|
66
|
+
)
|
|
67
|
+
open_interest_5d = models.FloatField(
|
|
68
|
+
null=True,
|
|
69
|
+
blank=True,
|
|
70
|
+
verbose_name="Open Interest 5D",
|
|
71
|
+
help_text="Option Open Interest (5D)",
|
|
72
|
+
)
|
|
73
|
+
open_interest_10d = models.FloatField(
|
|
74
|
+
null=True,
|
|
75
|
+
blank=True,
|
|
76
|
+
verbose_name="Open Interest 10D",
|
|
77
|
+
help_text="Option Open Interest (10D)",
|
|
78
|
+
)
|
|
79
|
+
open_interest_20d = models.FloatField(
|
|
80
|
+
null=True,
|
|
81
|
+
blank=True,
|
|
82
|
+
verbose_name="Open Interest 20D",
|
|
83
|
+
help_text="Option Open Interest (20D)",
|
|
84
|
+
)
|
|
85
|
+
open_interest_50d = models.FloatField(
|
|
86
|
+
null=True,
|
|
87
|
+
blank=True,
|
|
88
|
+
verbose_name="Open Interest 50D",
|
|
89
|
+
help_text="Option Open Interest (50D)",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
volatility = models.FloatField(
|
|
93
|
+
null=True,
|
|
94
|
+
blank=True,
|
|
95
|
+
verbose_name="Volatility",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
volatility_30d = models.FloatField(
|
|
99
|
+
null=True,
|
|
100
|
+
blank=True,
|
|
101
|
+
verbose_name="Volatility (30D)",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
volatility_60d = models.FloatField(
|
|
105
|
+
null=True,
|
|
106
|
+
blank=True,
|
|
107
|
+
verbose_name="Volatility (60D)",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
volatility_90d = models.FloatField(
|
|
111
|
+
null=True,
|
|
112
|
+
blank=True,
|
|
113
|
+
verbose_name="Volatility (90D)",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
class Meta:
|
|
117
|
+
abstract = True
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class OptionAggregate(BaseOptionAbstractModel):
|
|
121
|
+
import_export_handler_class = OptionAggregateImportHandler
|
|
122
|
+
|
|
123
|
+
class Meta:
|
|
124
|
+
verbose_name = "Option Aggregate"
|
|
125
|
+
verbose_name_plural = "Options Aggregates"
|
|
126
|
+
constraints = [
|
|
127
|
+
models.CheckConstraint(
|
|
128
|
+
check=~models.Q(date__week_day__in=[1, 7]),
|
|
129
|
+
name="%(app_label)s_%(class)s_weekday_constraint",
|
|
130
|
+
),
|
|
131
|
+
models.UniqueConstraint(fields=["instrument", "date", "type"], name="unique_option_aggregate"),
|
|
132
|
+
]
|
|
133
|
+
indexes = [
|
|
134
|
+
models.Index(
|
|
135
|
+
fields=["instrument", "date", "type"],
|
|
136
|
+
),
|
|
137
|
+
models.Index(
|
|
138
|
+
fields=["instrument", "date"],
|
|
139
|
+
),
|
|
140
|
+
models.Index(
|
|
141
|
+
fields=["type"],
|
|
142
|
+
),
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Option(BaseOptionAbstractModel):
|
|
147
|
+
import_export_handler_class = OptionImportHandler
|
|
148
|
+
|
|
149
|
+
contract_identifier = models.CharField(verbose_name="Contract Name", max_length=255)
|
|
150
|
+
strike = models.FloatField(verbose_name="Strike")
|
|
151
|
+
expiration_date = models.DateField(verbose_name="Expiration Date")
|
|
152
|
+
|
|
153
|
+
# EOD data
|
|
154
|
+
open = models.FloatField(null=True, blank=True, verbose_name="Open")
|
|
155
|
+
high = models.FloatField(null=True, blank=True, verbose_name="High")
|
|
156
|
+
low = models.FloatField(null=True, blank=True, verbose_name="Low")
|
|
157
|
+
close = models.FloatField(null=True, blank=True, verbose_name="Close")
|
|
158
|
+
bid = models.FloatField(null=True, blank=True, verbose_name="Bid")
|
|
159
|
+
ask = models.FloatField(null=True, blank=True, verbose_name="Ask")
|
|
160
|
+
vwap = models.FloatField(
|
|
161
|
+
null=True,
|
|
162
|
+
blank=True,
|
|
163
|
+
verbose_name="Open Interest",
|
|
164
|
+
help_text="Open Interest",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Option risk metrics:
|
|
168
|
+
|
|
169
|
+
risk_delta = models.FloatField(
|
|
170
|
+
null=True, blank=True, verbose_name="Delta", help_text='Option risk metrics "Delta"'
|
|
171
|
+
)
|
|
172
|
+
risk_theta = models.FloatField(
|
|
173
|
+
null=True, blank=True, verbose_name="Theta", help_text='Option risk metrics "Theta"'
|
|
174
|
+
)
|
|
175
|
+
risk_gamma = models.FloatField(
|
|
176
|
+
null=True, blank=True, verbose_name="Gamma", help_text='Option risk metrics "Gamma"'
|
|
177
|
+
)
|
|
178
|
+
risk_vega = models.FloatField(null=True, blank=True, verbose_name="Vega", help_text='Option risk metrics "Vega"')
|
|
179
|
+
risk_rho = models.FloatField(null=True, blank=True, verbose_name="Rho", help_text='Option risk metrics "Rho"')
|
|
180
|
+
risk_lambda = models.FloatField(
|
|
181
|
+
null=True, blank=True, verbose_name="Lambda", help_text='Option risk metrics "Lambda"'
|
|
182
|
+
)
|
|
183
|
+
risk_epsilon = models.FloatField(
|
|
184
|
+
null=True, blank=True, verbose_name="Epsilon", help_text='Option risk metrics "Epsilon"'
|
|
185
|
+
)
|
|
186
|
+
risk_vomma = models.FloatField(
|
|
187
|
+
null=True, blank=True, verbose_name="Vomma", help_text='Option risk metrics "Vomma"'
|
|
188
|
+
)
|
|
189
|
+
risk_vera = models.FloatField(null=True, blank=True, verbose_name="Vera", help_text='Option risk metrics "Vera"')
|
|
190
|
+
risk_speed = models.FloatField(
|
|
191
|
+
null=True, blank=True, verbose_name="Speed", help_text='Option risk metrics "Speed"'
|
|
192
|
+
)
|
|
193
|
+
risk_zomma = models.FloatField(
|
|
194
|
+
null=True, blank=True, verbose_name="Zomma", help_text='Option risk metrics "Zomma"'
|
|
195
|
+
)
|
|
196
|
+
risk_color = models.FloatField(
|
|
197
|
+
null=True, blank=True, verbose_name="Color", help_text='Option risk metrics "Color"'
|
|
198
|
+
)
|
|
199
|
+
risk_ultima = models.FloatField(
|
|
200
|
+
null=True, blank=True, verbose_name="Ultima", help_text='Option risk metrics "Ultima"'
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
class Meta:
|
|
204
|
+
verbose_name = "Option"
|
|
205
|
+
verbose_name_plural = "Options"
|
|
206
|
+
constraints = [
|
|
207
|
+
models.CheckConstraint(
|
|
208
|
+
check=~models.Q(date__week_day__in=[1, 7]),
|
|
209
|
+
name="%(app_label)s_%(class)s_weekday_constraint",
|
|
210
|
+
),
|
|
211
|
+
models.UniqueConstraint(
|
|
212
|
+
fields=["instrument", "contract_identifier", "date", "type"], name="unique_option"
|
|
213
|
+
),
|
|
214
|
+
]
|
|
215
|
+
indexes = [
|
|
216
|
+
models.Index(
|
|
217
|
+
fields=["instrument", "date", "type"],
|
|
218
|
+
),
|
|
219
|
+
models.Index(
|
|
220
|
+
fields=["instrument", "date"],
|
|
221
|
+
),
|
|
222
|
+
models.Index(
|
|
223
|
+
fields=["type"],
|
|
224
|
+
),
|
|
225
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from django.contrib.postgres.fields import ArrayField
|
|
2
|
+
from django.db import models
|
|
3
|
+
from wbcore.contrib.io.mixins import ImportMixin
|
|
4
|
+
from wbfdm.import_export.handlers.private_equities import DealImportHandler
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Deal(ImportMixin, models.Model):
|
|
8
|
+
import_export_handler_class = DealImportHandler
|
|
9
|
+
|
|
10
|
+
class Types(models.TextChoices):
|
|
11
|
+
DEAL = "DEAL", "Deal"
|
|
12
|
+
FUNDING = "FUNDING", "Funding"
|
|
13
|
+
INVESTMENT = "INVESTMENT", "Investment"
|
|
14
|
+
PORTFOLIO_EXIT = "PORTFOLIO_EXIT", "Portfolio Exit"
|
|
15
|
+
|
|
16
|
+
type = models.CharField(
|
|
17
|
+
default=Types.DEAL, choices=Types.choices, max_length=14, verbose_name="Type", help_text="The deal type"
|
|
18
|
+
)
|
|
19
|
+
external_id = models.CharField(max_length=64, blank=True, null=True)
|
|
20
|
+
date = models.DateField()
|
|
21
|
+
equity = models.ForeignKey(
|
|
22
|
+
"wbfdm.Instrument",
|
|
23
|
+
related_name="deals",
|
|
24
|
+
on_delete=models.CASCADE,
|
|
25
|
+
limit_choices_to=models.Q(instrument_type__key="equity"),
|
|
26
|
+
)
|
|
27
|
+
transaction_amount = models.FloatField(help_text="Deal Size (in millions")
|
|
28
|
+
investors = models.ManyToManyField(
|
|
29
|
+
"wbfdm.Instrument",
|
|
30
|
+
related_name="invested_deals",
|
|
31
|
+
blank=True,
|
|
32
|
+
verbose_name="Investors",
|
|
33
|
+
help_text="Investors",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
funding_round = models.CharField(max_length=128, verbose_name="Funding Round")
|
|
37
|
+
funding_round_category = models.CharField(max_length=128, verbose_name="Funding Round Category")
|
|
38
|
+
|
|
39
|
+
valuation = models.FloatField(
|
|
40
|
+
verbose_name="Valuaton",
|
|
41
|
+
blank=True,
|
|
42
|
+
null=True,
|
|
43
|
+
help_text="Valuation of the funded organization after this transaction (in Millions USD).",
|
|
44
|
+
)
|
|
45
|
+
valuation_estimated = models.BooleanField(
|
|
46
|
+
default=False, verbose_name="Is valuation estimated", help_text="True if the valuation is an estimate"
|
|
47
|
+
)
|
|
48
|
+
valuation_source_type = models.CharField(
|
|
49
|
+
max_length=24,
|
|
50
|
+
blank=True,
|
|
51
|
+
null=True,
|
|
52
|
+
help_text="The source type of the valuation",
|
|
53
|
+
)
|
|
54
|
+
valuation_media_mention_source_urls = ArrayField(
|
|
55
|
+
models.URLField(),
|
|
56
|
+
blank=True,
|
|
57
|
+
null=True,
|
|
58
|
+
help_text="List of URLs used to source the valuation for the Media Mentions source type.",
|
|
59
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from django.db.models import (
|
|
2
|
+
AutoField,
|
|
3
|
+
Exists,
|
|
4
|
+
ExpressionWrapper,
|
|
5
|
+
F,
|
|
6
|
+
OuterRef,
|
|
7
|
+
QuerySet,
|
|
8
|
+
Subquery,
|
|
9
|
+
)
|
|
10
|
+
from django.db.models.functions import Coalesce
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InstrumentQuerySet(QuerySet):
|
|
14
|
+
def annotate_classification_for_group(
|
|
15
|
+
self, classification_group, classification_height: int = 0, **kwargs
|
|
16
|
+
) -> QuerySet:
|
|
17
|
+
return classification_group.annotate_queryset(
|
|
18
|
+
self, classification_height, "", annotation_label="ancestor_classifications", **kwargs
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def annotate_base_data(self):
|
|
22
|
+
base_qs = InstrumentQuerySet(self.model, using=self._db)
|
|
23
|
+
return self.annotate(
|
|
24
|
+
is_investable=~Exists(base_qs.filter(parent=OuterRef("pk"))),
|
|
25
|
+
root=Subquery(base_qs.filter(tree_id=OuterRef("tree_id"), level=0).values("id")[:1]),
|
|
26
|
+
primary_security=ExpressionWrapper(
|
|
27
|
+
Coalesce(
|
|
28
|
+
Subquery(
|
|
29
|
+
base_qs.filter(
|
|
30
|
+
parent=OuterRef("pk"),
|
|
31
|
+
is_primary=True,
|
|
32
|
+
is_security=True,
|
|
33
|
+
).values(
|
|
34
|
+
"id"
|
|
35
|
+
)[:1]
|
|
36
|
+
),
|
|
37
|
+
F("id"),
|
|
38
|
+
),
|
|
39
|
+
output_field=AutoField(),
|
|
40
|
+
),
|
|
41
|
+
primary_quote=ExpressionWrapper(
|
|
42
|
+
Coalesce(
|
|
43
|
+
Subquery(
|
|
44
|
+
base_qs.filter(
|
|
45
|
+
parent=OuterRef("primary_security"),
|
|
46
|
+
is_primary=True,
|
|
47
|
+
).values(
|
|
48
|
+
"id"
|
|
49
|
+
)[:1]
|
|
50
|
+
),
|
|
51
|
+
F("primary_security"),
|
|
52
|
+
),
|
|
53
|
+
output_field=AutoField(),
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def annotate_all(self):
|
|
58
|
+
return self.annotate_base_data()
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def dl(self):
|
|
62
|
+
"""Provides access to the dataloader proxy for the entities in the QuerySet.
|
|
63
|
+
|
|
64
|
+
This method allows for easy retrieval of the DataloaderProxy instance
|
|
65
|
+
associated with the QuerySet. It enables the utilization of dataloader
|
|
66
|
+
functionalities directly from the QuerySet, facilitating data fetching and
|
|
67
|
+
processing tasks.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
DataloaderProxy: An instance of DataloaderProxy associated with the
|
|
71
|
+
entities in the QuerySet.
|
|
72
|
+
"""
|
|
73
|
+
return self.model.dl_proxy(self)
|