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.
Files changed (141) hide show
  1. wbfdm/admin/classifications.py +1 -0
  2. wbfdm/admin/esg.py +1 -0
  3. wbfdm/admin/exchanges.py +1 -0
  4. wbfdm/admin/instrument_lists.py +1 -0
  5. wbfdm/admin/instrument_prices.py +1 -0
  6. wbfdm/admin/instrument_requests.py +1 -0
  7. wbfdm/admin/instruments.py +1 -0
  8. wbfdm/admin/instruments_relationships.py +1 -0
  9. wbfdm/admin/options.py +1 -0
  10. wbfdm/analysis/esg/enums.py +2 -3
  11. wbfdm/analysis/esg/esg_analysis.py +1 -0
  12. wbfdm/analysis/esg/utils.py +1 -0
  13. wbfdm/analysis/financial_analysis/financial_metric_analysis.py +1 -0
  14. wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +1 -0
  15. wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +4 -1
  16. wbfdm/analysis/financial_analysis/statement_with_estimates.py +3 -4
  17. wbfdm/analysis/financial_analysis/utils.py +1 -0
  18. wbfdm/analysis/technical_analysis/technical_analysis.py +1 -0
  19. wbfdm/contrib/dsws/client.py +17 -2
  20. wbfdm/contrib/internal/dataloaders/market_data.py +1 -0
  21. wbfdm/contrib/metric/admin/instruments.py +1 -0
  22. wbfdm/contrib/metric/backends/base.py +5 -4
  23. wbfdm/contrib/metric/backends/performances.py +4 -3
  24. wbfdm/contrib/metric/backends/statistics.py +1 -0
  25. wbfdm/contrib/metric/factories.py +1 -0
  26. wbfdm/contrib/metric/filters.py +1 -0
  27. wbfdm/contrib/metric/serializers.py +1 -0
  28. wbfdm/contrib/metric/tasks.py +2 -1
  29. wbfdm/contrib/metric/tests/backends/test_performances.py +1 -0
  30. wbfdm/contrib/metric/tests/backends/test_statistics.py +1 -0
  31. wbfdm/contrib/metric/tests/test_models.py +1 -0
  32. wbfdm/contrib/metric/tests/test_viewsets.py +1 -0
  33. wbfdm/contrib/metric/viewsets/configs/display.py +1 -0
  34. wbfdm/contrib/metric/viewsets/mixins.py +1 -0
  35. wbfdm/contrib/metric/viewsets/viewsets.py +1 -0
  36. wbfdm/contrib/msci/dataloaders/esg.py +1 -0
  37. wbfdm/contrib/msci/dataloaders/esg_controversies.py +1 -0
  38. wbfdm/contrib/msci/sync.py +1 -0
  39. wbfdm/contrib/qa/dataloaders/adjustments.py +5 -5
  40. wbfdm/contrib/qa/dataloaders/corporate_actions.py +5 -5
  41. wbfdm/contrib/qa/dataloaders/financials.py +1 -0
  42. wbfdm/contrib/qa/dataloaders/market_data.py +9 -7
  43. wbfdm/contrib/qa/dataloaders/officers.py +8 -10
  44. wbfdm/contrib/qa/dataloaders/reporting_dates.py +1 -0
  45. wbfdm/contrib/qa/dataloaders/statements.py +1 -0
  46. wbfdm/contrib/qa/dataloaders/utils.py +2 -2
  47. wbfdm/dataloaders/proxies.py +1 -0
  48. wbfdm/factories/classifications.py +1 -0
  49. wbfdm/factories/controversies.py +1 -0
  50. wbfdm/factories/exchanges.py +1 -0
  51. wbfdm/factories/instrument_list.py +1 -0
  52. wbfdm/factories/instrument_prices.py +1 -0
  53. wbfdm/factories/instruments.py +1 -0
  54. wbfdm/factories/instruments_relationships.py +1 -0
  55. wbfdm/factories/options.py +1 -0
  56. wbfdm/figures/financials/financial_analysis_charts.py +1 -0
  57. wbfdm/figures/financials/financials_charts.py +1 -0
  58. wbfdm/filters/classifications.py +1 -0
  59. wbfdm/filters/exchanges.py +1 -0
  60. wbfdm/filters/financials.py +1 -0
  61. wbfdm/filters/financials_analysis.py +1 -0
  62. wbfdm/filters/instrument_prices.py +1 -0
  63. wbfdm/filters/instruments.py +1 -0
  64. wbfdm/filters/utils.py +1 -0
  65. wbfdm/import_export/backends/cbinsights/mixin.py +1 -0
  66. wbfdm/import_export/backends/refinitiv/mixin.py +1 -0
  67. wbfdm/import_export/backends/refinitiv/utils/controller.py +1 -0
  68. wbfdm/import_export/handlers/instrument.py +1 -0
  69. wbfdm/import_export/handlers/instrument_price.py +5 -0
  70. wbfdm/import_export/parsers/cbinsights/deals.py +1 -0
  71. wbfdm/import_export/parsers/cbinsights/equities.py +1 -0
  72. wbfdm/import_export/parsers/cbinsights/fundamentals.py +1 -0
  73. wbfdm/import_export/parsers/refinitiv/instrument.py +1 -0
  74. wbfdm/import_export/parsers/refinitiv/instrument_price.py +1 -0
  75. wbfdm/import_export/resources/classification.py +1 -0
  76. wbfdm/import_export/resources/instrument_prices.py +1 -0
  77. wbfdm/import_export/resources/instruments.py +1 -0
  78. wbfdm/models/esg/controversies.py +1 -0
  79. wbfdm/models/instruments/instrument_lists.py +1 -0
  80. wbfdm/models/instruments/instrument_prices.py +1 -0
  81. wbfdm/models/instruments/instrument_requests.py +1 -0
  82. wbfdm/models/instruments/instruments.py +12 -7
  83. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +2 -2
  84. wbfdm/models/instruments/mixin/instruments.py +24 -19
  85. wbfdm/models/instruments/options.py +1 -0
  86. wbfdm/models/instruments/private_equities.py +1 -0
  87. wbfdm/models/instruments/utils.py +1 -0
  88. wbfdm/serializers/esg.py +1 -0
  89. wbfdm/serializers/exchanges.py +1 -0
  90. wbfdm/serializers/instruments/classifications.py +1 -0
  91. wbfdm/serializers/instruments/instrument_lists.py +1 -0
  92. wbfdm/serializers/instruments/instrument_prices.py +1 -0
  93. wbfdm/serializers/instruments/instrument_relationships.py +1 -0
  94. wbfdm/serializers/instruments/instrument_requests.py +1 -0
  95. wbfdm/serializers/instruments/instruments.py +1 -0
  96. wbfdm/signals.py +3 -0
  97. wbfdm/tasks.py +45 -5
  98. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +1 -0
  99. wbfdm/tests/analysis/financial_analysis/test_utils.py +1 -0
  100. wbfdm/tests/analysis/test_esg.py +1 -0
  101. wbfdm/tests/dataloaders/test_cache.py +1 -0
  102. wbfdm/tests/models/test_classifications.py +1 -0
  103. wbfdm/tests/models/test_instrument_list.py +1 -0
  104. wbfdm/tests/models/test_instrument_prices.py +1 -0
  105. wbfdm/tests/models/test_instruments.py +2 -1
  106. wbfdm/tests/models/test_merge.py +14 -9
  107. wbfdm/tests/models/test_options.py +1 -0
  108. wbfdm/urls.py +1 -0
  109. wbfdm/viewsets/configs/buttons/instruments.py +1 -0
  110. wbfdm/viewsets/configs/display/esg.py +1 -0
  111. wbfdm/viewsets/configs/display/instrument_lists.py +1 -0
  112. wbfdm/viewsets/configs/display/instrument_prices.py +1 -0
  113. wbfdm/viewsets/configs/display/instrument_requests.py +1 -0
  114. wbfdm/viewsets/configs/display/instruments.py +31 -4
  115. wbfdm/viewsets/configs/endpoints/instrument_requests.py +1 -0
  116. wbfdm/viewsets/configs/titles/classifications.py +1 -0
  117. wbfdm/viewsets/configs/titles/financials_analysis.py +1 -0
  118. wbfdm/viewsets/configs/titles/instrument_prices.py +1 -0
  119. wbfdm/viewsets/configs/titles/market_data.py +1 -0
  120. wbfdm/viewsets/esg.py +1 -0
  121. wbfdm/viewsets/exchanges.py +1 -0
  122. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +1 -0
  123. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +2 -1
  124. wbfdm/viewsets/financial_analysis/financial_summary.py +4 -4
  125. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +1 -0
  126. wbfdm/viewsets/instruments/classifications.py +1 -0
  127. wbfdm/viewsets/instruments/financials_analysis.py +1 -0
  128. wbfdm/viewsets/instruments/instrument_lists.py +1 -0
  129. wbfdm/viewsets/instruments/instrument_prices.py +1 -0
  130. wbfdm/viewsets/instruments/instrument_requests.py +1 -0
  131. wbfdm/viewsets/instruments/instruments.py +1 -0
  132. wbfdm/viewsets/instruments/instruments_relationships.py +1 -0
  133. wbfdm/viewsets/market_data.py +3 -2
  134. wbfdm/viewsets/mixins.py +1 -0
  135. wbfdm/viewsets/officers.py +1 -0
  136. wbfdm/viewsets/prices.py +1 -0
  137. wbfdm/viewsets/statements/statements.py +1 -0
  138. wbfdm/viewsets/technical_analysis/monthly_performances.py +1 -0
  139. {wbfdm-1.44.5.dist-info → wbfdm-1.45.0.dist-info}/METADATA +1 -1
  140. {wbfdm-1.44.5.dist-info → wbfdm-1.45.0.dist-info}/RECORD +141 -141
  141. {wbfdm-1.44.5.dist-info → wbfdm-1.45.0.dist-info}/WHEEL +0 -0
