wbfdm 1.43.2__py2.py3-none-any.whl → 1.44.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.

@@ -25,6 +25,7 @@ class Loader:
25
25
  calendar_type: CalendarType = CalendarType.FISCAL,
26
26
  market_data_values: list[MarketData] | None = None,
27
27
  statement_values: list[Financial] | None = None,
28
+ period_type: PeriodType = PeriodType.ALL,
28
29
  ):
29
30
  self.instrument = instrument
30
31
  self.calendar_type = calendar_type
@@ -35,6 +36,7 @@ class Loader:
35
36
  self.statement_values = (
36
37
  statement_values # specify if any extra statement needs to be merged into the dataframe
37
38
  )
39
+ self.period_type = period_type
38
40
  self.errors: dict[str, list[str]] = defaultdict(list)
39
41
 
40
42
  def load(self) -> pd.DataFrame:
@@ -54,9 +56,9 @@ class Loader:
54
56
  df = pd.DataFrame(
55
57
  Instrument.objects.filter(id=self.instrument.id).dl.financials(
56
58
  values=self.values,
57
- period_type=PeriodType.ALL,
58
59
  from_year=date.today().year - 5,
59
60
  calendar_type=self.calendar_type,
61
+ period_type=self.period_type,
60
62
  )
61
63
  )
62
64
  if df.empty:
@@ -1,56 +1,69 @@
1
+ from contextlib import suppress
1
2
  from datetime import date
2
- from itertools import batched # type: ignore
3
+ from itertools import batched
3
4
  from typing import Iterator
4
5
 
5
- from django.db import connections
6
- from jinjasql import JinjaSql # type: ignore
6
+ from django.db import ProgrammingError, connections
7
7
  from wbcore.contrib.dataloader.dataloaders import Dataloader
8
8
  from wbcore.contrib.dataloader.utils import dictfetchall
9
+ from wbfdm.contrib.qa.dataloaders.utils import SOURCE_DS2
9
10
  from wbfdm.dataloaders.protocols import AdjustmentsProtocol
10
11
  from wbfdm.dataloaders.types import AdjustmentDataDict
11
12
 
13
+ import pypika as pk
14
+ from pypika import functions as fn
15
+ from pypika.enums import SqlTypes, Order
16
+ from pypika.terms import LiteralValue
17
+
12
18
 
13
19
  class DatastreamAdjustmentsDataloader(AdjustmentsProtocol, Dataloader):
14
- def adjustments(self, from_date: date, to_date: date) -> Iterator[AdjustmentDataDict]:
20
+ def adjustments(self, from_date: date | None = None, to_date: date | None = None) -> Iterator[AdjustmentDataDict]:
15
21
  lookup = {k: v for k, v in self.entities.values_list("dl_parameters__adjustments__parameters", "id")}
16
22
 
