TickAtlas
Go 1.21+ ~10 min setup

Go Integration

Strongly-typed API access with Go's concurrency primitives for parallel multi-symbol fetching. Zero external dependencies — uses only the standard library.

Struct Definitions

Strongly-typed structs for all API responses:

tickatlas/types.go
package tickatlas

import "time"

// IndicatorResponse represents a single indicator value.
type IndicatorResponse struct {
    Symbol    string    `json:"symbol"`
    Indicator string    `json:"indicator"`
    Timeframe string    `json:"timeframe"`
    Timestamp time.Time `json:"timestamp"`
    Value     float64   `json:"value"`
    Signal    string    `json:"signal"`
    OHLC      OHLC      `json:"ohlc"`
    Metadata  Metadata  `json:"metadata"`
}

type OHLC struct {
    Open  float64 `json:"open"`
    High  float64 `json:"high"`
    Low   float64 `json:"low"`
    Close float64 `json:"close"`
}

type Metadata struct {
    Period        int    `json:"period"`
    Source        string `json:"source"`
    DataAgeSeconds int   `json:"data_age_seconds"`
}

// QuoteResponse represents a live price 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 an AI-powered market summary.
type SummaryResponse struct {
    Symbol     string  `json:"symbol"`
    Timeframe  string  `json:"timeframe"`
    Bias       string  `json:"bias"`
    Confidence float64 `json:"confidence"`
    Narrative  string  `json:"narrative"`
}

// ScreenerResult represents one symbol in a screener scan.
type ScreenerResult struct {
    Symbol string  `json:"symbol"`
    Value  float64 `json:"value"`
    Signal string  `json:"signal"`
}

type ScreenerResponse struct {
    Indicator string           `json:"indicator"`
    Timeframe string           `json:"timeframe"`
    Results   []ScreenerResult `json:"results"`
}

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

    return io.ReadAll(resp.Body)
}

// 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 AI 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": {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"
)

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: ind.Signal}
        }(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)
}