Tuesday, September 18, 2012

Python Bloomberg API

Abstract

I know Bloomberg has made it's API open to the public. Unfortunately, I use Python and there begins my dilemma. It is not a Bloomberg supported programming language. Speaking of which, after any convesation I have with someone from their Helpdesk, there's nothing much I can do but bang my head on top of my desk.

I have written this document to provide ample information on how to interface with Bloomberg using Python. I have endured a great amount of pain in order to make sure that information here will work for all Bloomberg installations.

Bloomberg.Data.1

This is the legacy way of doing things. It DOESN'T work on all Bloomberg installations. But for completeness sake, the code is as simple as:

import win32com.client
BLP = win32com.client.Dispatch('Bloomberg.Data.1')

V3 API

comtypes, win32com, and ctypes are 3 python modules that I have experimented with. I will only provide information for comtypes and win32com since my tango with ctypes has gone into an infinite loop. According to Bloomberg, the library for V3 API can be accessed via registered or unregistered mode. The modules below will determine which one you have. I have broken down the modules for easy maintenance.

bbg_helper.py


import logger
import win32com.client, win32com.client.gencache
import comtypes.client as COM
import blpapicom2_unregistered as unreg_mode

try:
    logger.info('Trying blpapicom2.Session.1 registered mode (comtypes.client)')
    session = COM.CreateObject('blpapicom2.Session.1')
    logger.info('comtypes.client blpapicom2.Session.1 registered mode is OK')
except Exception as e:
    logger.warn('FAILED IN STARTING blpapicom2.Session.1 WITH MESSAGE: %s', str(e))
    try:
        logger.info('Trying blpapicom.Session registered mode (comtypes.client)')
        session = COM.CreateObject('blpapicom.Session')
        logger.info('comtypes.client blpapicom.Session registered mode is OK')
    except Exception as e:
        logger.warn('FAILED IN STARTING blpapicom.Session WITH MESSAGE: %s', str(e))
        try:
            logger.info('Trying blpapicom2.Session.1 registered mode (win32com.client)')
            session = win32com.client.CastTo(win32com.client.gencache.EnsureDispatch('blpapicom2.Session.1'),'ISession')
            logger.info(win32com.client blpapicom2.Session.1 registered mode is OK)
        except Exception as e:
            logger.warn('FAILED IN STARTING blpapicom2.Session.1 WITH MESSAGE: %s', str(e))
            try:
                logger.info('Trying blpapicom.Session registered mode (win32com.client)')
                session = win32com.client.CastTo(win32com.client.gencache.EnsureDispatch('blpapicom.Session'),'ISession')
                logger.info('win32com.client blpapicom.Session registered mode is OK')
            except Exception as e:
)               logger.warn('FAILED IN STARTING blpapicom.Session WITH MESSAGE: %s', str(e))
                try:
                    session = unreg_mode.BlpCoCreateInstance()
                except:
                    pass

blpapicom2_unregistered.py

import sys
import win32com.client
import comtypes.client as COM
import logger
import blpapicom_unregistered as unreg_mode

# config for Windows XP users
API_XP        = unreg_mode.API2_XP

# config for Windows 7 users
API_WIN7      = unreg_mode.API2_WIN7

def BlpCoCreateInstance():
    '''
        Return a Session object
    '''

    Session = None
    Module = None
    try:
        logger.info('Trying blpapicom2.dll un-registered mode (comtypes.client)')
        if sys.getwindowsversion()[0] < 6: 
            logger.info('Trying for Windows XP config')
            Module = COM.GetModule(API_XP)
        else: 
            logger.info('Trying for Windows 7 config')
            Module = COM.GetModule(API_WIN7)

        if Module is not None:
            Session = COM.CreateObject(Module.Session)
        logger.info('comtypes.client blpapicom2.dll un-registered mode is OK')
    except Exception as e:
        logger.warn('FAILED IN STARTING blpapicom2.dll WITH MESSAGE: %s', str(e))
        try:
            logger.info('Trying blpapicom2.dll un-registered mode (win32com.client)')
            Session = win32com.client.CastTo(win32com.client.Dispatch('blpapicom2.Session'),'ISession')
            logger.info('win32com.client blpapicom2.dll un-registered mode is OK')
        except Exception as e:
            logger.warn('FAILED IN STARTING blpapicom2.dll WITH MESSAGE: %s', str(e))
            try:
                Session = unreg_mode.BlpCoCreateInstance()
            except:
                pass

    return Session

