Importing RBA Exchange Rates

RBA Exchange Rate Historical Data ETL into Azure

The Reserve Bank Of Australia maintains an historical exchange rate data page. I have downloaded Exchange Rates ‐ Daily ‐ 2023 to Current. I will:

The source file has the Exchange Date on the vertical axis and the Exchange Rate for each currency on the horizontal access:

            
  1. F11.1 EXCHANGE RATES ,,,,,,,,,,,,,,,,,,,,,,,
  2. Title,A$1=USD,Trade-weighted Index May 1970 = 100,A$1=CNY,A$1=JPY,A$1=EUR,A$1=KRW,A$1=GBP,A$1=SGD,A$1=INR,A$1=THB,A$1=NZD,A$1=TWD,A$1=MYR,A$1=IDR,A$1=VND,A$1=AED,A$1=PGK,A$1=HKD,A$1=CAD,A$1=ZAR,A$1=CHF,A$1=PHP,A$1=SDR
  3. Description,AUD/USD Exchange Rate; see notes for further detail.,Australian Dollar Trade-weighted Index,AUD/CNY Exchange Rate,AUD/JPY Exchange Rate,AUD/EUR Exchange Rate,AUD/KRW Exchange Rate,AUD/GBP Exchange Rate,AUD/SGD Exchange Rate,AUD/INR Exchange Rate,AUD/THB Exchange Rate,AUD/NZD Exchange Rate,AUD/TWD Exchange Rate,AUD/MYR Exchange Rate,AUD/IDR Exchange Rate,AUD/VND Exchange Rate,AUD/AED Exchange Rate,AUD/PGK Exchange Rate,AUD/HKD Exchange Rate,AUD/CAD Exchange Rate,AUD/ZAR Exchange Rate,AUD/CHF Exchange Rate,AUD/PHP Exchange Rate,AUD/SDR Exchange Rate
  4. Frequency,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily
  5. Type,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative
  6. Units,USD,Index,CNY,JPY,EUR,KRW,GBP,SGD,INR,THB,NZD,TWD,MYR,IDR,VND,AED,PGK,HKD,CAD,ZAR,CHF,PHP,SDR
  7. ,,,,,,,,,,,,,,,,,,,,,,,
  8. ,,,,,,,,,,,,,,,,,,,,,,,
  9. Source,WM/Reuters,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,RBA,IMF
  10. Publication date,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,26-Mar-2025,01-Oct-2024,01-Oct-2024,26-Mar-2025,26-Mar-2025,01-Oct-2024,26-Mar-2025,26-Mar-2025,26-Mar-2025
  11. Series ID,FXRUSD,FXRTWI,FXRCR,FXRJY,FXREUR,FXRSKW,FXRUKPS,FXRSD,FXRIRE,FXRTB,FXRNZD,FXRNTD,FXRMR,FXRIR,FXRVD,FXRUAED,FXRPNGK,FXRHKD,FXRCD,FXRSARD,FXRSF,FXRPHP,FXRSDR
  12. 03-Jan-2023,0.6828,61.40,4.6994,88.48,0.6400,867.16,0.5656,0.9134,56.51,23.47,1.0760,20.97,2.9975,10635,16039,,,5.3316,,,0.6310,38.07,0.5131
  13. 04-Jan-2023,0.6809,61.50,4.6906,89.08,0.6439,866.04,0.5678,0.9138,56.41,23.33,1.0851,20.88,2.9990,10634,16022,,,5.3222,,,0.6351,38.12,0.5120
  14. 05-Jan-2023,0.6815,61.50,4.6916,90.17,0.6426,865.90,0.5661,0.9145,56.41,23.08,1.0852,20.92,3.0000,10644,16002,,,5.3276,,,0.6341,38.12,0.5106
  15. 06-Jan-2023,0.6769,61.20,4.6378,90.63,0.6431,856.58,0.5677,0.9100,55.92,23.00,1.0839,20.79,2.9753,10582,15887,,,5.2853,,,0.6338,37.66,0.5077
  16. 09-Jan-2023,0.6929,62.00,4.7029,91.25,0.6490,862.65,0.5707,0.9221,57.08,23.22,1.0830,21.15,3.0325,10794,16252,,,5.4091,,,0.6415,38.20,0.5224
  17. 10-Jan-2023,0.6904,61.70,4.6750,91.13,0.6434,857.24,0.5678,0.9196,56.77,23.08,1.0833,21.03,3.0205,10763,16190,,,5.3909,,,0.6364,37.88,0.5156
  18. 11-Jan-2023,0.6911,61.70,4.6803,91.43,0.6435,860.03,0.5684,0.9192,56.50,23.12,1.0832,21.04,3.0225,10691,16206,,,5.3985,,,0.6373,37.96,0.5152
  19. ...
  20. 19-Mar-2025,0.6357,60.20,4.6005,95.06,0.5816,924.02,0.4895,0.8468,55.03,21.38,1.0941,20.98,2.8177,10512,16239,,,4.9400,0.9098,,0.5577,36.38,0.4769
  21. 20-Mar-2025,0.6344,60.00,4.5905,94.14,0.5819,926.00,0.4879,0.8446,54.70,21.32,1.0953,20.94,2.8079,10451,16214,,,4.9296,0.9091,,0.5563,36.25,0.4761
  22. 21-Mar-2025,0.6289,59.70,4.5602,93.97,0.5807,922.66,0.4860,0.8403,54.25,21.28,1.0939,20.77,2.7819,10383,16084,,,4.8879,0.9014,,0.5557,36.07,0.4726
  23. 24-Mar-2025,0.6278,59.60,4.5575,94.03,0.5800,921.38,0.4860,0.8396,53.97,21.32,1.0970,20.74,2.7849,10400,16103,,,4.8803,0.9006,,0.5547,36.02,0.4719
  24. 25-Mar-2025,0.6287,59.80,4.5649,94.65,0.5819,923.97,0.4865,0.8416,53.87,21.38,1.0985,20.77,2.7911,10440,16115,,,4.8880,0.9005,,0.5550,36.06,0.4731
  25. 26-Mar-2025,0.6311,60.00,4.5848,95.01,0.5852,925.33,0.4879,0.8447,54.12,21.46,1.0981,20.89,2.7942,10464,16150,,,4.9064,0.9009,,0.5578,36.41,0.4752

