fxmacrodata 1.0.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fxmacrodata
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Python client for the FXMacroData API, providing forex macroeconomic data for traders, quants, and analysts.
5
5
  Author-email: FXMacroData <info@fxmacrodata.com>
6
6
  License: MIT
@@ -34,7 +34,7 @@ Dynamic: license-file
34
34
  ![License](https://img.shields.io/github/license/fxmacrodata/fxmacrodata?style=flat-square)
35
35
  ![Build](https://img.shields.io/github/actions/workflow/status/fxmacrodata/fxmacrodata/ci-cd.yml?style=flat-square&logo=github)
36
36
 
37
- The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators** and **forex price history** from [FXMacroData](https://fxmacrodata.com/?utm_source=github&utm_medium=readme&utm_campaign=python_sdk).
37
+ The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators**, **forex prices**, **release calendars**, **COT positioning**, and **commodity prices** from [FXMacroData](https://fxmacrodata.com/?utm_source=github&utm_medium=readme&utm_campaign=python_sdk).
38
38
 
39
39
  It includes both synchronous and asynchronous clients, supports free USD endpoints, and offers a free Forex Price API for exchange rate data.
40
40
 
@@ -43,12 +43,12 @@ It includes both synchronous and asynchronous clients, supports free USD endpoin
43
43
  ## 🌟 Features
44
44
 
45
45
  - Fetch:
46
- - **Policy Rates**
47
- - **Inflation & CPI**
48
- - **GDP**
49
- - **Unemployment**
50
- - **Balance of Trade**
51
- - **Government Bond Yields**
46
+ - **Macroeconomic indicators** — policy rates, inflation, GDP, unemployment, bond yields, and 100+ more
47
+ - **FX spot rates** with optional technical indicators (SMA, RSI, MACD, Bollinger Bands)
48
+ - **Release calendars** — upcoming economic data release dates
49
+ - **Data catalogue** — discover available indicators per currency
50
+ - **COT data** — CFTC Commitment of Traders positioning
51
+ - **Commodity prices** — gold, silver, platinum
52
52
  - Free access to **USD** macro data.
53
53
  - Free **Forex Price API** (`get_fx_price`).
54
54
  - API key required only for **non-USD** indicators.
@@ -95,6 +95,26 @@ print(data)
95
95
  # Free Forex Price Endpoint
96
96
  fx = client.get_fx_price("usd", "gbp", start_date="2025-01-01")
97
97
  print(fx)
98
+
99
+ # Forex with technical indicators
100
+ fx = client.get_fx_price("eur", "usd", indicators="sma_20,rsi_14,macd")
101
+ print(fx)
102
+
103
+ # Release calendar
104
+ calendar = client.get_calendar("usd")
105
+ print(calendar)
106
+
107
+ # Data catalogue — discover available indicators
108
+ catalogue = client.get_data_catalogue("usd")
109
+ print(catalogue)
110
+
111
+ # COT positioning data
112
+ cot = client.get_cot("eur", start_date="2025-01-01")
113
+ print(cot)
114
+
115
+ # Commodity prices
116
+ gold = client.get_commodities("gold", start_date="2026-01-01")
117
+ print(gold)
98
118
  ```
99
119
 
100
120
  ---
@@ -108,13 +128,29 @@ from fxmacrodata import AsyncClient
108
128
  async def main():
109
129
  async with AsyncClient(api_key="YOUR_API_KEY") as client:
110
130
  # Fetch macroeconomic indicators
111
- data = await client.get_indicator("eur", "cpi")
131
+ data = await client.get_indicator("eur", "inflation")
112
132
  print(data)
113
133
 
114
134
  # Free Forex Price Endpoint
115
135
  fx = await client.get_fx_price("usd", "jpy")
116
136
  print(fx)
117
137
 
138
+ # Release calendar
139
+ calendar = await client.get_calendar("usd")
140
+ print(calendar)
141
+
142
+ # Data catalogue
143
+ catalogue = await client.get_data_catalogue("usd")
144
+ print(catalogue)
145
+
146
+ # COT positioning
147
+ cot = await client.get_cot("jpy")
148
+ print(cot)
149
+
150
+ # Commodity prices
151
+ gold = await client.get_commodities("gold")
152
+ print(gold)
153
+
118
154
  asyncio.run(main())
119
155
  ```
120
156
 
@@ -125,12 +161,39 @@ asyncio.run(main())
125
161
  ### `get_indicator(currency, indicator, start_date=None, end_date=None)`
126
162
  Fetches macroeconomic indicator time series data.
127
163
 
128
- - `currency`: `"usd"`, `"aud"`, `"eur"`, `"gbp"`, `"cad"`, `"nok"`, `"nzd"`, `"jpy"`, etc.
129
- - `indicator`: `"policy_rate"`, `"cpi"`, `"inflation"`, `"gdp"`, `"unemployment"`, `"trade_balance"`, `"current_account"`, etc.
164
+ - `currency`: `"usd"`, `"aud"`, `"eur"`, `"gbp"`, `"cad"`, `"nok"`, `"nzd"`, `"jpy"`, `"brl"`, `"cny"`, `"dkk"`, `"pln"`, `"sek"`, `"sgd"`, etc.
165
+ - `indicator`: `"policy_rate"`, `"inflation"`, `"gdp"`, `"unemployment"`, `"trade_balance"`, `"current_account_balance"`, `"gov_bond_10y"`, etc.
130
166
  - **API key required for non-USD.**
131
167
 
132
- ### `get_fx_price(base, quote, start_date=None, end_date=None)`
133
- Fetches historical FX prices between two currencies. **No API key needed for USD-based queries.**
168
+ ### `get_fx_price(base, quote, start_date=None, end_date=None, indicators=None)`
169
+ Fetches daily FX spot rates (ECB reference rates) between two currencies.
170
+
171
+ - `indicators`: Optional comma-separated technical indicators — `"sma_20"`, `"sma_50"`, `"sma_200"`, `"rsi_14"`, `"macd"`, `"ema_12"`, `"ema_26"`, `"bollinger_bands"`, or `"all"`.
172
+ - **No API key needed.**
173
+
174
+ ### `get_calendar(currency, indicator=None)`
175
+ Fetches upcoming economic data release dates for a currency.
176
+
177
+ - `indicator`: Optional filter to a specific indicator slug.
178
+ - Returns `announcement_datetime` (Unix timestamp) and `release` (indicator slug).
179
+
180
+ ### `get_data_catalogue(currency, include_capabilities=False, include_coverage=False, indicator=None)`
181
+ Discovers available macroeconomic indicators for a given currency.
182
+
183
+ - Returns a dict keyed by indicator slug with `name`, `unit`, `frequency`, and `has_official_forecast`.
184
+ - **API key required for non-USD.**
185
+
186
+ ### `get_cot(currency, start_date=None, end_date=None)`
187
+ Fetches CFTC Commitment of Traders (COT) positioning data.
188
+
189
+ - Supported currencies: `AUD`, `CAD`, `CHF`, `EUR`, `GBP`, `JPY`, `NZD`, `USD`.
190
+ - **API key required for non-USD.**
191
+
192
+ ### `get_commodities(indicator, start_date=None, end_date=None)`
193
+ Fetches commodity price time series.
194
+
195
+ - `indicator`: `"gold"`, `"silver"`, or `"platinum"`.
196
+ - **API key required.**
134
197
 
135
198
  ---
136
199
 
@@ -170,3 +233,5 @@ MIT License © FXMacroData
170
233
  - API Docs: https://fxmacrodata.com/docs
171
234
  - GitHub: https://github.com/fxmacrodata/fxmacrodata
172
235
  - PyPI: https://pypi.org/project/fxmacrodata/
236
+ - Readthedocs: https://fxmacrodata.readthedocs.io/en/latest/
237
+ - Zenodo: https://zenodo.org/records/18280968
@@ -5,7 +5,7 @@
5
5
  ![License](https://img.shields.io/github/license/fxmacrodata/fxmacrodata?style=flat-square)
6
6
  ![Build](https://img.shields.io/github/actions/workflow/status/fxmacrodata/fxmacrodata/ci-cd.yml?style=flat-square&logo=github)
7
7
 
8
- The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators** and **forex price history** from [FXMacroData](https://fxmacrodata.com/?utm_source=github&utm_medium=readme&utm_campaign=python_sdk).
8
+ The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators**, **forex prices**, **release calendars**, **COT positioning**, and **commodity prices** from [FXMacroData](https://fxmacrodata.com/?utm_source=github&utm_medium=readme&utm_campaign=python_sdk).
9
9
 
10
10
  It includes both synchronous and asynchronous clients, supports free USD endpoints, and offers a free Forex Price API for exchange rate data.
11
11
 
@@ -14,12 +14,12 @@ It includes both synchronous and asynchronous clients, supports free USD endpoin
14
14
  ## 🌟 Features
15
15
 
16
16
  - Fetch:
17
- - **Policy Rates**
18
- - **Inflation & CPI**
19
- - **GDP**
20
- - **Unemployment**
21
- - **Balance of Trade**
22
- - **Government Bond Yields**
17
+ - **Macroeconomic indicators** — policy rates, inflation, GDP, unemployment, bond yields, and 100+ more
18
+ - **FX spot rates** with optional technical indicators (SMA, RSI, MACD, Bollinger Bands)
19
+ - **Release calendars** — upcoming economic data release dates
20
+ - **Data catalogue** — discover available indicators per currency
21
+ - **COT data** — CFTC Commitment of Traders positioning
22
+ - **Commodity prices** — gold, silver, platinum
23
23
  - Free access to **USD** macro data.
24
24
  - Free **Forex Price API** (`get_fx_price`).
25
25
  - API key required only for **non-USD** indicators.
@@ -66,6 +66,26 @@ print(data)
66
66
  # Free Forex Price Endpoint
67
67
  fx = client.get_fx_price("usd", "gbp", start_date="2025-01-01")
68
68
  print(fx)
69
+
70
+ # Forex with technical indicators
71
+ fx = client.get_fx_price("eur", "usd", indicators="sma_20,rsi_14,macd")
72
+ print(fx)
73
+
74
+ # Release calendar
75
+ calendar = client.get_calendar("usd")
76
+ print(calendar)
77
+
78
+ # Data catalogue — discover available indicators
79
+ catalogue = client.get_data_catalogue("usd")
80
+ print(catalogue)
81
+
82
+ # COT positioning data
83
+ cot = client.get_cot("eur", start_date="2025-01-01")
84
+ print(cot)
85
+
86
+ # Commodity prices
87
+ gold = client.get_commodities("gold", start_date="2026-01-01")
88
+ print(gold)
69
89
  ```
70
90
 
71
91
  ---
@@ -79,13 +99,29 @@ from fxmacrodata import AsyncClient
79
99
  async def main():
80
100
  async with AsyncClient(api_key="YOUR_API_KEY") as client:
81
101
  # Fetch macroeconomic indicators
82
- data = await client.get_indicator("eur", "cpi")
102
+ data = await client.get_indicator("eur", "inflation")
83
103
  print(data)
84
104
 
85
105
  # Free Forex Price Endpoint
86
106
  fx = await client.get_fx_price("usd", "jpy")
87
107
  print(fx)
88
108
 
109
+ # Release calendar
110
+ calendar = await client.get_calendar("usd")
111
+ print(calendar)
112
+
113
+ # Data catalogue
114
+ catalogue = await client.get_data_catalogue("usd")
115
+ print(catalogue)
116
+
117
+ # COT positioning
118
+ cot = await client.get_cot("jpy")
119
+ print(cot)
120
+
121
+ # Commodity prices
122
+ gold = await client.get_commodities("gold")
123
+ print(gold)
124
+
89
125
  asyncio.run(main())
90
126
  ```
91
127
 
@@ -96,12 +132,39 @@ asyncio.run(main())
96
132
  ### `get_indicator(currency, indicator, start_date=None, end_date=None)`
97
133
  Fetches macroeconomic indicator time series data.
98
134
 
99
- - `currency`: `"usd"`, `"aud"`, `"eur"`, `"gbp"`, `"cad"`, `"nok"`, `"nzd"`, `"jpy"`, etc.
100
- - `indicator`: `"policy_rate"`, `"cpi"`, `"inflation"`, `"gdp"`, `"unemployment"`, `"trade_balance"`, `"current_account"`, etc.
135
+ - `currency`: `"usd"`, `"aud"`, `"eur"`, `"gbp"`, `"cad"`, `"nok"`, `"nzd"`, `"jpy"`, `"brl"`, `"cny"`, `"dkk"`, `"pln"`, `"sek"`, `"sgd"`, etc.
136
+ - `indicator`: `"policy_rate"`, `"inflation"`, `"gdp"`, `"unemployment"`, `"trade_balance"`, `"current_account_balance"`, `"gov_bond_10y"`, etc.
101
137
  - **API key required for non-USD.**
102
138
 
103
- ### `get_fx_price(base, quote, start_date=None, end_date=None)`
104
- Fetches historical FX prices between two currencies. **No API key needed for USD-based queries.**
139
+ ### `get_fx_price(base, quote, start_date=None, end_date=None, indicators=None)`
140
+ Fetches daily FX spot rates (ECB reference rates) between two currencies.
141
+
142
+ - `indicators`: Optional comma-separated technical indicators — `"sma_20"`, `"sma_50"`, `"sma_200"`, `"rsi_14"`, `"macd"`, `"ema_12"`, `"ema_26"`, `"bollinger_bands"`, or `"all"`.
143
+ - **No API key needed.**
144
+
145
+ ### `get_calendar(currency, indicator=None)`
146
+ Fetches upcoming economic data release dates for a currency.
147
+
148
+ - `indicator`: Optional filter to a specific indicator slug.
149
+ - Returns `announcement_datetime` (Unix timestamp) and `release` (indicator slug).
150
+
151
+ ### `get_data_catalogue(currency, include_capabilities=False, include_coverage=False, indicator=None)`
152
+ Discovers available macroeconomic indicators for a given currency.
153
+
154
+ - Returns a dict keyed by indicator slug with `name`, `unit`, `frequency`, and `has_official_forecast`.
155
+ - **API key required for non-USD.**
156
+
157
+ ### `get_cot(currency, start_date=None, end_date=None)`
158
+ Fetches CFTC Commitment of Traders (COT) positioning data.
159
+
160
+ - Supported currencies: `AUD`, `CAD`, `CHF`, `EUR`, `GBP`, `JPY`, `NZD`, `USD`.
161
+ - **API key required for non-USD.**
162
+
163
+ ### `get_commodities(indicator, start_date=None, end_date=None)`
164
+ Fetches commodity price time series.
165
+
166
+ - `indicator`: `"gold"`, `"silver"`, or `"platinum"`.
167
+ - **API key required.**
105
168
 
106
169
  ---
107
170
 
@@ -141,3 +204,5 @@ MIT License © FXMacroData
141
204
  - API Docs: https://fxmacrodata.com/docs
142
205
  - GitHub: https://github.com/fxmacrodata/fxmacrodata
143
206
  - PyPI: https://pypi.org/project/fxmacrodata/
207
+ - Readthedocs: https://fxmacrodata.readthedocs.io/en/latest/
208
+ - Zenodo: https://zenodo.org/records/18280968
@@ -0,0 +1,160 @@
1
+ import aiohttp # type: ignore
2
+ from typing import Optional
3
+ from .exceptions import FXMacroDataError
4
+
5
+
6
+ class AsyncClient:
7
+ BASE_URL = "https://fxmacrodata.com/api"
8
+
9
+ def __init__(self, api_key: Optional[str] = None):
10
+ self.api_key: Optional[str] = api_key
11
+ self.session: Optional[aiohttp.ClientSession] = None
12
+
13
+ async def __aenter__(self) -> "AsyncClient":
14
+ self.session = aiohttp.ClientSession()
15
+ return self
16
+
17
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
18
+ if self.session:
19
+ await self.session.close()
20
+ self.session = None
21
+
22
+ async def _request(self, url: str, params: dict, headers: dict) -> dict:
23
+ if not self.session:
24
+ self.session = aiohttp.ClientSession()
25
+ async with self.session.get(url, headers=headers, params=params) as resp:
26
+ if resp.status != 200:
27
+ text = await resp.text()
28
+ raise FXMacroDataError(f"{resp.status} - {text}")
29
+ return await resp.json()
30
+
31
+ def _auth_headers(self, currency: str, *, required: bool = True) -> dict:
32
+ headers: dict[str, str] = {}
33
+ if currency != "usd":
34
+ if required and not self.api_key:
35
+ raise FXMacroDataError(
36
+ f"API key required for {currency.upper()} endpoints."
37
+ )
38
+ if self.api_key:
39
+ headers["X-API-Key"] = self.api_key
40
+ return headers
41
+
42
+ # ------------------------------------------------------------------
43
+ # Macroeconomic indicator time-series
44
+ # ------------------------------------------------------------------
45
+ async def get_indicator(
46
+ self,
47
+ currency: str,
48
+ indicator: str,
49
+ start_date: Optional[str] = None,
50
+ end_date: Optional[str] = None,
51
+ ) -> dict:
52
+ currency = currency.lower()
53
+ url = f"{self.BASE_URL}/v1/announcements/{currency}/{indicator}"
54
+ headers = self._auth_headers(currency)
55
+ params: dict[str, str] = {}
56
+ if start_date:
57
+ params["start_date"] = start_date
58
+ if end_date:
59
+ params["end_date"] = end_date
60
+ return await self._request(url, params, headers)
61
+
62
+ # ------------------------------------------------------------------
63
+ # FX spot rates (free)
64
+ # ------------------------------------------------------------------
65
+ async def get_fx_price(
66
+ self,
67
+ base: str,
68
+ quote: str,
69
+ start_date: Optional[str] = None,
70
+ end_date: Optional[str] = None,
71
+ indicators: Optional[str] = None,
72
+ ) -> dict:
73
+ base = base.lower()
74
+ quote = quote.lower()
75
+ url = f"{self.BASE_URL}/v1/forex/{base}/{quote}"
76
+ params: dict[str, str] = {}
77
+ if start_date:
78
+ params["start_date"] = start_date
79
+ if end_date:
80
+ params["end_date"] = end_date
81
+ if indicators:
82
+ params["indicators"] = indicators
83
+ return await self._request(url, params, {})
84
+
85
+ # ------------------------------------------------------------------
86
+ # Release calendar
87
+ # ------------------------------------------------------------------
88
+ async def get_calendar(
89
+ self,
90
+ currency: str,
91
+ indicator: Optional[str] = None,
92
+ ) -> dict:
93
+ currency = currency.lower()
94
+ url = f"{self.BASE_URL}/v1/calendar/{currency}"
95
+ headers = self._auth_headers(currency, required=False)
96
+ params: dict[str, str] = {}
97
+ if indicator:
98
+ params["indicator"] = indicator
99
+ return await self._request(url, params, headers)
100
+
101
+ # ------------------------------------------------------------------
102
+ # Data catalogue — available indicators for a currency
103
+ # ------------------------------------------------------------------
104
+ async def get_data_catalogue(
105
+ self,
106
+ currency: str,
107
+ include_capabilities: bool = False,
108
+ include_coverage: bool = False,
109
+ indicator: Optional[str] = None,
110
+ ) -> dict:
111
+ currency = currency.lower()
112
+ url = f"{self.BASE_URL}/v1/data_catalogue/{currency}"
113
+ headers = self._auth_headers(currency)
114
+ params: dict[str, str] = {}
115
+ if include_capabilities:
116
+ params["include_capabilities"] = "true"
117
+ if include_coverage:
118
+ params["include_coverage"] = "true"
119
+ if indicator:
120
+ params["indicator"] = indicator
121
+ return await self._request(url, params, headers)
122
+
123
+ # ------------------------------------------------------------------
124
+ # CFTC Commitment of Traders (COT)
125
+ # ------------------------------------------------------------------
126
+ async def get_cot(
127
+ self,
128
+ currency: str,
129
+ start_date: Optional[str] = None,
130
+ end_date: Optional[str] = None,
131
+ ) -> dict:
132
+ currency = currency.lower()
133
+ url = f"{self.BASE_URL}/v1/cot/{currency}"
134
+ headers = self._auth_headers(currency)
135
+ params: dict[str, str] = {}
136
+ if start_date:
137
+ params["start_date"] = start_date
138
+ if end_date:
139
+ params["end_date"] = end_date
140
+ return await self._request(url, params, headers)
141
+
142
+ # ------------------------------------------------------------------
143
+ # Commodities (gold, silver, platinum)
144
+ # ------------------------------------------------------------------
145
+ async def get_commodities(
146
+ self,
147
+ indicator: str,
148
+ start_date: Optional[str] = None,
149
+ end_date: Optional[str] = None,
150
+ ) -> dict:
151
+ if not self.api_key:
152
+ raise FXMacroDataError("API key required for commodities endpoints.")
153
+ url = f"{self.BASE_URL}/v1/commodities/{indicator.lower()}"
154
+ headers: dict[str, str] = {"X-API-Key": self.api_key}
155
+ params: dict[str, str] = {}
156
+ if start_date:
157
+ params["start_date"] = start_date
158
+ if end_date:
159
+ params["end_date"] = end_date
160
+ return await self._request(url, params, headers)
@@ -0,0 +1,150 @@
1
+ import requests
2
+ from typing import Optional
3
+ from .exceptions import FXMacroDataError
4
+
5
+
6
+ class Client:
7
+ BASE_URL = "https://fxmacrodata.com/api"
8
+
9
+ def __init__(self, api_key: Optional[str] = None):
10
+ self.api_key = api_key
11
+
12
+ def _request(self, url: str, params: dict, headers: dict) -> dict:
13
+ try:
14
+ response = requests.get(url, headers=headers, params=params)
15
+ except Exception as e:
16
+ raise FXMacroDataError(f"Request failed: {e}")
17
+ if response.status_code != 200:
18
+ raise FXMacroDataError(f"{response.status_code} - {response.text}")
19
+ return response.json()
20
+
21
+ def _auth_headers(self, currency: str, *, required: bool = True) -> dict:
22
+ headers: dict[str, str] = {}
23
+ if currency != "usd":
24
+ if required and not self.api_key:
25
+ raise FXMacroDataError(
26
+ f"API key required for {currency.upper()} endpoints."
27
+ )
28
+ if self.api_key:
29
+ headers["X-API-Key"] = self.api_key
30
+ return headers
31
+
32
+ # ------------------------------------------------------------------
33
+ # Macroeconomic indicator time-series
34
+ # ------------------------------------------------------------------
35
+ def get_indicator(
36
+ self,
37
+ currency: str,
38
+ indicator: str,
39
+ start_date: Optional[str] = None,
40
+ end_date: Optional[str] = None,
41
+ ) -> dict:
42
+ currency = currency.lower()
43
+ url = f"{self.BASE_URL}/v1/announcements/{currency}/{indicator}"
44
+ headers = self._auth_headers(currency)
45
+ params: dict[str, str] = {}
46
+ if start_date:
47
+ params["start_date"] = start_date
48
+ if end_date:
49
+ params["end_date"] = end_date
50
+ return self._request(url, params, headers)
51
+
52
+ # ------------------------------------------------------------------
53
+ # FX spot rates (free)
54
+ # ------------------------------------------------------------------
55
+ def get_fx_price(
56
+ self,
57
+ base: str,
58
+ quote: str,
59
+ start_date: Optional[str] = None,
60
+ end_date: Optional[str] = None,
61
+ indicators: Optional[str] = None,
62
+ ) -> dict:
63
+ base = base.lower()
64
+ quote = quote.lower()
65
+ url = f"{self.BASE_URL}/v1/forex/{base}/{quote}"
66
+ params: dict[str, str] = {}
67
+ if start_date:
68
+ params["start_date"] = start_date
69
+ if end_date:
70
+ params["end_date"] = end_date
71
+ if indicators:
72
+ params["indicators"] = indicators
73
+ return self._request(url, params, {})
74
+
75
+ # ------------------------------------------------------------------
76
+ # Release calendar
77
+ # ------------------------------------------------------------------
78
+ def get_calendar(
79
+ self,
80
+ currency: str,
81
+ indicator: Optional[str] = None,
82
+ ) -> dict:
83
+ currency = currency.lower()
84
+ url = f"{self.BASE_URL}/v1/calendar/{currency}"
85
+ headers = self._auth_headers(currency, required=False)
86
+ params: dict[str, str] = {}
87
+ if indicator:
88
+ params["indicator"] = indicator
89
+ return self._request(url, params, headers)
90
+
91
+ # ------------------------------------------------------------------
92
+ # Data catalogue — available indicators for a currency
93
+ # ------------------------------------------------------------------
94
+ def get_data_catalogue(
95
+ self,
96
+ currency: str,
97
+ include_capabilities: bool = False,
98
+ include_coverage: bool = False,
99
+ indicator: Optional[str] = None,
100
+ ) -> dict:
101
+ currency = currency.lower()
102
+ url = f"{self.BASE_URL}/v1/data_catalogue/{currency}"
103
+ headers = self._auth_headers(currency)
104
+ params: dict[str, str] = {}
105
+ if include_capabilities:
106
+ params["include_capabilities"] = "true"
107
+ if include_coverage:
108
+ params["include_coverage"] = "true"
109
+ if indicator:
110
+ params["indicator"] = indicator
111
+ return self._request(url, params, headers)
112
+
113
+ # ------------------------------------------------------------------
114
+ # CFTC Commitment of Traders (COT)
115
+ # ------------------------------------------------------------------
116
+ def get_cot(
117
+ self,
118
+ currency: str,
119
+ start_date: Optional[str] = None,
120
+ end_date: Optional[str] = None,
121
+ ) -> dict:
122
+ currency = currency.lower()
123
+ url = f"{self.BASE_URL}/v1/cot/{currency}"
124
+ headers = self._auth_headers(currency)
125
+ params: dict[str, str] = {}
126
+ if start_date:
127
+ params["start_date"] = start_date
128
+ if end_date:
129
+ params["end_date"] = end_date
130
+ return self._request(url, params, headers)
131
+
132
+ # ------------------------------------------------------------------
133
+ # Commodities (gold, silver, platinum)
134
+ # ------------------------------------------------------------------
135
+ def get_commodities(
136
+ self,
137
+ indicator: str,
138
+ start_date: Optional[str] = None,
139
+ end_date: Optional[str] = None,
140
+ ) -> dict:
141
+ if not self.api_key:
142
+ raise FXMacroDataError("API key required for commodities endpoints.")
143
+ url = f"{self.BASE_URL}/v1/commodities/{indicator.lower()}"
144
+ headers: dict[str, str] = {"X-API-Key": self.api_key}
145
+ params: dict[str, str] = {}
146
+ if start_date:
147
+ params["start_date"] = start_date
148
+ if end_date:
149
+ params["end_date"] = end_date
150
+ return self._request(url, params, headers)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fxmacrodata
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Python client for the FXMacroData API, providing forex macroeconomic data for traders, quants, and analysts.
5
5
  Author-email: FXMacroData <info@fxmacrodata.com>
6
6
  License: MIT
@@ -34,7 +34,7 @@ Dynamic: license-file
34
34
  ![License](https://img.shields.io/github/license/fxmacrodata/fxmacrodata?style=flat-square)
35
35
  ![Build](https://img.shields.io/github/actions/workflow/status/fxmacrodata/fxmacrodata/ci-cd.yml?style=flat-square&logo=github)
36
36
 
37
- The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators** and **forex price history** from [FXMacroData](https://fxmacrodata.com/?utm_source=github&utm_medium=readme&utm_campaign=python_sdk).
37
+ The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators**, **forex prices**, **release calendars**, **COT positioning**, and **commodity prices** from [FXMacroData](https://fxmacrodata.com/?utm_source=github&utm_medium=readme&utm_campaign=python_sdk).
38
38
 
39
39
  It includes both synchronous and asynchronous clients, supports free USD endpoints, and offers a free Forex Price API for exchange rate data.
40
40
 
@@ -43,12 +43,12 @@ It includes both synchronous and asynchronous clients, supports free USD endpoin
43
43
  ## 🌟 Features
44
44
 
45
45
  - Fetch:
46
- - **Policy Rates**
47
- - **Inflation & CPI**
48
- - **GDP**
49
- - **Unemployment**
50
- - **Balance of Trade**
51
- - **Government Bond Yields**
46
+ - **Macroeconomic indicators** — policy rates, inflation, GDP, unemployment, bond yields, and 100+ more
47
+ - **FX spot rates** with optional technical indicators (SMA, RSI, MACD, Bollinger Bands)
48
+ - **Release calendars** — upcoming economic data release dates
49
+ - **Data catalogue** — discover available indicators per currency
50
+ - **COT data** — CFTC Commitment of Traders positioning
51
+ - **Commodity prices** — gold, silver, platinum
52
52
  - Free access to **USD** macro data.
53
53
  - Free **Forex Price API** (`get_fx_price`).
54
54
  - API key required only for **non-USD** indicators.
@@ -95,6 +95,26 @@ print(data)
95
95
  # Free Forex Price Endpoint
96
96
  fx = client.get_fx_price("usd", "gbp", start_date="2025-01-01")
97
97
  print(fx)
98
+
99
+ # Forex with technical indicators
100
+ fx = client.get_fx_price("eur", "usd", indicators="sma_20,rsi_14,macd")
101
+ print(fx)
102
+
103
+ # Release calendar
104
+ calendar = client.get_calendar("usd")
105
+ print(calendar)
106
+
107
+ # Data catalogue — discover available indicators
108
+ catalogue = client.get_data_catalogue("usd")
109
+ print(catalogue)
110
+
111
+ # COT positioning data
112
+ cot = client.get_cot("eur", start_date="2025-01-01")
113
+ print(cot)
114
+
115
+ # Commodity prices
116
+ gold = client.get_commodities("gold", start_date="2026-01-01")
117
+ print(gold)
98
118
  ```
99
119
 
100
120
  ---
@@ -108,13 +128,29 @@ from fxmacrodata import AsyncClient
108
128
  async def main():
109
129
  async with AsyncClient(api_key="YOUR_API_KEY") as client:
110
130
  # Fetch macroeconomic indicators
111
- data = await client.get_indicator("eur", "cpi")
131
+ data = await client.get_indicator("eur", "inflation")
112
132
  print(data)
113
133
 
114
134
  # Free Forex Price Endpoint
115
135
  fx = await client.get_fx_price("usd", "jpy")
116
136
  print(fx)
117
137
 
138
+ # Release calendar
139
+ calendar = await client.get_calendar("usd")
140
+ print(calendar)
141
+
142
+ # Data catalogue
143
+ catalogue = await client.get_data_catalogue("usd")
144
+ print(catalogue)
145
+
146
+ # COT positioning
147
+ cot = await client.get_cot("jpy")
148
+ print(cot)
149
+
150
+ # Commodity prices
151
+ gold = await client.get_commodities("gold")
152
+ print(gold)
153
+
118
154
  asyncio.run(main())
119
155
  ```
120
156
 
@@ -125,12 +161,39 @@ asyncio.run(main())
125
161
  ### `get_indicator(currency, indicator, start_date=None, end_date=None)`
126
162
  Fetches macroeconomic indicator time series data.
127
163
 
128
- - `currency`: `"usd"`, `"aud"`, `"eur"`, `"gbp"`, `"cad"`, `"nok"`, `"nzd"`, `"jpy"`, etc.
129
- - `indicator`: `"policy_rate"`, `"cpi"`, `"inflation"`, `"gdp"`, `"unemployment"`, `"trade_balance"`, `"current_account"`, etc.
164
+ - `currency`: `"usd"`, `"aud"`, `"eur"`, `"gbp"`, `"cad"`, `"nok"`, `"nzd"`, `"jpy"`, `"brl"`, `"cny"`, `"dkk"`, `"pln"`, `"sek"`, `"sgd"`, etc.
165
+ - `indicator`: `"policy_rate"`, `"inflation"`, `"gdp"`, `"unemployment"`, `"trade_balance"`, `"current_account_balance"`, `"gov_bond_10y"`, etc.
130
166
  - **API key required for non-USD.**
131
167
 
132
- ### `get_fx_price(base, quote, start_date=None, end_date=None)`
133
- Fetches historical FX prices between two currencies. **No API key needed for USD-based queries.**
168
+ ### `get_fx_price(base, quote, start_date=None, end_date=None, indicators=None)`
169
+ Fetches daily FX spot rates (ECB reference rates) between two currencies.
170
+
171
+ - `indicators`: Optional comma-separated technical indicators — `"sma_20"`, `"sma_50"`, `"sma_200"`, `"rsi_14"`, `"macd"`, `"ema_12"`, `"ema_26"`, `"bollinger_bands"`, or `"all"`.
172
+ - **No API key needed.**
173
+
174
+ ### `get_calendar(currency, indicator=None)`
175
+ Fetches upcoming economic data release dates for a currency.
176
+
177
+ - `indicator`: Optional filter to a specific indicator slug.
178
+ - Returns `announcement_datetime` (Unix timestamp) and `release` (indicator slug).
179
+
180
+ ### `get_data_catalogue(currency, include_capabilities=False, include_coverage=False, indicator=None)`
181
+ Discovers available macroeconomic indicators for a given currency.
182
+
183
+ - Returns a dict keyed by indicator slug with `name`, `unit`, `frequency`, and `has_official_forecast`.
184
+ - **API key required for non-USD.**
185
+
186
+ ### `get_cot(currency, start_date=None, end_date=None)`
187
+ Fetches CFTC Commitment of Traders (COT) positioning data.
188
+
189
+ - Supported currencies: `AUD`, `CAD`, `CHF`, `EUR`, `GBP`, `JPY`, `NZD`, `USD`.
190
+ - **API key required for non-USD.**
191
+
192
+ ### `get_commodities(indicator, start_date=None, end_date=None)`
193
+ Fetches commodity price time series.
194
+
195
+ - `indicator`: `"gold"`, `"silver"`, or `"platinum"`.
196
+ - **API key required.**
134
197
 
135
198
  ---
136
199
 
@@ -170,3 +233,5 @@ MIT License © FXMacroData
170
233
  - API Docs: https://fxmacrodata.com/docs
171
234
  - GitHub: https://github.com/fxmacrodata/fxmacrodata
172
235
  - PyPI: https://pypi.org/project/fxmacrodata/
236
+ - Readthedocs: https://fxmacrodata.readthedocs.io/en/latest/
237
+ - Zenodo: https://zenodo.org/records/18280968
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fxmacrodata"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "Python client for the FXMacroData API, providing forex macroeconomic data for traders, quants, and analysts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,164 @@
1
+ import os
2
+ import pytest
3
+ from dotenv import load_dotenv
4
+ from fxmacrodata.client import Client
5
+ from fxmacrodata.exceptions import FXMacroDataError
6
+
7
+ # Load API key from .env for tests
8
+ load_dotenv()
9
+ API_KEY = os.getenv("FXMACRODATA_API_KEY")
10
+
11
+
12
+ @pytest.fixture
13
+ def client_with_key():
14
+ return Client(api_key=API_KEY)
15
+
16
+
17
+ @pytest.fixture
18
+ def client_without_key():
19
+ return Client(api_key=None)
20
+
21
+
22
+ # ------------------------------------------------------------------
23
+ # get_indicator
24
+ # ------------------------------------------------------------------
25
+
26
+
27
+ def test_usd_endpoint_no_key(client_without_key):
28
+ """USD indicator endpoint should work without API key."""
29
+ result = client_without_key.get_indicator("usd", "gdp")
30
+ assert result["currency"] == "USD"
31
+ assert result["indicator"] == "gdp"
32
+ assert isinstance(result["data"], list)
33
+
34
+
35
+ def test_non_usd_endpoint_no_key(client_without_key):
36
+ """Non-USD indicator endpoint should raise FXMacroDataError without API key."""
37
+ with pytest.raises(FXMacroDataError) as exc:
38
+ client_without_key.get_indicator("aud", "gdp")
39
+ assert "API key required" in str(exc.value)
40
+
41
+
42
+ def test_non_usd_endpoint_with_key(client_with_key):
43
+ """Non-USD indicator endpoint should succeed with API key."""
44
+ result = client_with_key.get_indicator(
45
+ "aud", "gdp", start_date="2023-01-01", end_date="2023-12-31"
46
+ )
47
+ assert result["currency"] == "AUD"
48
+ assert result["indicator"] == "gdp"
49
+ assert isinstance(result["data"], list)
50
+
51
+
52
+ # ------------------------------------------------------------------
53
+ # get_fx_price
54
+ # ------------------------------------------------------------------
55
+
56
+
57
+ def test_free_fx_price_endpoint():
58
+ """Free forex price endpoint should return data without API key."""
59
+ client = Client()
60
+ result = client.get_fx_price("usd", "gbp", start_date="2025-01-01")
61
+ assert result["base"] == "USD"
62
+ assert result["quote"] == "GBP"
63
+ assert isinstance(result["data"], list)
64
+
65
+
66
+ def test_fx_price_with_indicators():
67
+ """Forex endpoint should accept technical indicator parameter."""
68
+ client = Client()
69
+ result = client.get_fx_price(
70
+ "eur", "usd", start_date="2026-01-01", indicators="sma_20,rsi_14"
71
+ )
72
+ assert result["base"] == "EUR"
73
+ assert result["quote"] == "USD"
74
+ assert "indicators" in result
75
+
76
+
77
+ # ------------------------------------------------------------------
78
+ # get_calendar
79
+ # ------------------------------------------------------------------
80
+
81
+
82
+ def test_calendar_usd_no_key(client_without_key):
83
+ """Calendar should return release dates for USD without API key."""
84
+ result = client_without_key.get_calendar("usd")
85
+ assert result["currency"] == "USD"
86
+ assert isinstance(result["data"], list)
87
+
88
+
89
+ def test_calendar_with_indicator_filter(client_without_key):
90
+ """Calendar should accept an indicator filter."""
91
+ result = client_without_key.get_calendar("usd", indicator="inflation")
92
+ assert result["currency"] == "USD"
93
+ assert isinstance(result["data"], list)
94
+
95
+
96
+ # ------------------------------------------------------------------
97
+ # get_data_catalogue
98
+ # ------------------------------------------------------------------
99
+
100
+
101
+ def test_data_catalogue_usd_no_key(client_without_key):
102
+ """USD data catalogue should work without API key."""
103
+ result = client_without_key.get_data_catalogue("usd")
104
+ assert isinstance(result, dict)
105
+ assert "inflation" in result
106
+
107
+
108
+ def test_data_catalogue_non_usd_no_key(client_without_key):
109
+ """Non-USD data catalogue should raise FXMacroDataError without API key."""
110
+ with pytest.raises(FXMacroDataError) as exc:
111
+ client_without_key.get_data_catalogue("aud")
112
+ assert "API key required" in str(exc.value)
113
+
114
+
115
+ def test_data_catalogue_non_usd_with_key(client_with_key):
116
+ """Non-USD data catalogue should succeed with API key."""
117
+ result = client_with_key.get_data_catalogue("aud")
118
+ assert isinstance(result, dict)
119
+ assert "policy_rate" in result
120
+
121
+
122
+ # ------------------------------------------------------------------
123
+ # get_cot
124
+ # ------------------------------------------------------------------
125
+
126
+
127
+ def test_cot_usd_no_key(client_without_key):
128
+ """USD COT data should work without API key."""
129
+ result = client_without_key.get_cot("usd")
130
+ assert result["currency"] == "USD"
131
+ assert isinstance(result["data"], list)
132
+
133
+
134
+ def test_cot_non_usd_no_key(client_without_key):
135
+ """Non-USD COT should raise FXMacroDataError without API key."""
136
+ with pytest.raises(FXMacroDataError) as exc:
137
+ client_without_key.get_cot("eur")
138
+ assert "API key required" in str(exc.value)
139
+
140
+
141
+ def test_cot_non_usd_with_key(client_with_key):
142
+ """Non-USD COT should succeed with API key."""
143
+ result = client_with_key.get_cot("eur", start_date="2025-01-01")
144
+ assert result["currency"] == "EUR"
145
+ assert isinstance(result["data"], list)
146
+
147
+
148
+ # ------------------------------------------------------------------
149
+ # get_commodities
150
+ # ------------------------------------------------------------------
151
+
152
+
153
+ def test_commodities_gold(client_with_key):
154
+ """Commodities gold endpoint should return price data."""
155
+ result = client_with_key.get_commodities("gold", start_date="2026-01-01")
156
+ assert result["indicator"] == "gold"
157
+ assert isinstance(result["data"], list)
158
+
159
+
160
+ def test_commodities_no_key(client_without_key):
161
+ """Commodities should raise FXMacroDataError without API key."""
162
+ with pytest.raises(FXMacroDataError) as exc:
163
+ client_without_key.get_commodities("gold")
164
+ assert "API key required" in str(exc.value)
@@ -1,83 +0,0 @@
1
- import aiohttp # type: ignore
2
- from typing import Optional
3
- from .exceptions import FXMacroDataError
4
-
5
-
6
- class AsyncClient:
7
- BASE_URL = "https://fxmacrodata.com/api"
8
-
9
- def __init__(self, api_key: Optional[str] = None):
10
- self.api_key: Optional[str] = api_key
11
- self.session: Optional[aiohttp.ClientSession] = None
12
-
13
- async def __aenter__(self) -> "AsyncClient":
14
- self.session = aiohttp.ClientSession()
15
- return self
16
-
17
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
18
- if self.session:
19
- await self.session.close()
20
- self.session = None
21
-
22
- async def get_indicator(
23
- self,
24
- currency: str,
25
- indicator: str,
26
- start_date: Optional[str] = None,
27
- end_date: Optional[str] = None,
28
- ) -> dict:
29
- currency = currency.lower()
30
- url = f"{self.BASE_URL}/{currency}/{indicator}"
31
-
32
- headers = {}
33
- if currency != "usd":
34
- if not self.api_key:
35
- raise FXMacroDataError(
36
- f"API key required for {currency.upper()} endpoints."
37
- )
38
- headers["X-API-Key"] = self.api_key
39
-
40
- params = {}
41
- if start_date:
42
- params["start_date"] = start_date
43
- if end_date:
44
- params["end_date"] = end_date
45
-
46
- if not self.session:
47
- self.session = aiohttp.ClientSession()
48
-
49
- async with self.session.get(url, headers=headers, params=params) as resp:
50
- if resp.status != 200:
51
- text = await resp.text()
52
- raise FXMacroDataError(f"{resp.status} - {text}")
53
- return await resp.json()
54
-
55
- # --------------------------------------------
56
- # NEW: Free Forex Endpoint (Async)
57
- # --------------------------------------------
58
- async def get_fx_price(
59
- self,
60
- base: str,
61
- quote: str,
62
- start_date: Optional[str] = None,
63
- end_date: Optional[str] = None,
64
- ) -> dict:
65
- base = base.lower()
66
- quote = quote.lower()
67
-
68
- url = f"{self.BASE_URL}/forex/{base}/{quote}"
69
-
70
- params = {}
71
- if start_date:
72
- params["start_date"] = start_date
73
- if end_date:
74
- params["end_date"] = end_date
75
-
76
- if not self.session:
77
- self.session = aiohttp.ClientSession()
78
-
79
- async with self.session.get(url, params=params) as resp:
80
- if resp.status != 200:
81
- text = await resp.text()
82
- raise FXMacroDataError(f"{resp.status} - {text}")
83
- return await resp.json()
@@ -1,75 +0,0 @@
1
- import requests
2
- from typing import Optional
3
- from .exceptions import FXMacroDataError
4
-
5
-
6
- class Client:
7
- BASE_URL = "https://fxmacrodata.com/api"
8
-
9
- def __init__(self, api_key: Optional[str] = None):
10
- self.api_key = api_key
11
-
12
- def get_indicator(
13
- self,
14
- currency: str,
15
- indicator: str,
16
- start_date: Optional[str] = None,
17
- end_date: Optional[str] = None,
18
- ) -> dict:
19
- currency = currency.lower()
20
- url = f"{self.BASE_URL}/{currency}/{indicator}"
21
-
22
- headers = {}
23
- if currency != "usd":
24
- if not self.api_key:
25
- raise FXMacroDataError(
26
- f"API key required for {currency.upper()} endpoints."
27
- )
28
- headers["X-API-Key"] = self.api_key
29
-
30
- params = {}
31
- if start_date:
32
- params["start_date"] = start_date
33
- if end_date:
34
- params["end_date"] = end_date
35
-
36
- try:
37
- response = requests.get(url, headers=headers, params=params)
38
- except Exception as e:
39
- raise FXMacroDataError(f"Request failed: {e}")
40
-
41
- if response.status_code != 200:
42
- raise FXMacroDataError(f"{response.status_code} - {response.text}")
43
-
44
- return response.json()
45
-
46
- # --------------------------------------------
47
- # NEW: Free Forex Endpoint
48
- # --------------------------------------------
49
- def get_fx_price(
50
- self,
51
- base: str,
52
- quote: str,
53
- start_date: Optional[str] = None,
54
- end_date: Optional[str] = None,
55
- ) -> dict:
56
- base = base.lower()
57
- quote = quote.lower()
58
-
59
- url = f"{self.BASE_URL}/forex/{base}/{quote}"
60
-
61
- params = {}
62
- if start_date:
63
- params["start_date"] = start_date
64
- if end_date:
65
- params["end_date"] = end_date
66
-
67
- try:
68
- response = requests.get(url, params=params)
69
- except Exception as e:
70
- raise FXMacroDataError(f"Request failed: {e}")
71
-
72
- if response.status_code != 200:
73
- raise FXMacroDataError(f"{response.status_code} - {response.text}")
74
-
75
- return response.json()
@@ -1,53 +0,0 @@
1
- import os
2
- import pytest
3
- from dotenv import load_dotenv
4
- from fxmacrodata.client import Client
5
- from fxmacrodata.exceptions import FXMacroDataError
6
-
7
- # Load API key from .env for tests
8
- load_dotenv()
9
- API_KEY = os.getenv("FXMACRODATA_API_KEY")
10
-
11
-
12
- @pytest.fixture
13
- def client_with_key():
14
- return Client(api_key=API_KEY)
15
-
16
-
17
- @pytest.fixture
18
- def client_without_key():
19
- return Client(api_key=None)
20
-
21
-
22
- def test_usd_endpoint_no_key(client_without_key):
23
- """USD indicator endpoint should work without API key."""
24
- result = client_without_key.get_indicator("usd", "gdp")
25
- assert result["currency"] == "USD"
26
- assert result["indicator"] == "gdp"
27
- assert isinstance(result["data"], list)
28
-
29
-
30
- def test_non_usd_endpoint_no_key(client_without_key):
31
- """Non-USD indicator endpoint should raise FXMacroDataError without API key."""
32
- with pytest.raises(FXMacroDataError) as exc:
33
- client_without_key.get_indicator("aud", "gdp")
34
- assert "API key required" in str(exc.value)
35
-
36
-
37
- def test_non_usd_endpoint_with_key(client_with_key):
38
- """Non-USD indicator endpoint should succeed with API key."""
39
- result = client_with_key.get_indicator(
40
- "aud", "gdp", start_date="2023-01-01", end_date="2023-12-31"
41
- )
42
- assert result["currency"] == "AUD"
43
- assert result["indicator"] == "gdp"
44
- assert isinstance(result["data"], list)
45
-
46
-
47
- def test_free_fx_price_endpoint():
48
- """Free forex price endpoint should return data without API key."""
49
- client = Client() # no key needed for forex endpoints
50
- result = client.get_fx_price("usd", "gbp", start_date="2025-01-01")
51
- assert result["base"] == "USD"
52
- assert result["quote"] == "GBP"
53
- assert isinstance(result["data"], dict)
File without changes
File without changes