{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Example usage of `sgfixedincome_pkg` (MAS API Client)\n", "\n", "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`.\n", "\n", "## Disclaimer\n", "\n", "The API we query is not found in the official [MAS API catalogue](https://eservices.mas.gov.sg/apimg-portal/api-catalog), 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](https://www.mas.gov.sg/bonds-and-bills/treasury-bills-statistics) and [SSB webpage](https://www.mas.gov.sg/bonds-and-bills/auctions-and-issuance-calendar/issuance-singapore-savings-bond?issue_code=GX24120F&issue_date=2024-12-02) pulls their data from (picture below). \n", "\n", "Whilst no API key or token is required to pull data from this API, do use it responsibly (avoid flooding the API with requests).\n", "\n", "![Google Dev Tools Screen Capture](gdevtools_image.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "Let's first import the package and initialize the API client:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import sgfixedincome_pkg as sfi\n", "client = sfi.MAS_bondsandbills_APIClient()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## General fetch data method\n", "\n", "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. \n", "\n", "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%:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'success': True,\n", " 'result': {'total': 156,\n", " 'records': [{'issue_code': 'MD24112N',\n", " 'isin_code': 'SGXZ44349256',\n", " 'issue_no': '1',\n", " 'reopened_issue': 'N',\n", " 'raw_tenor': 25.0,\n", " 'auction_tenor': 4.0,\n", " 'auction_date': '2024-03-26',\n", " 'issue_date': '2024-04-01',\n", " 'first_issue_date': '2024-04-01',\n", " 'bill_bond_ind': 'bill',\n", " 'maturity_date': '2024-04-26',\n", " 'ann_date': '2024-03-25',\n", " 'rate': 0.0,\n", " 'coupon_date_1': None,\n", " 'coupon_date_2': None,\n", " 'product_type': 'M',\n", " 'sgs_type': 'U',\n", " 'total_amt_allot': '14100.00000000',\n", " 'amt_allot_non_cmpt_appls': '0.00000000',\n", " 'amt_allot_mas': '0.00000000',\n", " 'pct_cmpt_appls_cutoff': 98.05,\n", " 'pct_non_cmpt_appls_cutoff': 100.0,\n", " 'total_bids': 26201.713,\n", " 'bid_to_cover': 1.86,\n", " 'cutoff_yield': 4.12,\n", " 'cutoff_price': 99.718,\n", " 'median_yield': 3.87,\n", " 'median_price': 99.735,\n", " 'avg_yield': 3.61,\n", " 'avg_price': 99.753,\n", " 'auction_amt': 14100.0,\n", " 'intended_tender_amt': 0.0,\n", " 'accrued_int': 0.0,\n", " 'total_amount': 14100.0}]}}" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "response = client.fetch_data(\n", " endpoint=\"listbondsandbills\", \n", " params={\n", " \"rows\": 1, # Get the first row only\n", " \"filters\":\"bill_bond_ind:bill AND cutoff_yield:[4.1 TO *]\", \n", " \"sort\": \"auction_date desc\"\n", " }\n", ")\n", "response" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Built-in SSB methods and functions\n", "\n", "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.\n", "\n", "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):" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'issue_code': 'GX25010E',\n", " 'isin_code': 'SGXZ30907869',\n", " 'auction_tenor': 10.0,\n", " 'issue_size': 600.0,\n", " 'amt_applied': 0.0,\n", " 'total_applied_within_limits': 0.0,\n", " 'amt_alloted': 0.0,\n", " 'rndm_alloted_amt': 0.0,\n", " 'rndm_alloted_rate': 0.0,\n", " 'cutoff_amt': 0.0,\n", " 'first_int_date': '2025-07-01',\n", " 'sb_int_1': '2024-01-01',\n", " 'sb_int_2': '2024-07-01',\n", " 'payment_month': 'Jan,Jul',\n", " 'issue_date': '2025-01-02',\n", " 'maturity_date': '2035-01-01',\n", " 'ann_date': '2024-12-02',\n", " 'last_day_to_apply': '2024-12-26',\n", " 'tender_date': '2024-12-27',\n", " 'start_of_redemption': '2024-12-02',\n", " 'end_of_redemption': '2024-12-26'}" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get dictionary with details on the latest SSB\n", "client.get_latest_ssb_details()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead of all the details of the latest SSB, we may only be interested in the issue code:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'GX25010E'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get the latest SSB's issue code as string\n", "client.get_latest_ssb_issue_code()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'issue_code': 'GX25010E',\n", " 'year1_coupon': 2.73,\n", " 'year1_return': 2.73,\n", " 'year2_coupon': 2.82,\n", " 'year2_return': 2.77,\n", " 'year3_coupon': 2.82,\n", " 'year3_return': 2.79,\n", " 'year4_coupon': 2.82,\n", " 'year4_return': 2.8,\n", " 'year5_coupon': 2.82,\n", " 'year5_return': 2.8,\n", " 'year6_coupon': 2.85,\n", " 'year6_return': 2.81,\n", " 'year7_coupon': 2.9,\n", " 'year7_return': 2.82,\n", " 'year8_coupon': 2.95,\n", " 'year8_return': 2.84,\n", " 'year9_coupon': 2.99,\n", " 'year9_return': 2.85,\n", " 'year10_coupon': 3.01,\n", " 'year10_return': 2.86}" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get interest rate details of SSB with issue code GX25010E\n", "client.get_ssb_interest(\"GX25010E\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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()`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[2.73, 2.82, 2.82, 2.82, 2.82, 2.85, 2.9, 2.95, 2.99, 3.01]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get coupon rate details of SSB with issue code GX25010E\n", "client.get_ssb_coupons(\"GX25010E\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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()`:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TenureRate
012.76
122.76
232.76
342.75
452.75
.........
1151162.56
1161172.56
1171182.56
1181192.56
1191202.56
\n", "

120 rows × 2 columns

\n", "
" ], "text/plain": [ " Tenure Rate\n", "0 1 2.76\n", "1 2 2.76\n", "2 3 2.76\n", "3 4 2.75\n", "4 5 2.75\n", ".. ... ...\n", "115 116 2.56\n", "116 117 2.56\n", "117 118 2.56\n", "118 119 2.56\n", "119 120 2.56\n", "\n", "[120 rows x 2 columns]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coupons = client.get_ssb_coupons(\"GX25010E\")\n", "client.calculate_ssb_tenure_rates(coupons)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TenureRateDeposit lower boundDeposit upper boundRequired multiplesProduct providerProduct
012.76500195000500MASSSB GX25010E
122.76500195000500MASSSB GX25010E
232.76500195000500MASSSB GX25010E
342.75500195000500MASSSB GX25010E
452.75500195000500MASSSB GX25010E
........................
1151162.56500195000500MASSSB GX25010E
1161172.56500195000500MASSSB GX25010E
1171182.56500195000500MASSSB GX25010E
1181192.56500195000500MASSSB GX25010E
1191202.56500195000500MASSSB GX25010E
\n", "

120 rows × 7 columns

\n", "
" ], "text/plain": [ " Tenure Rate Deposit lower bound Deposit upper bound \\\n", "0 1 2.76 500 195000 \n", "1 2 2.76 500 195000 \n", "2 3 2.76 500 195000 \n", "3 4 2.75 500 195000 \n", "4 5 2.75 500 195000 \n", ".. ... ... ... ... \n", "115 116 2.56 500 195000 \n", "116 117 2.56 500 195000 \n", "117 118 2.56 500 195000 \n", "118 119 2.56 500 195000 \n", "119 120 2.56 500 195000 \n", "\n", " Required multiples Product provider Product \n", "0 500 MAS SSB GX25010E \n", "1 500 MAS SSB GX25010E \n", "2 500 MAS SSB GX25010E \n", "3 500 MAS SSB GX25010E \n", "4 500 MAS SSB GX25010E \n", ".. ... ... ... \n", "115 500 MAS SSB GX25010E \n", "116 500 MAS SSB GX25010E \n", "117 500 MAS SSB GX25010E \n", "118 500 MAS SSB GX25010E \n", "119 500 MAS SSB GX25010E \n", "\n", "[120 rows x 7 columns]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create dataframe of latest SSB details\n", "sfi.create_ssb_df(client, current_ssb_holdings=5000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Built-in T-bill methods and functions\n", "\n", "We also have some built-in methods and functions to get and process MAS T-bill data:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'issue_code': 'BS24124Z',\n", " 'isin_code': 'SGXZ29257813',\n", " 'issue_no': '1',\n", " 'reopened_issue': 'N',\n", " 'raw_tenor': 182.0,\n", " 'auction_tenor': 0.5,\n", " 'auction_date': '2024-12-05',\n", " 'issue_date': '2024-12-10',\n", " 'first_issue_date': '2024-12-10',\n", " 'bill_bond_ind': 'bill',\n", " 'maturity_date': '2025-06-10',\n", " 'ann_date': '2024-11-28',\n", " 'rate': 0.0,\n", " 'coupon_date_1': None,\n", " 'coupon_date_2': None,\n", " 'product_type': 'B',\n", " 'sgs_type': 'U',\n", " 'total_amt_allot': '7100.00000000',\n", " 'amt_allot_non_cmpt_appls': '2423.02100000',\n", " 'amt_allot_mas': '0.00000000',\n", " 'pct_cmpt_appls_cutoff': 4.22,\n", " 'pct_non_cmpt_appls_cutoff': 100.0,\n", " 'total_bids': 17428.248,\n", " 'bid_to_cover': 2.45,\n", " 'cutoff_yield': 3.0,\n", " 'cutoff_price': 98.504,\n", " 'median_yield': 2.9,\n", " 'median_price': 98.554,\n", " 'avg_yield': 2.73,\n", " 'avg_price': 98.639,\n", " 'auction_amt': 7100.0,\n", " 'intended_tender_amt': 0.0,\n", " 'accrued_int': 0.0,\n", " 'total_amount': None}" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get data on most recent 6-month T-bill which has completed auction\n", "client.get_most_recent_6m_tbill()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TenureRateDeposit lower boundDeposit upper boundRequired multiplesProduct providerProduct
063.01000999999991000MAST-bill BS24124Z
\n", "
" ], "text/plain": [ " Tenure Rate Deposit lower bound Deposit upper bound Required multiples \\\n", "0 6 3.0 1000 99999999 1000 \n", "\n", " Product provider Product \n", "0 MAS T-bill BS24124Z " ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tbill_details = client.get_most_recent_6m_tbill()\n", "sfi.create_tbill_df(tbill_details)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Built-in warnings\n", "\n", "### T-bill warning\n", "\n", "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.\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.0" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get bid yield on most recent 6-month T-bill\n", "client.get_6m_tbill_bid_yield()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "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.\n" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "client.sudden_6m_tbill_yield_change_warning()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SSB warning\n", "\n", "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.\n", "\n", "Get the last day to apply for the latest SSB with this method:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'2024-12-26'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "client.get_latest_ssb_last_day_to_apply()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "client.past_last_day_to_apply_ssb_warning()" ] } ], "metadata": { "kernelspec": { "display_name": "sgfixedincome_pkg", "language": "python", "name": "sgfixedincome_pkg" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.5" } }, "nbformat": 4, "nbformat_minor": 4 }