def getOtherAssetType(blpData, position): """ For Fixed Income or Equity asset type, use Bloomberg "MARKET_SECTOR_DES" field to lookup: If the field is "Equity", then asset class = "Equity", sub category "Listed Equity" if Bloomberg field "EXCH_MARKET_STATUS" = "ACTV", else sub category "Unlisted Equity". If the field is "Comdty", then asset type = ('Commodity', 'Derivatives') Otherwise its Bloomberg field "CAPITAL_CONTINGENT_SECURITY" = "Y", then asset class is: ('Fixed Income', 'Additional Tier 1, Contingent Convertibles') Else use the below mapping Corp -> Fixed Income, sub catetory "Corporate Bond" Govt -> Fixed Income, sub catetory "Government Bond" """ isEquityType = lambda blpData, position: \ blpData[getIdnType(position)[0]]['MARKET_SECTOR_DES'] == 'Equity' isCommodityType = lambda blpData, position: \ blpData[getIdnType(position)[0]]['MARKET_SECTOR_DES'] == 'Comdty' isFIType = lambda blpData, position: \ blpData[getIdnType(position)[0]]['MARKET_SECTOR_DES'] in ['Corp', 'Govt'] isCapitalContingentSecurity = lambda blpData, position: \ blpData[getIdnType(position)[0]]['CAPITAL_CONTINGENT_SECURITY'] == 'Y' # FIXME: this function is not complete, as index futures are not included getEquityAssetType = lambda blpData, position: \ ('Equity', 'Listed Equities') if blpData[getIdnType(position)[0]]['EXCH_MARKET_STATUS'] == 'ACTV' \ else ('Equity', 'Unlisted Equities') # FIXME: this function is not complete, as physical commodity is not included # getCommodityAssetType = lambda blpData, position: \ # ('Commodity', 'Derivatives') getFIAssetType = lambda blpData, position: \ ('Fixed Income', 'Additional Tier 1, Contingent Convertibles') \ if isCapitalContingentSecurity(blpData, position) else \ ('Fixed Income', 'Corporate') if blpData[getIdnType(position)[0]]['MARKET_SECTOR_DES'] == 'Corp' else \ ('Fixed Income', 'Government') if blpData[getIdnType(position)[0]]['MARKET_SECTOR_DES'] == 'Govt' else \ lognRaise('getFIAssetType(): unsupported FI type {0}'.format(getIdnType(position))) return \ getEquityAssetType(blpData, position) if isEquityType(blpData, position) else \ getCommodityAssetType(blpData, position) if isCommodityType(blpData, position) else \ getFIAssetType(blpData, position) if isFIType(blpData, position) else \ lognRaise('getOtherAssetType(): invalid asset type: {0}'.format(getIdnType(position)))
def getOthersCountry(blpData, position): """ [Dictionary] blpData, [Dictionary] position => [String] country The logic to deal with commodity product is not yet clear, so we put it here. """ _id, _id_type = getIdnType(position) if (_id, _id_type) == ('TYM1 Comdty', 'TICKER'): return 'US' else: # FIXME: Add implementation lognRaise('getCommodityCountry(): {0}'.format(getIdnType(position)))
def getFundCountry(blpData, position): """ [Dictionary] blpData, [Dictionary] position => [String] country The logic to deal with fund, no matter listed fund (ETF) or open ended fund, is not yet clear, so we put it here. """ # FIXME: Need a formal implementation, now just case by case fundCountry = {} try: return fundCountry[getIdnType(position)[0]] except KeyError: lognRaise('getFundCountry(): {0}'.format(getIdnType(position)))
def verifyBlpPosition(self, p): self.assertEqual('20200131', getPositionDate(p)) self.assertEqual('12734', getPortfolioId(p)) self.assertEqual('USD', getBookCurrency(p)) self.assertEqual(('USF2R125CE38', 'ISIN'), getIdnType(p)) self.assertEqual(11698622.22, getMarketValue(p)) self.assertEqual(11000000, getQuantity(p))
def verifyInvestmentPosition(self, p): self.assertEqual('20200429', getPositionDate(p)) self.assertEqual('19437', getPortfolioId(p)) self.assertEqual('HKD', getBookCurrency(p)) self.assertEqual(('1299 HK Equity', 'TICKER'), getIdnType(p)) self.assertEqual(12749540, getMarketValue(p)) self.assertEqual(177200, getQuantity(p))
def getCommodityCountry(blpData, position): """ [Dictionary] blpData, [Dictionary] position => [String] country The logic to deal with commodity product is not yet clear, so we put it here. """ # FIXME: not implemented lognRaise('getCommodityCountry(): {0}'.format(getIdnType(position)))
def getMoneyMarketCountry(position): """ [Dictionary] position => [String] country A money market product can be an OTC product, for example, a fixed deposit, so we deal with them here. """ # FIXME: Assume all money market instruments are made in Hong Kong return lognContinue( 'getMoneyMarketCountry(): {0}'.format(getIdnType(position)), 'HK')
def getPrivateSecurityCountry(position): """ [Dictionary] position => [String] country A private security is a non-listed security, non-listed fund or others that cannot find their information through Bloomberg. So we deal with them here. """ # FIXME: Add implementation lognRaise('getPrivateSecurityCountry(): {0}'.format(getIdnType(position)))
def getAverageRatingScoreSpecialCase(position): """ [Dictionary] position => [Float] score When none of the rating agencies gives a credit rating, we provide the ratings here. The current implementation gives a rating score of 0 in such case. """ logger.warning('getAverageRatingScoreSpecialCase(): {0}'.format( getIdnType(position))) return 0
def isSpecialCase(position): """ [Dictionary] position => [Bool] is this a special case in asset type, private security, open ended fund or something that needs override. """ portfolioMatched = lambda p1, p2: True if p2 == '' or p1 == p2 else False return compose( lambda t: t[0] in t[2] and portfolioMatched(t[1], t[2][t[0]][ 'Portfolio']), lambda position: (getIdnType(position)[0], getPortfolioId(position), getAssetTypeSpecialCaseData()))(position)
def getAssetType(blpData, position): """ [Dictionary] position (a Geneva or Blp position) => [Tuple] asset type The asset type is a tuple containing the category and sub category, like ('Cash', ), ('Fixed Income', 'Corporate') or ('Equity', 'Listed') """ logger.debug('getAssetType(): {0}'.format(getIdnType(position))) return \ getSpecialCaseAssetType(position) if isSpecialCase(position) else \ getPrivateSecurityAssetType(position) if isPrivateSecurity(position) else \ ('Cash', ) if isCash(position) else \ ('Foreign Exchange Derivatives', ) if isFxForward(position) else \ ('Fixed Income', 'Cash Equivalents') if isMoneyMarket(position) else \ getRepoAssetType(position) if isRepo(position) else \ getFundAssetType(position) if isFund(position) else \ getOtherAssetType(blpData, position)
def getAverageRatingScore(blpData, position, specialCaseHandler=getAverageRatingScoreSpecialCase): """ [Dictionary] blpData, [Dictionary] position => [Float] score """ logger.debug('getAverageRatingScore(): {0}'.format(getIdnType(position))) averageScore = lambda position, scores: \ specialCaseHandler(position) if len(scores) == 0 else \ scores[0] if len(scores) == 1 else \ min(scores) if len(scores) == 2 else \ sorted(scores)[1] return \ compose( partial(averageScore, position) , lambda scores: list(filterfalse(lambda x: x == 0, scores)) , getRatingScores )(blpData, position)
def getGenevaLqaPositions(positions): """ [Iterable] positions => [Iterable] positions Read Geneva consolidated tax lot positions, then do the following: 1) take out those not suitable for liquidity test (cash, FX forward, etc.); 2) Add Id, IdType and Position fields for LQA processing. """ # [Dictonary] p => [Dictionary] enriched position with id and idType addIdnType = compose( lambda t: mergeDict(t[2], {'Id': t[0], 'IdType': t[1]}) , lambda p: (*getIdnType(p), p) ) return compose( partial(map, addIdnType) , partial(filterfalse, noNeedLiquidityGeneva) , lambda positions: lognContinue('getGenevaLqaPositions(): start', positions) )(positions)
def getCountryCode(blpData, position): """ [Dictionary] blpInfo, [Dictionary] position => [String] country code """ logger.debug('getCountryCode(): {0}'.format(getIdnType(position))) isCommodityType = lambda blpData, position: \ getAssetType(blpData, position)[0] == 'Commodity' isFundType = lambda blpData, position: \ getAssetType(blpData, position)[0] == 'Fund' isEquityType = lambda blpData, position: \ getAssetType(blpData, position)[0] == 'Equity' isFIType = lambda blpData, position: \ getAssetType(blpData, position)[0] == 'Fixed Income' isOthersType = lambda blpData, position: \ getAssetType(blpData, position)[0] == 'Others' return \ getSpecialCaseCountry(position) if isSpecialCase(position) else \ getPrivateSecurityCountry(position) if isPrivateSecurity(position) else \ getRepoCountry(position) if isRepo(position) else \ getMoneyMarketCountry(position) if isMoneyMarket(position) else \ getCommodityCountry(blpData, position) if isCommodityType(blpData, position) else \ getFundCountry(blpData, position) if isFundType(blpData, position) else \ getOthersCountry(blpData, position) if isOthersType(blpData, position) else \ getEquityCountry(blpData, position) if isEquityType(blpData, position) else \ getFICountry(blpData, position) if isFIType(blpData, position) else \ lognRaise('getCountryCode(): unsupported asset type')
def getBlpLqaPositions(positions): """ [Iterable] positions => ( [Iterable] CLO positions , [Iterable] nonCLO positions ) Read Bloomberg raw positions, then do the following: 1) take out those not suitable to do liquidity test (cash, FX forward, etc.); 2) take out DIF fund positions, since they will come from Geneva; 2) split into clo and nonCLO positions. Return (CLO positions, nonCLO positions) """ removeUnwantedPositions = compose( partial( filterfalse , lambda p: p['Asset Type'] in [ 'Cash', 'Foreign Exchange Forward' , 'Repo Liability', 'Money Market'] \ or p['Name'] in ['.FSFUND HK', 'CLFLDIF HK'] # open ended funds ) , partial(filterfalse, lambda p: p['Position'] == '' or p['Position'] <= 0) ) # [Dictionary] position => [Dictioanry] position with id and idtype updatePositionId = compose( lambda t: mergeDict(t[2], {'Id': t[0], 'IdType': t[1]}) , lambda position: (*getIdnType(position), position) ) isCLOPortfolio = lambda p: p['Account Code'] in \ ['12229', '12734', '12366', '12630', '12549', '12550', '13007'] """ [Iterable] positions => ( [Iterable] CLO positions , [Iterable] non CLO positions ) Split the positions into All, CLO and non-CLO group """ splitCLO = lambda positions: \ reduce( lambda acc, el: ( chain(acc[0], [el]) , acc[1] ) if isCLOPortfolio(el) else \ ( acc[0] , chain(acc[1], [el]) ) , positions , ([], []) ) return \ compose( splitCLO , partial(map, updatePositionId) , removeUnwantedPositions , lambda positions: lognContinue('getBlpLqaPositions(): start', positions) )(positions)
def getPrivateSecurityAssetType(position): """ [Dictionary] position => [Tuple] Asset Type Handle special cases for private securities """ # FIXME: add implementation logger.debug('getPrivateSecurityAssetType()') raise ValueError """ [Dictionary] position => [Tuple] asset type """ getSpecialCaseAssetType = lambda position: \ getAssetTypeSpecialCaseData()[getIdnType(position)[0]]['AssetType'] """ [Dictionary] position => [String] country """ getSpecialCaseCountry = lambda position: \ getAssetTypeSpecialCaseData()[getIdnType(position)[0]]['CountryCode'] def isSpecialCase(position): """ [Dictionary] position => [Bool] is this a special case in asset type, private security, open ended fund or something that needs override. """ portfolioMatched = lambda p1, p2: True if p2 == '' or p1 == p2 else False return compose( lambda t: t[0] in t[2] and portfolioMatched(t[1], t[2][t[0]][
def getEquityCountry(blpData, position): """ [Dictionary] blpData, [Dictionary] position => [String] country """ logger.debug('getEquityCountry()') return blpData[getIdnType(position)[0]]['CNTRY_ISSUE_ISO']
def getFICountry(blpData, position): """ [Dictionary] blpData, [Dictionary] position => [String] country """ logger.debug('getFICountry()') return blpData[getIdnType(position)[0]]['CNTRY_OF_RISK']