Skip to content

Commit b545aab

Browse files
authored
Expand functionality in ExportApi.signal_query (#91)
1 parent cbb50be commit b545aab

File tree

3 files changed

+86
-7
lines changed

3 files changed

+86
-7
lines changed

exabel_data_sdk/client/api/export_api.py

+40-3
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,13 @@ def signal_query(
8484
self,
8585
signal: Union[str, Column, Sequence[Union[str, Column]]],
8686
bloomberg_ticker: Union[str, Sequence[str]] = None,
87+
*,
8788
factset_id: Union[str, Sequence[str]] = None,
8889
tag: Union[str, Sequence[str]] = None,
8990
start_time: Union[str, pd.Timestamp] = None,
9091
end_time: Union[str, pd.Timestamp] = None,
92+
identifier: Union[Column, Sequence[Column]] = None,
93+
version: Union[str, pd.Timestamp, Sequence[str], Sequence[pd.Timestamp]] = None,
9194
) -> Union[pd.Series, pd.DataFrame]:
9295
"""
9396
Run a query for one or more signals.
@@ -106,6 +109,15 @@ def signal_query(
106109
or with any of the provided tags if several.
107110
start_time: the first date to retrieve data for
108111
end_time: the last date to retrieve data for
112+
identifier: the identifier(s) to return to identify the entities in the result.
113+
By default, will use Signals.NAME if multiple identifiers
114+
or a tag are given, or else no identifier.
115+
version: the Point-in-Time at which to evaluate the signals,
116+
or a list of such dates at which to evaluate the signals.
117+
If a list of dates is provided, 'version' will be included as a column
118+
in the result.
119+
If no version is specified, then the signals will be evaluated with the
120+
latest available data (the default).
109121
110122
Returns:
111123
A pandas Series if the result is a single time series,
@@ -116,6 +128,8 @@ def signal_query(
116128
"""
117129
if not signal:
118130
raise ValueError("Must specify signal to retrieve")
131+
132+
# Specify entity filter
119133
multi_company = False
120134
predicates: List[Predicate] = []
121135
if factset_id:
@@ -138,18 +152,41 @@ def signal_query(
138152
multi_company = True
139153
if len(predicates) > 1:
140154
raise ValueError("At most one company identification method can be specified")
141-
if multi_company:
142-
index = [Signals.NAME, Signals.TIME]
155+
156+
# Specify the identifier(s)
157+
index: List[Column] = []
158+
if identifier is None:
159+
if multi_company:
160+
index.append(Signals.NAME)
161+
elif isinstance(identifier, Column):
162+
index.append(identifier)
143163
else:
144-
index = [Signals.TIME]
164+
index.extend(identifier)
165+
index.append(Signals.TIME)
166+
167+
# Specify version(s), if provided
168+
if version:
169+
if isinstance(version, (str, pd.Timestamp)):
170+
predicates.append(Signals.VERSION.equal(version))
171+
else:
172+
predicates.append(Signals.VERSION.in_list(*version))
173+
index.insert(0, Signals.VERSION)
174+
175+
# The columns to query for
145176
columns: List[Union[str, Column]] = list(index)
146177
if isinstance(signal, (str, Column)):
147178
columns.append(signal)
148179
else:
149180
columns.extend(signal)
181+
182+
# Execute the query
150183
query = Signals.query(
151184
columns, start_time=start_time, end_time=end_time, predicates=predicates
152185
)
153186
df = self.run_query(query.sql())
187+
188+
# Set the row index
154189
df.set_index([col.name for col in index], inplace=True)
190+
# Squeeze to a Series if a single time series was returned,
191+
# and fix the data type (the backend returns a DataFrame with dtype=object)
155192
return df.squeeze(axis=1).infer_objects()

exabel_data_sdk/query/signals.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,19 @@ class Signals:
1616

1717
TABLE = Table("signals")
1818

19+
# Special columns. Refer to online documentation for descriptions:
20+
# https://help.exabel.com/docs/exporting-via-exabel-sdk
1921
TIME = Column("time")
20-
EXABEL_ID = Column("exabel_id")
21-
FACTSET_ID = Column("factset_id")
22-
BLOOMBERG_TICKER = Column("bloomberg_ticker")
22+
VERSION = Column("version")
23+
LABEL = Column("label")
2324
NAME = Column("name")
25+
BLOOMBERG_TICKER = Column("bloomberg_ticker")
26+
FACTSET_ID = Column("factset_id")
27+
ISIN = Column("isin")
28+
MIC = Column("mic")
29+
TICKER = Column("ticker")
30+
EXABEL_ID = Column("exabel_id")
31+
RESOURCE_NAME = Column("resource_name")
2432

2533
@staticmethod
2634
def query(

exabel_data_sdk/tests/client/api/test_export_api.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import unittest
22
from unittest.mock import MagicMock
33

4+
import pandas as pd
5+
46
from exabel_data_sdk.client.api.export_api import ExportApi
57
from exabel_data_sdk.query.column import Column
8+
from exabel_data_sdk.query.signals import Signals
69

710

811
class TestExportApi(unittest.TestCase):
@@ -19,12 +22,20 @@ def test_signal_query(self):
1922
"SELECT time, Sales_Actual FROM signals WHERE factset_id = 'QLGSL2-R'"
2023
)
2124

22-
export_api.signal_query("Sales_Actual", bloomberg_ticker=["AAPL US", "TSLA US"])
25+
export_api.signal_query("Sales_Actual", ["AAPL US", "TSLA US"])
2326
mock.assert_called_with(
2427
"SELECT name, time, Sales_Actual FROM signals "
2528
"WHERE bloomberg_ticker IN ('AAPL US', 'TSLA US')"
2629
)
2730

31+
export_api.signal_query(
32+
"Sales_Actual", ["AAPL US", "TSLA US"], identifier=Signals.EXABEL_ID
33+
)
34+
mock.assert_called_with(
35+
"SELECT exabel_id, time, Sales_Actual FROM signals "
36+
"WHERE bloomberg_ticker IN ('AAPL US', 'TSLA US')"
37+
)
38+
2839
export_api.signal_query("Sales_Actual", tag="graph:tag:user:xyz-123")
2940
mock.assert_called_with(
3041
"SELECT name, time, Sales_Actual FROM signals WHERE has_tag('graph:tag:user:xyz-123')"
@@ -36,3 +47,26 @@ def test_signal_query(self):
3647
"SELECT time, Sales_Actual_fiscal, 'sales_actual(alignment=''afp'')' AS Q "
3748
"FROM signals WHERE bloomberg_ticker = 'AAPL US'"
3849
)
50+
51+
export_api.signal_query(
52+
Column("Prediction", "allocations(analysis=7)"), identifier=Signals.NAME
53+
)
54+
mock.assert_called_with(
55+
"SELECT name, time, 'allocations(analysis=7)' AS Prediction FROM signals"
56+
)
57+
58+
export_api.signal_query("Sales_Actual", factset_id="QLGSL2-R", version="2019-02-03")
59+
mock.assert_called_with(
60+
"SELECT time, Sales_Actual FROM signals WHERE factset_id = 'QLGSL2-R'"
61+
" AND version = '2019-02-03'"
62+
)
63+
64+
export_api.signal_query(
65+
"Sales_Actual",
66+
factset_id=["QLGSL2-R", "FOOBAR"],
67+
version=[pd.Timestamp("2019-01-01"), pd.Timestamp("2019-02-02")],
68+
)
69+
mock.assert_called_with(
70+
"SELECT version, name, time, Sales_Actual FROM signals "
71+
"WHERE factset_id IN ('QLGSL2-R', 'FOOBAR') AND version IN ('2019-01-01', '2019-02-02')"
72+
)

0 commit comments

Comments
 (0)