17
- sql = """
18
- SELECT
19
- InfoCode as external_identifier,
20
- CONCAT(InfoCode, '_', CONVERT(DATE, AdjDate)) as id,
21
- CONVERT(DATE, AdjDate) as adjustment_date,
22
- CONVERT(DATE, EndAdjDate) as adjustment_end_date,
23
- 'qa-ds2' as source,
24
- AdjFactor as adjustment_factor,
25
- CumAdjFactor as cumulative_adjustment_factor
26
-
27
- FROM Ds2Adj
28
- WHERE
29
- AdjType = 2
30
- AND (
31
- {% for instrument in instruments %}
32
- InfoCode = {{instrument}} {% if not loop.last %} OR {% endif %}
33
- {% endfor %}
34
- )
35
- {% if from_date %} AND AdjDate >= {{ from_date }} {% endif %}
36
- {% if to_date %} AND AdjDate <= {{ to_date }} {% endif %}
37
-
38
- ORDER BY AdjDate DESC
39
- """
40
- for batch in batched(lookup.keys(), 1000):
41
- query, bind_params = JinjaSql(param_style="format").prepare_query(
42
- sql,
43
- {
44
- "instruments": batch,
45
- "from_date": from_date,
46
- "to_date": to_date,
47
- },
23
+ adj = pk.Table("DS2Adj")
24
+ adj_date = fn.Cast(adj.AdjDate, SqlTypes.DATE).as_("adjustment_date")
25
+ adj_end_date = fn.Cast(adj.EndAdjDate, SqlTypes.DATE).as_("adjustment_end_date")
26
+
27
+ infocode = pk.Table("#ds2infocode")
28
+
29
+ query = (
30
+ pk.MSSQLQuery.select(
31
+ adj.InfoCode.as_("external_identifier"),
32
+ fn.Concat(adj.InfoCode, "_", adj_date).as_("id"),
33
+ adj_date,
34
+ adj_end_date,
35
+ SOURCE_DS2,
36
+ adj.AdjFactor.as_("adjustment_factor"),
37
+ adj.CumAdjFactor.as_("cumulative_adjustement_factor"),
48
38
  )
49
- with connections["qa"].cursor() as cursor:
39
+ .from_(adj)
40
+ .where(adj.AdjType == 2)
41
+ .where(adj.InfoCode.isin([LiteralValue("select infocode from #ds2infocode")]))
42
+ .orderby(adj.AdjDate, order=Order.desc)
43
+ )
44
+
45
+ if from_date:
46
+ query = query.where(adj.AdjDate >= from_date)
47
+
48
+ if to_date:
49
+ query = query.where(adj.AdjDate <= to_date)
50
+
51
+ with connections["qa"].cursor() as cursor:
52
+ # we suppress an error here, because if the temporary table already exists
53
+ # then we do not want to fail. It should not fail, but if a previous run did
54
+ # not clean up the table properly, then at least we do not get stuck here
55
+ with suppress(ProgrammingError):
50
56
  cursor.execute(
51
- query,
52
- bind_params,
57
+ pk.MSSQLQuery.create_table(infocode).columns(pk.Column("infocode", SqlTypes.INTEGER)).get_sql()
53
58
  )
54
- for row in dictfetchall(cursor):
55
- row["instrument_id"] = lookup[row["external_identifier"]]
56
- yield row
59
+ for batch in batched(lookup.keys(), 1000):
60
+ cursor.execute(f"insert into #ds2infocode values {",".join(map(lambda x: f"({x})", batch))};")
61
+
62
+ cursor.execute(query.get_sql())
63
+
64
+ for row in dictfetchall(cursor, AdjustmentDataDict):
65
+ row["instrument_id"] = lookup[row["external_identifier"]]
66
+ yield row
67
+
68
+ # here we remove the temporary table again to avoid data spillage
69
+ # cursor.execute(pk.MSSQLQuery.drop_table(infocode).get_sql())
@@ -1,13 +1,20 @@
1
+ from contextlib import suppress
1
2
  from datetime import date
3
+ from itertools import batched
2
4
  from typing import Iterator
3
5
 
4
- from django.db import connections
5
- from jinjasql import JinjaSql # type: ignore
6
+ from django.db import ProgrammingError, connections
6
7
  from wbcore.contrib.dataloader.dataloaders import Dataloader
7
8
  from wbcore.contrib.dataloader.utils import dictfetchall
9
+ from wbfdm.contrib.qa.dataloaders.utils import SOURCE_DS2
8
10
  from wbfdm.dataloaders.protocols import CorporateActionsProtocol
9
11
  from wbfdm.dataloaders.types import CorporateActionDataDict
10
12
 
13
+ import pypika as pk
14
+ from pypika import functions as fn
15
+ from pypika.enums import SqlTypes
16
+ from pypika.terms import LiteralValue
17
+
11
18
 
12
19
  class DatastreamCorporateActionsDataloader(CorporateActionsProtocol, Dataloader):
13
20
  def corporate_actions(
@@ -17,43 +24,46 @@ class DatastreamCorporateActionsDataloader(CorporateActionsProtocol, Dataloader)
17
24
  ) -> Iterator[CorporateActionDataDict]:
18
25
  lookup = {k: v for k, v in self.entities.values_list("dl_parameters__corporate_actions__parameters", "id")}
19
26
 
20
- sql = """
21
- SELECT
22
- InfoCode as external_identifier,
23
- CONCAT(InfoCode, '_', CONVERT(DATE, EffectiveDate)) as id,
24
- CONVERT(DATE, EffectiveDate) as valuation_date,
25
- 'qa-ds2' as source,
26
- ActionTypeCode as action_code,
27
- EventStatusCode as event_code,
28
- NumOldShares as old_shares,
29
- NumNewShares as new_shares,
30
- ISOCurrCode as currency
31
-
32
- FROM Ds2CapEvent
33
- WHERE (
34
- {% for instrument in instruments %}
35
- InfoCode = {{instrument}} {% if not loop.last %} OR {% endif %}
36
- {% endfor %}
27
+ cap_event = pk.Table("Ds2CapEvent")
28
+ effective_date = fn.Cast(cap_event.EffectiveDate, SqlTypes.DATE)
29
+ infocode = pk.Table("#ds2infocode")
30
+
31
+ query = (
32
+ pk.MSSQLQuery.select(
33
+ cap_event.InfoCode.as_("external_identifier"),
34
+ fn.Concat(cap_event.InfoCode, "_", effective_date).as_("id"),
35
+ effective_date.as_("valuation_date"),
36
+ SOURCE_DS2,
37
+ cap_event.ActionTypeCode.as_("action_code"),
38
+ cap_event.EventStatusCode.as_("event_code"),
39
+ cap_event.NumOldShares.as_("old_shares"),
40
+ cap_event.NumNewShares.as_("new_shares"),
41
+ cap_event.ISOCurrCode.as_("currency"),
37
42
  )
38
- {% if from_date %} AND EffectiveDate >= {{ from_date }} {% endif %}
39
- {% if to_date %} AND EffectiveDate <= {{ to_date }} {% endif %}
40
-
41
- ORDER BY EffectiveDate DESC
42
- """
43
-
44
- query, bind_params = JinjaSql(param_style="format").prepare_query(
45
- sql,
46
- {
47
- "instruments": self.entities.values_list("dl_parameters__corporate_actions__parameters", flat=True),
48
- "from_date": from_date,
49
- "to_date": to_date,
50
- },
43
+ .from_(cap_event)
44
+ .where(cap_event.InfoCode.isin([LiteralValue("select infocode from #ds2infocode")]))
45
+ .orderby(cap_event.EffectiveDate, order=pk.Order.desc)
51
46
  )
47
+
48
+ if from_date:
49
+ query = query.where(cap_event.EffectiveDate >= from_date)
50
+
51
+ if to_date:
52
+ query = query.where(cap_event.EffectiveDate <= to_date)
53
+
52
54
  with connections["qa"].cursor() as cursor:
53
- cursor.execute(
54
- query,
55
- bind_params,
56
- )
57
- for row in dictfetchall(cursor):
55
+ # Create temporary table if it doesn't exist
56
+ with suppress(ProgrammingError):
57
+ cursor.execute(
58
+ pk.MSSQLQuery.create_table(infocode).columns(pk.Column("infocode", SqlTypes.INTEGER)).get_sql()
59
+ )
60
+ for batch in batched(lookup.keys(), 1000):
61
+ cursor.execute(f"insert into #ds2infocode values {','.join(map(lambda x: f'({x})', batch))};")
62
+
63
+ cursor.execute(query.get_sql())
64
+ for row in dictfetchall(cursor, CorporateActionDataDict):
58
65
  row["instrument_id"] = lookup[row["external_identifier"]]
59
66
  yield row
67
+
68
+ # Clean up temporary table
69
+ cursor.execute(pk.MSSQLQuery.drop_table(infocode).get_sql())
@@ -1,12 +1,19 @@
1
+ from contextlib import suppress
2
+ from functools import reduce
3
+ from itertools import batched
1
4
  from datetime import date
2
5
  from enum import Enum
3
6
  from typing import Iterator
7
+ import pypika as pk
8
+ from pypika import Column, MSSQLQuery, functions as fn
9
+ from pypika.enums import SqlTypes, Order
10
+ from pypika.terms import ValueWrapper, LiteralValue
4
11
 
5
- from django.db import connections
6
- from jinjasql import JinjaSql # type: ignore
12
+ from django.db import ProgrammingError, connections
7
13
  from wbcore.contrib.dataloader.dataloaders import Dataloader
8
14
  from wbcore.contrib.dataloader.utils import dictfetchall
9
15
  from wbfdm.dataloaders.protocols import MarketDataProtocol
16
+ from wbfdm.contrib.qa.dataloaders.utils import create_table
10
17
  from wbfdm.dataloaders.types import MarketDataDict
11
18
  from wbfdm.enums import Frequency, MarketData
12
19
 
@@ -53,65 +60,83 @@ class DatastreamMarketDataDataloader(MarketDataProtocol, Dataloader):
53
60
  }