@@ -6,6 +6,7 @@ import numpy as np
6
6
  import pandas as pd
7
7
  from django.db import models
8
8
  from wbcore.contrib.currency.models import CurrencyFXRates
9
+
9
10
  from wbfdm.models import Instrument
10
11
 
11
12
 
@@ -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
 
@@ -1,4 +1,5 @@
1
1
  from wbcore import filters as wb_filters
2
+
2
3
  from wbfdm.models import (
3
4
  Classification,
4
5
  ClassificationGroup,
@@ -1,4 +1,5 @@
1
1
  from wbcore import filters as wb_filters
2
+
2
3
  from wbfdm.models import Exchange
3
4
 
4
5
 
@@ -1,5 +1,6 @@
1
1
  from wbcore import filters
2
2
  from wbcore.filters.defaults import five_year_data_range
3
+
3
4
  from wbfdm.enums import CalendarType, DataType, Indicator, MarketDataChartType
4
5
  from wbfdm.models.instruments import Instrument
5
6
 
@@ -4,6 +4,7 @@ from django.db import models
4
4
  from pandas.tseries.offsets import BYearEnd
5
5
  from psycopg.types.range import DateRange
6
6
  from wbcore import filters as wb_filters
7
+
7
8
  from wbfdm.figures.financials.financial_analysis_charts import (
8
9
  PeriodChoices,
9
10
  VariableChoices,
@@ -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
 
@@ -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
@@ -3,6 +3,7 @@ from datetime import date
3
3
  from pandas.tseries.offsets import BDay, BMonthBegin, BMonthEnd
4
4
  from psycopg.types.range import DateRange
5
5
  from wbcore.utils.date import current_quarter_date_start
6
+
6
7
  from wbfdm.models import Instrument
7
8
 
8
9
 
@@ -1,6 +1,7 @@
1
1
  from datetime import date
2
2
 
3
3
  from django.db import models
4
+
4
5
  from wbfdm.models.instruments import Instrument
5
6
 
6
7
 
@@ -1,6 +1,7 @@
1
1
  from datetime import date
2
2
 
3
3
  from django.db import models
4
+
4
5
  from wbfdm.models.instruments import Instrument
5
6
 
6
7
 
@@ -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],
@@ -1,5 +1,6 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
+
3
4
  from wbfdm.models import Deal
4
5
 
5
6
  COLUMNS_MAP = {
@@ -1,6 +1,7 @@
1
1
  import json
2
2
 
3
3
  from wbcore.contrib.geography.models import Geography
4
+
4
5
  from wbfdm.models import ClassificationGroup
5
6
 
6
7
  FIELDS_MAP = {
@@ -2,6 +2,7 @@ import json
2
2
 
3
3
  import pandas as pd
4
4
  from django.contrib.contenttypes.models import ContentType
5
+
5
6
  from wbfdm.models import Instrument
6
7
 
7
8
 
@@ -1,5 +1,6 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
+
3
4
  from wbfdm.import_export.backends.refinitiv.instrument import DEFAULT_MAPPING
4
5
  from wbfdm.models import ClassificationGroup
5
6
 
@@ -1,5 +1,6 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
+
3
4
  from wbfdm.import_export.backends.refinitiv.instrument_price import DEFAULT_MAPPING
4
5
 
5
6
  from .utils import _clean_and_return_dict
@@ -1,4 +1,5 @@
1
1
  from import_export.widgets import ManyToManyWidget
2
+
2
3
  from wbfdm.models import Classification, ClassificationGroup
3
4
  from wbfdm.preferences import get_default_classification_group
4
5
 
@@ -1,6 +1,7 @@
1
1
  from import_export import fields
2
2
  from import_export.widgets import ForeignKeyWidget
3
3
  from wbcore.contrib.io.resources import FilterModelResource
4
+
4
5
  from wbfdm.models import Instrument, InstrumentPrice
5
6
 
6
7
 
@@ -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
@@ -1,6 +1,7 @@
1
1
  from typing import Any, Self
2
2
 
3
3
  from django.db import models
4
+
4
5
  from wbfdm.enums import (
5
6
  ESGControveryFlag,
6
7
  ESGControverySeverity,
@@ -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(fields=["trigram_search_vector"], opclasses=["gin_trgm_ops"], name="instrument_trigram_sv_gin_idx"), # type: ignore
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 import_prices(self, start: date | None = None, end: date | None = None, clear: bool = False):
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
- # Import instrument prices
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.error(str(e))
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 save_prices_in_db(self, from_date: date, to_date: date, clear: bool = False):
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
- update_objs = []
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
- update_objs.append(p)
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
- create_objs.append(p)
285
- InstrumentPrice.objects.bulk_update(
286
- update_objs,
287
- fields=[
288
- "net_value",
289
- "gross_value",
290
- "volume",
291
- "market_capitalization",
292
- "market_capitalization_consolidated",
293
- "calculated",
294
- "volume_50d",
295
- ],
296
- )
297
- InstrumentPrice.objects.bulk_create(create_objs, ignore_conflicts=True)
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
+ )
@@ -1,5 +1,6 @@
1
1
  from django.db import models
2
2
  from wbcore.contrib.io.mixins import ImportMixin
3
+
3
4
  from wbfdm.import_export.handlers.option import (
4
5
  OptionAggregateImportHandler,
5
6
  OptionImportHandler,
@@ -1,6 +1,7 @@
1
1
  from django.contrib.postgres.fields import ArrayField
2
2
  from django.db import models
3
3
  from wbcore.contrib.io.mixins import ImportMixin
4
+
4
5
  from wbfdm.import_export.handlers.private_equities import DealImportHandler
5
6
 
6
7
 
@@ -3,6 +3,7 @@ from functools import lru_cache
3
3
 
4
4
  from wbcore.contrib.currency.models import Currency
5
5
  from wbcore.contrib.geography.models import Geography
6
+
6
7
  from wbfdm.preferences import get_non_ticker_words
7
8
 
8
9
  START_DELIMITER = r"(?:^|(?<=[(\s\.\-<]))"
wbfdm/serializers/esg.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from wbcore import serializers
2
+
2
3
  from wbfdm.enums import (
3
4
  ESGControveryFlag,
4
5
  ESGControverySeverity,
@@ -1,5 +1,6 @@
1
1
  from wbcore import serializers as wb_serializers
2
2
  from wbcore.contrib.geography.serializers import GeographyRepresentationSerializer
3
+
3
4
  from wbfdm.models import Exchange
4
5
 
5
6
 
@@ -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
 
@@ -1,5 +1,6 @@
1
1
  from rest_framework.reverse import reverse
2
2
  from wbcore import serializers as wb_serializers
3
+
3
4
  from wbfdm.models.instruments.instrument_lists import (
4
5
  InstrumentList,
5
6
  InstrumentListThroughModel,
@@ -1,4 +1,5 @@
1
1
  from wbcore import serializers as wb_serializers
2
+
2
3
  from wbfdm.models import InstrumentPrice
3
4
  from wbfdm.serializers.instruments.instruments import (
4
5
  InvestableInstrumentRepresentationSerializer,
@@ -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
- from wbfdm.models import Instrument
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(start: date | None = None, end: date | None = None, clear: bool = False):
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.import_prices(start=start, end=end, clear=clear)
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
+ )
@@ -4,6 +4,7 @@ from unittest.mock import patch
4
4
  import pandas as pd
5
5
  import pytest
6
6
  from faker import Faker
7
+
7
8
  from wbfdm.analysis.financial_analysis.statement_with_estimates import (
8
9
  StatementWithEstimates,
9
10
  )
@@ -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
@@ -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
 
@@ -1,6 +1,7 @@
1
1
  import pytest
2
2
  from django.core.cache import cache as cache_layer
3
3
  from faker import Faker
4
+
4
5
  from wbfdm.dataloaders.cache import Cache
5
6
 
6
7
  fake = Faker()
@@ -1,4 +1,5 @@
1
1
  import pytest
2
+
2
3
  from wbfdm.models.instruments import (
3
4
  Classification,
4
5
  ClassificationGroup,
@@ -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
 
@@ -6,6 +6,7 @@ import pytest
6
6
  from faker import Faker
7
7
  from pandas.tseries.offsets import BDay
8
8
  from wbcore.models import DynamicDecimalField, DynamicFloatField
9
+
9
10
  from wbfdm.models import Instrument, InstrumentPrice, RelatedInstrumentThroughModel
10
11
 
11
12
  fake = Faker()