oanda

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 10 Imported by: 0

README

oanda-go

A dependency-free Go client for the Oanda REST v20 API. It covers instrument candles, account lookup, open trades, market orders, take-profit orders, and instrument facts — the surface needed to fetch price history and drive a simple trading flow. Structured after Oanda's own development guide.

The core module imports nothing but the Go standard library. A separate, nested module (backtestsource/) adapts the client to gobacktest's source.Source interface for backtesting — pull it in only if you need that integration; the core client never depends on it.

Install

go get github.com/florinel-chis/oanda-go

Authentication

Every call is authenticated with an Oanda personal access token, supplied by the caller at construction:

c := oanda.New(token)              // live: api-fxtrade.oanda.com
c := oanda.New(token, oanda.Practice())  // practice: api-fxpractice.oanda.com

The token travels only in the Authorization: Bearer <token> header. The package never reads it from an environment variable itself (callers choose how to supply it — see the examples, which read OANDA_TOKEN), never logs it, and never includes it in an error message or a request URL. This is covered by a dedicated test (TestGetCandlesNon200ErrorExcludesToken) that asserts the token cannot leak into error text.

Quickstart

Fetch the last day of H1 candles for EUR_USD and print the 5 most recent:

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	oanda "github.com/florinel-chis/oanda-go"
)

func main() {
	c := oanda.New(os.Getenv("OANDA_TOKEN"), oanda.Practice())

	end := time.Now().UTC()
	start := end.Add(-24 * time.Hour)

	candles, err := c.GetCandles(context.Background(), "EUR_USD", "H1", start, end)
	if err != nil {
		log.Fatal(err)
	}
	for _, cd := range candles {
		fmt.Println(cd.Time, cd.Open, cd.High, cd.Low, cd.Close)
	}
}

Runnable versions of this and two other flows live in examples/:

Example What it does
examples/candles Fetch H1 EUR_USD candles, print the last 5
examples/account Resolve the account ID, print InstrumentFacts + avg spread for EUR_USD
examples/marketorder Place a 1-unit market buy, attach a take-profit (practice-only, guarded)

Each reads its token from the OANDA_TOKEN environment variable — export it to a personal access token, then run e.g.:

go run ./examples/candles

Endpoint coverage

Endpoint Method Status
instruments/{instrument}/candles GetCandles done
accounts AccountID done
accounts/{id}/openTrades OpenTrades done
accounts/{id}/orders (market order) MarketBuy done
accounts/{id}/trades/{id}/orders (take-profit) SetTakeProfit done
accounts/{id}/instruments InstrumentFacts done
instruments/{instrument}/candles (bid/ask spread estimate) AvgSpread done
Pricing / streaming endpoints roadmap
Open/closed positions roadmap

Error handling

Non-2xx responses are returned as errors that include the HTTP status and response body (never the token — see Authentication above), for example:

oanda: HTTP 401 for https://api-fxpractice.oanda.com/v3/instruments/EUR_USD/candles?...: {"errorMessage":"..."}

Orders that Oanda cancels (e.g. insufficient margin) are not returned as a successful Fill; the cancel reason is surfaced as an error:

fill, err := c.MarketBuy(ctx, "EUR_USD", 1)
// err: "oanda: order cancelled: INSUFFICIENT_MARGIN"

Common cancel reasons include INSUFFICIENT_MARGIN, MARKET_HALTED, and FIFO_VIOLATION — see Oanda's Transaction docs for the full list of orderCancelTransaction reason codes.

Rate limits

Oanda enforces roughly 120 requests/second per token. GetCandles retries once on HTTP 429 after a short backoff, then gives up with an error. All other calls (AccountID, OpenTrades, MarketBuy, SetTakeProfit, InstrumentFacts, AvgSpread) surface a 429 immediately as an error. Callers doing bursts of account or trading calls should implement their own backoff strategy.

Pagination and the includeFirst quirk

GetCandles paginates in pages of up to 5000 candles using Oanda's from + count + includeFirst parameters. Two behaviors worth knowing if you're reading the pagination loop or writing your own client against this API:

  • Boundary candle: with includeFirst=true, Oanda returns the candle that covers from, which can be timestamped before your requested start when start isn't aligned to the candle grid. GetCandles drops it (and anything at or after end) to honor the documented [start, end) contract.
  • Count quirk: after the first page, subsequent requests use includeFirst=false to avoid re-fetching the last candle — but Oanda's count still charges for that excluded candle. A "full" follow-up page is therefore count - 1 candles, not count. Treating it as a short (final) page would silently truncate history; GetCandles accounts for this explicitly (see TestFetchPaginationCountQuirk).

The backtestsource sub-module

backtestsource/ is a separate Go module (github.com/florinel-chis/oanda-go/backtestsource) that adapts *oanda.Client to gobacktest's source.Source interface, mapping gobacktest's canonical intervals (source.M1source.Mo1) to Oanda's native granularity strings:

import (
	backtestsource "github.com/florinel-chis/oanda-go/backtestsource"
	oanda "github.com/florinel-chis/oanda-go"
)

