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,271 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from math import pow
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FinancialStatistics:
|
|
9
|
+
def __init__(self, prices: pd.Series):
|
|
10
|
+
prices = prices.copy().astype("float")
|
|
11
|
+
if type(prices) is not pd.Series:
|
|
12
|
+
raise ValueError("prices is expected to be a pandas Series")
|
|
13
|
+
self.prices = prices.sort_index()
|
|
14
|
+
if not prices.empty:
|
|
15
|
+
self.start = self.prices.index[0]
|
|
16
|
+
self.end = self.prices.index[-1]
|
|
17
|
+
else:
|
|
18
|
+
self.start = self.end = None
|
|
19
|
+
self.prices.index = pd.to_datetime(self.prices.index)
|
|
20
|
+
|
|
21
|
+
def is_valid(self):
|
|
22
|
+
return not self.prices.empty
|
|
23
|
+
|
|
24
|
+
def extract_monthly_performance_df(self) -> pd.Series:
|
|
25
|
+
return self.compute_performance()
|
|
26
|
+
|
|
27
|
+
def extract_annual_performance_df(self) -> pd.Series:
|
|
28
|
+
return self.compute_performance(freq="BYE")
|
|
29
|
+
|
|
30
|
+
def extract_inception_performance_df(self) -> float:
|
|
31
|
+
if not self.prices.empty and self.prices.iloc[0]:
|
|
32
|
+
return self.prices.iloc[-1] / self.prices.iloc[0] - 1
|
|
33
|
+
return 0
|
|
34
|
+
|
|
35
|
+
def extract_daily_performance_df(self) -> pd.Series:
|
|
36
|
+
return self.compute_performance(freq="B")
|
|
37
|
+
|
|
38
|
+
# ---------------------------- Financial Statistics ----------------------------
|
|
39
|
+
# ------------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
def compute_performance(self, freq: str | int = "BME", extrapolate: bool = True) -> pd.Series:
|
|
42
|
+
perfs = self.prices
|
|
43
|
+
if not perfs.empty:
|
|
44
|
+
if isinstance(freq, int):
|
|
45
|
+
perfs = perfs.resample(f"{freq}D", origin="end").ffill().pct_change()
|
|
46
|
+
else:
|
|
47
|
+
perfs = perfs.asfreq(freq, method="ffill")
|
|
48
|
+
if extrapolate:
|
|
49
|
+
perfs.loc[self.prices.index[0]] = self.prices.iloc[0]
|
|
50
|
+
perfs.loc[self.prices.index[-1]] = self.prices.iloc[-1]
|
|
51
|
+
perfs = perfs.sort_index()
|
|
52
|
+
perfs = perfs / perfs.shift(1) - 1
|
|
53
|
+
return perfs.dropna(how="any").rename("performance")
|
|
54
|
+
|
|
55
|
+
def get_mean_return(self, freq: str = "B") -> float | None:
|
|
56
|
+
"""Calculates the mean of returns"""
|
|
57
|
+
if self.is_valid():
|
|
58
|
+
return self.compute_performance(freq=freq).mean()
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def get_best_and_worst_returns(self, top: int = 10, freq: str = "B") -> pd.DataFrame:
|
|
62
|
+
"""Returns a DataFrame with the top best returns and top worst returns"""
|
|
63
|
+
df = pd.DataFrame(columns=["Date Best Return", "Best Return", "Date Worst Return", "Worst Return"])
|
|
64
|
+
if self.is_valid():
|
|
65
|
+
sort_returns = self.compute_performance(freq=freq).sort_values()
|
|
66
|
+
df.loc[:, "Worst Return"] = sort_returns[0:top].values
|
|
67
|
+
df.loc[:, "Date Worst Return"] = sort_returns[0:top].index
|
|
68
|
+
sort_returns = sort_returns.sort_values(ascending=False)
|
|
69
|
+
df.loc[:, "Best Return"] = sort_returns[0:top].values
|
|
70
|
+
df.loc[:, "Date Best Return"] = sort_returns[0:top].index
|
|
71
|
+
df["Date Worst Return"] = pd.to_datetime(df["Date Worst Return"])
|
|
72
|
+
df["Date Best Return"] = pd.to_datetime(df["Date Best Return"])
|
|
73
|
+
return df
|
|
74
|
+
return df
|
|
75
|
+
|
|
76
|
+
def get_drawdowns(self, freq: str = "B") -> pd.Series:
|
|
77
|
+
"""
|
|
78
|
+
Investopedia: A drawdown is a peak-to-trough decline during a specific period for an investment,
|
|
79
|
+
trading account, or fund. A drawdown is usually quoted as the percentage between the peak and the
|
|
80
|
+
subsequent trough.
|
|
81
|
+
"""
|
|
82
|
+
high_net_value = self.prices.cummax() # keep highest price the column
|
|
83
|
+
drawdowns = self.prices / high_net_value - 1
|
|
84
|
+
if freq:
|
|
85
|
+
drawdowns = drawdowns.asfreq(freq, method="ffill")
|
|
86
|
+
return drawdowns
|
|
87
|
+
|
|
88
|
+
def get_last_drawdowns(self) -> pd.Series:
|
|
89
|
+
"""Returns a Dataframe with the last drawdowns"""
|
|
90
|
+
df = self.get_drawdowns()
|
|
91
|
+
last_highest_price_date = df[df == 0].index[-1].strftime("%Y-%m-%d")
|
|
92
|
+
return df.loc[last_highest_price_date : df.index[-1]]
|
|
93
|
+
|
|
94
|
+
def get_maximum_drawdown(self) -> float:
|
|
95
|
+
"""Returns the maximum drawdown."""
|
|
96
|
+
df = self.get_drawdowns()
|
|
97
|
+
maximum_dd = df.min() # maximum in negative value
|
|
98
|
+
return maximum_dd
|
|
99
|
+
|
|
100
|
+
def get_last_recent_maximum_drawdown(self) -> float:
|
|
101
|
+
"""Returns the last recent maximum drawdown."""
|
|
102
|
+
df = self.get_last_drawdowns()
|
|
103
|
+
maximum_dd = df.min() # maximum in negative value
|
|
104
|
+
return maximum_dd
|
|
105
|
+
|
|
106
|
+
def get_maximum_drawdown_date(self) -> float:
|
|
107
|
+
df = self.get_drawdowns()
|
|
108
|
+
mdd_date = df[df == df.min()].index.to_pydatetime()[0].strftime("%Y-%m-%d")
|
|
109
|
+
return mdd_date
|
|
110
|
+
|
|
111
|
+
def get_last_recent_maximum_drawdown_date(self) -> date:
|
|
112
|
+
"""Returns the last recent maximum drawdown date."""
|
|
113
|
+
df = self.get_last_drawdowns()
|
|
114
|
+
mdd_date = df[df == df.min()].index.to_pydatetime()[0].strftime("%Y-%m-%d")
|
|
115
|
+
return mdd_date
|
|
116
|
+
|
|
117
|
+
def get_longest_drawdown_period(self) -> int:
|
|
118
|
+
"""Returns the longest number of day during drawdown"""
|
|
119
|
+
df = self.get_drawdowns()
|
|
120
|
+
df["Drawdown Period"] = df.mask(df < 0.0, 1)
|
|
121
|
+
a = df["Drawdown Period"] != 0
|
|
122
|
+
df1 = a.cumsum() - a.cumsum().where(~a).ffill().fillna(0).astype(int)
|
|
123
|
+
return df1.max()
|
|
124
|
+
|
|
125
|
+
def get_cumulative_returns(self, freq: str = "B") -> pd.Series:
|
|
126
|
+
"""Returns a dataframe with the cumulative return each day."""
|
|
127
|
+
returns = self.compute_performance(freq=freq)
|
|
128
|
+
# R = [(1+r1) * (1 + r2) * ... * (1+rn)] - 1
|
|
129
|
+
return returns.add(1).cumprod() - 1
|
|
130
|
+
|
|
131
|
+
def get_last_cumulative_return(self, freq: str = "B") -> float:
|
|
132
|
+
"""Returns the last cumulative return."""
|
|
133
|
+
cumulative_returns = self.get_cumulative_returns(freq=freq)
|
|
134
|
+
return cumulative_returns.iat[-1]
|
|
135
|
+
|
|
136
|
+
def get_positive_outliers(self, quantile: float = 0.95, freq: str = "B") -> pd.Series:
|
|
137
|
+
"""Extracts the outliers in a DataFrame."""
|
|
138
|
+
returns = self.compute_performance(freq=freq)
|
|
139
|
+
return returns[returns > returns.quantile(quantile)].dropna(how="all")
|
|
140
|
+
|
|
141
|
+
def get_value_at_risk(self, alpha: float = 0.05, freq: str = "B") -> float:
|
|
142
|
+
"""Calculates the value at risk for a given confident interval."""
|
|
143
|
+
if alpha < 0 or alpha > 1:
|
|
144
|
+
raise ValueError(f"Alpha = {alpha} is not possible, alpha must be between 0 and 1.")
|
|
145
|
+
|
|
146
|
+
returns = self.compute_performance(freq=freq)
|
|
147
|
+
return returns.quantile(alpha, interpolation="higher")
|
|
148
|
+
|
|
149
|
+
def get_conditional_value_at_risk(self, alpha: float = 0.05, freq: str = "B") -> float:
|
|
150
|
+
"""Calculates the conditional value at risk for a given confident interval."""
|
|
151
|
+
if alpha < 0 or alpha > 1:
|
|
152
|
+
raise ValueError(f"Alpha = {alpha} is not possible, alpha must be between 0 and 1.")
|
|
153
|
+
|
|
154
|
+
returns = self.compute_performance(freq=freq)
|
|
155
|
+
return returns[returns <= returns.quantile(alpha, interpolation="lower")].dropna(how="all").mean()
|
|
156
|
+
|
|
157
|
+
def get_skewness(self, freq: str = "B") -> float:
|
|
158
|
+
"""Calculates the skewness of the returns."""
|
|
159
|
+
returns = self.compute_performance(freq=freq)
|
|
160
|
+
return returns.skew()
|
|
161
|
+
|
|
162
|
+
def get_kurtosis(self, freq: str = "B") -> float:
|
|
163
|
+
"""Calculates the Kurtosis of the returns."""
|
|
164
|
+
returns = self.compute_performance(freq=freq)
|
|
165
|
+
return returns.kurtosis()
|
|
166
|
+
|
|
167
|
+
def get_excess_kurtosis(self, freq: str = "B") -> float:
|
|
168
|
+
return self.get_kurtosis(freq=freq) - 3.0
|
|
169
|
+
|
|
170
|
+
def get_compound_annual_growth_rate(self) -> float:
|
|
171
|
+
"""Calculates the compound annual growth rate (Last Price / First Price)(¹/year) - 1"""
|
|
172
|
+
first_price, last_price = self.prices.iat[0], self.prices.iat[-1]
|
|
173
|
+
years = (self.prices.index[-1] - self.prices.index[0]).days / 365.0
|
|
174
|
+
return (last_price / first_price) ** (1 / years) - 1 if first_price != 0 and years != 0 else 0
|
|
175
|
+
|
|
176
|
+
def get_sortino_ratio(self, target_ratio: float = 0, freq: str = "B") -> float:
|
|
177
|
+
"""
|
|
178
|
+
Calculates the adjusted Sortino ratio:
|
|
179
|
+
Sortino ratio = (Rp - target_ratio) / below_target_semi_deviation
|
|
180
|
+
below_target_semi_deviation = √(1 / T * Σ (d²))
|
|
181
|
+
d = (Rt-1 - target_ratio) only if the result is negative
|
|
182
|
+
"""
|
|
183
|
+
returns = self.compute_performance(freq=freq)
|
|
184
|
+
below_target_sd = np.sqrt((returns[returns < target_ratio] ** 2).sum() / len(returns))
|
|
185
|
+
annualized_return = self.get_compound_annual_growth_rate()
|
|
186
|
+
below_target_annualized = below_target_sd * np.sqrt(260)
|
|
187
|
+
return (annualized_return - target_ratio) / below_target_annualized
|
|
188
|
+
|
|
189
|
+
def get_adjusted_sortino_ratio(self, freq: str = "B") -> float:
|
|
190
|
+
"""Calculates the adjusted Sortino ratio (Sortino Ratio / √(2))"""
|
|
191
|
+
return self.get_sortino_ratio(freq=freq) / np.sqrt(2)
|
|
192
|
+
|
|
193
|
+
def get_calmar_ratio(self) -> float:
|
|
194
|
+
"""Calculates the calmar ratio (CAGR% / MaxDD%)"""
|
|
195
|
+
compound_annual_growth_rate = self.get_compound_annual_growth_rate()
|
|
196
|
+
maximum_dd = self.get_maximum_drawdown()
|
|
197
|
+
return compound_annual_growth_rate / abs(maximum_dd)
|
|
198
|
+
|
|
199
|
+
def get_risk_free_rate(self, risk_free_rate_prices_df: pd.Series) -> float | None:
|
|
200
|
+
# if the instrument has no prices, we do not know the lifetime of instrument, so we do not know for the risk
|
|
201
|
+
# instrument too
|
|
202
|
+
if not risk_free_rate_prices_df.empty:
|
|
203
|
+
nb_days = (self.prices.index[-1] - self.prices.index[0]).days
|
|
204
|
+
nb_days = 1 if nb_days == 0 else nb_days
|
|
205
|
+
exp = 365.0 / nb_days
|
|
206
|
+
annualized_free_rate = 1 + risk_free_rate_prices_df.sort_index(ascending=False) / (365 * 100)
|
|
207
|
+
tmp = annualized_free_rate.shift(1, fill_value=1).cumprod().dropna()
|
|
208
|
+
risk_free_rate = pow(tmp.iloc[-1], exp) - 1
|
|
209
|
+
return risk_free_rate
|
|
210
|
+
|
|
211
|
+
def get_sterling_ratio(self, risk_free_rate_prices_df: pd.Series) -> float | None:
|
|
212
|
+
"""Calculates the adjusted Sterling ratio ((Rp- Rf) / MDD)"""
|
|
213
|
+
if not risk_free_rate_prices_df.empty:
|
|
214
|
+
maximum_dd = self.get_maximum_drawdown()
|
|
215
|
+
annualized_return = self.get_compound_annual_growth_rate()
|
|
216
|
+
rf = self.get_risk_free_rate(risk_free_rate_prices_df)
|
|
217
|
+
rf = 0 if not rf else rf
|
|
218
|
+
return (annualized_return - rf) / abs(maximum_dd)
|
|
219
|
+
|
|
220
|
+
def get_burke_ratio(self, risk_free_rate_prices_df: pd.Series) -> float | None:
|
|
221
|
+
"""Calculates the adjusted Burke ratio ((Rp- Rf) / √(Σ (DD²)))"""
|
|
222
|
+
if not risk_free_rate_prices_df.empty:
|
|
223
|
+
drawdowns = self.get_drawdowns()
|
|
224
|
+
drawdowns = np.sqrt(drawdowns[drawdowns != 0].pow(2).sum())
|
|
225
|
+
annualized_return = self.get_compound_annual_growth_rate()
|
|
226
|
+
rf = self.get_risk_free_rate(risk_free_rate_prices_df)
|
|
227
|
+
rf = 0 if not rf else rf
|
|
228
|
+
return (annualized_return - rf) / drawdowns
|
|
229
|
+
|
|
230
|
+
def get_volatility(self, freq: str = "B") -> float:
|
|
231
|
+
"""Calculates the volatility of returns"""
|
|
232
|
+
adj = 1
|
|
233
|
+
if freq == "B":
|
|
234
|
+
adj = 260
|
|
235
|
+
elif freq == "W-MON":
|
|
236
|
+
adj = 52
|
|
237
|
+
elif freq == "BME":
|
|
238
|
+
adj = 12
|
|
239
|
+
return self.compute_performance(freq=freq).std() * np.sqrt(adj)
|
|
240
|
+
|
|
241
|
+
def prepare_daily_returns_instrument_vs_benchmark(self, benchmark_prices_df: pd.Series) -> pd.DataFrame:
|
|
242
|
+
if benchmark_prices_df.empty:
|
|
243
|
+
raise ValueError("benchmark price cannot be empty")
|
|
244
|
+
df = pd.DataFrame()
|
|
245
|
+
df["benchmark"] = FinancialStatistics(benchmark_prices_df).extract_daily_performance_df()
|
|
246
|
+
df["instrument"] = self.extract_daily_performance_df()
|
|
247
|
+
return df
|
|
248
|
+
|
|
249
|
+
def get_beta(self, benchmark_prices_df: pd.Series) -> float | None:
|
|
250
|
+
if not benchmark_prices_df.empty:
|
|
251
|
+
df = self.prepare_daily_returns_instrument_vs_benchmark(benchmark_prices_df)
|
|
252
|
+
cov = df["instrument"].cov(df["benchmark"])
|
|
253
|
+
var = df["benchmark"].var()
|
|
254
|
+
beta = cov / var
|
|
255
|
+
return beta
|
|
256
|
+
|
|
257
|
+
def get_correlation(self, benchmark_prices_df: pd.Series) -> float | None:
|
|
258
|
+
if not benchmark_prices_df.empty:
|
|
259
|
+
df = self.prepare_daily_returns_instrument_vs_benchmark(benchmark_prices_df)
|
|
260
|
+
return df["instrument"].corr(df["benchmark"])
|
|
261
|
+
|
|
262
|
+
def get_sharpe_ratio(self, risk_free_rate_prices_df: pd.Series) -> float | None:
|
|
263
|
+
if not risk_free_rate_prices_df.empty:
|
|
264
|
+
annualized_volatility = self.get_volatility()
|
|
265
|
+
annualized_return = self.get_compound_annual_growth_rate()
|
|
266
|
+
rf = self.get_risk_free_rate(risk_free_rate_prices_df)
|
|
267
|
+
rf = 0 if not rf else rf
|
|
268
|
+
return (annualized_return - rf) / annualized_volatility
|
|
269
|
+
|
|
270
|
+
# -------------------------- End of Financial Statistics -----------------------
|
|
271
|
+
# ------------------------------------------------------------------------------
|