Cómo utilizar la API FXMacroData con Go banner image

Implementation

How-To Guides

Cómo utilizar la API FXMacroData con Go

Una guía paso a paso para llamar a la API REST FXMacroData desde Go que cubre la configuración del cliente net/http, la autenticación de parámetros de consulta, la decodificación JSON y los ayudantes reutilizables para anuncios, el calendario de lanzamiento y las tasas spot de FX.

Disponible también en English

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

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 revised valores desde el punto final del calendario.
  • Añadir un errgroup- basado en ventilador para obtener varias monedas simultáneamente el Client es seguro compartirlo entre los grupos porque http.Client es segura para la concurrencia por diseño.
  • Persistir resultados a una base de datos SQLite local ¿ Cómo lo haces ? database/sql para 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.

Blogroll