54
61
  value_mapping = [(DS2MarketData[x.name].value, x.value) for x in values or []]
55
62
 
56
- sql = """
57
- SELECT
58
- CONCAT(pricing.InfoCode, ',', ExchIntCode) as external_identifier,
59
- CONCAT(pricing.InfoCode, ',', ExchIntCode, '_', CONVERT(DATE, MarketDate)) as id,
60
- CONVERT(DATE, MarketDate) as valuation_date,
61
- 'qa-ds2' as source,
62
- {% if target_currency %}
63
- '{{ target_currency|sqlsafe }}' as 'currency',
64
- {% else %}
65
- Currency as 'currency',
66
- {% endif %}
67
- {% for value in values %}
68
- {% if target_currency %}
69
- COALESCE(fx_rate.midrate, 1) *
70
- {% endif %}
71
- {{ value[0][0] | sqlsafe }}{% if value[0][1] %} * {{ value[0][1] | sqlsafe }}{% endif %} as '{{ value[1] | sqlsafe }}'{% if not loop.last %}, {% endif %}
72
- {% endfor %}
73
- FROM vw_Ds2Pricing as pricing
74
- LEFT JOIN DS2MktVal as market_val
75
- ON pricing.InfoCode = market_val.InfoCode
76
- AND pricing.MarketDate = market_val.ValDate
77
- {% if target_currency %}
78
- LEFT JOIN Ds2FxCode as fx_code
79
- ON fx_code.FromCurrCode = '{{target_currency|sqlsafe}}'
80
- AND fx_code.ToCurrCode = pricing.Currency
81
- AND fx_code.RateTypeCode = 'SPOT'
82
- LEFT JOIN Ds2FxRate as fx_rate
83
- ON fx_rate.ExRateIntCode = fx_code.ExRateIntCode
84
- AND fx_rate.ExRateDate = pricing.MarketDate
85
- {% endif %}
86
-
87
- WHERE (
88
- {% for instrument in instruments %}
89
- (pricing.InfoCode = {{instrument[0]}} AND ExchIntCode = {{instrument[1]}}) {% if not loop.last %} OR {% endif %}
90
- {% endfor %}
91
- )
92
- AND AdjType = 2
93
- {% if from_date %} AND MarketDate >= {{ from_date }} {% endif %}
94
- {% if to_date %} AND MarketDate <= {{ to_date }} {% endif %}
95
- {% if exact_date %} AND MarketDate = {{ exact_date }} {% endif %}
63
+ # Define tables
64
+ pricing = pk.Table("vw_DS2Pricing")
65
+ market_val = pk.Table("DS2MktVal")
66
+ fx_code = pk.Table("DS2FxCode")
67
+ fx = pk.Table("DS2FxRate")
96
68
 
97
- ORDER BY MarketDate DESC
98
- """
99
- query, bind_params = JinjaSql(param_style="format").prepare_query(
100
- sql,
101
- {
102
- "instruments": self.entities.values_list("dl_parameters__market_data__parameters", flat=True),
103
- "values": value_mapping,
104
- "from_date": from_date,
105
- "to_date": to_date,
106
- "exact_date": exact_date,
107
- "target_currency": target_currency,
108
- },
69
+ mapping, create_mapping_table = create_table(
70
+ "#ds2infoexchcode", Column("InfoCode", SqlTypes.INTEGER), Column("ExchIntCode", SqlTypes.INTEGER)
109
71
  )
110
- with connections["qa"].cursor() as cursor:
111
- cursor.execute(
112
- query,
113
- bind_params,
72
+
73
+ # Base query to get data we always need unconditionally
74
+ query = (
75
+ pk.MSSQLQuery.from_(pricing)
76
+ .select(
77
+ fn.Concat(pricing.InfoCode, ",", pricing.ExchIntCode).as_("external_identifier"),
78
+ fn.Concat(
79
+ pricing.InfoCode, ",", pricing.ExchIntCode, "_", fn.Cast(pricing.MarketDate, SqlTypes.DATE)
80
+ ).as_("id"),
81
+ fn.Cast(pricing.MarketDate, SqlTypes.DATE).as_("valuation_date"),
82
+ ValueWrapper("qa-ds2").as_("source"),
83
+ )
84
+ .left_join(market_val)
85
+ .on((pricing.InfoCode == market_val.InfoCode) & (pricing.MarketDate == market_val.ValDate))
86
+ # We join on _codes, which removes all instruments not in _codes - implicit where
87
+ .join(mapping)
88
+ .on((pricing.InfoCode == mapping.InfoCode) & (pricing.ExchIntCode == mapping.ExchIntCode))
89
+ .where(pricing.AdjType == 2)
90
+ .orderby(pricing.MarketDate, order=Order.desc)
91
+ )
92
+
93
+ # If we need to convert to a target currency, we need the fx rate table and multiply all values with the fx rate
94
+ if target_currency:
95
+ query = (
96
+ query.select(
97
+ ValueWrapper(target_currency).as_("currency"),
98
+ *[
99
+ (LiteralValue(value[0][0]) * fn.Coalesce(fx.midrate, 1)).as_(value[1])
100
+ for value in value_mapping
101
+ ],
102
+ )
103
+ .left_join(fx_code)
104
+ .on(
105
+ (fx_code.FromCurrCode == target_currency)
106
+ & (fx_code.ToCurrCode == pricing.Currency)
107
+ & (fx_code.RateTypeCode == "SPOT")
108
+ )
109
+ .left_join(fx)
110
+ .on((fx_code.ExRateIntCode == fx.ExRateIntCode) & (fx.ExRateDate == pricing.MarketDate))
111
+ )
112
+ else:
113
+ query = query.select(
114
+ pricing.Currency.as_("currency"),
115
+ *[LiteralValue(value[0][0]).as_(value[1]) for value in value_mapping],
114
116
  )
115
- for row in dictfetchall(cursor):
117
+
118
+ # Add conditional where clauses
119
+ if from_date:
120
+ query = query.where(pricing.MarketDate >= from_date)
121
+
122
+ if to_date:
123
+ query = query.where(pricing.MarketDate <= to_date)
124
+
125
+ if exact_date:
126
+ query = query.where(pricing.MarketDate == exact_date)
127
+
128
+ with connections["qa"].cursor() as cursor:
129
+ with suppress(ProgrammingError):
130
+ cursor.execute(create_mapping_table.get_sql())
131
+ for batch in batched(
132
+ self.entities.values_list("dl_parameters__market_data__parameters", flat=True), 1000
133
+ ):
134
+ cursor.execute(reduce(lambda x, y: x.insert(y), batch, MSSQLQuery.into(mapping)).get_sql())
135
+
136
+ cursor.execute(query.get_sql())
137
+
138
+ for row in dictfetchall(cursor, MarketDataDict):
116
139
  row["instrument_id"] = lookup[row["external_identifier"]]
117
140
  yield row
141
+
142
+ cursor.execute(MSSQLQuery.drop_table(mapping).get_sql())
@@ -1,11 +1,20 @@
1
1
  from typing import Iterator
2
2
 
3
- from django.db import connections
4
- from jinjasql import JinjaSql # type: ignore
3
+ from django.db import connections, ProgrammingError
5
4
  from wbcore.contrib.dataloader.dataloaders import Dataloader
6
5
  from wbcore.contrib.dataloader.utils import dictfetchall
7
6
  from wbfdm.dataloaders.protocols import OfficersProtocol
8
7
  from wbfdm.dataloaders.types import OfficerDataDict
8
+ from itertools import batched
9
+ from contextlib import suppress
10
+
11
+ import pypika as pk
12
+ from pypika import functions as fn
13
+ from pypika.enums import SqlTypes
14
+ from pypika.terms import LiteralValue
15
+
16
+
17
+ from pypika.analytics import RowNumber
9
18
 
10
19
 
11
20
  class RKDOfficersDataloader(OfficersProtocol, Dataloader):
@@ -14,46 +23,52 @@ class RKDOfficersDataloader(OfficersProtocol, Dataloader):
14
23
  ) -> Iterator[OfficerDataDict]:
15
24
  lookup = {k: v for k, v in self.entities.values_list("dl_parameters__officers__parameters", "id")}
16
25
 
17
- sql = """
18
- SELECT
19
- CONCAT(designation.Code, '-', ROW_NUMBER() OVER (ORDER BY officer.OfficerRank)) as id,
20
- designation.Code as external_identifier,
21
- designation.Title as position,
22
- CONCAT(
26
+ # Define tables
27
+ designation = pk.Table("RKDFndCmpOffTitleChg")
28
+ officer = pk.Table("RKDFndCmpOfficer")
29
+ temp_codes = pk.Table("#rkd_codes")
30
+
31
+ # Build the query
32
+ query = (
33
+ pk.MSSQLQuery.select(
34
+ fn.Concat(designation.Code, "-", RowNumber().orderby(officer.OfficerRank)).as_("id"),
35
+ designation.Code.as_("external_identifier"),
36
+ designation.Title.as_("position"),
37
+ fn.Concat(
23
38
  officer.Prefix,
24
- ' ',
39
+ " ",
25
40
  officer.FirstName,
26
- ' ',
41
+ " ",
27
42
  officer.LastName,
28
- CASE
29
- WHEN officer.Suffix IS NOT NULL THEN CONCAT(', ', officer.Suffix)
30
- ELSE ''
31
- END
32
- ) as name,
33
- officer.Age as age,
34
- officer.Sex as sex,
35
- CONVERT(DATE, designation.DesgStartDt) as start
36
- FROM RKDFndCmpOffTitleChg AS designation
37
- JOIN RKDFndCmpOfficer AS officer
38
- ON designation.Code = officer.Code
39
- AND designation.OfficerID = officer.Officerid
40
-
41
- WHERE
42
- designation.Code in (
43
- {% for instrument in instruments %}
44
- {{instrument}} {% if not loop.last %}, {% endif %}
45
- {% endfor %})
46
- AND DesgEndDt IS NULL
47
-
48
- ORDER BY
49
- officer.OfficerRank
50
- """
51
- query, bind_params = JinjaSql(param_style="format").prepare_query(sql, {"instruments": lookup.keys()})
52
- with connections["qa"].cursor() as cursor:
53
- cursor.execute(
54
- query,
55
- bind_params,
43
+ pk.Case().when(officer.Suffix.isnull(), "").else_(fn.Concat(", ", officer.Suffix)),
44
+ ).as_("name"),
45
+ officer.Age.as_("age"),
46
+ officer.Sex.as_("sex"),
47
+ fn.Cast(designation.DesgStartDt, SqlTypes.DATE).as_("start"),
56
48
  )
