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.
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/PKG-INFO +78 -13
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/README.md +77 -12
- fxmacrodata-1.1.0/fxmacrodata/async_client.py +160 -0
- fxmacrodata-1.1.0/fxmacrodata/client.py +150 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata.egg-info/PKG-INFO +78 -13
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/pyproject.toml +1 -1
- fxmacrodata-1.1.0/tests/test_client.py +164 -0
- fxmacrodata-1.0.0/fxmacrodata/async_client.py +0 -83
- fxmacrodata-1.0.0/fxmacrodata/client.py +0 -75
- fxmacrodata-1.0.0/tests/test_client.py +0 -53
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/LICENSE +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata/__init__.py +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata/exceptions.py +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata/utils.py +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata.egg-info/SOURCES.txt +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata.egg-info/dependency_links.txt +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata.egg-info/requires.txt +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/fxmacrodata.egg-info/top_level.txt +0 -0
- {fxmacrodata-1.0.0 → fxmacrodata-1.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fxmacrodata
|
|
3
|
-
Version: 1.
|
|
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
|

|
|
35
35
|

|
|
36
36
|
|
|
37
|
-
The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators**
|
|
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
|
-
- **
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
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", "
|
|
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"`, `"
|
|
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
|
|
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
|

|
|
6
6
|

|
|
7
7
|
|
|
8
|
-
The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators**
|
|
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
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
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", "
|
|
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"`, `"
|
|
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
|
|
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.
|
|
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
|

|
|
35
35
|

|
|
36
36
|
|
|
37
|
-
The **FXMacroData Python SDK** provides a simple and efficient interface for fetching **macroeconomic indicators**
|
|
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
|
-
- **
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
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", "
|
|
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"`, `"
|
|
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
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|