wbfdm 1.44.5__py2.py3-none-any.whl → 1.45.0__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.
- wbfdm/admin/classifications.py +1 -0
- wbfdm/admin/esg.py +1 -0
- wbfdm/admin/exchanges.py +1 -0
- wbfdm/admin/instrument_lists.py +1 -0
- wbfdm/admin/instrument_prices.py +1 -0
- wbfdm/admin/instrument_requests.py +1 -0
- wbfdm/admin/instruments.py +1 -0
- wbfdm/admin/instruments_relationships.py +1 -0
- wbfdm/admin/options.py +1 -0
- wbfdm/analysis/esg/enums.py +2 -3
- wbfdm/analysis/esg/esg_analysis.py +1 -0
- wbfdm/analysis/esg/utils.py +1 -0
- wbfdm/analysis/financial_analysis/financial_metric_analysis.py +1 -0
- wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +1 -0
- wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +4 -1
- wbfdm/analysis/financial_analysis/statement_with_estimates.py +3 -4
- wbfdm/analysis/financial_analysis/utils.py +1 -0
- wbfdm/analysis/technical_analysis/technical_analysis.py +1 -0
- wbfdm/contrib/dsws/client.py +17 -2
- wbfdm/contrib/internal/dataloaders/market_data.py +1 -0
- wbfdm/contrib/metric/admin/instruments.py +1 -0
- wbfdm/contrib/metric/backends/base.py +5 -4
- wbfdm/contrib/metric/backends/performances.py +4 -3
- wbfdm/contrib/metric/backends/statistics.py +1 -0
- wbfdm/contrib/metric/factories.py +1 -0
- wbfdm/contrib/metric/filters.py +1 -0
- wbfdm/contrib/metric/serializers.py +1 -0
- wbfdm/contrib/metric/tasks.py +2 -1
- wbfdm/contrib/metric/tests/backends/test_performances.py +1 -0
- wbfdm/contrib/metric/tests/backends/test_statistics.py +1 -0
- wbfdm/contrib/metric/tests/test_models.py +1 -0
- wbfdm/contrib/metric/tests/test_viewsets.py +1 -0
- wbfdm/contrib/metric/viewsets/configs/display.py +1 -0
- wbfdm/contrib/metric/viewsets/mixins.py +1 -0
- wbfdm/contrib/metric/viewsets/viewsets.py +1 -0
- wbfdm/contrib/msci/dataloaders/esg.py +1 -0
- wbfdm/contrib/msci/dataloaders/esg_controversies.py +1 -0
- wbfdm/contrib/msci/sync.py +1 -0
- wbfdm/contrib/qa/dataloaders/adjustments.py +5 -5
- wbfdm/contrib/qa/dataloaders/corporate_actions.py +5 -5
- wbfdm/contrib/qa/dataloaders/financials.py +1 -0
- wbfdm/contrib/qa/dataloaders/market_data.py +9 -7
- wbfdm/contrib/qa/dataloaders/officers.py +8 -10
- wbfdm/contrib/qa/dataloaders/reporting_dates.py +1 -0
- wbfdm/contrib/qa/dataloaders/statements.py +1 -0
- wbfdm/contrib/qa/dataloaders/utils.py +2 -2
- wbfdm/dataloaders/proxies.py +1 -0
- wbfdm/factories/classifications.py +1 -0
- wbfdm/factories/controversies.py +1 -0
- wbfdm/factories/exchanges.py +1 -0
- wbfdm/factories/instrument_list.py +1 -0
- wbfdm/factories/instrument_prices.py +1 -0
- wbfdm/factories/instruments.py +1 -0
- wbfdm/factories/instruments_relationships.py +1 -0
- wbfdm/factories/options.py +1 -0
- wbfdm/figures/financials/financial_analysis_charts.py +1 -0
- wbfdm/figures/financials/financials_charts.py +1 -0
- wbfdm/filters/classifications.py +1 -0
- wbfdm/filters/exchanges.py +1 -0
- wbfdm/filters/financials.py +1 -0
- wbfdm/filters/financials_analysis.py +1 -0
- wbfdm/filters/instrument_prices.py +1 -0
- wbfdm/filters/instruments.py +1 -0
- wbfdm/filters/utils.py +1 -0
- wbfdm/import_export/backends/cbinsights/mixin.py +1 -0
- wbfdm/import_export/backends/refinitiv/mixin.py +1 -0
- wbfdm/import_export/backends/refinitiv/utils/controller.py +1 -0
- wbfdm/import_export/handlers/instrument.py +1 -0
- wbfdm/import_export/handlers/instrument_price.py +5 -0
- wbfdm/import_export/parsers/cbinsights/deals.py +1 -0
- wbfdm/import_export/parsers/cbinsights/equities.py +1 -0
- wbfdm/import_export/parsers/cbinsights/fundamentals.py +1 -0
- wbfdm/import_export/parsers/refinitiv/instrument.py +1 -0
- wbfdm/import_export/parsers/refinitiv/instrument_price.py +1 -0
- wbfdm/import_export/resources/classification.py +1 -0
- wbfdm/import_export/resources/instrument_prices.py +1 -0
- wbfdm/import_export/resources/instruments.py +1 -0
- wbfdm/models/esg/controversies.py +1 -0
- wbfdm/models/instruments/instrument_lists.py +1 -0
- wbfdm/models/instruments/instrument_prices.py +1 -0
- wbfdm/models/instruments/instrument_requests.py +1 -0
- wbfdm/models/instruments/instruments.py +12 -7
- wbfdm/models/instruments/llm/create_instrument_news_relationships.py +2 -2
- wbfdm/models/instruments/mixin/instruments.py +24 -19
- wbfdm/models/instruments/options.py +1 -0
- wbfdm/models/instruments/private_equities.py +1 -0
- wbfdm/models/instruments/utils.py +1 -0
- wbfdm/serializers/esg.py +1 -0
- wbfdm/serializers/exchanges.py +1 -0
- wbfdm/serializers/instruments/classifications.py +1 -0
- wbfdm/serializers/instruments/instrument_lists.py +1 -0
- wbfdm/serializers/instruments/instrument_prices.py +1 -0
- wbfdm/serializers/instruments/instrument_relationships.py +1 -0
- wbfdm/serializers/instruments/instrument_requests.py +1 -0
- wbfdm/serializers/instruments/instruments.py +1 -0
- wbfdm/signals.py +3 -0
- wbfdm/tasks.py +45 -5
- wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +1 -0
- wbfdm/tests/analysis/financial_analysis/test_utils.py +1 -0
- wbfdm/tests/analysis/test_esg.py +1 -0
- wbfdm/tests/dataloaders/test_cache.py +1 -0
- wbfdm/tests/models/test_classifications.py +1 -0
- wbfdm/tests/models/test_instrument_list.py +1 -0
- wbfdm/tests/models/test_instrument_prices.py +1 -0
- wbfdm/tests/models/test_instruments.py +2 -1
- wbfdm/tests/models/test_merge.py +14 -9
- wbfdm/tests/models/test_options.py +1 -0
- wbfdm/urls.py +1 -0
- wbfdm/viewsets/configs/buttons/instruments.py +1 -0
- wbfdm/viewsets/configs/display/esg.py +1 -0
- wbfdm/viewsets/configs/display/instrument_lists.py +1 -0
- wbfdm/viewsets/configs/display/instrument_prices.py +1 -0
- wbfdm/viewsets/configs/display/instrument_requests.py +1 -0
- wbfdm/viewsets/configs/display/instruments.py +31 -4
- wbfdm/viewsets/configs/endpoints/instrument_requests.py +1 -0
- wbfdm/viewsets/configs/titles/classifications.py +1 -0
- wbfdm/viewsets/configs/titles/financials_analysis.py +1 -0
- wbfdm/viewsets/configs/titles/instrument_prices.py +1 -0
- wbfdm/viewsets/configs/titles/market_data.py +1 -0
- wbfdm/viewsets/esg.py +1 -0
- wbfdm/viewsets/exchanges.py +1 -0
- wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +1 -0
- wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +2 -1
- wbfdm/viewsets/financial_analysis/financial_summary.py +4 -4
- wbfdm/viewsets/financial_analysis/statement_with_estimates.py +1 -0
- wbfdm/viewsets/instruments/classifications.py +1 -0
- wbfdm/viewsets/instruments/financials_analysis.py +1 -0
- wbfdm/viewsets/instruments/instrument_lists.py +1 -0
- wbfdm/viewsets/instruments/instrument_prices.py +1 -0
- wbfdm/viewsets/instruments/instrument_requests.py +1 -0
- wbfdm/viewsets/instruments/instruments.py +1 -0
- wbfdm/viewsets/instruments/instruments_relationships.py +1 -0
- wbfdm/viewsets/market_data.py +3 -2
- wbfdm/viewsets/mixins.py +1 -0
- wbfdm/viewsets/officers.py +1 -0
- wbfdm/viewsets/prices.py +1 -0
- wbfdm/viewsets/statements/statements.py +1 -0
- wbfdm/viewsets/technical_analysis/monthly_performances.py +1 -0
- {wbfdm-1.44.5.dist-info → wbfdm-1.45.0.dist-info}/METADATA +1 -1
- {wbfdm-1.44.5.dist-info → wbfdm-1.45.0.dist-info}/RECORD +141 -141
- {wbfdm-1.44.5.dist-info → wbfdm-1.45.0.dist-info}/WHEEL +0 -0
|
@@ -11,6 +11,7 @@ from django.db.models import ExpressionWrapper, F, FloatField, QuerySet
|
|
|
11
11
|
from pandas.tseries.offsets import BYearEnd
|
|
12
12
|
from plotly.subplots import make_subplots
|
|
13
13
|
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
14
|
+
|
|
14
15
|
from wbfdm.enums import MarketData
|
|
15
16
|
from wbfdm.models import Instrument, InstrumentPrice
|
|
16
17
|
|
wbfdm/filters/classifications.py
CHANGED
wbfdm/filters/exchanges.py
CHANGED
wbfdm/filters/financials.py
CHANGED
|
@@ -3,6 +3,7 @@ from datetime import date
|
|
|
3
3
|
from django.db import models
|
|
4
4
|
from psycopg.types.range import DateRange
|
|
5
5
|
from wbcore import filters as wb_filters
|
|
6
|
+
|
|
6
7
|
from wbfdm.filters.utils import get_earliest_date, get_latest_date
|
|
7
8
|
from wbfdm.models import Instrument, InstrumentPrice
|
|
8
9
|
|
wbfdm/filters/instruments.py
CHANGED
|
@@ -4,6 +4,7 @@ from django.db.models import Q
|
|
|
4
4
|
from psycopg.types.range import DateRange
|
|
5
5
|
from wbcore import filters as wb_filters
|
|
6
6
|
from wbcore.contrib.tags.filters import TagFilterMixin
|
|
7
|
+
|
|
7
8
|
from wbfdm.filters.utils import get_earliest_date, get_latest_date
|
|
8
9
|
from wbfdm.models.instruments import (
|
|
9
10
|
ClassificationGroup,
|
wbfdm/filters/utils.py
CHANGED
|
@@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
6
6
|
from pandas.tseries.offsets import QuarterEnd, YearEnd
|
|
7
7
|
from tqdm import tqdm
|
|
8
8
|
from wbcore.contrib.io.models import ImportedObjectProviderRelationship, Provider
|
|
9
|
+
|
|
9
10
|
from wbfdm.contrib.dsws.client import Client
|
|
10
11
|
from wbfdm.models.instruments.instruments import Instrument
|
|
11
12
|
|
|
@@ -12,6 +12,7 @@ from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
|
12
12
|
from wbcore.contrib.geography.models import Geography
|
|
13
13
|
from wbcore.contrib.io.exceptions import DeserializationError
|
|
14
14
|
from wbcore.contrib.io.imports import ImportExportHandler, ImportState
|
|
15
|
+
|
|
15
16
|
from wbfdm.models.exchanges import Exchange
|
|
16
17
|
|
|
17
18
|
|
|
@@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError
|
|
|
5
5
|
from django.db import models
|
|
6
6
|
from wbcore.contrib.io.exceptions import DeserializationError
|
|
7
7
|
from wbcore.contrib.io.imports import ImportExportHandler
|
|
8
|
+
|
|
8
9
|
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
9
10
|
|
|
10
11
|
MAX_NB_DATES_BEFORE_HISTORICAL_IMPORT = 3
|
|
@@ -61,6 +62,10 @@ class InstrumentPriceImportHandler(ImportExportHandler):
|
|
|
61
62
|
if price.exists():
|
|
62
63
|
return price.first()
|
|
63
64
|
|
|
65
|
+
def _save_object(self, _object, **kwargs):
|
|
66
|
+
_object.compute_and_update_statistics() # compute beta, correlation, sharpe and annualized daily volatility everytime object changes
|
|
67
|
+
return super()._save_object(_object, **kwargs)
|
|
68
|
+
|
|
64
69
|
def _post_processing_objects(
|
|
65
70
|
self,
|
|
66
71
|
created_objs: List[models.Model],
|
|
@@ -13,6 +13,7 @@ from wbcore.contrib.geography.import_export.resources.geography import (
|
|
|
13
13
|
)
|
|
14
14
|
from wbcore.contrib.io.resources import FilterModelResource
|
|
15
15
|
from wbcore.contrib.notifications.dispatch import send_notification
|
|
16
|
+
|
|
16
17
|
from wbfdm.models import Exchange, Instrument, InstrumentClassificationThroughModel
|
|
17
18
|
|
|
18
19
|
from .classification import ClassificationManyToManyWidget
|
|
@@ -4,6 +4,7 @@ from wbcore.contrib.io.mixins import ImportMixin
|
|
|
4
4
|
from wbcore.contrib.notifications.utils import create_notification_type
|
|
5
5
|
from wbcore.models import WBModel
|
|
6
6
|
from wbcore.utils.models import ComplexToStringMixin
|
|
7
|
+
|
|
7
8
|
from wbfdm.import_export.handlers.instrument_list import InstrumentListImportHandler
|
|
8
9
|
from wbfdm.models.instruments.instruments import Instrument
|
|
9
10
|
|
|
@@ -23,6 +23,7 @@ from wbcore.contrib.currency.models import CurrencyFXRates
|
|
|
23
23
|
from wbcore.contrib.io.mixins import ImportMixin
|
|
24
24
|
from wbcore.models import DynamicDecimalField, DynamicFloatField, DynamicModel, WBModel
|
|
25
25
|
from wbcore.signals import pre_merge
|
|
26
|
+
|
|
26
27
|
from wbfdm.analysis.financial_analysis.financial_statistics_analysis import (
|
|
27
28
|
FinancialStatistics,
|
|
28
29
|
)
|
|
@@ -10,6 +10,7 @@ from wbcore.contrib.tags.models import Tag
|
|
|
10
10
|
from wbcore.enums import RequestType
|
|
11
11
|
from wbcore.metadata.configs.buttons import ActionButton, ButtonDefaultColor
|
|
12
12
|
from wbcore.models import WBModel
|
|
13
|
+
|
|
13
14
|
from wbfdm.models.instruments.classifications import Classification
|
|
14
15
|
|
|
15
16
|
from .instruments import Instrument, InstrumentType
|
|
@@ -30,6 +30,9 @@ from wbcore.contrib.tags.models import TagModelMixin
|
|
|
30
30
|
from wbcore.models import WBModel
|
|
31
31
|
from wbcore.signals import pre_merge
|
|
32
32
|
from wbcore.utils.models import ComplexToStringMixin
|
|
33
|
+
from wbnews.models import News
|
|
34
|
+
from wbnews.signals import create_news_relationships
|
|
35
|
+
|
|
33
36
|
from wbfdm.analysis import TechnicalAnalysis
|
|
34
37
|
from wbfdm.contrib.internal.dataloaders.market_data import MarketDataDataloader
|
|
35
38
|
from wbfdm.contrib.metric.dispatch import compute_metrics
|
|
@@ -42,8 +45,6 @@ from wbfdm.signals import (
|
|
|
42
45
|
add_instrument_to_investable_universe,
|
|
43
46
|
instrument_price_imported,
|
|
44
47
|
)
|
|
45
|
-
from wbnews.models import News
|
|
46
|
-
from wbnews.signals import create_news_relationships
|
|
47
48
|
|
|
48
49
|
from ...dataloaders.proxies import InstrumentDataloaderProxy
|
|
49
50
|
from .instrument_relationships import RelatedInstrumentThroughModel
|
|
@@ -514,7 +515,9 @@ class Instrument(ComplexToStringMixin, TagModelMixin, ImportMixin, InstrumentPMS
|
|
|
514
515
|
models.Index(fields=["is_security"], name="instrument_security_index"),
|
|
515
516
|
models.Index(fields=["level"], name="instrument_level_index"),
|
|
516
517
|
GinIndex(fields=["search_vector"], name="instrument_sv_gin_idx"), # type: ignore
|
|
517
|
-
GinIndex(
|
|
518
|
+
GinIndex(
|
|
519
|
+
fields=["trigram_search_vector"], opclasses=["gin_trgm_ops"], name="instrument_trigram_sv_gin_idx"
|
|
520
|
+
), # type: ignore
|
|
518
521
|
]
|
|
519
522
|
notification_types = [
|
|
520
523
|
create_notification_type(
|
|
@@ -841,7 +844,7 @@ class Instrument(ComplexToStringMixin, TagModelMixin, ImportMixin, InstrumentPMS
|
|
|
841
844
|
def technical_benchmark_analysis(self, from_date: date | None = None, to_date: date | None = None):
|
|
842
845
|
return TechnicalAnalysis.init_close_from_instrument(self, from_date, to_date)
|
|
843
846
|
|
|
844
|
-
def
|
|
847
|
+
def get_price_objects(self, start: date | None = None, end: date | None = None, clear: bool = False):
|
|
845
848
|
if not self.is_leaf_node():
|
|
846
849
|
raise ValueError("Cannot import price on a non-leaf node")
|
|
847
850
|
if not start:
|
|
@@ -858,12 +861,14 @@ class Instrument(ComplexToStringMixin, TagModelMixin, ImportMixin, InstrumentPMS
|
|
|
858
861
|
# we detect when was the last date price imported before start and switch the start date from there
|
|
859
862
|
with suppress(ObjectDoesNotExist):
|
|
860
863
|
start = self.prices.filter(date__lte=start).latest("date").date
|
|
861
|
-
|
|
862
|
-
self.save_prices_in_db(start, end, clear=clear)
|
|
864
|
+
yield from self._get_price_objects(start, end, clear=clear)
|
|
863
865
|
|
|
866
|
+
def import_prices(self, start: date | None = None, end: date | None = None, clear: bool = False):
|
|
867
|
+
# Import instrument prices
|
|
868
|
+
objs = list(self.get_price_objects(start=start, end=end, clear=clear))
|
|
869
|
+
self.bulk_save_instrument_prices(objs)
|
|
864
870
|
# compute daily statistics & performances
|
|
865
871
|
self.update_last_valuation_date()
|
|
866
|
-
|
|
867
872
|
instrument_price_imported.send(sender=Instrument, instrument=self, start=start, end=end)
|
|
868
873
|
|
|
869
874
|
@classmethod
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from typing import Any
|
|
2
3
|
|
|
3
4
|
from celery import shared_task
|
|
@@ -6,7 +7,6 @@ from langchain_core.messages import HumanMessage, SystemMessage
|
|
|
6
7
|
from pydantic import BaseModel, Field
|
|
7
8
|
from wbcore.contrib.ai.exceptions import APIStatusErrors
|
|
8
9
|
from wbcore.contrib.ai.llm.utils import run_llm
|
|
9
|
-
import logging
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger("llm")
|
|
12
12
|
|
|
@@ -82,5 +82,5 @@ def run_company_extraction_llm(title: str, description: str, *args) -> list[dict
|
|
|
82
82
|
except tuple(APIStatusErrors) as e: # for APIStatusError, we let celery retry it
|
|
83
83
|
raise e
|
|
84
84
|
except Exception as e: # otherwise we log the error and silently fail
|
|
85
|
-
logger.
|
|
85
|
+
logger.warning(str(e))
|
|
86
86
|
return relationships
|
|
@@ -4,12 +4,13 @@ from collections import defaultdict
|
|
|
4
4
|
from contextlib import suppress
|
|
5
5
|
from datetime import date, timedelta
|
|
6
6
|
from decimal import Decimal
|
|
7
|
-
from typing import Dict, Optional
|
|
7
|
+
from typing import Dict, Iterable, Optional
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
import pandas as pd
|
|
11
11
|
from pandas.tseries.offsets import BDay
|
|
12
12
|
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
13
|
+
|
|
13
14
|
from wbfdm.analysis.financial_analysis.financial_statistics_analysis import (
|
|
14
15
|
FinancialStatistics,
|
|
15
16
|
)
|
|
@@ -198,7 +199,7 @@ class InstrumentPMSMixin:
|
|
|
198
199
|
|
|
199
200
|
return df
|
|
200
201
|
|
|
201
|
-
def
|
|
202
|
+
def _get_price_objects(self, from_date: date, to_date: date, clear: bool = False) -> Iterable[InstrumentPrice]:
|
|
202
203
|
df = pd.DataFrame(
|
|
203
204
|
self.__class__.objects.filter(id=self.id).dl.market_data(
|
|
204
205
|
from_date=from_date
|
|
@@ -236,8 +237,7 @@ class InstrumentPMSMixin:
|
|
|
236
237
|
df = df[df.index.date >= from_date] # we import only from the requested from_date
|
|
237
238
|
df = df.reset_index().dropna(subset=["index", "close"])
|
|
238
239
|
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
239
|
-
|
|
240
|
-
create_objs = []
|
|
240
|
+
|
|
241
241
|
for row in df.to_dict("records"):
|
|
242
242
|
if (_date := row.get("index")) and (close := row.get("close", None)):
|
|
243
243
|
# we make sure data does not exist 10 digits (for db constraint)
|
|
@@ -264,7 +264,8 @@ class InstrumentPMSMixin:
|
|
|
264
264
|
p.market_capitalization = row.get("market_capitalization", p.market_capitalization)
|
|
265
265
|
p.market_capitalization_consolidated = p.market_capitalization
|
|
266
266
|
p.set_dynamic_field(False)
|
|
267
|
-
|
|
267
|
+
p.id = None
|
|
268
|
+
yield p
|
|
268
269
|
except InstrumentPrice.DoesNotExist:
|
|
269
270
|
with suppress(CurrencyFXRates.DoesNotExist):
|
|
270
271
|
p = InstrumentPrice(
|
|
@@ -281,17 +282,21 @@ class InstrumentPMSMixin:
|
|
|
281
282
|
volume_50d=row.get("volume_50d", None),
|
|
282
283
|
)
|
|
283
284
|
p.set_dynamic_field(False)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
285
|
+
yield p
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def bulk_save_instrument_prices(cls, objs):
|
|
289
|
+
InstrumentPrice.objects.bulk_create(
|
|
290
|
+
objs,
|
|
291
|
+
unique_fields=["instrument", "calculated", "date"],
|
|
292
|
+
update_conflicts=True,
|
|
293
|
+
update_fields=[
|
|
294
|
+
"net_value",
|
|
295
|
+
"gross_value",
|
|
296
|
+
"volume",
|
|
297
|
+
"market_capitalization",
|
|
298
|
+
"market_capitalization_consolidated",
|
|
299
|
+
"calculated",
|
|
300
|
+
"volume_50d",
|
|
301
|
+
],
|
|
302
|
+
)
|
wbfdm/serializers/esg.py
CHANGED
wbfdm/serializers/exchanges.py
CHANGED
|
@@ -2,6 +2,7 @@ from rest_framework import serializers as rf_serializers
|
|
|
2
2
|
from rest_framework.reverse import reverse
|
|
3
3
|
from wbcore import serializers as wb_serializers
|
|
4
4
|
from wbcore.serializers import DefaultAttributeFromRemoteField, DefaultFromView
|
|
5
|
+
|
|
5
6
|
from wbfdm.models.instruments import Classification, ClassificationGroup
|
|
6
7
|
from wbfdm.preferences import get_default_classification_group
|
|
7
8
|
|
|
@@ -4,6 +4,7 @@ from wbcore import serializers as wb_serializers
|
|
|
4
4
|
from wbcore.contrib.directory.models import Person
|
|
5
5
|
from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
|
|
6
6
|
from wbcore.contrib.tags.serializers import TagSerializerMixin
|
|
7
|
+
|
|
7
8
|
from wbfdm.models import (
|
|
8
9
|
Classification,
|
|
9
10
|
Instrument,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from wbcore import serializers as wb_serializers
|
|
2
2
|
from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
|
|
3
|
+
|
|
3
4
|
from wbfdm.models.instruments import InstrumentRequest
|
|
4
5
|
|
|
5
6
|
from .instruments import InstrumentModelSerializer, InstrumentRepresentationSerializer
|
|
@@ -8,6 +8,7 @@ from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
|
|
|
8
8
|
from wbcore.contrib.geography.models import Geography
|
|
9
9
|
from wbcore.contrib.geography.serializers import CountryRepresentationSerializer
|
|
10
10
|
from wbcore.contrib.tags.serializers import TagRepresentationSerializer
|
|
11
|
+
|
|
11
12
|
from wbfdm.models import Instrument, InstrumentType
|
|
12
13
|
|
|
13
14
|
from ..exchanges import ExchangeRepresentationSerializer
|
wbfdm/signals.py
CHANGED
|
@@ -5,3 +5,6 @@ add_instrument_to_investable_universe = ModelSignal(use_caching=False)
|
|
|
5
5
|
|
|
6
6
|
# this signal is triggered whenever prices are stored in the system and action needs to be considered
|
|
7
7
|
instrument_price_imported = ModelSignal(use_caching=False)
|
|
8
|
+
|
|
9
|
+
# this signal is triggered whenever all prices are imported in the system and action needs to be considered
|
|
10
|
+
investable_universe_updated = ModelSignal(use_caching=False)
|
wbfdm/tasks.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
from datetime import date
|
|
1
|
+
from datetime import date, timedelta
|
|
2
2
|
|
|
3
3
|
from celery import shared_task
|
|
4
4
|
from django.db import transaction
|
|
5
5
|
from django.db.models import Q
|
|
6
6
|
from pandas.tseries.offsets import BDay
|
|
7
7
|
from tqdm import tqdm
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
from wbfdm.models import Instrument, InstrumentPrice
|
|
9
10
|
from wbfdm.sync.runner import ( # noqa: F401
|
|
10
11
|
initialize_exchanges,
|
|
11
12
|
initialize_instruments,
|
|
@@ -13,9 +14,13 @@ from wbfdm.sync.runner import ( # noqa: F401
|
|
|
13
14
|
synchronize_instruments,
|
|
14
15
|
)
|
|
15
16
|
|
|
17
|
+
from .signals import investable_universe_updated
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
@shared_task(queue="portfolio")
|
|
18
|
-
def update_of_investable_universe_data(
|
|
21
|
+
def update_of_investable_universe_data(
|
|
22
|
+
start: date | None = None, end: date | None = None, clear: bool = False, with_background_tasks: bool = True
|
|
23
|
+
):
|
|
19
24
|
"""
|
|
20
25
|
Update the investable universe data on a daily basis.
|
|
21
26
|
|
|
@@ -38,7 +43,6 @@ def update_of_investable_universe_data(start: date | None = None, end: date | No
|
|
|
38
43
|
).date() # we don't import today price in case the dataloader returns duplicates (e.g. DSWS)
|
|
39
44
|
if not start:
|
|
40
45
|
start = (end - BDay(3)).date() # override three last day by default
|
|
41
|
-
|
|
42
46
|
Instrument.investable_universe.update(
|
|
43
47
|
is_investable_universe=True
|
|
44
48
|
) # ensure all the investable universe is marked as such
|
|
@@ -47,8 +51,24 @@ def update_of_investable_universe_data(start: date | None = None, end: date | No
|
|
|
47
51
|
Q(is_managed=True)
|
|
48
52
|
| Q(dl_parameters__market_data__path="wbfdm.contrib.internal.dataloaders.market_data.MarketDataDataloader")
|
|
49
53
|
) # we exclude product and index managed to avoid circular import
|
|
54
|
+
prices = []
|
|
55
|
+
for instrument in tqdm(instruments, total=instruments.count()):
|
|
56
|
+
try:
|
|
57
|
+
prices.extend(list(instrument.get_price_objects(start=start, end=end, clear=clear)))
|
|
58
|
+
except ConnectionError:
|
|
59
|
+
print(f"Connection error while processing instrument {instrument}") # noqa
|
|
60
|
+
Instrument.bulk_save_instrument_prices(prices)
|
|
61
|
+
investable_universe_updated.send(sender=Instrument, end_date=end)
|
|
62
|
+
if with_background_tasks:
|
|
63
|
+
daily_update_instrument_price_statistics.delay(from_date=start, to_date=end)
|
|
64
|
+
update_instrument_metrics_as_task.delay()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@shared_task(queue="portfolio")
|
|
68
|
+
def update_instrument_metrics_as_task():
|
|
69
|
+
instruments = Instrument.active_objects.filter(is_investable_universe=True)
|
|
50
70
|
for instrument in tqdm(instruments, total=instruments.count()):
|
|
51
|
-
instrument.
|
|
71
|
+
instrument.update_last_valuation_date()
|
|
52
72
|
|
|
53
73
|
|
|
54
74
|
@shared_task(queue="portfolio")
|
|
@@ -67,3 +87,23 @@ def full_synchronization_as_task():
|
|
|
67
87
|
initialize_instruments()
|
|
68
88
|
with transaction.atomic():
|
|
69
89
|
Instrument.objects.rebuild() # rebuild MPTT tree
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Daily synchronization tasks.
|
|
93
|
+
# This tasks needs to be ran at maximum once a day in order to guarantee data consitency in
|
|
94
|
+
# case of change in change (e.g. reimport).
|
|
95
|
+
@shared_task(queue="portfolio")
|
|
96
|
+
def daily_update_instrument_price_statistics(from_date: date = None, to_date: date = None):
|
|
97
|
+
if not to_date:
|
|
98
|
+
to_date = date.today()
|
|
99
|
+
if not from_date:
|
|
100
|
+
from_date = to_date - timedelta(days=3)
|
|
101
|
+
# We query for the last 7 days unsynch instrument prices.
|
|
102
|
+
prices = InstrumentPrice.objects.filter(date__gte=from_date, date__lte=to_date)
|
|
103
|
+
objs = []
|
|
104
|
+
for p in tqdm(prices.iterator(), total=prices.count()):
|
|
105
|
+
p.compute_and_update_statistics()
|
|
106
|
+
objs.append(p)
|
|
107
|
+
InstrumentPrice.objects.bulk_update(
|
|
108
|
+
objs, fields=["sharpe_ratio", "correlation", "beta", "annualized_daily_volatility"], batch_size=1000
|
|
109
|
+
)
|
|
@@ -6,6 +6,7 @@ import numpy as np
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
import pytest
|
|
8
8
|
from faker import Faker
|
|
9
|
+
|
|
9
10
|
from wbfdm.analysis.financial_analysis.utils import FinancialAnalysisResult, Loader
|
|
10
11
|
from wbfdm.dataloaders.proxies import InstrumentDataloaderProxy
|
|
11
12
|
from wbfdm.enums import Financial, MarketData
|
wbfdm/tests/analysis/test_esg.py
CHANGED
|
@@ -4,6 +4,7 @@ import pytest
|
|
|
4
4
|
from faker import Faker
|
|
5
5
|
from wbcore.contrib.currency.factories import CurrencyFXRatesFactory
|
|
6
6
|
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
7
|
+
|
|
7
8
|
from wbfdm.analysis.esg.enums import ESGAggregation
|
|
8
9
|
from wbfdm.analysis.esg.esg_analysis import DataLoader
|
|
9
10
|
|
|
@@ -4,6 +4,7 @@ import pytest
|
|
|
4
4
|
from django.contrib.auth.models import Permission
|
|
5
5
|
from faker import Faker
|
|
6
6
|
from wbcore.contrib.io.exceptions import DeserializationError
|
|
7
|
+
|
|
7
8
|
from wbfdm.import_export.handlers.instrument_list import InstrumentListImportHandler
|
|
8
9
|
from wbfdm.models import InstrumentListThroughModel
|
|
9
10
|
|