Firstly I will create a table to hold the final data in Azure:

            
  1. create table forex
  2. (
  3. exchg_id int not null identity,
  4. exchg_date date not null,
  5. exchg_currency varchar(4) not null,
  6. exchg_rate decimal(19,4) null,
  7. constraint PK_forex primary key (exchg_id),
  8. constraint UQ_EXCHG_DATE_CURRENCY unique(exchg_date, exchg_currency)
  9. )

The Unique Constraint UQ_EXCHG_DATE_CURRENCY not only ensures data integrity and 3rd normal form but will also act as an index and will be very useful where results are ordered by exchg_date, exchg_currency.

The following Python code takes the source CSV and transforms it into an output file with three columns: Date, Currency and Exchange Rate. The third parameter, format_for_sql determines whether the output file is a SQL select statement which could be run directly in SQL Server Manegement Studio or a flat file for use in ETL.

            
  1. import os
  2. import sys
  3. CLI_SEPARATOR = "======================================================================\n"
  4. CLI_HEADER = "\nprocess_forex_csv\nProcesses CSV version of RBA Exchange Rate history:\nhttps://www.rba.gov.au/statistics/historical-data.html#exchange-rates\nfrom supplied format to a format ready for ETL"
  5. CLI_USAGE = "Usage:\nprocess_forex_csv.py [input_filename] [output_filename] [format_as_sql (y/n)]\n"
  6. OUTPUT_HEADER = "Date,Currency,Rate\n"
  7. FILE_HEADER = "EXCHANGE RATES ,,,,,,,,,,,,,,,,,,,,,,,"
  8. FOREX_HEADER = "Series ID"
  9. months = {
  10. "jan": "01",
  11. "feb": "02",
  12. "mar": "03",
  13. "apr": "04",
  14. "may": "05",
  15. "jun": "06",
  16. "jul": "07",
  17. "aug": "08",
  18. "sep": "09",
  19. "oct": "10",
  20. "nov": "11",
  21. "dec": "12"
  22. }
  23. class ProcessForexCSV:
  24. def __init__(self, input_filename, output_filename, format_for_sql):
  25. self.input_filename = input_filename
  26. self.output_filename = output_filename
  27. self.format_for_sql = format_for_sql
  28. self.currencies = []
  29. def process_file(self):
  30. input_file_path = os.path.join(os.getcwd(), self.input_filename)
  31. output_file_path = os.path.join(os.getcwd(), self.output_filename)
  32. with open(input_file_path, "rt") as f:
  33. new_lines = ""
  34. line = f.readline()
  35. if line.find(FILE_HEADER) < 0:
  36. print("Invalid file format.")
  37. return
  38. line = f.readline()
  39. while line.find(FOREX_HEADER) < 0:
  40. line = f.readline()
  41. tokens = line.split(",")
  42. i = 0
  43. for token in tokens:
  44. if i > 0:
  45. self.currencies.append(token[len(token)-3:].strip())
  46. i += 1
  47. print("Currencies:\n{0}".format(CLI_SEPARATOR))
  48. for currency in self.currencies:
  49. print("{0}".format(currency))
  50. line = f.readline().strip()
  51. line_count = 0
  52. while line != "":
  53. line_count += 1
  54. line_tokens = line.split(",")
  55. token_count = len(line_tokens)
  56. exchg_date = line_tokens[0]
  57. original_exchg_date = exchg_date
  58. date_tokens = exchg_date.split("-")
  59. exchg_date = date_tokens[2] + months[date_tokens[1].lower()] + date_tokens[0]
  60. i = 1
  61. while i < token_count:
  62. if line_tokens[i] == "":
  63. line_tokens[i] = "null"
  64. exchg_line = ""
  65. if not self.format_for_sql:
  66. exchg_line = "{0},{1},{2}\n".format(original_exchg_date, self.currencies[i-1], line_tokens[i])
  67. else:
  68. exchg_line = "select '" + exchg_date + "','" + self.currencies[i-1] + "'," + line_tokens[i] + " union all\n"
  69. i += 1
  70. if exchg_line != "":
  71. #print(exchg_line)
  72. new_lines += exchg_line
  73. line = f.readline().strip()
  74. f.close()
  75. # get rid of the final "union all"
  76. if self.format_for_sql:
  77. new_lines = new_lines[:len(new_lines)-len(" union all\n")]
  78. else:
  79. new_lines = new_lines[:len(new_lines)-len("\n")]
  80. #print(new_lines)
  81. with open(output_file_path, "wt") as f2:
  82. f2.write(OUTPUT_HEADER)
  83. f2.write(new_lines)
  84. f2.close()
  85. print("{0}\n{1} exchange rates written to {2}\n{3}".format(CLI_SEPARATOR, line_count, self.output_filename, CLI_HEADER))
  86. if __name__ == "__main__":
  87. print(CLI_HEADER)
  88. print(CLI_SEPARATOR)
  89. print(CLI_USAGE)
  90. if len(sys.argv) == 4:
  91. input_filename = sys.argv[1]
  92. output_filename = sys.argv[2]
  93. format_for_sql = True if str(sys.argv[3]).lower() == "y" else False
  94. process = ProcessForexCSV(input_filename, output_filename, format_for_sql)
  95. process.process_file()
  96. else:
  97. print("Invalid number of parameters.")

If format_for_sql is 'y' SQL a select union all statement with 12,719 rows is produced:

            
  1. select '20230103','USD',0.6828 union all
  2. select '20230103','TWI',61.40 union all
  3. select '20230103','RCR',4.6994 union all
  4. select '20230103','RJY',88.48 union all
  5. select '20230103','EUR',0.6400 union all
  6. select '20230103','SKW',867.16 union all
  7. select '20230103','KPS',0.5656 union all
  8. select '20230103','RSD',0.9134 union all
  9. select '20230103','IRE',56.51 union all
  10. select '20230103','RTB',23.47 union all
  11. select '20230103','NZD',1.0760 union all
  12. select '20230103','NTD',20.97 union all
  13. select '20230103','RMR',2.9975 union all
  14. select '20230103','RIR',10635 union all
  15. select '20230103','RVD',16039 union all
  16. select '20230103','AED',NULL union all
  17. select '20230103','NGK',NULL union all
  18. select '20230103','HKD',5.3316 union all
  19. select '20230103','RCD',NULL union all
  20. select '20230103','ARD',NULL union all
  21. select '20230103','RSF',0.6310 union all
  22. select '20230103','PHP',38.07 union all
  23. select '20230103','DR',0.5131 union all

If format_as_sql is 'n' a flat CSV file is produced:

            
  1. Date,Currency,Rate
  2. 03-Jan-2023,USD,0.6828
  3. 03-Jan-2023,TWI,61.40
  4. 03-Jan-2023,RCR,4.6994
  5. 03-Jan-2023,RJY,88.48
  6. 03-Jan-2023,EUR,0.6400
  7. 03-Jan-2023,SKW,867.16
  8. 03-Jan-2023,KPS,0.5656
  9. 03-Jan-2023,RSD,0.9134
  10. 03-Jan-2023,IRE,56.51
  11. 03-Jan-2023,RTB,23.47
  12. 03-Jan-2023,NZD,1.0760
  13. 03-Jan-2023,NTD,20.97
  14. 03-Jan-2023,RMR,2.9975
  15. 03-Jan-2023,RIR,10635
  16. 03-Jan-2023,RVD,16039
  17. 03-Jan-2023,AED,NULL
  18. 03-Jan-2023,NGK,NULL
  19. 03-Jan-2023,HKD,5.3316
  20. 03-Jan-2023,RCD,NULL
  21. 03-Jan-2023,ARD,NULL
  22. 03-Jan-2023,RSF,0.6310
  23. 03-Jan-2023,PHP,38.07
  24. 03-Jan-2023,DR,0.5131

It actually works out a lot easier to use the SQL statements rather than construct an ETL package. Just need to add one line to the top of the select statements. Though for a more complex ETL project we would create staging tables and use an ETL platform such as SSIS. This will be the subject of my next SQL Blog.

insert forex(exchg_date, exchg_currency, exchg_rate)

SSMS Insert Result

Select USD Exchange Rate ordered by date:

SSMS Select USD Result