Last updated: September 2024
Overview
Due to Greater Bay Area (GBA) has a lot of population (86M) and huge number of GDP ($1.6T), there’s a huge potential of this market with investment opportunities across multiple asset classes and propositions.
In this article, we’re focusing on Kung Fu Bonds (The bond issued by a Chinese entity in USD currency) as we notice that their market has been growing. Hence, the Data Library is being used to create the solution to identify, aggregate and visualize KungFu bonds.
What is KungFu bonds?
Kungfu bonds or Chinese-issued U.S. dollars bonds are dollar-denominated bond issued by Chinese financial institutions and corporations.
In 2017 this part of the bond market doubled to 214 billion USD as tighter domestic regulations and market conditions saw Chinese companies look offshore to raise capital. This far outpaced the other major foreign currency bonds issued in Asia. Chinese issuance of dollar bonds makes up nearly 70% of corporate dollar bonds in Asia (excluding Japan). Major issuers include Tencent Holdings Limited, Industrial and Commercial Bank of China Limited and Sinopec Group. In 2017, China's Ministry of Finance revealed plans to sell US$2 billion worth of sovereign dollar bonds in Hong Kong, its first dollar bond offering since October 2004. The technology and communications sector in China made up a significant share of the offshore U.S. dollar bond market. Tencent priced 5 billion USD of notes in January 2018.
What is LSEG Data Library for Python?
The LSEG Data Library for Python provides a set of ease-of-use interfaces offering coders uniform access to the breadth and depth of financial data and services available on the LSEG Data Platform. The API is designed to provide consistent access through multiple access channels and target both Professional Developers and Financial Coders. Developers can choose to access content from the desktop, through their deployed streaming services, or directly to the cloud. With the LSEG Data Library, the same Python code can be used to retrieve data regardless of which access point you choose to connect to the platform.
- Introduction
- Getting started with Python
- Document - Reference Guide
- Tutorials
Prerequisite
The code can be run on LSEG Codebook or Jupyter Notebook that has the Data library installed and ready to be used. To run examples in this article, I'll be using my local Jupyter Notebook.
- In case you’re running the Jupyter Notebook in your own environment (not using Codebook),
- The Access Credential is required to use the Data Library. For more detail, please check page Data Library - Access Credentials, or it will be taken from Workspace application automatically.
- Workspace Desktop should be running and being logged-in in the same machine where the code is running
- Required python libraries:
- lseg-data==2.0.0
- pandas==2.2.2
- plotly==5.22.0
- datetime
- dateutil
You may contact your account representative to help you to access LSEG workspace.
Data Library Code Example
1) Set up the environment
import the Data Library and other necessary libraries and open the Data Library desktop session. See more example code of the data library's session here.
import lseg.data as ld
import datetime
import pandas as pd
ld.open_session()
2) Using the Search function to retrieve the data
from government corporate bond with several filters regarding Kungfu bonds
The search service identifies a matching set of documents which satisfy the caller's criteria, sorts it, and selects a subset of the matches to return as the result set. Each step can be controlled via parameters. You may check the article Building Search into your Application Workflow for more detail about the Search function. Plus, you may also check This guidelines showing how to determine the properties to use within your search using the metadata, debug output, and navigators.
Or the app Advanced Search - Government and Corporate Bonds (GOVSRCH) can be used to generate Python code for the data library
To apply filter(s) in Advanced Search application:
1 ) Type GOVSRCH in the Workspace search bar and press Enter or select Government and Corporate Bonds Advanced Search from the autosuggest list. The Government and Corporate Bonds Advanced Search opens.
2) In the left-hand Search Summary panel, click the Add Filters + (plus sign) icon.
3) Type the filter you want to add, such as, Country of Risk in the Filter Categories search bar and select the filter option you want.
4) In the selected filter panel, select the value(s) that you want to filter by.
5) Click Done to apply the filters to your Search.
The search criteria for Kungfu bonds is as below
1 ) Country of Risk includes China(mainland)/Hong Kong/Macau/Taiwan
2 ) Principal Currency includes US Dollar
3 ) Country of Issue excludes China(mainland)
4 ) Then click the dropdown at the top right and select Export Query
Here’s the screenshot of GOVSRCH app with the filters applied
The Export query window will pop-up, click CODEBOOK tab to get the code to be used (Access Layer and Content Layer can be picked) then click Copy.
- The Access Layer is the easiest way to get LSEG data. It provides simple interfaces allowing you to quickly prototype solutions in interactive environments such as Jupyter Notebooks.
- The Content Layer is the foundation of the Access Layer. It provides developers with interfaces suitable for more advanced use cases (synchronous finction calls, async/await, event driven).
In this case, as number of the result is exceeding 10,000 records, which is the maximum number of top parameter. We're going to use Content Layer code instead, so we can find the number of result of each label in selected filter with navigators parameter, then retrieve the data in chunk divided by label(s) that their result number aren't exceed the maximum number of top parameter.
In case we use the Access layer with the search criteria with the result more than the maximum number of top row, the warning will be returned to warn the user that you're not getting all of the result, as below.
Navigators parameter
The underlying service enforces an upper limit in terms of the results requested. In some cases, this can be managed through techniques such as grouping or by using navigators.
Navigators provide the ability to summarize the distribution of your results. They are particularly useful when you are interested in gathering the domain of values for a specific property. Navigators can be used against a specific View, used in conjunction with either a query, a filter, or both. Navigators can be simple or very powerful, but provide a very useful way to capture results in logical buckets.
Example of how to deal with Upper limit can be found in Limit section of article Building Search into your Application Workflow.
I will start with applying a Navigator by selecting the 'RCSRiskOrganisationCountry' property. What this does is instruct search to bucket all unique Country of Risk and summarize the total found for each.
from lseg.data.content import search
response = search.Definition(
view = search.Views.GOV_CORP_INSTRUMENTS,
filter = "((DbType eq 'GOVT' or DbType eq 'CORP' or DbType eq 'AGNC' or DbType eq 'OMUN' or DbType eq 'OTHR') and IsActive eq true and ((RCSRiskOrganisationCountry xeq 'G:53' or RCSRiskOrganisationCountry xeq 'G:3H' or RCSRiskOrganisationCountry xeq 'G:7U' or RCSRiskOrganisationCountry xeq 'G:3I') and RCSCurrency in ('C:6') and RCSCountryGenealogy ne 'M:DH\G:K\G:S\G:6\G:53'))",
top = 0,
navigators = "RCSRiskOrganisationCountry"
).get_data()
response.data.raw["Navigators"]["RCSRiskOrganisationCountry"]["Buckets"]
Here's the result, we're going to group the labels into two groups which do not exceed 10,000 records limit.
[{'Label': 'G:3H', 'Count': 6898},
{'Label': 'G:53', 'Count': 4694},
{'Label': 'G:7U', 'Count': 158},
{'Label': 'G:3I', 'Count': 114}]
Then perform the search with two filter groups from the step above, then concatenate them to be one dataframe as below with these modification.
- The properties under select parameter were adjusted to be the properties needed for an analysis
- Update number in top parameter to 10000 to get all result of the bonds
# assign each group of label into the filter
filter1 = "(RCSRiskOrganisationCountry xeq 'G:53' or RCSRiskOrganisationCountry xeq 'G:3I')"
filter2 = "(RCSRiskOrganisationCountry xeq 'G:3H' or RCSRiskOrganisationCountry xeq 'G:7U')"
# create function that use filter above as an input parameter
def bond_search(filter):
response = search.Definition(
view = search.Views.GOV_CORP_INSTRUMENTS,
filter = f"((DbType eq 'GOVT' or DbType eq 'CORP' or DbType eq 'AGNC' or DbType eq 'OMUN' or DbType eq 'OTHR') and IsActive eq true and ({filter} and RCSCurrency in ('C:6') and RCSCountryGenealogy ne 'M:DH\G:K\G:S\G:6\G:53'))",
top = 10000,
select = "RIC,RCSTRBC2012Leaf,IssueDate,EOMAmountOutstanding,PricingRedemDate,IssuerLegalName,PricingClosingYield, CurrentYield, FaceIssuedTotal,EOMPriceChange,RCSBondGradeLeaf,EOMPriceReturn"
).get_data()
df = response.data.df
return df
# perform the search with filter 1
df1 = bond_search(filter1)
# perform the search with filter 2
df2 = bond_search(filter2)
# concatenate two dataframes retrieved from filter 1 and filter 2
df = pd.concat([df1, df2], ignore_index=True)
df
Now we have got the result of Kung Fu bonds in one dataframe! Let's use this for the visualization in the next step.
3. Visualize the Kungfu bonds (df dataframe)
3.1 ) TRBC Pie
import plotly.express as px
rt = df.groupby("RCSTRBC2012Leaf",as_index=False).agg('count')
rt = rt.sort_values('RIC', ascending = False).head(10)
fig = px.pie(rt, values='RIC', names='RCSTRBC2012Leaf', title='TRBC Pie',color_discrete_sequence=px.colors.sequential.RdBu)
fig.show()
3.2 ) Bond Grade Pie
df['RCSBondGradeLeaf'].fillna("No Grade", inplace=True)
grad = df.groupby("RCSBondGradeLeaf",as_index=False).agg('count')
fig = px.pie(grad, values='RIC', names='RCSBondGradeLeaf', title='Bond Grade Pie',color_discrete_sequence=px.colors.sequential.RdBu)
fig.show()
3.3 ) Issued amount last 12 months
from datetime import date
from dateutil.relativedelta import relativedelta
decb = date.today() + relativedelta(months=-12)
start = datetime.datetime(decb.year, decb.month, 1).strftime("%Y-%m-%d %H:%M:%S")[0:10]
end = date.today().strftime("%Y-%m-%d %H:%M:%S")[0:10]
cols = ['RIC','IssueDate','FaceIssuedTotal']
c = df[cols].copy()
c.loc[:,"IssueDate"] = pd.to_datetime(c["IssueDate"])
c = c.set_index("IssueDate").sort_index(ascending=False)
c_month = c.resample('BM').sum()
c_monthb = c_month[start:end]
c_monthb.index.name = None
import plotly.express as px
fig = px.bar(c_monthb, y='FaceIssuedTotal', title = 'Issued amount last 12 months', color_discrete_sequence=px.colors.sequential.RdBu)
fig.show()
3.4 ) Redem amount in 10 years
yearsf = date.today() + relativedelta(years=+10)
end = datetime.datetime(yearsf.year, yearsf.month, 1).strftime("%Y-%m-%d %H:%M:%S")[0:4]
start = date.today().strftime("%Y-%m-%d %H:%M:%S")[0:4]
cols = ['RIC','PricingRedemDate','FaceIssuedTotal']
d = df[cols].copy()
d.loc[:,"PricingRedemDate"] = pd.to_datetime(d["PricingRedemDate"], errors = 'coerce')
d = d.set_index("PricingRedemDate").sort_index(ascending=True)
d_month = d.resample('BY').sum()
d_monthf = d_month[start:end]
d_monthf.index.name = None
import plotly.express as px
fig = px.bar(d_monthf, y='FaceIssuedTotal',title = 'Redem amount in 10 years', color_discrete_sequence=px.colors.sequential.RdBu)
fig.show()
3.5 ) Newly issued bonds last 3 months
monb3 = date.today() + relativedelta(months=-3)
start = datetime.datetime(monb3.year, monb3.month, 1).strftime("%Y-%m-%d %H:%M:%S")[0:10]
end = date.today().strftime("%Y-%m-%d %H:%M:%S")[0:10]
print(start, end)
cols = ['RIC','IssueDate','FaceIssuedTotal','RCSTRBC2012Leaf']
f = df[cols].copy()
f.loc[:,"IssueDate"] = pd.to_datetime(f["IssueDate"])
f = f.set_index("IssueDate").sort_index(ascending=True)
f_3m = f[start:end]
rt = f_3m.groupby("RCSTRBC2012Leaf",as_index=False).agg('count')
rt = rt.sort_values('RIC', ascending = False).head(10)
fig = px.pie(rt, values='RIC', names='RCSTRBC2012Leaf', title='newly issued bonds last 3 months',color_discrete_sequence=px.colors.sequential.RdBu)
fig.show()
3.6 ) Price top 10
cols = ['RIC','IssuerLegalName','EOMPriceReturn','EOMPriceChange']
price = df[cols].copy()
price_top10 = price.sort_values('EOMPriceReturn', ascending = False).head(10)
price_top10
3.7 ) Yield
cols = ['RIC','IssuerLegalName','CurrentYield','PricingClosingYield']
y = df[cols]
y = y[y['CurrentYield']>0]
3.7.1 ) Yield top 10
yield_top10 = y.sort_values('CurrentYield', ascending = False).head(10)
yield_top10
3.7.2 ) Yield last 10
yield_last10 = y.sort_values('CurrentYield', ascending = True).head(10)
yield_last10
The full code can be found in this GitHub Repository and here's the Jupyter Notebook with the output
- Register or Log in to applaud this article
- Let the author know how much this article helped you