c := oanda.New(token, oanda.Practice())
src := backtestsource.New(c)
data, err := src.Fetch(ctx, "EUR_USD", start, end, source.H1)

It is a nested module specifically so that depending on the core oanda-go package never pulls in gobacktest (or vice versa) — only programs that need the backtesting integration import it.

License

MIT — see LICENSE.

Documentation

Overview

Package oanda is a dependency-free Go client for the Oanda REST v20 API: instrument candles (GetCandles), account/trading calls (trading.go), and instrument facts (instrument.go). It uses only net/http and the standard library.

The access token is caller-supplied at construction; the package never reads environment variables, never logs the token, and sends it only in the Authorization header (never in URLs or error messages).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Candle

type Candle struct {
	Time                           time.Time
	Open, High, Low, Close, Volume float64
	Complete                       bool
}

Candle is one OHLCV bar as returned by GetCandles: mid prices, tick-count volume, and whether the candle has fully closed.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is an Oanda REST v20 client: candle fetching (GetCandles, in candles.go) plus the account/trading calls in trading.go and instrument.go. Create with New.

func New

func New(token string, opts ...Option) *Client

New returns a Client for the live environment (api-fxtrade.oanda.com) authenticated with the given personal access token.

func (*Client) AccountID

func (c *Client) AccountID(ctx context.Context) (string, error)

AccountID returns the configured account ID, resolving (and caching) the token's first account when none was pinned with WithAccount. Safe for concurrent use: the cache is mutex-guarded and concurrent first calls are serialized so the /v3/accounts lookup runs at most once.

func (*Client) AvgSpread

func (c *Client) AvgSpread(ctx context.Context, instrument string, n int) (float64, error)

AvgSpread returns the average bid-ask spread (in price units) over the last n complete M10 candles — a live estimate of the instrument's trading cost.

func (*Client) GetCandles

func (c *Client) GetCandles(ctx context.Context, instrument, granularity string, start, end time.Time) ([]Candle, error)

GetCandles downloads mid-price OHLCV candles for instrument in [start, end), paginating in pages of 5000. Incomplete (still forming) candles are skipped. granularity is an Oanda-native granularity string (e.g. "M1", "H4", "D", "W", "M").

func (*Client) InstrumentFacts

func (c *Client) InstrumentFacts(ctx context.Context, instrument string) (*InstrumentFacts, error)

InstrumentFacts fetches the facts for instrument from the account's instruments endpoint.

func (*Client) MarketBuy

func (c *Client) MarketBuy(ctx context.Context, instrument string, units float64) (*Fill, error)

MarketBuy places a FOK market buy and returns the fill. No take-profit is attached — call SetTakeProfit with the fill price for a fill-relative TP. A cancelled order (e.g. INSUFFICIENT_MARGIN) is returned as an error.

func (*Client) OpenTrades

func (c *Client) OpenTrades(ctx context.Context, instrument string) ([]Trade, error)

OpenTrades returns the account's open LONG trades for instrument, in the order Oanda returns them.

func (*Client) SetTakeProfit

func (c *Client) SetTakeProfit(ctx context.Context, tradeID, price string) error

SetTakeProfit sets/replaces the GTC take-profit order on an open trade. price is the already-formatted price string (instrument precision applied).

type Fill

type Fill struct {
	TradeID string
	Price   float64
	Units   float64
}

Fill is the result of a filled market order.

type InstrumentFacts

type InstrumentFacts struct {
	Name             string
	DisplayPrecision int
	MarginRate       float64
	LongRate         float64 // annual financing, long side (e.g. -0.0468 = 4.68%/yr cost)
	ShortRate        float64 // annual financing, short side
}

InstrumentFacts are the account-relative trading facts for one instrument: price precision, margin requirement, and annual financing rates (negative values are costs, per Oanda convention).

type Option

type Option func(*Client)

Option configures a Client.

func Practice

func Practice() Option

Practice targets the practice (demo) environment api-fxpractice.oanda.com.

func WithAccount

func WithAccount(id string) Option

WithAccount pins the account ID, skipping the /v3/accounts lookup.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient replaces the default HTTP client (30s timeout).

type Trade

type Trade struct {
	ID         string
	Instrument string
	Units      float64
	Price      float64 // entry (fill) price
	TP         float64 // take-profit price; 0 if none attached
	OpenTime   time.Time
}

Trade is an open trade on the account.

Directories

Path Synopsis
examples
account command
Command account resolves the account ID for the given token and prints a summary-style dump of InstrumentFacts for EUR_USD.
Command account resolves the account ID for the given token and prints a summary-style dump of InstrumentFacts for EUR_USD.
candles command
Command candles fetches the last day of H1 candles for EUR_USD and prints the 5 most recent complete ones.
Command candles fetches the last day of H1 candles for EUR_USD and prints the 5 most recent complete ones.
marketorder command
Command marketorder places a 1-unit market buy on EUR_USD and attaches a take-profit at the fill price plus 0.0010.
Command marketorder places a 1-unit market buy on EUR_USD and attaches a take-profit at the fill price plus 0.0010.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL