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:
- F11.1 EXCHANGE RATES ,,,,,,,,,,,,,,,,,,,,,,,
- 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
- 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
- Frequency,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily,Daily
- Type,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative,Indicative
- Units,USD,Index,CNY,JPY,EUR,KRW,GBP,SGD,INR,THB,NZD,TWD,MYR,IDR,VND,AED,PGK,HKD,CAD,ZAR,CHF,PHP,SDR
- ,,,,,,,,,,,,,,,,,,,,,,,
- ,,,,,,,,,,,,,,,,,,,,,,,
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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-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
- 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
- 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
- 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
- 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
- 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:
- create table forex
- (
- exchg_id int not null identity,
- exchg_date date not null,
- exchg_currency varchar(4) not null,
- exchg_rate decimal(19,4) null,
- constraint PK_forex primary key (exchg_id),
- constraint UQ_EXCHG_DATE_CURRENCY unique(exchg_date, exchg_currency)
- )
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.
- import os
- import sys
- CLI_SEPARATOR = "======================================================================\n"
- 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"
- CLI_USAGE = "Usage:\nprocess_forex_csv.py [input_filename] [output_filename] [format_as_sql (y/n)]\n"
- OUTPUT_HEADER = "Date,Currency,Rate\n"
- FILE_HEADER = "EXCHANGE RATES ,,,,,,,,,,,,,,,,,,,,,,,"
- FOREX_HEADER = "Series ID"
- months = {
- "jan": "01",
- "feb": "02",
- "mar": "03",
- "apr": "04",
- "may": "05",
- "jun": "06",
- "jul": "07",
- "aug": "08",
- "sep": "09",
- "oct": "10",
- "nov": "11",
- "dec": "12"
- }
- class ProcessForexCSV:
- def __init__(self, input_filename, output_filename, format_for_sql):
- self.input_filename = input_filename
- self.output_filename = output_filename
- self.format_for_sql = format_for_sql
- self.currencies = []
- def process_file(self):
- input_file_path = os.path.join(os.getcwd(), self.input_filename)
- output_file_path = os.path.join(os.getcwd(), self.output_filename)
- with open(input_file_path, "rt") as f:
- new_lines = ""
- line = f.readline()
- if line.find(FILE_HEADER) < 0:
- print("Invalid file format.")
- return
- line = f.readline()
- while line.find(FOREX_HEADER) < 0:
- line = f.readline()
- tokens = line.split(",")
- i = 0
- for token in tokens:
- if i > 0:
- self.currencies.append(token[len(token)-3:].strip())
- i += 1
- print("Currencies:\n{0}".format(CLI_SEPARATOR))
- for currency in self.currencies:
- print("{0}".format(currency))
- line = f.readline().strip()
- line_count = 0
- while line != "":
- line_count += 1
- line_tokens = line.split(",")
- token_count = len(line_tokens)
- exchg_date = line_tokens[0]
- original_exchg_date = exchg_date
- date_tokens = exchg_date.split("-")
- exchg_date = date_tokens[2] + months[date_tokens[1].lower()] + date_tokens[0]
- i = 1
- while i < token_count:
- if line_tokens[i] == "":
- line_tokens[i] = "null"
- exchg_line = ""
- if not self.format_for_sql:
- exchg_line = "{0},{1},{2}\n".format(original_exchg_date, self.currencies[i-1], line_tokens[i])
- else:
- exchg_line = "select '" + exchg_date + "','" + self.currencies[i-1] + "'," + line_tokens[i] + " union all\n"
- i += 1
- if exchg_line != "":
- #print(exchg_line)
- new_lines += exchg_line
- line = f.readline().strip()
- f.close()
- # get rid of the final "union all"
- if self.format_for_sql:
- new_lines = new_lines[:len(new_lines)-len(" union all\n")]
- else:
- new_lines = new_lines[:len(new_lines)-len("\n")]
- #print(new_lines)
- with open(output_file_path, "wt") as f2:
- f2.write(OUTPUT_HEADER)
- f2.write(new_lines)
- f2.close()
- print("{0}\n{1} exchange rates written to {2}\n{3}".format(CLI_SEPARATOR, line_count, self.output_filename, CLI_HEADER))
- if __name__ == "__main__":
- print(CLI_HEADER)
- print(CLI_SEPARATOR)
- print(CLI_USAGE)
- if len(sys.argv) == 4:
- input_filename = sys.argv[1]
- output_filename = sys.argv[2]
- format_for_sql = True if str(sys.argv[3]).lower() == "y" else False
- process = ProcessForexCSV(input_filename, output_filename, format_for_sql)
- process.process_file()
- else:
- print("Invalid number of parameters.")
If format_for_sql
is 'y' SQL a select union all statement with 12,719 rows is produced:
- select '20230103','USD',0.6828 union all
- select '20230103','TWI',61.40 union all
- select '20230103','RCR',4.6994 union all
- select '20230103','RJY',88.48 union all
- select '20230103','EUR',0.6400 union all
- select '20230103','SKW',867.16 union all
- select '20230103','KPS',0.5656 union all
- select '20230103','RSD',0.9134 union all
- select '20230103','IRE',56.51 union all
- select '20230103','RTB',23.47 union all
- select '20230103','NZD',1.0760 union all
- select '20230103','NTD',20.97 union all
- select '20230103','RMR',2.9975 union all
- select '20230103','RIR',10635 union all
- select '20230103','RVD',16039 union all
- select '20230103','AED',NULL union all
- select '20230103','NGK',NULL union all
- select '20230103','HKD',5.3316 union all
- select '20230103','RCD',NULL union all
- select '20230103','ARD',NULL union all
- select '20230103','RSF',0.6310 union all
- select '20230103','PHP',38.07 union all
- select '20230103','DR',0.5131 union all
If format_as_sql
is 'n' a flat CSV file is produced:
- Date,Currency,Rate
- 03-Jan-2023,USD,0.6828
- 03-Jan-2023,TWI,61.40
- 03-Jan-2023,RCR,4.6994
- 03-Jan-2023,RJY,88.48
- 03-Jan-2023,EUR,0.6400
- 03-Jan-2023,SKW,867.16
- 03-Jan-2023,KPS,0.5656
- 03-Jan-2023,RSD,0.9134
- 03-Jan-2023,IRE,56.51
- 03-Jan-2023,RTB,23.47
- 03-Jan-2023,NZD,1.0760
- 03-Jan-2023,NTD,20.97
- 03-Jan-2023,RMR,2.9975
- 03-Jan-2023,RIR,10635
- 03-Jan-2023,RVD,16039
- 03-Jan-2023,AED,NULL
- 03-Jan-2023,NGK,NULL
- 03-Jan-2023,HKD,5.3316
- 03-Jan-2023,RCD,NULL
- 03-Jan-2023,ARD,NULL
- 03-Jan-2023,RSF,0.6310
- 03-Jan-2023,PHP,38.07
- 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)
Select USD Exchange Rate ordered by date: