Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support beqs #26

Merged
merged 3 commits into from
Jun 21, 2023
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 86 additions & 5 deletions src/blp/blp.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@
_TOKEN_FAILURE = blpapi.Name("TokenGenerationFailure")
_SESSION_TERMINATED = blpapi.Name("SessionTerminated")
_SESSION_DOWN = blpapi.Name("SessionConnectionDown")
_MARKET_DATA_EVENTS = [
blpapi.Name("MarketDataEvents"),
blpapi.Name("MarketBarStart"),
blpapi.Name("MarketBarUpdate")
]
_MARKET_DATA_EVENTS = [blpapi.Name("MarketDataEvents"), blpapi.Name("MarketBarStart"), blpapi.Name("MarketBarUpdate")]

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -489,6 +485,7 @@ class BlpQuery(BlpSession):
"ReferenceDataRequest": "//blp/refdata",
"IntradayTickRequest": "//blp/refdata",
"IntradayBarRequest": "//blp/refdata",
"BeqsRequest": "//blp/refdata",
"FieldInfoRequest": "//blp/apiflds",
"FieldListRequest": "//blp/apiflds",
"instrumentListRequest": "//blp/instruments",
Expand Down Expand Up @@ -803,6 +800,44 @@ def collect_to_bdh(self, responses: Iterable) -> Dict[str, pandas.DataFrame]:

return dfs

def beqs(
self,
screen_name: str,
screen_type: str = "PRIVATE",
overrides: Optional[Sequence] = None,
options: Optional[Dict] = None,
) -> pandas.DataFrame:
"""Bloomberg equity screening request.

Args:
screen_name: name of the screen
screen_type: type of screen, either 'PRIVATE' or 'GLOBAL'
overrides: List of tuples containing the field to override and its value
options: key value pairs to to set in request

Returns: A pandas.DataFrame with columns ['security', eqs_data[0], ...]
"""
query = create_eqs_query(screen_name, screen_type, overrides, options)
df = self.query(query, self.parser, self.collect_to_beqs)
columns = ["security"] + sorted([col for col in df.columns if col != "security"])
df = (
df.sort_values("security").reset_index(drop=True).loc[:, columns].drop(["Ticker"], axis=1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for dropping Ticker? Is this a redundant piece of information that is returned by the Bloomberg api that is always equal to security?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly! However, if you prefer, we could just leave the Ticker information and let the user drop it if he wants to.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya I think that would be more consistent with other approaches in the library, where I try to pass through the Bloomberg response in a fairly unmodified way.

) # Ticker = security
return df

def collect_to_beqs(self, responses: Iterable) -> pandas.DataFrame:
"""Collector for beqs()."""
rows = []
fields = {"security"}
for response in responses:
data = response["data"]
# possible some fields are missing for different securities
fields = fields.union(set(response["fields"]))
data["security"] = response["security"]
rows.append(data)
df = pandas.DataFrame(rows)
return self.cast_columns(df, fields)

def bdp(
self,
securities: Sequence[str],
Expand Down Expand Up @@ -1070,6 +1105,7 @@ def _validate_response_type(response, _):
"ReferenceDataResponse",
"HistoricalDataResponse",
"IntradayBarResponse",
"BeqsResponse",
"IntradayTickResponse",
"fieldResponse",
"InstrumentListResponse",
Expand All @@ -1093,6 +1129,7 @@ def _process_field_exception(response, _):
if rtype in (
"IntradayBarResponse",
"IntradayTickResponse",
"BeqsResponse",
"fieldResponse",
"InstrumentListResponse",
"GetFillsResponse",
Expand Down Expand Up @@ -1135,6 +1172,7 @@ def _validate_security_error(response, _):
if rtype in (
"IntradayBarResponse",
"IntradayTickResponse",
"BeqsResponse",
"fieldResponse",
"InstrumentListResponse",
"GetFillsResponse",
Expand All @@ -1155,6 +1193,7 @@ def _warn_security_error(response, _):
if rtype in (
"IntradayBarResponse",
"IntradayTickResponse",
"BeqsResponse",
"fieldResponse",
"InstrumentListResponse",
"GetFillsResponse",
Expand Down Expand Up @@ -1293,6 +1332,8 @@ def __call__(self, response, request_data):
sec_data_parser = self._parse_bar_security_data
elif rtype == "IntradayTickResponse":
sec_data_parser = self._parse_tick_security_data
elif rtype == "BeqsResponse":
sec_data_parser = self._parse_equity_screening_data
elif rtype == "fieldResponse":
sec_data_parser = self._parse_field_info_data
elif rtype == "InstrumentListResponse":
Expand All @@ -1305,6 +1346,7 @@ def __call__(self, response, request_data):
"HistoricalDataResponse",
"IntradayBarResponse",
"IntradayTickResponse",
"BeqsResponse",
"fieldResponse",
"InstrumentListResponse",
"GetFillsResponse",
Expand Down Expand Up @@ -1388,6 +1430,20 @@ def _parse_tick_security_data(response, request_data):
}
yield result

@staticmethod
def _parse_equity_screening_data(response, _):
rtype = list(response["message"]["element"].keys())[0]
response_data = response["message"]["element"][rtype]["data"]
fields = list(response_data["fieldDisplayUnits"]["fieldDisplayUnits"].keys())

for sec_data in response_data["securityData"]:
result = {
"security": sec_data["securityData"]["security"],
"fields": fields,
"data": sec_data["securityData"]["fieldData"]["fieldData"],
}
yield result

@staticmethod
def _parse_field_info_data(response, request_data):
rtype = "fieldResponse"
Expand Down Expand Up @@ -1473,6 +1529,31 @@ def create_query(request_type: str, values: Dict, overrides: Optional[Sequence]
return request_dict


def create_eqs_query(
screen_name: str,
screen_type: str = "PRIVATE",
overrides: Optional[Sequence] = None,
options: Optional[Dict] = None,
) -> Dict:
"""Create a BeqsRequest dictionary request.

Args:
screen_name: name of the screen
screen_type: type of screen; either PRIVATE or GLOBAL
overrides: List of tuples containing the field to override and its value
options: key value pairs to to set in request

Returns: A dictionary representation of a blpapi.Request
"""
values = {
"screenName": screen_name,
"screenType": screen_type,
}
if options:
values.update(options)
return create_query("BeqsRequest", values, overrides)


def create_historical_query(
securities: Union[str, Sequence[str]],
fields: Union[str, Sequence[str]],
Expand Down