57
- for row in dictfetchall(cursor):
49
+ .from_(designation)
50
+ .join(officer)
51
+ .on((designation.Code == officer.Code) & (designation.OfficerID == officer.Officerid))
52
+ .where(designation.Code.isin([LiteralValue("select code from #rkd_codes")]))
53
+ .where(designation.DesgEndDt.isnull())
54
+ .orderby(officer.OfficerRank)
55
+ )
56
+
57
+ with connections["qa"].cursor() as cursor:
58
+ # Create and populate temporary table
59
+ with suppress(ProgrammingError):
60
+ cursor.execute(
61
+ pk.MSSQLQuery.create_table(temp_codes).columns(pk.Column("code", SqlTypes.INTEGER)).get_sql()
62
+ )
63
+ for batch in batched(lookup.keys(), 1000):
64
+ placeholders = ",".join(map(lambda x: f"('{x}')", batch))
65
+ cursor.execute(f"insert into #rkd_codes values {placeholders};")
66
+
67
+ cursor.execute(query.get_sql())
68
+
69
+ for row in dictfetchall(cursor, OfficerDataDict):
58
70
  row["instrument_id"] = lookup[row["external_identifier"]]
59
71
  yield row
72
+
73
+ # Clean up temporary table
74
+ cursor.execute(pk.MSSQLQuery.drop_table(temp_codes).get_sql())
@@ -0,0 +1,22 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from pypika.terms import ValueWrapper
4
+ from pypika import Column, MSSQLQuery, Table
5
+
6
+ if TYPE_CHECKING:
7
+ from pypika.terms import Term
8
+ from pypika.queries import CreateQueryBuilder
9
+
10
+
11
+ def compile_source(source: str) -> "Term":
12
+ return ValueWrapper(source).as_("source")
13
+
14
+
15
+ SOURCE_DS2 = compile_source("qa-ds2")
16
+ SOURCE_RKD = compile_source("qa-rkd")
17
+ SOURCE_IBES = compile_source("qa-ibes")
18
+
19
+
20
+ def create_table(tablename: "str", *columns: "Column") -> tuple[Table, "CreateQueryBuilder"]:
21
+ table = Table(tablename)
22
+ return table, MSSQLQuery.create_table(table).columns(*columns)
@@ -27,13 +27,13 @@ from wbfdm.enums import (
27
27
 
28
28
 
29
29
  class ReportDateProtocol(Protocol):
30
- def reporting_dates(self, only_next: bool = True) -> Iterator[ReportDateDataDict]:
31
- ...
30
+ def reporting_dates(self, only_next: bool = True) -> Iterator[ReportDateDataDict]: ...
32
31
 
33
32
 
34
33
  class AdjustmentsProtocol(Protocol):
35
- def adjustments(self, from_date: date, to_date: date) -> Iterator[AdjustmentDataDict]:
36
- ...
34
+ def adjustments(
35
+ self, from_date: date | None = None, to_date: date | None = None
36
+ ) -> Iterator[AdjustmentDataDict]: ...
37
37
 
38
38
 
39
39
  class MarketDataProtocol(Protocol):
@@ -45,20 +45,17 @@ class MarketDataProtocol(Protocol):
45
45
  exact_date: date | None = None,
46
46
  frequency: Frequency = Frequency.DAILY,
47
47
  target_currency: str | None = None,
48
- ) -> Iterator[MarketDataDict]:
49
- ...
48
+ ) -> Iterator[MarketDataDict]: ...
50
49
 
51
50
 
52
51
  class CorporateActionsProtocol(Protocol):
53
52
  def corporate_actions(
54
53
  self, from_date: date | None = None, to_date: date | None = None
55
- ) -> Iterator[CorporateActionDataDict]:
56
- ...
54
+ ) -> Iterator[CorporateActionDataDict]: ...
57
55
 
58
56
 
59
57
  class OfficersProtocol(Protocol):
60
- def officers(self) -> Iterator[OfficerDataDict]:
61
- ...
58
+ def officers(self) -> Iterator[OfficerDataDict]: ...
62
59
 
63
60
 
64
61
  class StatementsProtocol(Protocol):
@@ -73,8 +70,7 @@ class StatementsProtocol(Protocol):
73
70
  data_type: DataType = DataType.STANDARDIZED,
74
71
  financials: list[Financial] | None = None,
75
72
  target_currency: str | None = None,
76
- ) -> Iterator[StatementDataDict]:
77
- ...
73
+ ) -> Iterator[StatementDataDict]: ...
78
74
 
79
75
 
80
76
  class FinancialsProtocol(Protocol):
@@ -95,18 +91,15 @@ class FinancialsProtocol(Protocol):
95
91
  data_type: DataType = DataType.STANDARDIZED,
96
92
  estimate_type: EstimateType = EstimateType.VALID,
