Example usage of sgfixedincome_pkg (MAS API Client)
This Jupyter Notebook vignette shows how to use sgfixedincome_pkg’s API client and related functions for working with the Monetary Authority of Singapore’s (MAS) ‘bonds and bills’ endpoints. The functions and methods shown here are primarily found in mas_api_client.py and consolidate.py.
Disclaimer
The API we query is not found in the official MAS API catalogue, and there is thus no documentation for it. Instead, I found this API and its endpoints by using Google DevTools to see where the MAS T-bill webpage and SSB webpage pulls their data from (picture below).
Whilst no API key or token is required to pull data from this API, do use it responsibly (avoid flooding the API with requests).

Setup
Let’s first import the package and initialize the API client:
import sgfixedincome_pkg as sfi
client = sfi.MAS_bondsandbills_APIClient()
General fetch data method
The fetch_data() method provides the most flexibility in fetching data from MAS bonds and bills API endpoints. Simply input the endpoint name and optionally include query parameters.
In the example below, we query the listbondsandbills endpoint which provides information on MAS bonds and bills. Using input parameters, we retrieve details of the most recently auctioned MAS bill that had a cutoff yield of at least 4.1%. This bill turns out to be the ‘MD24112N’ bill issued on ‘2024-04-01’ with a cutoff yield of 4.12%:
response = client.fetch_data(
endpoint="listbondsandbills",
params={
"rows": 1, # Get the first row only
"filters":"bill_bond_ind:bill AND cutoff_yield:[4.1 TO *]",
"sort": "auction_date desc"
}
)
response
{'success': True,
'result': {'total': 156,
'records': [{'issue_code': 'MD24112N',
'isin_code': 'SGXZ44349256',
'issue_no': '1',
'reopened_issue': 'N',
'raw_tenor': 25.0,
'auction_tenor': 4.0,
'auction_date': '2024-03-26',
'issue_date': '2024-04-01',
'first_issue_date': '2024-04-01',
'bill_bond_ind': 'bill',
'maturity_date': '2024-04-26',
'ann_date': '2024-03-25',
'rate': 0.0,
'coupon_date_1': None,
'coupon_date_2': None,
'product_type': 'M',
'sgs_type': 'U',
'total_amt_allot': '14100.00000000',
'amt_allot_non_cmpt_appls': '0.00000000',
'amt_allot_mas': '0.00000000',
'pct_cmpt_appls_cutoff': 98.05,
'pct_non_cmpt_appls_cutoff': 100.0,
'total_bids': 26201.713,
'bid_to_cover': 1.86,
'cutoff_yield': 4.12,
'cutoff_price': 99.718,
'median_yield': 3.87,
'median_price': 99.735,
'avg_yield': 3.61,
'avg_price': 99.753,
'auction_amt': 14100.0,
'intended_tender_amt': 0.0,
'accrued_int': 0.0,
'total_amount': 14100.0}]}}
Built-in SSB methods and functions
Using fetch_data() requires knowledge of the available endpoints and output structure (to know what parameters we could possibly input). However, since MAS does not provide documentation for its ‘bondsandbills’ endpoints, it may be time-consuming to figure that out.
As such, this package has built in a number of methods that fetch data from the API which you may find useful. Let’s first cover methods related to Singapore Savings Bonds (SSBs):
# Get dictionary with details on the latest SSB
client.get_latest_ssb_details()
{'issue_code': 'GX25010E',
'isin_code': 'SGXZ30907869',
'auction_tenor': 10.0,
'issue_size': 600.0,
'amt_applied': 0.0,
'total_applied_within_limits': 0.0,
'amt_alloted': 0.0,
'rndm_alloted_amt': 0.0,
'rndm_alloted_rate': 0.0,
'cutoff_amt': 0.0,
'first_int_date': '2025-07-01',
'sb_int_1': '2024-01-01',
'sb_int_2': '2024-07-01',
'payment_month': 'Jan,Jul',
'issue_date': '2025-01-02',
'maturity_date': '2035-01-01',
'ann_date': '2024-12-02',
'last_day_to_apply': '2024-12-26',
'tender_date': '2024-12-27',
'start_of_redemption': '2024-12-02',
'end_of_redemption': '2024-12-26'}
Instead of all the details of the latest SSB, we may only be interested in the issue code:
# Get the latest SSB's issue code as string
client.get_latest_ssb_issue_code()
'GX25010E'
We can get details on the interest rate of any given Singapore Savings Bond (SSB) issue by passing the issue code into get_ssb_interest(). Note that yearX_return values are simple averages of the coupon rates up to that year.
# Get interest rate details of SSB with issue code GX25010E
client.get_ssb_interest("GX25010E")
{'issue_code': 'GX25010E',
'year1_coupon': 2.73,
'year1_return': 2.73,
'year2_coupon': 2.82,
'year2_return': 2.77,
'year3_coupon': 2.82,
'year3_return': 2.79,
'year4_coupon': 2.82,
'year4_return': 2.8,
'year5_coupon': 2.82,
'year5_return': 2.8,
'year6_coupon': 2.85,
'year6_return': 2.81,
'year7_coupon': 2.9,
'year7_return': 2.82,
'year8_coupon': 2.95,
'year8_return': 2.84,
'year9_coupon': 2.99,
'year9_return': 2.85,
'year10_coupon': 3.01,
'year10_return': 2.86}
Instead of getting this long dictionary, we may simply be interested in the coupon rates for the SSBs. This can be extracted with get_ssb_coupons():
# Get coupon rate details of SSB with issue code GX25010E
client.get_ssb_coupons("GX25010E")
[2.73, 2.82, 2.82, 2.82, 2.82, 2.85, 2.9, 2.95, 2.99, 3.01]
Based on a list of SSB coupon rates, we may want to calculate the SSB monthly tenure rates in percentage per annum, assuming compounding. This can be done with calculate_ssb_tenure_rates():
coupons = client.get_ssb_coupons("GX25010E")
client.calculate_ssb_tenure_rates(coupons)
| Tenure | Rate | |
|---|---|---|
| 0 | 1 | 2.76 |
| 1 | 2 | 2.76 |
| 2 | 3 | 2.76 |
| 3 | 4 | 2.75 |
| 4 | 5 | 2.75 |
| ... | ... | ... |
| 115 | 116 | 2.56 |
| 116 | 117 | 2.56 |
| 117 | 118 | 2.56 |
| 118 | 119 | 2.56 |
| 119 | 120 | 2.56 |
120 rows × 2 columns
In consolidate.py, we have a function that enables you to create a dataframe with information on the latest SSB. You can optionally include your current SSB holdings which is used to calculate the maximum possible deposit, since individuals are only allowed to hold $200,000 in SSBs per person:
# Create dataframe of latest SSB details
sfi.create_ssb_df(client, current_ssb_holdings=5000)
| Tenure | Rate | Deposit lower bound | Deposit upper bound | Required multiples | Product provider | Product | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 2.76 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 1 | 2 | 2.76 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 2 | 3 | 2.76 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 3 | 4 | 2.75 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 4 | 5 | 2.75 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 115 | 116 | 2.56 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 116 | 117 | 2.56 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 117 | 118 | 2.56 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 118 | 119 | 2.56 | 500 | 195000 | 500 | MAS | SSB GX25010E |
| 119 | 120 | 2.56 | 500 | 195000 | 500 | MAS | SSB GX25010E |
120 rows × 7 columns
Built-in T-bill methods and functions
We also have some built-in methods and functions to get and process MAS T-bill data:
# Get data on most recent 6-month T-bill which has completed auction
client.get_most_recent_6m_tbill()
{'issue_code': 'BS24124Z',
'isin_code': 'SGXZ29257813',
'issue_no': '1',
'reopened_issue': 'N',
'raw_tenor': 182.0,
'auction_tenor': 0.5,
'auction_date': '2024-12-05',
'issue_date': '2024-12-10',
'first_issue_date': '2024-12-10',
'bill_bond_ind': 'bill',
'maturity_date': '2025-06-10',
'ann_date': '2024-11-28',
'rate': 0.0,
'coupon_date_1': None,
'coupon_date_2': None,
'product_type': 'B',
'sgs_type': 'U',
'total_amt_allot': '7100.00000000',
'amt_allot_non_cmpt_appls': '2423.02100000',
'amt_allot_mas': '0.00000000',
'pct_cmpt_appls_cutoff': 4.22,
'pct_non_cmpt_appls_cutoff': 100.0,
'total_bids': 17428.248,
'bid_to_cover': 2.45,
'cutoff_yield': 3.0,
'cutoff_price': 98.504,
'median_yield': 2.9,
'median_price': 98.554,
'avg_yield': 2.73,
'avg_price': 98.639,
'auction_amt': 7100.0,
'intended_tender_amt': 0.0,
'accrued_int': 0.0,
'total_amount': None}
In consolidate.py, we have a function that enables you to create a dataframe with information on the latest T-bill which has completed auction. The rate is the cutoff yield from the auction, and can be used as a benchmark for the next 6-month T-bill’s cutoff yield:
tbill_details = client.get_most_recent_6m_tbill()
sfi.create_tbill_df(tbill_details)
| Tenure | Rate | Deposit lower bound | Deposit upper bound | Required multiples | Product provider | Product | |
|---|---|---|---|---|---|---|---|
| 0 | 6 | 3.0 | 1000 | 99999999 | 1000 | MAS | T-bill BS24124Z |
Built-in warnings
T-bill warning
It is possible that sudden changes in the macroeconomic environment may mean that the cutoff yield from the most recent 6-month T-bill is no longer a good gauge of the cutoff yield for the next 6-month T-bill. Users should be made aware of this when using this data to make investment decisions.
As such, we built some methods to check this. Firstly, you can find the yield of the most recent bid on the most recent 6-month T-bill with get_6m_tbill_bid_yield(). Note that since the secondary market for MAS T-bills has low volumes, we still prefer to use the cutoff yield of the previous T-bill as the benchmark rate rather than the bid yield.
# Get bid yield on most recent 6-month T-bill
client.get_6m_tbill_bid_yield()
3.0
We have a method sudden_6m_tbill_yield_change_warning() which raises a warning if the yield difference between the most recent bid on the most recent 6-month T-bill and its cutoff yield exceeds a given threshold (defaults to 10 basis points). You may optionally change the threshold.
Since the Monetary Authority of Singapore typically issues two 6-month T-bills per month, the remaining tenor for the most recent 6-month T-bill will never fall below 5 months. Hence, it should not be too different from the cut-off yield of this T-bill, unless there have been sudden changes in the macroeconomic environment.
client.sudden_6m_tbill_yield_change_warning()
SSB warning
Another potential issue is that the last day to apply for the latest SSB has already passed. Then, users would be unable to invest into the SSB in the dataset, and the next SSB likely has different rates. However, the data is nevertheless useful as a benchmark for the next SSB’s rates. Nevertheless, users should be made aware of this.
Get the last day to apply for the latest SSB with this method:
client.get_latest_ssb_last_day_to_apply()
'2024-12-26'
Directly issue warnings with the past_last_day_to_apply_ssb_warning() method. This warning is unlikely to be triggered, since details on the next SSB is often provided promptly within day(s) of the prior SSB’s last day of application.
client.past_last_day_to_apply_ssb_warning()