Go es cada vez más popular para herramientas financieras su rápida compilación, rendimiento predecible y primitivas de concurrencia de primera clase lo convierten en un ajuste natural para tuberías de datos y automatización de operaciones. Esta guía recorre todo lo que necesita para llamar a la API REST FXMacroData de Go, desde una simple solicitud única hasta un cliente reutilizable que maneja tiempos de espera, decodificación JSON y filtrado de fechas opcional. Al final tendrá código idiomático de Go que recupera anuncios de indicadores macro, extrae un calendario de lanzamiento próximo y consulta las tasas spot FX todo en menos de 150 líneas.
Lo que construirás
Un cliente HTTP Go ligero que se autentica contra la API REST FXMacroData utilizando la clave de API de autenticación de parámetros de consulta, decodifica las respuestas JSON estructuradas en estructuras Go escritas y expone funciones de ayuda reutilizables para las tres familias de puntos finales más comunes: anuncios, calendario de lanzamiento y tasas spot FX.
Los requisitos previos
- Go 1.21 o posterior (el sistema de gestión de datos de la empresa)
- Una clave de la API de FXMacroData registrarse en / suscribirse para recibir uno
- Familiaridad básica con los módulos de Go y la biblioteca estándar
No se necesitan bibliotecas HTTP o JSON de terceros el estándar net/http ¿ Qué ?
encoding/json Los paquetes manejan todo.
Paso 1 Comprender la forma de la API
Cada punto final del indicador FXMacroData sigue el mismo patrón de URL:
GET https://fxmacrodata.com/api/v1/announcements/{currency}/{indicator}?api_key=YOUR_API_KEY
La respuesta es un objeto JSON con un nivel superior data Cada elemento contiene un
date una cadena, un número. val, y opcionalmente un announcement_datetime
que da precisión de segundo nivel para cuando la cifra fue lanzada oficialmente.
curl "https://fxmacrodata.com/api/v1/announcements/usd/wages?api_key=YOUR_API_KEY&start=2024-01-01"
{
"data": [
{ "date": "2025-03-14", "val": 4.0, "announcement_datetime": "2025-03-14T12:30:00Z" },
{ "date": "2025-02-07", "val": 4.1, "announcement_datetime": "2025-02-07T13:30:00Z" },
{ "date": "2025-01-10", "val": 3.9, "announcement_datetime": "2025-01-10T13:30:00Z" }
]
}
La misma estructura de sobre se aplica a los puntos finales de calendario y forex, lo que hace que sea sencillo escribir un único decodificador genérico y tipos especializados por punto final en la parte superior.
Paso 2 Inicie su módulo
Crear un nuevo directorio para el proyecto e iniciar un módulo Go:
mkdir fxmd-go && cd fxmd-go
go mod init fxmd
Almacene su clave de API en una variable de entorno en lugar de codificarla.
export FXMD_API_KEY="your_actual_api_key_here"
Consejo de seguridad
Nunca codifique las claves de API en archivos fuente o las comprometa al control de versiones. En producción, use un administrador de secretos o una variable de entorno CI / CD. El patrón que se muestra aquí lee la clave al inicio y falla rápidamente si falta la variable.
Paso 3 Definir los tipos de respuesta
Crear un archivo llamado fxmd.go. Comience definiendo las estructuras Go que se asignan a las formas JSON devueltas por las tres familias de puntos finales que utilizará:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
const baseURL = "https://fxmacrodata.com/api/v1"
// DataPoint represents a single indicator observation.
type DataPoint struct {
Date string `json:"date"`
Val float64 `json:"val"`
AnnouncementDatetime string `json:"announcement_datetime,omitempty"`
}
// AnnouncementsResponse is the envelope for indicator announcement endpoints.
type AnnouncementsResponse struct {
Data []DataPoint `json:"data"`
}
// CalendarEvent represents one upcoming or recent release on the calendar.
type CalendarEvent struct {
Date string `json:"date"`
Indicator string `json:"indicator"`
Expected float64 `json:"expected,omitempty"`
Prior float64 `json:"prior,omitempty"`
Actual float64 `json:"actual,omitempty"`
}
// CalendarResponse is the envelope for the release calendar endpoint.
type CalendarResponse struct {
Data []CalendarEvent `json:"data"`
}
// ForexPoint represents a single FX spot-rate observation.
type ForexPoint struct {
Date string `json:"date"`
Rate float64 `json:"rate"`
}
// ForexResponse is the envelope for the FX spot-rate endpoint.
type ForexResponse struct {
Data []ForexPoint `json:"data"`
}
Paso 4 Construir un cliente reutilizable
Un envoltorio delgado alrededor . net/http El cliente lee la clave de la API una vez en la construcción, la inyecta como un parámetro de consulta en cada solicitud, y aplica un tiempo de espera para que un lento upstream nunca pueda detener su programa indefinidamente:
// Client is a lightweight FXMacroData API client.
type Client struct {
apiKey string
httpClient *http.Client
}
// NewClient creates a Client that reads the API key from the FXMD_API_KEY environment variable.
// It panics at startup if the variable is not set — fail-fast is safer than silent empty results.
func NewClient() *Client {
key := os.Getenv("FXMD_API_KEY")
if key == "" {
panic("FXMD_API_KEY environment variable is not set")
}
return &Client{
apiKey: key,
httpClient: &http.Client{
Timeout: 15 * time.Second,
},
}
}
// get performs a GET request to the given path with optional query parameters.
// It decodes the JSON body into dest.
func (c *Client) get(path string, params url.Values, dest any) error {
if params == nil {
params = url.Values{}
}
params.Set("api_key", c.apiKey)
u := baseURL + path + "?" + params.Encode()
resp, err := c.httpClient.Get(u)
if err != nil {
return fmt.Errorf("http get %s: %w", path, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("api error %d on %s: %s", resp.StatusCode, path, body)
}
if err := json.NewDecoder(resp.Body).Decode(dest); err != nil {
return fmt.Errorf("json decode %s: %w", path, err)
}
return nil
}
Usando json.NewDecoder En el cuerpo de respuesta (en lugar de leer primero el cuerpo en una porción de byte) es el enfoque idiomático en Go: transmite la decodificación directamente desde la conexión HTTP, evitando una asignación intermedia para grandes cargas útiles.
Paso 5 Añadir auxiliares de escritura para cada familia de puntos finales
Envuelva el genérico . get método con ayudantes escritos para que los llamadores nunca tengan que construir rutas o decodificar sobres manualmente:
// GetAnnouncements retrieves indicator announcement data for the given currency and indicator slug.
// start and end are optional date strings in "YYYY-MM-DD" format; pass "" to omit them.
//
// Example: client.GetAnnouncements("usd", "wages", "2024-01-01", "")
// Full indicator catalogue: https://fxmacrodata.com/api-data-docs/usd/wages
func (c *Client) GetAnnouncements(currency, indicator, start, end string) (*AnnouncementsResponse, error) {
params := url.Values{}
if start != "" {
params.Set("start", start)
}
if end != "" {
params.Set("end", end)
}
var out AnnouncementsResponse
err := c.get("/announcements/"+currency+"/"+indicator, params, &out)
return &out, err
}
// GetCalendar retrieves the upcoming release calendar for the given currency code.
//
// Example: client.GetCalendar("usd")
func (c *Client) GetCalendar(currency string) (*CalendarResponse, error) {
var out CalendarResponse
err := c.get("/calendar/"+currency, nil, &out)
return &out, err
}
// GetForex retrieves FX spot-rate history for a currency pair.
// start and end are optional date strings in "YYYY-MM-DD" format.
//
// Example: client.GetForex("eur", "usd", "2024-01-01", "")
func (c *Client) GetForex(base, quote, start, end string) (*ForexResponse, error) {
params := url.Values{}
if start != "" {
params.Set("start", start)
}
if end != "" {
params.Set("end", end)
}
var out ForexResponse
err := c.get("/forex/"+base+"/"+quote, params, &out)
return &out, err
}
Paso 6 Enlaza todo main
Con el cliente y los ayudantes en su lugar, llamar a la API es concisa y segura de tipo. main
¿ Qué es ? fxmd.go que ejerce las tres familias de puntos de referencia:
func main() {
client := NewClient()
// --- Announcements: US wages ---
fmt.Println("=== US Wages (last 5 releases) ===")
wages, err := client.GetAnnouncements("usd", "wages", "2024-01-01", "")
if err != nil {
fmt.Println("error:", err)
} else {
for i, pt := range wages.Data {
if i >= 5 {
break
}
fmt.Printf(" %s val=%.2f released=%s\n", pt.Date, pt.Val, pt.AnnouncementDatetime)
}
}
// --- Release calendar: upcoming USD events ---
fmt.Println("\n=== Upcoming USD Releases ===")
cal, err := client.GetCalendar("usd")
if err != nil {
fmt.Println("error:", err)
} else {
for i, ev := range cal.Data {
if i >= 5 {
break
}
fmt.Printf(" %s %s prior=%.2f expected=%.2f\n",
ev.Date, ev.Indicator, ev.Prior, ev.Expected)
}
}
// --- FX spot rates: EUR/USD ---
fmt.Println("\n=== EUR/USD Spot Rate (last 5 days) ===")
fx, err := client.GetForex("eur", "usd", "2025-01-01", "")
if err != nil {
fmt.Println("error:", err)
} else {
for i, pt := range fx.Data {
if i >= 5 {
break
}
fmt.Printf(" %s rate=%.5f\n", pt.Date, pt.Rate)
}
}
}
Ejecuta el programa:
go run fxmd.go
Debería ver una salida similar a:
=== US Wages (last 5 releases) ===
2025-03-14 val=4.00 released=2025-03-14T12:30:00Z
2025-02-07 val=4.10 released=2025-02-07T13:30:00Z
2025-01-10 val=3.90 released=2025-01-10T13:30:00Z
2024-12-06 val=4.00 released=2024-12-06T13:30:00Z
2024-11-01 val=4.00 released=2024-11-01T12:30:00Z
=== Upcoming USD Releases ===
2025-04-25 gdp prior=2.30 expected=0.40
2025-05-02 non_farm_payrolls prior=228.00 expected=135.00
2025-05-13 inflation prior=2.40 expected=2.30
=== EUR/USD Spot Rate (last 5 days) ===
2025-04-17 rate=1.13452
2025-04-16 rate=1.13680
2025-04-15 rate=1.13590
2025-04-14 rate=1.13320
2025-04-11 rate=1.13580
Consejo Recogidas concomitantes con las rutinas de oro
Debido a que el cliente es seguro para uso simultáneo, se puede extender varias solicitudes de indicadores en paralelo utilizando goroutines y sync.WaitGroup ¿ Qué ? errgroupEsto es especialmente útil cuando se construye un panel de control que extrae varias monedas a la vez el tiempo del reloj de pared se mantiene cerca de la latencia de la solicitud individual más lenta en lugar de la suma de todas las solicitudes.
Paso 7 Manejar la paginación y los rangos de fecha
Por defecto, el punto final de anuncios devuelve todo el historial disponible.
Suministro de dinero en M2 ¿ Qué ? El PIB a menudo querrá limitar la ventana usando start ¿ Qué ? end los dos ayudantes ya los admiten. Aquí hay un ejemplo enfocado que sólo recupera el trimestre más reciente de
Empleo a tiempo parcial en EE.UU. datos:
// Fetch US part-time employment for the last 90 days
end := time.Now().Format("2006-01-02")
start := time.Now().AddDate(0, 0, -90).Format("2006-01-02")
pt, err := client.GetAnnouncements("usd", "part_time_employment", start, end)
if err != nil {
log.Fatal(err)
}
for _, dp := range pt.Data {
fmt.Printf("%s %.0f\n", dp.Date, dp.Val)
}
Es el estándar de Go. time Los formatos de los paquetes datan de "2006-01-02" (la hora de referencia es una mnemónica: 01/02 03:04:05 PM '06 -0700), que coincide con las cadenas de fecha ISO-8601 esperadas por la API.
Paso 8 Parse announcement_datetime como un time.Time
El announcement_datetime Si su aplicación necesita calcular cuentas regresivas de tiempo de liberación, ordenar eventos o datos de cubo por sesión, analizarlo en un
time.Time el valor:
const rfc3339 = time.RFC3339
for _, dp := range wages.Data {
if dp.AnnouncementDatetime == "" {
continue
}
t, err := time.Parse(rfc3339, dp.AnnouncementDatetime)
if err != nil {
log.Printf("bad datetime %q: %v", dp.AnnouncementDatetime, err)
continue
}
until := time.Until(t)
fmt.Printf("%s val=%.2f in %s\n", dp.Date, dp.Val, until.Round(time.Minute))
}
Lista completa de archivos
Para su referencia, aquí está la implementación completa de un solo archivo descrita en los pasos anteriores:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
const baseURL = "https://fxmacrodata.com/api/v1"
type DataPoint struct {
Date string `json:"date"`
Val float64 `json:"val"`
AnnouncementDatetime string `json:"announcement_datetime,omitempty"`
}
type AnnouncementsResponse struct {
Data []DataPoint `json:"data"`
}
type CalendarEvent struct {
Date string `json:"date"`
Indicator string `json:"indicator"`
Expected float64 `json:"expected,omitempty"`
Prior float64 `json:"prior,omitempty"`
Actual float64 `json:"actual,omitempty"`
}
type CalendarResponse struct {
Data []CalendarEvent `json:"data"`
}
type ForexPoint struct {
Date string `json:"date"`
Rate float64 `json:"rate"`
}
type ForexResponse struct {
Data []ForexPoint `json:"data"`
}
type Client struct {
apiKey string
httpClient *http.Client
}
func NewClient() *Client {
key := os.Getenv("FXMD_API_KEY")
if key == "" {
panic("FXMD_API_KEY environment variable is not set")
}
return &Client{
apiKey: key,
httpClient: &http.Client{Timeout: 15 * time.Second},
}
}
func (c *Client) get(path string, params url.Values, dest any) error {
if params == nil {
params = url.Values{}
}
params.Set("api_key", c.apiKey)
u := baseURL + path + "?" + params.Encode()
resp, err := c.httpClient.Get(u)
if err != nil {
return fmt.Errorf("http get %s: %w", path, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("api error %d on %s: %s", resp.StatusCode, path, body)
}
return json.NewDecoder(resp.Body).Decode(dest)
}
func (c *Client) GetAnnouncements(currency, indicator, start, end string) (*AnnouncementsResponse, error) {
params := url.Values{}
if start != "" { params.Set("start", start) }
if end != "" { params.Set("end", end) }
var out AnnouncementsResponse
return &out, c.get("/announcements/"+currency+"/"+indicator, params, &out)
}
func (c *Client) GetCalendar(currency string) (*CalendarResponse, error) {
var out CalendarResponse
return &out, c.get("/calendar/"+currency, nil, &out)
}
func (c *Client) GetForex(base, quote, start, end string) (*ForexResponse, error) {
params := url.Values{}
if start != "" { params.Set("start", start) }
if end != "" { params.Set("end", end) }
var out ForexResponse
return &out, c.get("/forex/"+base+"/"+quote, params, &out)
}
func main() {
client := NewClient()
wages, err := client.GetAnnouncements("usd", "wages", "2024-01-01", "")
if err != nil {
fmt.Println("wages error:", err)
} else {
fmt.Println("=== US Wages ===")
for i, pt := range wages.Data {
if i >= 5 { break }
fmt.Printf(" %s %.2f\n", pt.Date, pt.Val)
}
}
cal, err := client.GetCalendar("usd")
if err != nil {
fmt.Println("calendar error:", err)
} else {
fmt.Println("=== Upcoming USD Releases ===")
for i, ev := range cal.Data {
if i >= 5 { break }
fmt.Printf(" %s %s\n", ev.Date, ev.Indicator)
}
}
fx, err := client.GetForex("eur", "usd", "2025-01-01", "")
if err != nil {
fmt.Println("forex error:", err)
} else {
fmt.Println("=== EUR/USD ===")
for i, pt := range fx.Data {
if i >= 5 { break }
fmt.Printf(" %s %.5f\n", pt.Date, pt.Rate)
}
}
}
¿Qué sigue?
Ahora tiene un cliente Go que cubre las tres familias de puntos finales FXMacroData más comunes.
- Ampliar las definiciones de estructura para capturar campos adicionales como
revisedvalores desde el punto final del calendario. - Añadir un
errgroup- basado en ventilador para obtener varias monedas simultáneamente elClientes seguro compartirlo entre los grupos porquehttp.Clientes segura para la concurrencia por diseño. - Persistir resultados a una base de datos SQLite local ¿ Cómo lo haces ?
database/sqlpara pruebas de retroceso ligeras o para la investigación de señales. - Explore el catálogo completo de indicadores para USD en /api-data-docs/USD/salarios Las personas que trabajan en el sector privado son más favorecidas por la y para otras monedas en el panel de control busque M2 de las demás¿ Qué ? trabajo a tiempo parcial, y El PIB En el caso de los productos de la industria, la demanda de productos de alta calidad es la principal fuente de demanda.