S$1,000 has bought a record amount of yen, won, ringgit and baht since 2024
Yen and won hit their records in 2026, ringgit and baht in 2024. They are the highest months in MAS's monthly series, which starts in January 1988 for yen and ringgit and January 2003 for won and baht. Pick a country to see S$1,000 month by month since January 2015.
Pick a country
Everything below changes with it.
How many yen did S$1,000 buy each month since January 2015?
Each tile is one month, from January 2015 at the top left. A darker tile means S$1,000 bought more. MAS monthly average rates.
Record month. Lowest month since 2015. The top left tile is January 2015. Tap a tile to read it.
In the 141 months since January 2015, S$1,000 bought the most yen in July 2026 (¥125,818) and the fewest in October 2016 (¥74,974).
Which five months bought the most yen?
Out of the 141 months since January 2015. September 2026, the latest month, is marked.
6 of the 141 months bought more yen than September 2026 (¥122,941), so the latest month ranks number 7.
How much more did S$1,000 buy in September 2026 than in January 2015?
The four currencies side by side, S$1,000 each time. Yours is marked.
S$1,000 bought ¥88,488 in January 2015 and ¥122,941 in September 2026: 39% more yen. That is the biggest change of the four.
More finds
The short version
In MAS's monthly exchange rates, S$1,000 bought a record ¥125,818 in July 2026, a record ₩1,187,648 in June 2026, a record ฿27,158 in June 2024 and a record RM3,547 in February 2024. The yen and ringgit records are the highest of 465 months since January 1988; the won and baht records are the highest of 285 months since January 2003. In September 2026, S$1,000 bought ¥122,941, ₩1,067,236, RM3,198 and ฿26,108.
In January 2015, S$1,000 bought ¥88,488, ₩814,332, RM2,679 and ฿24,492. By September 2026 that is 39% more yen, 31% more won, 19% more ringgit and 7% more baht. At the record months the gains are 42%, 46%, 32% and 11%.
These are exchange rates only. They say nothing about prices in Japan, South Korea, Malaysia or Thailand, or about flights and hotels, and money changers, banks and cards use their own rates, not MAS's monthly averages. We give no forecast.
By Ben (opens in a new tab). Updated . Data: Monetary Authority of Singapore (through SingStat Table Builder).
Questions people ask
How much yen does S$1,000 buy?
In September 2026 it bought ¥122,941, using MAS's monthly average rate. The record month in MAS's series, which starts in January 1988, is July 2026, at ¥125,818.
How much won does S$1,000 buy?
In September 2026 it bought ₩1,067,236, using MAS's monthly average rate. The record month in MAS's series, which starts in January 2003, is June 2026, at ₩1,187,648.
How much ringgit does S$1,000 buy?
In September 2026 it bought RM3,198, using MAS's monthly average rate. The record month in MAS's series, which starts in January 1988, is February 2024, at RM3,547.
How much baht does S$1,000 buy?
In September 2026 it bought ฿26,108, using MAS's monthly average rate. The record month in MAS's series, which starts in January 2003, is June 2024, at ฿27,158.
When did S$1,000 buy the fewest since 2015?
Yen: ¥74,974 in October 2016. Won: ₩804,505 in May 2018. Ringgit: RM2,656 in February 2015. Baht: ฿22,150 in September 2019. Each is the lowest of the 141 months from January 2015 to September 2026.
How does September 2026 compare with the other months?
Counting the 141 months from January 2015, 6 bought more yen than September 2026, 18 more won, 39 more ringgit and 15 more baht. So September 2026 ranks number 7 for yen, 19 for won, 40 for ringgit and 16 for baht, where 1 is the most.
Are these the highest months on record?
In MAS's monthly series, yes. It covers 465 months for yen and ringgit, from January 1988, and 285 months for won and baht, from January 2003. We say nothing about earlier years because this table has no earlier months.
Does S$1,000 buy more than it did in January 2015?
In September 2026 it does for all four: 39% more yen, 31% more won, 19% more ringgit and 7% more baht. Counting the 141 months from January 2015, S$1,000 bought more than in January 2015 in 58 months for yen, 126 for won, 138 for ringgit and 84 for baht.
Will the Singapore dollar buy more next month?
We do not know and we give no forecast. This page shows past monthly averages only.
Does a higher number mean a cheaper holiday?
Not by itself. It means S$1,000 converts into more of that currency at MAS's average rate. Prices in each country, flights and hotels also move, and this page covers none of them.
Is this the rate a money changer gives?
No. MAS publishes the average rate for each month. Money changers, banks and cards use their own rates, so what you get for S$1,000 on the day differs.
Why does the grid start in January 2015?
It shows 141 months, close to twelve years, on one screen. The records and the gains use the whole MAS series, not only the grid.
How is the amount for S$1,000 worked out?
We divide 1,000 by MAS's rate for the month, scaled to one unit of the currency. MAS gives yen, won and baht per 100 units and ringgit per 1. We round half up to whole units. S$500 buys half as much, S$2,000 twice as much.
Where do the numbers come from?
The Monetary Authority of Singapore, through SingStat Table Builder, table M700051, Exchange Rates (Average For Period), Monthly. SingStat says it was last updated on 14 Oct 2026 and we downloaded it on 16 Oct 2026. SingStat says recent figures are provisional and may be revised.
The numbers behind it
| Currency | Record month | At the record | Lowest month since 2015 | At the lowest | January 2015 | Latest, September 2026 |
|---|---|---|---|---|---|---|
| Yen (Japan) | July 2026 | ¥125,818 | October 2016 | ¥74,974 | ¥88,488 | ¥122,941 |
| Won (South Korea) | June 2026 | ₩1,187,648 | May 2018 | ₩804,505 | ₩814,332 | ₩1,067,236 |
| Ringgit (Malaysia) | February 2024 | RM3,547 | February 2015 | RM2,656 | RM2,679 | RM3,198 |
| Baht (Thailand) | June 2024 | ฿27,158 | September 2019 | ฿22,150 | ฿24,492 | ฿26,108 |
| Currency | Record against January 2015 | September 2026 against January 2015 | Record against the lowest since 2015 | September 2026 below the record | Months above January 2015, of 141 |
|---|---|---|---|---|---|
| Yen (Japan) | +42% | +39% | +68% | 2% | 58 |
| Won (South Korea) | +46% | +31% | +48% | 10% | 126 |
| Ringgit (Malaysia) | +32% | +19% | +34% | 10% | 138 |
| Baht (Thailand) | +11% | +7% | +23% | 4% | 84 |
| Currency | First month in series | Months in series | Rank of September 2026, of 141 (1 is the most) | Biggest rise in a year | Biggest fall in a year |
|---|---|---|---|---|---|
| Yen (Japan) | January 1988 | 465 | 7 | +24%, Sep 2021 to Sep 2022 | 15% less, Jun 2015 to Jun 2016 |
| Won (South Korea) | January 2003 | 285 | 19 | +14%, Oct 2021 to Oct 2022 | 7% less, Feb 2016 to Feb 2017 |
| Ringgit (Malaysia) | January 1988 | 465 | 40 | +13%, Jan 2015 to Jan 2016 | 7% less, Feb 2024 to Feb 2025 |
| Baht (Thailand) | January 2003 | 285 | 16 | +9%, Dec 2020 to Dec 2021 | 8% less, Aug 2018 to Aug 2019 |
Amounts are for S$1,000, in whole units of each currency, using MAS's monthly average rate. Per cent figures are rounded half up to whole numbers.
Every month since January 2015, as a table
| Month | Yen | Won | Ringgit | Baht |
|---|---|---|---|---|
| Sep 2026 | ¥122,941 | ₩1,067,236 | RM3,198 | ฿26,108 |
| Aug 2026 | ¥124,425 | ₩1,098,901 | RM3,184 | ฿25,877 |
| Jul 2026 | ¥125,818 | ₩1,154,734 | RM3,162 | ฿25,938 |
| Jun 2026 | ¥124,782 | ₩1,187,648 | RM3,161 | ฿25,538 |
| May 2026 | ¥123,993 | ₩1,169,591 | RM3,099 | ฿25,498 |
| Apr 2026 | ¥124,750 | ₩1,162,791 | RM3,113 | ฿25,308 |
| Mar 2026 | ¥124,008 | ₩1,164,144 | RM3,088 | ฿25,212 |
| Feb 2026 | ¥122,399 | ₩1,142,857 | RM3,090 | ฿24,664 |
| Jan 2026 | ¥122,474 | ₩1,137,656 | RM3,146 | ฿24,427 |
| Dec 2025 | ¥120,627 | ₩1,135,074 | RM3,169 | ฿24,461 |
| Nov 2025 | ¥119,005 | ₩1,121,076 | RM3,189 | ฿24,864 |
| Oct 2025 | ¥116,822 | ₩1,098,901 | RM3,255 | ฿25,138 |
| Sep 2025 | ¥115,101 | ₩1,084,599 | RM3,280 | ฿24,907 |
| Aug 2025 | ¥114,877 | ₩1,081,081 | RM3,287 | ฿25,244 |
| Jul 2025 | ¥114,639 | ₩1,075,269 | RM3,307 | ฿25,329 |
| Jun 2025 | ¥112,511 | ₩1,063,830 | RM3,304 | ฿25,416 |
| May 2025 | ¥111,732 | ₩1,075,269 | RM3,295 | ฿25,460 |
| Apr 2025 | ¥108,956 | ₩1,089,325 | RM3,334 | ฿25,495 |
| Mar 2025 | ¥111,607 | ₩1,090,513 | RM3,320 | ฿25,311 |
| Feb 2025 | ¥112,689 | ₩1,072,961 | RM3,299 | ฿25,092 |
| Jan 2025 | ¥114,982 | ₩1,067,236 | RM3,284 | ฿25,187 |
| Dec 2024 | ¥113,714 | ₩1,066,098 | RM3,304 | ฿25,302 |
| Nov 2024 | ¥114,969 | ₩1,043,841 | RM3,319 | ฿25,770 |
| Oct 2024 | ¥114,168 | ₩1,038,422 | RM3,281 | ฿25,482 |
| Sep 2024 | ¥110,448 | ₩1,027,749 | RM3,288 | ฿25,725 |
| Aug 2024 | ¥111,198 | ₩1,026,694 | RM3,355 | ฿26,411 |
| Jul 2024 | ¥117,316 | ₩1,026,694 | RM3,476 | ฿26,931 |
| Jun 2024 | ¥116,809 | ₩1,020,408 | RM3,484 | ฿27,158 |
| May 2024 | ¥115,354 | ₩1,010,101 | RM3,492 | ฿27,116 |
| Apr 2024 | ¥113,353 | ₩1,009,082 | RM3,512 | ฿27,103 |
| Mar 2024 | ¥111,682 | ₩993,049 | RM3,519 | ฿26,813 |
| Feb 2024 | ¥111,136 | ₩991,080 | RM3,547 | ฿26,681 |
| Jan 2024 | ¥109,302 | ₩993,049 | RM3,508 | ฿26,345 |
| Dec 2023 | ¥108,143 | ₩978,474 | RM3,497 | ฿26,281 |
| Nov 2023 | ¥111,025 | ₩968,992 | RM3,477 | ฿26,281 |
| Oct 2023 | ¥109,218 | ₩987,167 | RM3,467 | ฿26,678 |
| Sep 2023 | ¥108,342 | ₩977,517 | RM3,434 | ฿26,310 |
| Aug 2023 | ¥107,250 | ₩978,474 | RM3,414 | ฿25,938 |
| Jul 2023 | ¥105,675 | ₩961,538 | RM3,441 | ฿25,948 |
| Jun 2023 | ¥104,866 | ₩962,464 | RM3,441 | ฿25,925 |
| May 2023 | ¥102,323 | ₩990,099 | RM3,374 | ฿25,540 |
| Apr 2023 | ¥100,190 | ₩993,049 | RM3,322 | ฿25,730 |
| Mar 2023 | ¥99,701 | ₩972,763 | RM3,331 | ฿25,727 |
| Feb 2023 | ¥99,800 | ₩957,854 | RM3,280 | ฿25,547 |
| Jan 2023 | ¥98,348 | ₩938,967 | RM3,265 | ฿25,091 |
| Dec 2022 | ¥99,860 | ₩957,854 | RM3,265 | ฿25,733 |
| Nov 2022 | ¥102,659 | ₩978,474 | RM3,324 | ฿26,243 |
| Oct 2022 | ¥103,146 | ₩1,001,001 | RM3,294 | ฿26,630 |
| Sep 2022 | ¥101,215 | ₩987,167 | RM3,214 | ฿26,210 |
| Aug 2022 | ¥97,580 | ₩954,198 | RM3,227 | ฿25,912 |
| Jul 2022 | ¥97,962 | ₩937,207 | RM3,184 | ฿26,070 |
| Jun 2022 | ¥96,740 | ₩923,361 | RM3,181 | ฿25,251 |
| May 2022 | ¥93,214 | ₩917,431 | RM3,173 | ฿24,911 |
| Apr 2022 | ¥92,404 | ₩904,159 | RM3,125 | ฿24,734 |
| Mar 2022 | ¥87,116 | ₩898,473 | RM3,090 | ฿24,459 |
| Feb 2022 | ¥85,558 | ₩889,680 | RM3,109 | ฿24,212 |
| Jan 2022 | ¥85,063 | ₩885,740 | RM3,102 | ฿24,617 |
| Dec 2021 | ¥83,549 | ₩868,056 | RM3,088 | ฿24,623 |
| Nov 2021 | ¥84,069 | ₩872,600 | RM3,080 | ฿24,377 |
| Oct 2021 | ¥83,710 | ₩874,891 | RM3,082 | ฿24,771 |
| Sep 2021 | ¥81,739 | ₩870,322 | RM3,091 | ฿24,526 |
| Aug 2021 | ¥81,044 | ₩857,633 | RM3,114 | ฿24,428 |
| Jul 2021 | ¥81,446 | ₩845,309 | RM3,100 | ฿24,093 |
| Jun 2021 | ¥82,583 | ₩841,751 | RM3,102 | ฿23,569 |
| May 2021 | ¥82,068 | ₩844,595 | RM3,103 | ฿23,518 |
| Apr 2021 | ¥81,693 | ₩837,521 | RM3,089 | ฿23,504 |
| Mar 2021 | ¥80,919 | ₩842,460 | RM3,061 | ฿22,936 |
| Feb 2021 | ¥79,327 | ₩838,223 | RM3,048 | ฿22,602 |
| Jan 2021 | ¥78,235 | ₩828,500 | RM3,045 | ฿22,635 |
| Dec 2020 | ¥77,906 | ₩821,018 | RM3,044 | ฿22,579 |
| Nov 2020 | ¥77,429 | ₩827,815 | RM3,053 | ฿22,615 |
| Oct 2020 | ¥77,405 | ₩841,751 | RM3,054 | ฿23,004 |
| Sep 2020 | ¥77,328 | ₩862,069 | RM3,039 | ฿22,963 |
| Aug 2020 | ¥77,429 | ₩866,551 | RM3,058 | ฿22,790 |
| Jul 2020 | ¥76,976 | ₩863,558 | RM3,072 | ฿22,631 |
| Jun 2020 | ¥77,166 | ₩866,551 | RM3,067 | ฿22,361 |
| May 2020 | ¥75,603 | ₩866,551 | RM3,063 | ฿22,611 |
| Apr 2020 | ¥75,666 | ₩859,845 | RM3,059 | ฿22,916 |
| Mar 2020 | ¥75,798 | ₩861,326 | RM3,033 | ฿22,660 |
| Feb 2020 | ¥79,183 | ₩859,845 | RM2,996 | ฿22,548 |
| Jan 2020 | ¥80,926 | ₩862,813 | RM3,020 | ฿22,520 |
| Dec 2019 | ¥80,412 | ₩865,052 | RM3,056 | ฿22,265 |
| Nov 2019 | ¥79,923 | ₩858,369 | RM3,054 | ฿22,213 |
| Oct 2019 | ¥78,839 | ₩863,558 | RM3,054 | ฿22,153 |
| Sep 2019 | ¥77,845 | ₩866,551 | RM3,032 | ฿22,150 |
| Aug 2019 | ¥76,775 | ₩874,126 | RM3,024 | ฿22,221 |
| Jul 2019 | ¥79,517 | ₩864,304 | RM3,031 | ฿22,637 |
| Jun 2019 | ¥79,271 | ₩861,326 | RM3,054 | ฿22,845 |
| May 2019 | ¥80,238 | ₩863,558 | RM3,043 | ฿23,201 |
| Apr 2019 | ¥82,305 | ₩842,460 | RM3,033 | ฿23,498 |
| Mar 2019 | ¥82,088 | ₩835,422 | RM3,013 | ฿23,433 |
| Feb 2019 | ¥81,546 | ₩829,187 | RM3,011 | ฿23,127 |
| Jan 2019 | ¥80,276 | ₩827,130 | RM3,035 | ฿23,463 |
| Dec 2018 | ¥81,967 | ₩819,001 | RM3,045 | ฿23,867 |
| Nov 2018 | ¥82,393 | ₩819,672 | RM3,044 | ฿23,969 |
| Oct 2018 | ¥81,820 | ₩820,345 | RM3,016 | ฿23,762 |
| Sep 2018 | ¥81,606 | ₩816,327 | RM3,018 | ฿23,785 |
| Aug 2018 | ¥81,162 | ₩819,672 | RM2,990 | ฿24,142 |
| Jul 2018 | ¥81,719 | ₩823,723 | RM2,970 | ฿24,415 |
| Jun 2018 | ¥81,633 | ₩811,030 | RM2,968 | ฿24,096 |
| May 2018 | ¥81,954 | ₩804,505 | RM2,960 | ฿23,879 |
| Apr 2018 | ¥81,780 | ₩812,348 | RM2,955 | ฿23,803 |
| Mar 2018 | ¥80,632 | ₩814,996 | RM2,970 | ฿23,784 |
| Feb 2018 | ¥81,820 | ₩818,331 | RM2,964 | ฿23,856 |
| Jan 2018 | ¥83,977 | ₩806,452 | RM2,991 | ฿24,128 |
| Dec 2017 | ¥83,850 | ₩805,802 | RM3,028 | ฿24,249 |
| Nov 2017 | ¥83,222 | ₩813,670 | RM3,077 | ฿24,282 |
| Oct 2017 | ¥83,029 | ₩834,028 | RM3,108 | ฿24,436 |
| Sep 2017 | ¥82,001 | ₩839,631 | RM3,120 | ฿24,561 |
| Aug 2017 | ¥80,723 | ₩831,255 | RM3,149 | ฿24,446 |
| Jul 2017 | ¥81,960 | ₩825,764 | RM3,128 | ฿24,601 |
| Jun 2017 | ¥80,122 | ₩816,993 | RM3,090 | ฿24,574 |
| May 2017 | ¥80,438 | ₩807,103 | RM3,093 | ฿24,706 |
| Apr 2017 | ¥78,734 | ₩811,030 | RM3,153 | ฿24,646 |
| Mar 2017 | ¥80,379 | ₩806,452 | RM3,159 | ฿24,832 |
| Feb 2017 | ¥79,898 | ₩807,754 | RM3,142 | ฿24,748 |
| Jan 2017 | ¥80,412 | ₩826,446 | RM3,121 | ฿24,818 |
| Dec 2016 | ¥80,717 | ₩824,402 | RM3,107 | ฿24,925 |
| Nov 2016 | ¥76,546 | ₩825,083 | RM3,073 | ฿25,051 |
| Oct 2016 | ¥74,974 | ₩814,332 | RM3,018 | ฿25,338 |
| Sep 2016 | ¥74,979 | ₩815,661 | RM3,024 | ฿25,547 |
| Aug 2016 | ¥75,143 | ₩824,402 | RM2,990 | ฿25,761 |
| Jul 2016 | ¥77,101 | ₩844,595 | RM2,975 | ฿25,948 |
| Jun 2016 | ¥77,742 | ₩862,813 | RM3,014 | ฿26,053 |
| May 2016 | ¥79,491 | ₩857,633 | RM2,953 | ฿25,846 |
| Apr 2016 | ¥81,129 | ₩849,618 | RM2,890 | ฿25,989 |
| Mar 2016 | ¥82,176 | ₩862,813 | RM2,966 | ฿25,627 |
| Feb 2016 | ¥81,599 | ₩866,551 | RM2,975 | ฿25,336 |
| Jan 2016 | ¥82,433 | ₩839,631 | RM3,030 | ฿25,228 |
| Dec 2015 | ¥86,490 | ₩833,333 | RM3,040 | ฿25,565 |
| Nov 2015 | ¥86,670 | ₩815,661 | RM3,051 | ฿25,326 |
| Oct 2015 | ¥85,631 | ₩816,993 | RM3,041 | ฿25,469 |
| Sep 2015 | ¥84,854 | ₩838,223 | RM3,046 | ฿25,452 |
| Aug 2015 | ¥87,889 | ₩843,882 | RM2,913 | ฿25,334 |
| Jul 2015 | ¥90,555 | ₩841,751 | RM2,793 | ฿25,223 |
| Jun 2015 | ¥91,929 | ₩826,446 | RM2,781 | ฿25,073 |
| May 2015 | ¥90,457 | ₩819,001 | RM2,699 | ฿25,122 |
| Apr 2015 | ¥88,543 | ₩805,153 | RM2,692 | ฿24,084 |
| Mar 2015 | ¥87,443 | ₩808,407 | RM2,672 | ฿23,691 |
| Feb 2015 | ¥87,566 | ₩811,688 | RM2,656 | ฿24,051 |
| Jan 2015 | ¥88,488 | ₩814,332 | RM2,679 | ฿24,492 |
The boring bit
Where the information comes from
Every number comes from the Monetary Authority of Singapore (MAS), taken from SingStat Table Builder: table M700051, Exchange Rates (Average For Period), Monthly (opens in a new tab). SingStat shows the table as last updated on 14 Oct 2026, and we downloaded it as an .xlsx file on 16 Oct 2026. The table gives the Singapore dollars needed for 100 Japanese yen, 100 Korean won, 1 Malaysian ringgit and 100 Thai baht. SingStat notes that data for the most recent periods are provisional and may be revised later.
What is not claimed
- This is not a forecast and not financial advice. We do not say whether the Singapore dollar will buy more or less next month.
- These are exchange rates only. They do not show what a holiday, a meal or a flight costs in any of the four countries.
- MAS publishes the average for each month. It is not the rate a money changer, bank or card gives on a given day, and daily moves inside a month are hidden.
- Amounts are not adjusted for inflation in Singapore or in the other countries.
- Records are records within MAS's monthly series (from January 1988 for yen and ringgit, from January 2003 for won and baht). We say nothing about earlier years.
- The months of 2020 and 2021 came during COVID, when travel was heavily restricted, so those months say little about what a trip would have felt like.
- We do not rank the four countries.
Coverage
All 1,500 monthly rates in the table for the four currencies were used: 465 months each for yen and ringgit (January 1988 to September 2026) and 285 months each for won and baht (January 2003 to September 2026). The tiles use 141 months per currency, from January 2015 to September 2026, which is 564 tiles in all. No month is missing between the first and the last for any currency. The table also has other currencies, which we did not use.
Report an error. Corrections are logged publicly with a date.
How we did it
- Downloaded the table. We saved the monthly exchange rate table (M700051) from SingStat Table Builder as an .xlsx file on 16 Oct 2026.
- Checked the file. The script stops if the header row, the MAS source line, the newest first month order, a duplicate month, a gap in months or a rate at or below zero is found.
- Kept four series. Yen, won, ringgit and baht, as published: 1,500 rows, written to a .csv file with the rate exactly as MAS gave it.
- Worked out S$1,000. 1,000 divided by the rate, scaled to one unit (the file gives yen, won and baht per 100 units), rounded half up to whole units with exact decimals.
- Found the records and gaps. The record is the highest month in the whole series (the script stops on a tie); the lowest is the lowest of the 141 months from January 2015; the climb is the biggest rise against the same month a year earlier; per cent figures are rounded half up.
- Drew the page. The same JavaScript writes the yen view into the page and redraws it when you pick another country, so the first view and the picker agree.
Show the code
"""Prep for the SGD to yen, won, ringgit and baht insight (demo).
Reads MAS's monthly exchange rates (SingStat Table Builder, table M700051, saved as an .xlsx on 16 Oct 2026), checks the file,
writes data/monthly_rates.csv (one row per month and currency, the rate exactly as published) and prep/data.json (every figure the page uses).
Usage: python3 -I prep/prep_fx.py (run from the build folder; needs openpyxl for the .xlsx; the page build itself needs only the standard library)
"""
import csv, json, os, sys
from decimal import Decimal, ROUND_HALF_UP
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
SRC = os.path.join(ROOT, "data", "M700051.xlsx")
CSV = os.path.join(ROOT, "data", "monthly_rates.csv")
OUT = os.path.join(HERE, "data.json")
MON = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
# code: (series label in the file, units of foreign currency per published rate, name, country, symbol)
CUR = {
"JPY": ("Japanese Yen", 100, "Yen", "Japan", "¥"),
"KRW": ("Korean Won", 100, "Won", "South Korea", "₩"),
"MYR": ("Malaysian Ringgit", 1, "Ringgit", "Malaysia", "RM"),
"THB": ("Thai Baht", 100, "Baht", "Thailand", "฿"),
}
GRID_START = (2015, 1)
def half_up(x, places=0):
return Decimal(x).quantize(Decimal(1).scaleb(-places), rounding=ROUND_HALF_UP)
def ym(label):
s = str(label).strip()
y, m = int(s[:4]), MON.index(s[5:8]) + 1
assert s[4] == " " and 1988 <= y <= 2100, label
return (y, m)
def read_xlsx():
import openpyxl
ws = openpyxl.load_workbook(SRC, data_only=True).active
rows = list(ws.iter_rows(values_only=True))
hi = [i for i, r in enumerate(rows) if r and str(r[0]).strip() == "Data Series"]
assert len(hi) == 1, "header row not found"
hdr = rows[hi[0]]
assert "MONETARY AUTHORITY OF SINGAPORE" in " ".join(str(r[0]) for r in rows[:hi[0]] if r and r[0]), "source line missing"
months = [ym(h) for h in hdr[1:] if h not in (None, "")]
assert months == sorted(months, reverse=True), "months should run newest first"
assert len(set(months)) == len(months), "duplicate months"
out = []
for code, (label, unit, *_r) in CUR.items():
hit = [r for r in rows[hi[0] + 1:] if r and r[0] and str(r[0]).startswith(label + " (")]
assert len(hit) == 1, (code, "series not found exactly once")
r = hit[0]
assert ("Per %d " % unit if unit > 1 else "Per Malaysian") in str(r[0]) or unit == 1, (code, r[0])
for m, v in zip(months, r[1:]):
if v in (None, "", "na", "-"):
continue
out.append((m, code, str(v).strip()))
return out, months
def main():
recs, months = read_xlsx()
with open(CSV, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["year", "month", "currency", "sgd_per_unit_as_published"])
for (y, m), code, v in sorted(recs):
w.writerow([y, m, code, v])
rows = list(csv.DictReader(open(CSV)))
data = {}
for code, (label, unit, name, country, sym) in CUR.items():
s = sorted(((int(r["year"]), int(r["month"])), Decimal(r["sgd_per_unit_as_published"])) for r in rows if r["currency"] == code)
keys = [k for k, _ in s]
# no gaps: every month between the first and the last is present
n = (keys[-1][0] - keys[0][0]) * 12 + keys[-1][1] - keys[0][1] + 1
assert n == len(keys), (code, "gap in months", n, len(keys))
assert all(v > 0 for _, v in s)
# what S$1,000 buys, whole units, half up. rate = SGD per `unit` units
amt = [(k, half_up(Decimal(1000) * unit / v)) for k, v in s]
full = dict(amt)
grid = [(k, a) for k, a in amt if k >= GRID_START]
last = amt[-1]
rec = max(amt, key=lambda x: (x[1], x[0]))
assert sum(1 for k, a in amt if a == rec[1]) == 1, (code, "tie for the record")
g_rec = max(grid, key=lambda x: x[1])
g_low = min(grid, key=lambda x: x[1])
assert sum(1 for k, a in grid if a == g_low[1]) == 1 and g_rec == rec, (code, "grid extremes")
low_all = min(amt, key=lambda x: x[1])
jan15 = full[GRID_START]
above = [k for k, a in grid if a > jan15]
below_or_eq = [k for k, a in grid if a <= jan15]
last_below = max(below_or_eq) if below_or_eq else None
# biggest rise over 12 months (same month a year earlier)
climb = fall = None
for k, a in amt:
p = (k[0] - 1, k[1])
if p in full and k >= (GRID_START[0] + 1, 1):
r = a / full[p] - 1
if climb is None or r > climb[0]:
climb = (r, p, k, full[p], a)
if fall is None or r < fall[0]:
fall = (r, p, k, full[p], a)
# the middle month: line the grid months up by amount; with an odd count the middle one is exact
assert len(grid) % 2 == 1
by_amt = sorted(grid, key=lambda x: (x[1], x[0]))
mid = by_amt[len(grid) // 2]
# ranking within the grid: 1 + the number of months that bought strictly more; the top five, most first (later month first on a tie)
top = sorted(grid, key=lambda x: (-x[1], [-x[0][0], -x[0][1]]))
latest_rank = 1 + sum(1 for k, a in grid if a > last[1])
before_rec_year = max((x for x in amt if x[0][0] < rec[0][0]), key=lambda x: x[1])
data[code] = {
"name": name, "country": country, "symbol": sym, "unit": unit,
"first_month": list(keys[0]), "last_month": list(keys[-1]), "n_full": len(keys), "n_grid": len(grid),
"grid": [[k[0], k[1], int(a)] for k, a in grid],
"jan15": int(jan15), "latest": [last[0][0], last[0][1], int(last[1])],
"record": [rec[0][0], rec[0][1], int(rec[1])], "grid_low": [g_low[0][0], g_low[0][1], int(g_low[1])],
"full_low": [low_all[0][0], low_all[0][1], int(low_all[1])],
"before_record_year": [before_rec_year[0][0], before_rec_year[0][1], int(before_rec_year[1])],
"months_above_jan15": len(above), "last_at_or_below_jan15": list(last_below) if last_below else None,
"months_in_2026": sum(1 for k, _ in grid if k[0] == 2026),
"climb12": {"from": [climb[1][0], climb[1][1]], "to": [climb[2][0], climb[2][1]], "a": int(climb[3]), "b": int(climb[4]), "pct": float(half_up(climb[0] * 100, 0))},
"fall12": ({"from": [fall[1][0], fall[1][1]], "to": [fall[2][0], fall[2][1]], "a": int(fall[3]), "b": int(fall[4]), "pct": float(half_up(-fall[0] * 100, 0))} if fall[0] < 0 else None),
"median": [mid[0][0], mid[0][1], int(mid[1])],
"latest_rank": latest_rank, "n_more_than_latest": latest_rank - 1,
"top5": [[k[0], k[1], int(a)] for k, a in top[:5]],
"gain_record_pct": float(half_up((rec[1] / jan15 - 1) * 100, 0)),
"gain_latest_pct": float(half_up((last[1] / jan15 - 1) * 100, 0)),
"gap_record_low_pct": float(half_up((g_rec[1] / g_low[1] - 1) * 100, 0)),
"latest_vs_record_pct": float(half_up((1 - last[1] / rec[1]) * 100, 0)),
}
print(code, "months", len(keys), keys[0], "to", keys[-1], "| record", rec, "| grid low", g_low, "| jan15", jan15, "| latest", last,
"| above jan15", len(above), "of", len(grid), "| last at/below jan15", last_below)
D = {"asof": "16 Oct 2026", "src_updated": "14 Oct 2026", "cur": data, "order": ["JPY", "KRW", "MYR", "THB"],
"n_rows": len(rows), "grid_start": list(GRID_START)}
json.dump(D, open(OUT, "w"), indent=1)
print("wrote", CSV, len(rows), "rows;", OUT)
if __name__ == "__main__":
main()What could make this wrong: MAS or SingStat revising recent months; a misread series in the .xlsx file (the script checks the header and the series name); rounding to whole units, which can move a per cent figure by one; and the table's months being averages, so a day's rate can sit well above or below them.
Source: Monetary Authority of Singapore, through SingStat Table Builder, table M700051, accessed 16 Oct 2026. MAS and SingStat give no guarantee of accuracy. Licence wording for this table: not yet confirmed.