97
93
  target_currency: str | None = None,
98
- ) -> Iterator[FinancialDataDict]:
99
- ...
94
+ ) -> Iterator[FinancialDataDict]: ...
100
95
 
101
96
 
102
97
  class ESGControversyProtocol(Protocol):
103
- def esg_controversies(self) -> Iterator[ESGControversyDataDict]:
104
- ...
98
+ def esg_controversies(self) -> Iterator[ESGControversyDataDict]: ...
105
99
 
106
100
 
107
101
  class ESGProtocol(Protocol):
108
102
  def esg(
109
103
  self,
110
104
  values: list[ESG],
111
- ) -> Iterator[ESGDataDict]:
112
- ...
105
+ ) -> Iterator[ESGDataDict]: ...
@@ -67,7 +67,7 @@ class InstrumentDataloaderProxy(
67
67
  for dl in self.iterate_dataloaders("reporting_dates"):
68
68
  yield from dl.reporting_dates(only_next=only_next)
69
69
 
70
- def adjustments(self, from_date: date, to_date: date) -> Iterator[AdjustmentDataDict]:
70
+ def adjustments(self, from_date: date | None = None, to_date: date | None = None) -> Iterator[AdjustmentDataDict]:
71
71
  for dl in self.iterate_dataloaders("adjustments"):
72
72
  yield from dl.adjustments(from_date=from_date, to_date=to_date)
73
73
 
@@ -33,6 +33,8 @@ class MarketDataDict(BaseDict):
33
33
  Attributes:
34
34
  valuation_date: date
35
35
  The date of valuation.
36
+ external_identifier: str
37
+ The external identifier of the instrument
36
38
  open: float | None
37
39
  The opening value (if available).
38
40
  close: float | None
@@ -52,6 +54,7 @@ class MarketDataDict(BaseDict):
52
54
  """
53
55
 
54
56
  valuation_date: date
57
+ external_identifier: str
55
58
 
56
59
  open: NotRequired[float]
57
60
  close: NotRequired[float]
wbfdm/enums.py CHANGED
@@ -50,12 +50,12 @@ class Financial(ChoiceEnum):
50
50
  NET_INCOME_BEFORE_TAXES = "pbt"
51
51
  NET_INCOME = "net_income"
52
52
  EPS = "eps"
53
- FREE_CASH_FLOW = "fcf"
53
+ FREE_CASH_FLOW = "free_cash_flow"
54
54
  EBITDA = "ebitda"
55
55
  EBITDA_PER_SHARE = "ebitda_sh"
56
56
  NET_DEBT = "net_debt"
57
57
  ENTERPRISE_VALUE = "ev"
58
- SHARES_OUTSTANDING = "outstanding_shares"
58
+ SHARES_OUTSTANDING = "shares_outstanding"
59
59
  COST_OF_GOODS_SOLD = "cogs"
60
60
  GROSS_PROFIT_MARGIN = "gross_profit_margin"
61
61
  SELLING_MARKETING_EXPENSES = "selling_marketing_expenses"
@@ -79,7 +79,7 @@ class Financial(ChoiceEnum):
79
79
  CASH_FLOW_FROM_OPERATIONS = "cash_flow_from_operations"
80
80
  CAPEX = "capex"
81
81
  CASH_FLOW_FROM_INVESTING = "cash_flow_from_investing"
82
- FREE_CASH_FLOW_PER_SHARE = "fcf_per_share"
82
+ FREE_CASH_FLOW_PER_SHARE = "free_cash_flow_per_share"
83
83
  TOTAL_DIVIDENDS = "total_dividends"
84
84
  CASH_FLOW_FROM_FINANCING = "cash_flow_from_financing"
85
85
  CASH_FLOW_PER_SHARE = "cash_flow_per_share"