Como usar a API FXMacroData com o Go banner image

Implementation

How-To Guides

Como usar a API FXMacroData com o Go

Um guia passo a passo para chamar a FXMacroData REST API a partir de Go cobrindo configuração de cliente net/http, autenticação de parâmetros de consulta, decodificação JSON e auxiliares reutilizáveis para anúncios, o calendário de lançamento e taxas spot FX.

Também disponível em English

Go é cada vez mais popular para ferramentas financeiras sua rápida compilação, desempenho previsível e primitivos de simultâneo de primeira classe tornam-no um ajuste natural para pipelines de dados e automação de negociação. Este guia percorre tudo o que você precisa para chamar a API REST FXMacroData do Go, desde uma simples solicitação única até um cliente reutilizável que lida com timeouts, decodificação JSON e filtragem de data opcional. No final, você terá código idiomático Go que recupera anúncios de indicadores macro, puxa um calendário de lançamento próximo e consulta taxas spot FX tudo em menos de 150 linhas.

O que você vai construir

Um cliente HTTP Go leve que autentica contra a API REST FXMacroData usando a autenticação do parâmetro de consulta API-key, decodifica respostas JSON estruturadas em estruturas Go digitadas e expõe funções auxiliares reutilizáveis para as três famílias de endpoints mais comuns: anúncios, calendário de lançamento e taxas spot FX.

Requisitos prévios

  • Go 1.21 ou posterior (Go.dev/dl)
  • Uma chave da API do FXMacroData registe-se em / subscrever para receber uma
  • Familiarização básica com os módulos Go e com a biblioteca padrão

Não são necessárias bibliotecas HTTP ou JSON de terceiros o padrão net/http E ... encoding/json Os pacotes tratam de tudo.

Passo 1 Compreender a forma da API

Cada ponto final do indicador FXMacroData segue o mesmo padrão de URL:

GET https://fxmacrodata.com/api/v1/announcements/{currency}/{indicator}?api_key=YOUR_API_KEY

A resposta é um objeto JSON com um nível superior data Cada elemento contém um date Uma corda numérica. val, e opcionalmente um announcement_datetime que dá precisão de segundo nível para quando o número foi oficialmente lançado.

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" }
  ]
}

A mesma estrutura de envelope se aplica aos endpoints de calendário e forex, tornando mais fácil escrever um decodificador genérico único e tipos especializados por endpoint em cima dele.

Passo 2 Iniciar o seu módulo

Crie um novo diretório para o projeto e inicie um módulo Go:

mkdir fxmd-go && cd fxmd-go
go mod init fxmd

Você pode exportá-lo para a sessão do shell atual:

export FXMD_API_KEY="your_actual_api_key_here"

Dica de segurança

Nunca codifique as chaves da API em arquivos de origem ou as comprometa ao controle de versão. Na produção, use um gerenciador de segredos ou uma variável de ambiente CI / CD. O padrão mostrado aqui lê a chave no início e falha rapidamente se a variável estiver faltando.

Passo 3 Definir tipos de resposta

Crie um arquivo chamado fxmd.go. Comece definindo as estruturas Go que mapeam as formas JSON retornadas pelas três famílias de endpoints que você usará:

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"`
}

Passo 4 Construir um cliente reutilizável

Um envoltório fino à volta . net/http O cliente lê a chave API uma vez na construção, injeta-a como um parâmetro de consulta em cada solicitação e impõe um timeout para que um upstream lento nunca possa bloquear o seu 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 no corpo da resposta (em vez de ler o corpo em uma fatia de byte primeiro) é a abordagem idiomática em Go: transmite a decodificação diretamente da conexão HTTP, evitando uma alocação intermediária para grandes cargas úteis.

Passo 5 Adicionar auxiliares de digitação para cada família de pontos finais

Enrole o genérico . get método com ajudantes digitados para que os chamadores nunca tenham que construir caminhos ou decodificar envelopes 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
}

Passo 6 Junta tudo main

Com o cliente e auxiliares no lugar, chamando a API é concisa e tipo-seguro. main Função para fxmd.go que exerce as três famílias de endpoints:

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)
		}
	}
}

Execute o programa:

go run fxmd.go

Você deve ver uma saída semelhante 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

Dica Acompanhamento de buscas com rotinas

Como o cliente é seguro para uso simultâneo, você pode distribuir várias solicitações de indicadores em paralelo usando goroutines e sync.WaitGroup Ou ... errgroup. Isto é especialmente útil quando se constrói um painel que puxa várias moedas de uma só vez o tempo do relógio de parede fica perto da latência da solicitação mais lenta em vez da soma de todas as solicitações.

Passo 7 Lidar com a paginação e os intervalos de datas

Por padrão, o ponto final de anúncios retorna todo o histórico disponível. M2 oferta monetária Ou ... PIB muitas vezes você vai querer limitar a janela usando start E ... end Para o exemplo acima, a função de "query" é a de "request" e "question" ambos os auxiliares já os suportam. Emprego a tempo parcial nos EUA dados:

// 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)
}

É o padrão do Go. time formatos de embalagem data de "2006-01-02" (a hora de referência é uma mnemônica: 01/02 03:04:05 PM '06 -0700), que corresponde às cadeias de datas ISO-8601 esperadas pela API.

Passo 8 Analisar announcement_datetime Como um time.Time

O ... announcement_datetime campo é uma string UTC RFC-3339. Se o seu aplicativo precisa calcular contagens regressivas de tempo de lançamento, ordenar eventos ou dados de balde por sessão, analise-o em um time.Time 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 arquivos

Para referência, aqui está a implementação completa de um único arquivo descrita nas etapas acima:

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)
		}
	}
}

O que vem a seguir?

Agora você tem um cliente Go que cobre as três famílias de endpoints FXMacroData mais comuns.

  • Aumentar as definições de estruturas para capturar campos adicionais como revised valores a partir do ponto final do calendário.
  • Adicione um errgroup- baseado em ventilador para obter várias moedas simultaneamente o Client É seguro compartilhar entre as rotinas porque http.Client é segura de simultâneo por conceção.
  • Perseverar resultados para um banco de dados SQLite local Usando database/sql para backtesting leve ou pesquisa de sinais.
  • Explorar o catálogo completo dos indicadores para USD em /api-data-docs/usd/salários e para outras moedas no painel de instrumentos procure M2- Não . trabalho a tempo parcialE ... PIB A Comissão considera que a utilização de um sistema de informação de base é uma das principais razões para a criação de um "sistema de informação baseado em dados".

Blogroll