Go 1.21+
Go Integration
Strongly-typed API access with Go's concurrency primitives for parallel multi-symbol fetching. Zero external dependencies — uses only the standard library.
~10 min setupREST + JSONX-API-Key authentication
~10 minTypical setup
Programming LanguagesIntegration type
REST + JSONUniversal interface
X-API-KeyOne auth pattern
Struct Definitions
Strongly-typed structs for all API responses:
tickatlas/types.go
package tickatlas
import "time"
// Every success response is {"success": true, "data": {...}}; the client
// below unwraps the envelope, so these structs describe the "data" block.
// IndicatorResponse represents a single indicator value (GET /v1/indicator).
type IndicatorResponse struct {
Symbol string `json:"symbol"`
Timeframe string `json:"timeframe"`
Indicator string `json:"indicator"`
Value float64 `json:"value"`
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
UpdatedAt string `json:"updated_at"`
ServerTime string `json:"server_time"`
}
// QuoteResponse represents a live price quote (GET /v1/quote).
type QuoteResponse struct {
Symbol string `json:"symbol"`
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
Spread float64 `json:"spread"`
Timestamp time.Time `json:"timestamp"`
}
// SummaryResponse represents a rule-based market summary (GET /v1/summary).
// Summary is the human-readable text.
type SummaryResponse struct {
Symbol string `json:"symbol"`
Timeframe string `json:"timeframe"`
Bias string `json:"bias"`
Confidence float64 `json:"confidence"`
TrendScore float64 `json:"trend_score"`
MomentumScore float64 `json:"momentum_score"`
BullishSignals []string `json:"bullish_signals"`
BearishSignals []string `json:"bearish_signals"`
Summary string `json:"summary"`
}
// ScreenerResult represents one symbol in a screener scan.
type ScreenerResult struct {
Symbol string `json:"symbol"`
Value float64 `json:"value"`
Bid float64 `json:"bid"`
}
type ScreenerResponse struct {
Indicator string `json:"indicator"`
Timeframe string `json:"timeframe"`
Results []ScreenerResult `json:"results"`
TotalMatches int `json:"total_matches"`
} API Client
A clean client using net/http with proper error handling:
tickatlas/client.go
package tickatlas
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
const baseURL = "https://tickatlas.com/v1"
// Client is the TickAtlas API client.
type Client struct {
apiKey string
httpClient *http.Client
}
// NewClient creates a new TickAtlas client.
func NewClient(apiKey string) *Client {
return &Client{
apiKey: apiKey,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
}
}
func (c *Client) get(endpoint string, params url.Values) ([]byte, error) {
reqURL := fmt.Sprintf("%s/%s?%s", baseURL, endpoint, params.Encode())
req, err := http.NewRequest("GET", reqURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("X-API-Key", c.apiKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 429 {
return nil, fmt.Errorf("rate limited: retry after %s", resp.Header.Get("Retry-After"))
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("API error: %d %s", resp.StatusCode, resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// Unwrap the {"success": true, "data": {...}} envelope once, here.
var env struct {
Success bool `json:"success"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(body, &env); err != nil {
return nil, err
}
return env.Data, nil
}
// Indicator fetches a single indicator value.
func (c *Client) Indicator(symbol, indicator, timeframe string) (*IndicatorResponse, error) {
params := url.Values{
"symbol": {symbol}, "indicator": {indicator}, "timeframe": {timeframe},
}
body, err := c.get("indicator", params)
if err != nil {
return nil, err
}
var result IndicatorResponse
return &result, json.Unmarshal(body, &result)
}
// Quote fetches a live price quote.
func (c *Client) Quote(symbol string) (*QuoteResponse, error) {
body, err := c.get("quote", url.Values{"symbol": {symbol}})
if err != nil {
return nil, err
}
var result QuoteResponse
return &result, json.Unmarshal(body, &result)
}
// Summary fetches the Market Summary.
func (c *Client) Summary(symbol, timeframe string) (*SummaryResponse, error) {
body, err := c.get("summary", url.Values{"symbol": {symbol}, "timeframe": {timeframe}})
if err != nil {
return nil, err
}
var result SummaryResponse
return &result, json.Unmarshal(body, &result)
}
// Screener scans symbols by indicator criteria.
func (c *Client) Screener(indicator, timeframe string, maxVal float64) (*ScreenerResponse, error) {
params := url.Values{
"indicator": {indicator}, "timeframe": {timeframe},
"max_val": {fmt.Sprintf("%.2f", maxVal)},
}
body, err := c.get("screener", params)
if err != nil {
return nil, err
}
var result ScreenerResponse
return &result, json.Unmarshal(body, &result)
} Concurrent Multi-Symbol Fetching
Use goroutines and channels to fetch indicators for multiple symbols simultaneously:
main.go
package main
import (
"fmt"
"os"
"sync"
)
// classify turns an RSI value into a label. This is your rule — the API
// returns the value, it does not return a signal field.
func classify(rsi float64) string {
switch {
case rsi < 30:
return "oversold"
case rsi > 70:
return "overbought"
default:
return "neutral"
}
}
func main() {
client := tickatlas.NewClient(os.Getenv("CLAW_API_KEY"))
symbols := []string{"EURUSD", "GBPUSD", "USDJPY", "XAUUSD", "BTCUSD"}
var wg sync.WaitGroup
type result struct {
Symbol string
RSI float64
Signal string
Err error
}
results := make(chan result, len(symbols))
// Fetch RSI for all symbols concurrently
for _, sym := range symbols {
wg.Add(1)
go func(s string) {
defer wg.Done()
ind, err := client.Indicator(s, "RSI_14", "H1")
if err != nil {
results <- result{Symbol: s, Err: err}
return
}
results <- result{Symbol: s, RSI: ind.Value, Signal: classify(ind.Value)}
}(sym)
}
go func() {
wg.Wait()
close(results)
}()
fmt.Println("Symbol | RSI | Signal")
fmt.Println("----------|---------|--------")
for r := range results {
if r.Err != nil {
fmt.Printf("%-9s | ERROR | %v\n", r.Symbol, r.Err)
} else {
fmt.Printf("%-9s | %6.2f | %s\n", r.Symbol, r.RSI, r.Signal)
}
}
} Error Handling with Retry
go
// Robust error handling pattern
func fetchWithRetry(client *tickatlas.Client, symbol, indicator, tf string) (*tickatlas.IndicatorResponse, error) {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
result, err := client.Indicator(symbol, indicator, tf)
if err == nil {
return result, nil
}
// Don't retry on auth errors
if strings.Contains(err.Error(), "401") || strings.Contains(err.Error(), "403") {
return nil, fmt.Errorf("auth error (not retryable): %w", err)
}
// Exponential backoff
wait := time.Duration(1<<uint(i)) * time.Second
log.Printf("Attempt %d/%d failed: %v. Retrying in %v...", i+1, maxRetries, err, wait)
time.Sleep(wait)
}
return nil, fmt.Errorf("failed after %d retries", maxRetries)
}