blpapicom_unregistered.py

import sys
import win32com.client
import comtypes.client as COM
import logger
import ctypes_unregistered as unreg_mode

# config for Windows XP users
API2_XP         = unreg_mode.API2_XP
API_XP          = unreg_mode.API_XP

# config for Windows 7 users
API2_WIN7     = unreg_mode.API2_WIN7
API_WIN7      = unreg_mode.API_WIN7

def BlpCoCreateInstance():
    '''
        Return a Session object
    '''
    Session = None
    Module = None
    try:
        logger.info('Trying blpapicom.dll un-registered mode (comtypes.client)')
        if sys.getwindowsversion()[0] < 6: 
            logger.info('Trying for Windows XP config')
            Module = COM.GetModule(API_XP)
        else: 
            logger.info('Trying for Windows 7 config')
            Module = COM.GetModule(API_WIN7)

        if Module is not None:
            Session = COM.CreateObject(Module.Session)
        logger.info('comtypes.client blpapicom.dll un-registered mode is OK')
    except Exception as e:
        logger.warn('FAILED IN STARTING blpapicom.dll WITH MESSAGE: %s', str(e))
        try:
            logger.info('Trying blpapicom.dll un-registered mode (win32com.client)')
            Session = win32com.client.CastTo(win32com.client.Dispatch('blpapicom.Session'),'ISession')
            logger.info('win32com.client blpapicom.dll un-registered mode is OK')
        except Exception as e:
            try:
                Session = unreg_mode.BlpCoCreateInstance()
            except:
                pass

    return Session

ctypes_unregistered.py

import sys
import ctypes
import logger

OLEAUT32 = ctypes.oledll.oleaut32
OLE32    = ctypes.oledll.ole32
OLE32.CoInitialize(0)

# IIDs
RCLSID  = '{9FDBE237-38A5-4d8c-BB9C-2EB55FB1EABE}'
RIID    = '{4AC751C2-BB10-4702-BB05-791D93BB461C}'

BYTE    = ctypes.c_byte
WORD    = ctypes.c_int
DWORD   = ctypes.c_long

# config for Windows XP users
ROOT_XP         = r'C:\Program Files\BLP\API'
LOADER_XP       = ROOT_XP + '\\' + 'bbloaderv3.dll'
API2_XP         = ROOT_XP + '\\' + 'blpapicom2.dll'
API_XP          = ROOT_XP + '\\' + 'blpapicom.dll'

# config for Windows 7 users
ROOT_WIN7     = r'C:\Program Files (x86)\BLP\API'
LOADER_WIN7   = ROOT_WIN7 + '\\' + 'bbloaderv3.dll'
API2_WIN7     = ROOT_WIN7 + '\\' + 'blpapicom2.dll'
API_WIN7      = ROOT_WIN7 + '\\' + 'blpapicom.dll'

def PrintGUIDValue(guid):
    '''
        Return GUID _fields_ values
    '''
    return "Data1: %s; Data2: %s; Data3: %s; Data4: %s" %(str(guid.Data1), str(guid.Data2), str(guid.Data3),"".join(str(guid.Data4)))

class GUID(ctypes.Structure):
    _fields_ = [
        ("Data1", ctypes.c_long),
        ("Data2", ctypes.c_int),
        ("Data3", ctypes.c_int),
        ("Data4", ctypes.c_byte * 8),
        ]

    def __init__(self, name=None):
        if name is not None:
            self._as_parameter = OLE32.CLSIDFromString(unicode(name), ctypes.byref(self))

    def __repr__(self):
        s = (ctypes.c_wchar * 39)()
        OLE32.StringFromGUID2(ctypes.byref(self), s, 39)
        return "guid: %s" % s.value

    def __str__(self):
        s = (ctypes.c_wchar * 39)()
        OLE32.StringFromGUID2(ctypes.byref(self), s, 39)
        return s.value

    def __cmp__(self, other):
        if isinstance(other, GUID):
            return not OLE32.IsEqualGUID(ctypes.byref(self), ctypes.byref(other))
        return -1

    def __nonzero__(self):
        result = str(buffer(self)) != "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
        return result

    def __eq__(self, other, IsEqualGUID=OLE32.IsEqualGUID):
        return isinstance(other, GUID) and IsEqualGUID(ctypes.byref(self), ctypes.byref(other))

    def PrintGUIDValue(self):
        ''' Return GUID _fields_ values '''
        return PrintGUIDValue(self)

def BlpCoCreateInstance():
    '''
        Implementation is incomplete. DO NOT USE!
    '''
    Session = None
    bbloaderv3 = None
    blpapicom2 = None
    if sys.getwindowsversion()[0] < 6:
        bbloaderv3 = ctypes.windll.LoadLibrary(LOADER_XP)
        blpapicom2 = API_XP
    else:
        bbloaderv3 = ctypes.windll.LoadLibrary(LOADER_ISPACE)
        blpapicom2 = API2_WIN7

    rclsidguid = GUID(RCLSID)
    riidguid = GUID(RIID)
    rclsid = ctypes.byref(rclsidguid)
    riid   = ctypes.byref(riidguid)

    logger.info(repr(rclsidguid) + " := " + rclsidguid.PrintGUIDValue())
    logger.info(repr(riidguid) + " := " + riidguid.PrintGUIDValue())
    BlpCoCreateInstance = getattr(bbloaderv3, 'BlpCoCreateInstance')
    BlpCoCreateInstance.restype = ctypes.c_char_p

    bbloaderv3.BlpCoCreateInstance(blpapicom2, rclsid, riid)

    return Session

"""
We are trying to mimic how Bloomberg uses the data control in unregistered mode (VBA).
Below is taken from their WAPI documentation Ref# COMv3RegUnreg

' Please note that the full path must be included to the location of the
' bbloaderv3.dll file. If this path is not correct, please, replace it
' with the correct path

Private Declare Function BlpCoCreateIntance Lib "bbloaderv3.dll" _
    (ByVal lpszFilePathName As String, ByRef rclsid As GUID, riid As GUID) As Object

Private Declare Function CLSIDFromString Lib "ole32.dll" (pstCLS As Long, _
    clsid As GUID) As Long

Private Declare Function GetActiveObject Lib "oleaut32.dll" (lpRclsid As Long, _
    pvReserved As Long, lpUnk As Long) As Long

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4 (7) As Byte
End Type

Dim rclsid As GUID
Dim riid as GUID
Dim FileName As String
Private WithEvents session As Object

Private Function GuidFromStGuid(ByVal stGuid As String) As GUID

    Dim rc As Long

    If Left$(stGuid, 7) = "{guid {" Then
        If Right$(stGuid, 2) = "}}" Then
            stGuid = Mid$(stGuid, 7, 38)
        End If
    End If

    rc = CLSIDFromString(ByVal StrPtr(stGuid), GuidFromStGuid)

End Function

' Instantiate the Bloomberg COM Data Control
Private Sub Class_Initialize()

    FileName = "blpapicom2.dll"
    Set session = BlpCoCreateInstance(FileName, _
        GuidFromStGuid("{guid {9FDBE237-38A5-4d8c-BB9C-2EB55FB1EABE}}"), _
        GuidFromStGuid("{guid {4AC751C2-BB10-4702-BB05-791D93BB461C}}"))

    session.Start

End Sub
"""

Session

The ultimate goal is to get a Session object. Once that is done, sending request should be a breeze.

import datetime

TODAY = datetime.datetime.today()
APIREFDATA_SVC    = '//blp/refdata'
RESPONSE = 5
PARTIAL_RESPONSE = 6

def MakeRequest(security, fldList, overrideFields=None, overrideValues=None):
    '''
        Request test harness
    '''
    result = []
    session.QueueEvents = True
    session.Start()
    session.OpenService(APIREFDATA_SVC)
    service = session.GetService(APIREFDATA_SVC)

    req = service.CreateRequest("ReferenceDataRequest")

    #not sure if this will work
    sec = req.GetElement('securities')
    sec.AppendValue(security)

    flds = req.GetElement('fields')
    for fld in fldList:
        flds.AppendValue(fld)

    if overrideFields:
        overridables = zip(overrideFields, overrideValues)
        overrides = req.GetElement("overrides")
        for overrideField, overrideValue in overridables:
            override = overrides.AppendElment()
            override.SetElement('fieldId', overrideField)
            override.SetElement('value', overrideValue)

    session.SendRequest(req)

    while True:
        eventObj = session.NextEvent()
        if eventObj.EventType == PARTIAL_RESPONSE or eventObj.EventType == RESPONSE:
            it = eventObj.CreateMessageIterator()

            while it.Next():
                msg = it.Message
                numSecurities = msg.GetElement("securityData").NumValues
                for secIndex in xrange(0, numSecurities):
                    security = msg.GetElement("securityData").GetValue(secIndex)

                    fields = security.GetElement("fieldData")
                    numFields = fields.NumElements
                    for fldIndex in xrange(0, numFields):
                        field = fields.GetElement(fldIndex).Value
                        result.append(field)
            if eventObj.EventType == RESPONSE:
                break
    return result

def MakeHistoricalDataRequest(security, fldList, overrideFields=None, overrideValues=None, dateFrom=None, dateTo=None):
    '''
        Historical request test harness
    '''
    result = []
    session.QueueEvents = True
    session.Start()
    session.OpenService(APIREFDATA_SVC)
    service = session.GetService(APIREFDATA_SVC)

    req = service.CreateRequest("HistoricalDataRequest")

    sec = req.GetElement('securities')
    sec.AppendValue(security)

    flds = req.GetElement('fields')
    for fld in fldList:
        flds.AppendValue(fld)

    if dateFrom:
        req.Set('startDate', dateFrom)
    else:
        req.Set('startDate', TODAY.strftime('%Y%m%d'))

    if dateTo:
        req.Set('endDate', dateTo)
    else:
        req.Set('endDate', TODAY.strftime('%Y%m%d'))
    req.Set('periodicitySelection', 'DAILY')

    if overrideFields:
        overridables = zip(overrideFields, overrideValues)
        overrides = req.GetElement("overrides")
        for overrideField, overrideValue in overridables:
            override = overrides.AppendElment()
            override.SetElement('fieldId', overrideField)
            override.SetElement('value', overrideValue)

    session.SendRequest(req)

    while True:
        eventObj = session.NextEvent()
        if eventObj.EventType == PARTIAL_RESPONSE or eventObj.EventType == RESPONSE:
            it = eventObj.CreateMessageIterator()

            while it.Next():
                msg      = it.Message
                security = msg.GetElement("securityData")
                #secVal   = security.GetElement("security")
                field_exceptions = security.GetElement("fieldExceptions")

                # process exceptions
                if field_exceptions.NumValues > 0:
                    element = field_exceptions.GetValuesAsElement(0)
                    field_id = element.GetElement("fieldId")
                    error_info = element.GetElement("errorInfo")
                    error_message = error_info.GetElement("message")
                    logger.warn('Exception with %s with message: %s' %(field_id, error_message))

                # process field data
                field_data = security.GetElement("fieldData")
                numFields = field_data.NumValues
                for fldIndex in xrange(0, numFields):
                    element = field_data.GetValueAsElement(fldIndex)

                    # repack everything by date
                    subResult = []
                    for field in fldList:
                        if element.HasElement(field):
                            subResult.append(element.GetElement(field).Value)
                    result.append(subResult)

            if eventObj.EventType == RESPONSE:
                break

    return result

2 comments:

  1. Bloomberg Open API (BLPAPI) v3.5 was just released and includes a native Python SDK.

    http://www.openbloomberg.com/2012/11/21/open-api-blpapi-v3-5-x-released/

    ReplyDelete
  2. Would you be interested in any bloomberg open api development work, for a project I am working on? Drop me an email knowlesi07@googlemail.com

    ReplyDelete